diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..3cce0beb --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +.git +.github +.idea +.vscode +**/.DS_Store +**/*.iml +**/target +data +backend/web/src/main/resources/static diff --git a/.github/workflows/jms-build-test.yml.disable b/.github/workflows/jms-build-test.yml.disable index 704f22e0..0c340d30 100644 --- a/.github/workflows/jms-build-test.yml.disable +++ b/.github/workflows/jms-build-test.yml.disable @@ -5,8 +5,6 @@ on: - 'Dockerfile' - 'Dockerfile*' - 'Dockerfile-*' - - 'frontend/package.json' - - 'frontend/yarn.lock' jobs: build: @@ -21,7 +19,6 @@ jobs: - name: Prepare Build run: | - sed -i 's@registry.npmmirror.com@registry.yarnpkg.com@g' frontend/yarn.lock sed -i 's@^FROM registry.fit2cloud.com/jumpserver@FROM ghcr.io/jumpserver@g' Dockerfile-ee - name: Login to GitHub Container Registry @@ -59,4 +56,4 @@ jobs: VERSION=${{ matrix.version }} outputs: type=image,oci-mediatypes=true,compression=zstd,compression-level=3,force-compression=true cache-from: type=gha - cache-to: type=gha,mode=max \ No newline at end of file + cache-to: type=gha,mode=max diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 03b2f523..66b56af1 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -12,8 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java_version: [ '17' ] - node_version: [ '20' ] + java_version: [ '21' ] outputs: upload_url: ${{ steps.create_release.outputs.upload_url }} steps: @@ -22,7 +21,6 @@ jobs: with: path: | ~/.m2 - ~/.npm ~/.cache key: ${{ runner.os }}-build-${{ github.sha }} restore-keys: ${{ runner.os }}-build- @@ -43,20 +41,10 @@ jobs: version: ${{ steps.get_version.outputs.TAG }} tag: ${{ steps.get_version.outputs.TAG }} - - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node_version }} - - uses: actions/setup-java@v4 with: distribution: 'oracle' java-version: ${{ matrix.java_version }} - - - name: Build web - run: | - cd frontend - yarn install - yarn build - name: Build chen run: | diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..7dca2599 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,12 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node-terminal", + "name": "Docker: Chen Backend", + "request": "launch", + "command": "docker compose up --build", + "cwd": "${workspaceFolder}" + } + ] +} diff --git a/Dockerfile b/Dockerfile index 5065fe51..56c013d8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,14 @@ -FROM jumpserver/chen-base:20260811_065334 AS stage-build +FROM jumpserver/chen-base:20260821_111128 AS stage-build ENV LANG=en_US.UTF-8 WORKDIR /opt/chen/ COPY . . -RUN cd frontend \ - && npm run build RUN mvn clean package -Dmaven.test.skip=true FROM debian:trixie-slim +ARG TARGETARCH ARG DEPENDENCIES=" \ ca-certificates \ @@ -17,8 +16,12 @@ ARG DEPENDENCIES=" \ ARG APT_MIRROR=http://deb.debian.org -RUN set -ex \ +# Dockerfile-ee uses the same per-architecture cache IDs for its extra packages. +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=chen-apt-cache-${TARGETARCH} \ + --mount=type=cache,target=/var/lib/apt,sharing=locked,id=chen-apt-lib-${TARGETARCH} \ + set -ex \ && sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list.d/debian.sources \ + && rm -f /etc/apt/apt.conf.d/docker-clean \ && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && apt-get update \ && apt-get install -y --no-install-recommends ${DEPENDENCIES} \ diff --git a/Dockerfile-base b/Dockerfile-base index d2087272..e1bb0645 100644 --- a/Dockerfile-base +++ b/Dockerfile-base @@ -24,19 +24,12 @@ RUN set -ex \ && dpkg -i check_linux_${TARGETARCH}.deb \ && rm -f check_linux_${TARGETARCH}.deb -ARG WISP_VERSION=v0.2.14 +ARG WISP_VERSION=v0.2.15 RUN set -ex \ && wget https://github.com/jumpserver/wisp/releases/download/${WISP_VERSION}/wisp_linux_${TARGETARCH}.deb \ && dpkg -i wisp_linux_${TARGETARCH}.deb \ && rm -f wisp_linux_${TARGETARCH}.deb -# Install NPM dependencies -WORKDIR /opt/chen/frontend -COPY frontend/package.json frontend/package-lock.json frontend/yarn.lock ./ - -RUN --mount=type=cache,target=/usr/local/share/.cache/yarn,sharing=locked,id=chen \ - npm install - # Install Maven dependencies ARG MAVEN_VERSION=3.9.16 ARG USER_HOME_DIR="/root" @@ -61,7 +54,6 @@ RUN set -ex \ && rm -f /tmp/apache-maven.tar.gz \ && ln -s /usr/share/maven/bin/mvn /usr/bin/mvn \ && mkdir -p /root/.m2 \ - && mkdir -p /opt/chen/frontend/dist \ && sed -i "s@https://repo.maven.apache.org/maven2@${MAVEN_MIRROR}@g" settings.xml \ && cp -f settings.xml /root/.m2/ \ - && mvn clean install \ No newline at end of file + && mvn clean install diff --git a/Dockerfile-ee b/Dockerfile-ee index 168ea47f..8b563abb 100644 --- a/Dockerfile-ee +++ b/Dockerfile-ee @@ -5,11 +5,11 @@ ARG TARGETARCH ARG DEPENDENCIES=" \ iputils-ping \ - telnet" + telnet" -RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=chen \ - --mount=type=cache,target=/var/lib/apt,sharing=locked,id=chen \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=chen-apt-cache-${TARGETARCH} \ + --mount=type=cache,target=/var/lib/apt,sharing=locked,id=chen-apt-lib-${TARGETARCH} \ set -ex \ + && rm -f /etc/apt/apt.conf.d/docker-clean \ && apt-get update \ && apt-get install -y --no-install-recommends ${DEPENDENCIES} - \ No newline at end of file diff --git a/Dockerfile.wisp-dev b/Dockerfile.wisp-dev new file mode 100644 index 00000000..ff330cf0 --- /dev/null +++ b/Dockerfile.wisp-dev @@ -0,0 +1,52 @@ +# syntax=docker/dockerfile:1.7 + +ARG GO_VERSION=1.25 +FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-bookworm AS stage-build + +ARG TARGETOS=linux +ARG TARGETARCH=amd64 +ARG WISP_BUILD_PROCS=2 +ARG WISP_BUILD_MEMORY=1GiB + +ENV CGO_ENABLED=0 +WORKDIR /opt/wisp + +COPY --from=wisp-source go.mod go.sum ./ +RUN --mount=type=cache,target=/go/pkg/mod,sharing=locked \ + GOMAXPROCS=${WISP_BUILD_PROCS} \ + GOMEMLIMIT=${WISP_BUILD_MEMORY} \ + go mod download + +COPY --from=wisp-source main.go ./ +COPY --from=wisp-source cmd ./cmd +COPY --from=wisp-source pkg ./pkg +COPY --from=wisp-source protobuf-go ./protobuf-go + +RUN --mount=type=cache,target=/go/pkg/mod,sharing=locked \ + --mount=type=cache,target=/root/.cache/go-build,sharing=locked \ + GOMAXPROCS=${WISP_BUILD_PROCS} \ + GOMEMLIMIT=${WISP_BUILD_MEMORY} \ + GOOS=${TARGETOS} \ + GOARCH=${TARGETARCH} \ + go build -p=${WISP_BUILD_PROCS} -trimpath -o /out/wisp . + +FROM debian:bookworm-slim + +ARG APT_MIRROR=http://deb.debian.org +RUN set -ex \ + && sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list.d/debian.sources \ + && apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates netcat-openbsd \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /opt/wisp +COPY --from=stage-build /out/wisp ./wisp + +VOLUME ["/opt/wisp/data"] +EXPOSE 9090 + +HEALTHCHECK --interval=2s --timeout=1s --start-period=5s --retries=30 \ + CMD ["nc", "-z", "127.0.0.1", "9090"] + +CMD ["./wisp"] diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..cff8d59e --- /dev/null +++ b/Makefile @@ -0,0 +1,98 @@ +SHELL := /bin/bash +.DEFAULT_GOAL := help +.NOTPARALLEL: + +MAVEN ?= mvn +LOCAL_REVISION ?= 0.0.1 +LOCAL_MAVEN_TEST_ARGS ?= -Dmaven.test.skip=true +LOCAL_JAVA_HOME ?= $(shell if [[ -x /usr/libexec/java_home ]]; then /usr/libexec/java_home -v 21 2>/dev/null; elif [[ -n "$$JAVA_HOME" ]]; then printf '%s' "$$JAVA_HOME"; fi) +LOCAL_JAVA_ENV := $(if $(strip $(LOCAL_JAVA_HOME)),JAVA_HOME="$(LOCAL_JAVA_HOME)") + +DOCKER_COMPOSE ?= docker compose +COMPOSE_PARALLEL_LIMIT ?= 1 +WISP_IMAGE_REPOSITORY ?= ghcr.io/jumpserver/wisp +WISP_BRANCH ?= +export WISP_BRANCH +WISP_TAG ?= $(shell ./scripts/resolve-wisp-tag.sh) +WISP_IMAGE ?= $(WISP_IMAGE_REPOSITORY):$(WISP_TAG) +WISP_PULL_POLICY ?= missing + +export COMPOSE_PARALLEL_LIMIT +export WISP_IMAGE_REPOSITORY +export WISP_TAG +export WISP_IMAGE +export WISP_PULL_POLICY +export WISP_SOURCE + +DEV_COMPOSE_FILES := -f docker-compose.yml -f docker-compose.wisp-dev.yml +ifneq ($(strip $(WISP_SOURCE)),) +DEV_COMPOSE_FILES += -f docker-compose.wisp-source.yml +endif +DEV_COMPOSE := $(DOCKER_COMPOSE) $(DEV_COMPOSE_FILES) + +.PHONY: help dev run dev-run dev-up dev-wisp dev-wisp-image dev-logs dev-ps dev-config dev-down proto-sync + +help: + @printf '%s\n' \ + 'Chen development commands:' \ + ' make dev Build and run Chen locally' \ + ' make run Run Chen and Wisp with Compose in the foreground' \ + ' make dev-up Build and run in the background' \ + ' make dev-wisp Rebuild/recreate only Wisp' \ + ' make dev-wisp-image Show the resolved Wisp image' \ + ' make dev-logs Follow Chen and Wisp logs' \ + ' make dev-ps Show development services' \ + ' make dev-config Render the merged Compose configuration' \ + ' make dev-down Stop and remove development services' \ + ' make proto-sync Copy generated Java protobuf files from Wisp' \ + '' \ + 'Wisp source mode:' \ + ' make run WISP_SOURCE=/absolute/path/to/wisp' \ + '' \ + 'Wisp image mode:' \ + ' Wisp tag defaults to the current Chen tracking branch' \ + ' make run WISP_TAG=latest' \ + ' make run WISP_IMAGE=registry/wisp:tag WISP_PULL_POLICY=always' + +run: dev-run + @: + +dev-run: + @$(DEV_COMPOSE) up --build + +dev: + @$(LOCAL_JAVA_ENV) $(MAVEN) -U \ + -Drevision=$(LOCAL_REVISION) \ + $(LOCAL_MAVEN_TEST_ARGS) \ + -Dmaven.antrun.skip=true \ + clean org.codehaus.mojo:flatten-maven-plugin:1.6.0:flatten install + @cd backend/web && $(LOCAL_JAVA_ENV) $(MAVEN) \ + -Drevision=$(LOCAL_REVISION) \ + $(LOCAL_MAVEN_TEST_ARGS) \ + spring-boot:run \ + -Dmaven.antrun.skip=true \ + -Dspring-boot.run.arguments="--spring.config.additional-location=file:$(CURDIR)/config/ --driver.driver-path=$(CURDIR)/drivers" + +dev-up: + @$(DEV_COMPOSE) up -d --build + +dev-wisp: + @$(DEV_COMPOSE) up -d --build --no-deps wisp + +dev-wisp-image: + @printf '%s\n' '$(WISP_IMAGE)' + +dev-logs: + @$(DEV_COMPOSE) logs -f chen-backend wisp + +dev-ps: + @$(DEV_COMPOSE) ps + +dev-config: + @$(DEV_COMPOSE) config + +dev-down: + @$(DEV_COMPOSE) down + +proto-sync: + @./scripts/sync-wisp-proto.sh diff --git a/README.md b/README.md index eda7016e..16037c6a 100644 --- a/README.md +++ b/README.md @@ -24,3 +24,83 @@ Chen is implemented in Java, and its name is derived from the Dota hero [Chen](h - [x] SQL Server (X-Pack) - [x] Oracle (X-Pack) - [x] DB2 (X-Pack) + +## Develop with Wisp + +The default Chen image contains a released Wisp binary. The development +override separates Wisp into its own service, starts Chen's Java process +directly, and connects Chen to `wisp:9090` over the Compose network. + +### Use a Wisp image + +`make run` defaults to `ghcr.io/jumpserver/wisp:`. The branch is +resolved from the current branch's tracking branch first, then from the local +branch. JumpServer-style branches such as `pr@new_terminal@feat_ai` resolve to +their base branch (`new_terminal`), and characters that cannot appear in a +Docker tag are replaced with `-`. A detached checkout falls back to `latest`. + +Check the result without starting containers: + +```bash +make dev-wisp-image +``` + +The matching tag must have been published in the Wisp container registry. If +it is not available, select a known tag with `WISP_TAG`, use `WISP_SOURCE` as +described below, or set `WISP_IMAGE` to a private-registry or locally built +image: + +```bash +make run WISP_TAG=latest + +make run \ + WISP_IMAGE=registry.example.com/wisp:my-branch \ + WISP_PULL_POLICY=always +``` + +Use `WISP_BRANCH` to resolve a tag from a branch other than the checked-out +Chen branch. `WISP_IMAGE_REPOSITORY` changes only the registry/repository while +retaining automatic tag selection. For an image already built locally, set +`WISP_PULL_POLICY=never`. + +Calling `docker compose` directly (without the Makefile) retains the Compose +file's `ghcr.io/jumpserver/wisp:latest` fallback. + +### Build from Wisp source + +Add `docker-compose.wisp-source.yml` and set `WISP_SOURCE` to any local checkout +path. It does not need to be `../wisp`: + +```bash +make run WISP_SOURCE=/absolute/path/to/wisp +``` + +`WISP_SOURCE` is a BuildKit named context, so it may also be a Git context URL +when the changes have been pushed to a branch. Uncommitted changes require a +local directory. + +After a Go-only Wisp change, rebuild just Wisp; the Java container can stay up +and its gRPC channel will reconnect: + +```bash +make dev-wisp WISP_SOURCE=/absolute/path/to/wisp +``` + +If a Wisp `.proto` file changes, regenerate both Go and Java outputs in Wisp, +then synchronize the Java output before rebuilding Chen: + +```bash +make -C /absolute/path/to/wisp proto-go proto-java +make proto-sync WISP_SOURCE=/absolute/path/to/wisp +make dev-up WISP_SOURCE=/absolute/path/to/wisp +``` + +With JDK 21 and Maven installed, use `make dev` to build and start Chen directly +on the host. Use `make run` to build and start Chen and Wisp together with +Compose. Use `make dev-up` for detached Compose startup, `make dev-logs` to +follow both service logs, and `make dev-down` to stop the environment. Run +`make help` for all development commands. + +The build and runtime limits can be adjusted with `WISP_BUILD_PROCS`, +`WISP_BUILD_MEMORY`, `WISP_CPUS`, `WISP_MEMORY_LIMIT`, `CHEN_CPUS`, and +`CHEN_MEMORY_LIMIT`. diff --git a/backend/framework/pom.xml b/backend/framework/pom.xml index 16ff34a6..4bbfa361 100644 --- a/backend/framework/pom.xml +++ b/backend/framework/pom.xml @@ -12,6 +12,11 @@ framework + + org.junit.jupiter + junit-jupiter + test + com.github.freva ascii-table @@ -21,7 +26,7 @@ com.jumpserver druid - 1.2.28-jms-chen.1 + ${druid.version} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/AbstractConsole.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/AbstractConsole.java index 1791a86a..64b54364 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/AbstractConsole.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/AbstractConsole.java @@ -5,6 +5,7 @@ import org.jumpserver.chen.framework.datasource.Datasource; import org.jumpserver.chen.framework.console.component.Logger; import org.jumpserver.chen.framework.console.component.Messager; +import org.jumpserver.chen.framework.console.context.ConsoleContext; import org.jumpserver.chen.framework.ws.io.PacketIO; import org.jumpserver.chen.framework.console.entity.request.Connect; import org.springframework.web.socket.WebSocketSession; @@ -20,18 +21,25 @@ public abstract class AbstractConsole implements Console { private final PacketIO packetIO; private final Messager messager; private String title; - private String nodeKey; + private final ConsoleContext context; public String getTitle() { return this.title; } public void onInit(Connect connect) { - this.packetIO.sendPacket("init", Map.of("title", this.title)); + this.packetIO.sendPacket("init", Map.of( + "title", this.title, + "consoleId", this.packetIO.getWsSession().getId() + )); } - protected AbstractConsole(Datasource datasource, WebSocketSession ws, String nodeKey) { - this.nodeKey = nodeKey; + public String getNodeKey() { + return this.context.nodeKey(); + } + + protected AbstractConsole(Datasource datasource, WebSocketSession ws, ConsoleContext context) { + this.context = context; this.datasource = datasource; this.packetIO = new PacketIO(ws); this.consoleLogger = new Logger(this.packetIO); diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/Console.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/Console.java index 9ea44e63..c7e6a196 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/Console.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/Console.java @@ -3,10 +3,12 @@ import org.jumpserver.chen.framework.ws.io.Packet; import org.jumpserver.chen.framework.console.entity.request.Connect; +import org.jumpserver.chen.framework.console.context.ConsoleContext; public interface Console { String getTitle(); String getNodeKey(); + ConsoleContext getContext(); void onInit(Connect connect); void handle(Packet packet); diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/ConsoleStatementBoundaryScanner.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/ConsoleStatementBoundaryScanner.java new file mode 100644 index 00000000..273db8b4 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/ConsoleStatementBoundaryScanner.java @@ -0,0 +1,166 @@ +package org.jumpserver.chen.framework.console; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +/** + * Splits raw console input on top-level statement boundaries without rewriting it. + */ +final class ConsoleStatementBoundaryScanner { + private ConsoleStatementBoundaryScanner() { + } + + static List split(String sql) throws SQLException { + if (sql == null || sql.isBlank()) { + throw new SQLException("Console raw execution requires a database command"); + } + + List statements = splitOpaqueStatements(sql); + if (statements.isEmpty()) { + throw new SQLException("Console raw execution requires a database command"); + } + return statements; + } + + private static List splitOpaqueStatements(String sql) { + List statements = new ArrayList<>(); + int statementStart = 0; + boolean hasStatementContent = false; + int blockCommentDepth = 0; + char quote = 0; + String dollarQuote = null; + + for (int index = 0; index < sql.length(); index++) { + char current = sql.charAt(index); + char next = index + 1 < sql.length() ? sql.charAt(index + 1) : 0; + + if (dollarQuote != null) { + if (sql.startsWith(dollarQuote, index)) { + index += dollarQuote.length() - 1; + dollarQuote = null; + } + continue; + } + + if (quote != 0) { + if (current == '\\' && index + 1 < sql.length()) { + index++; + continue; + } + if (current == quote) { + if (next == quote) { + index++; + } else { + quote = 0; + } + } + continue; + } + + if (blockCommentDepth > 0) { + if (current == '/' && next == '*') { + blockCommentDepth++; + index++; + } else if (current == '*' && next == '/') { + blockCommentDepth--; + index++; + } + continue; + } + + if (current == '-' && next == '-') { + index = skipLineComment(sql, index + 2); + continue; + } + if (current == '/' && next == '*') { + blockCommentDepth = 1; + index++; + continue; + } + if (current == '\'' || current == '"' || current == '`') { + quote = current; + hasStatementContent = true; + continue; + } + if (current == '$') { + String delimiter = dollarQuoteDelimiter(sql, index); + if (delimiter != null) { + dollarQuote = delimiter; + hasStatementContent = true; + index += delimiter.length() - 1; + continue; + } + } + if (current == ';') { + if (hasStatementContent) { + addStatement(statements, sql, statementStart, index + 1); + hasStatementContent = false; + } + statementStart = index + 1; + continue; + } + if (!Character.isWhitespace(current)) { + hasStatementContent = true; + } + } + + if (hasStatementContent) { + addStatement(statements, sql, statementStart, sql.length()); + } + return statements; + } + + private static void addStatement(List statements, String sql, int start, int end) { + String statement = sql.substring(start, end).trim(); + if (!statement.isEmpty()) { + statements.add(statement); + } + } + + private static int skipLineComment(String sql, int index) { + while (index < sql.length()) { + char current = sql.charAt(index); + if (current == '\n' || current == '\r') { + return index; + } + index++; + } + return sql.length() - 1; + } + + private static String dollarQuoteDelimiter(String sql, int start) { + if (start > 0 && isIdentifierPart(sql.charAt(start - 1))) { + return null; + } + int end = sql.indexOf('$', start + 1); + if (end < 0) { + return null; + } + String tag = sql.substring(start + 1, end); + if (!tag.isEmpty()) { + if (!isTagStart(tag.charAt(0))) { + return null; + } + for (int index = 1; index < tag.length(); index++) { + if (!isTagPart(tag.charAt(index))) { + return null; + } + } + } + String delimiter = sql.substring(start, end + 1); + return sql.indexOf(delimiter, end + 1) >= 0 ? delimiter : null; + } + + private static boolean isTagStart(char value) { + return value == '_' || Character.isLetter(value); + } + + private static boolean isTagPart(char value) { + return isTagStart(value) || Character.isDigit(value); + } + + private static boolean isIdentifierPart(char value) { + return isTagPart(value) || value == '$'; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/DataViewConsole.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/DataViewConsole.java index e808d647..303b7212 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/DataViewConsole.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/DataViewConsole.java @@ -1,21 +1,29 @@ package org.jumpserver.chen.framework.console; import com.google.gson.Gson; -import org.apache.commons.lang3.StringUtils; +import lombok.Getter; import org.jumpserver.chen.framework.console.action.DataViewAction; +import org.jumpserver.chen.framework.console.context.ConsoleContext; import org.jumpserver.chen.framework.console.dataview.DataView; import org.jumpserver.chen.framework.console.dataview.UpdateDataView; import org.jumpserver.chen.framework.console.entity.request.Connect; +import org.jumpserver.chen.framework.console.entity.request.SaveChangesRequest; import org.jumpserver.chen.framework.console.entity.response.Message; import org.jumpserver.chen.framework.console.state.State; import org.jumpserver.chen.framework.console.state.StateManager; import org.jumpserver.chen.framework.datasource.Datasource; +import org.jumpserver.chen.framework.datasource.edit.TableBrowseSaveExecutionContext; +import org.jumpserver.chen.framework.datasource.edit.TableChangesPreviewService; +import org.jumpserver.chen.framework.datasource.edit.TableChangesSaveService; +import org.jumpserver.chen.framework.datasource.edit.TableEditContext; +import org.jumpserver.chen.framework.datasource.sql.SQL; import org.jumpserver.chen.framework.i18n.MessageUtils; import org.jumpserver.chen.framework.jms.entity.CommandRecord; import org.jumpserver.chen.framework.session.SessionManager; +import org.jumpserver.chen.framework.session.controller.DialogHandle; import org.jumpserver.chen.framework.session.controller.dialog.Button; import org.jumpserver.chen.framework.session.controller.dialog.Dialog; -import org.jumpserver.chen.framework.utils.TreeUtils; +import org.jumpserver.chen.framework.utils.PageUtils; import org.jumpserver.chen.framework.ws.io.Packet; import org.jumpserver.wisp.Common; import org.springframework.web.socket.WebSocketSession; @@ -23,29 +31,49 @@ import java.sql.SQLException; import java.util.Map; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantLock; public class DataViewConsole extends AbstractConsole { + private static final String PACKET_SAVE_CHANGES_PREVIEW_RESULT = "save_changes_preview_result"; + private static final String PACKET_SAVE_CHANGES_RESULT = "save_changes_result"; + private static final long WARNING_DIALOG_TIMEOUT_SECONDS = 300; + private final TableChangesPreviewService tableChangesPreviewService = new TableChangesPreviewService(); + private final TableChangesSaveService tableChangesSaveService = new TableChangesSaveService(); private DataView tableDataView; private StateManager stateManager; + private final AtomicBoolean closed = new AtomicBoolean(false); + private final ReentrantLock executionLock = new ReentrantLock(); - private String schema; - private String table; + @Getter + private final String schema; + @Getter + private final String table; private static final Gson GSON = new Gson(); - public DataViewConsole(Datasource datasource, WebSocketSession ws, String nodeKey) { - super(datasource, ws, nodeKey); + public DataViewConsole(Datasource datasource, WebSocketSession ws, ConsoleContext context) { + super(datasource, ws, context); + this.schema = context.schema(); + this.table = context.table(); } @Override public void onInit(Connect connect) { - this.schema = TreeUtils.getValue(connect.getNodeKey(), "schema"); - this.table = StringUtils.isEmpty(TreeUtils.getValue(connect.getNodeKey(), "table")) ? - TreeUtils.getValue(connect.getNodeKey(), "view") : TreeUtils.getValue(connect.getNodeKey(), "table"); + if (!this.beginExecution()) { + return; + } + try { + this.initialize(connect); + } finally { + this.executionLock.unlock(); + } + } + private void initialize(Connect connect) { var title = ""; try { title = this.generateConsoleName(); @@ -72,13 +100,35 @@ private String generateConsoleName() { @Override public void handle(Packet packet) { - switch (packet.getType()) { - case "ping" -> this.getPacketIO().sendPacket("pong", null); - case Packet.TYPE_DATA_VIEW_ACTION -> { - var action = GSON.fromJson(GSON.toJson(packet.getData()), DataViewAction.class); - this.onDataViewAction(action); + if ("ping".equals(packet.getType())) { + this.getPacketIO().sendPacket("pong", null); + return; + } + if (!this.beginExecution()) { + return; + } + try { + switch (packet.getType()) { + case Packet.TYPE_DATA_VIEW_ACTION -> { + var action = GSON.fromJson(GSON.toJson(packet.getData()), DataViewAction.class); + this.onDataViewAction(action); + } } + } finally { + this.executionLock.unlock(); + } + } + + private boolean beginExecution() { + if (this.closed.get()) { + return false; } + this.executionLock.lock(); + if (this.closed.get()) { + this.executionLock.unlock(); + return false; + } + return true; } public void onConnect(Connect connect) { @@ -94,6 +144,12 @@ public void onConnect(Connect connect) { this.tableDataView.loadData(); } catch (SQLException e) { this.getMessager().send(Message.error(MessageUtils.get("FetchError"), e.getMessage())); + } catch (RuntimeException e) { + // A single DataView init/load failure (e.g. a ParserException from an + // unparseable generated SQL) must surface as an error to the client instead + // of tearing down the whole console websocket session. + this.getConsoleLogger().error("load data view error: %s", e.getMessage()); + this.getMessager().send(Message.error(MessageUtils.get("FetchError"), e.getMessage())); } finally { this.tableDataView.getStateManager().getState().setLoading(false); this.tableDataView.getStateManager().commit(); @@ -112,17 +168,28 @@ public void createDataView(String schemaName, String tableName) { var session = SessionManager.getCurrentSession(); dataView.setLoadDataInterface((sqlQueryParams) -> { - var plan = this.getDatasource() + var basePlan = this.getDatasource() .getConnectionManager() .getSqlActuator() .createPlan(schemaName, tableName, null); - var sql = plan.getTargetSQL(); + final String sql; + try { + sql = PageUtils.filter( + basePlan.getTargetSQL(), + this.getDatasource().getDruidDbType(), + sqlQueryParams.getFilter() + ); + } catch (RuntimeException e) { + throw new SQLException("Invalid data view WHERE condition: " + e.getMessage()); + } finally { + basePlan.close(); + } var aclResult = session.checkACL(sql); if (aclResult != null && (aclResult.getRiskLevel() == Common.RiskLevel.Reject || aclResult.getRiskLevel() == Common.RiskLevel.ReviewReject)) { this.getConsoleLogger().error("%s", MessageUtils.get("ACLRejectError")); CommandRecord commandRecord = new CommandRecord(sql); - commandRecord.setRiskLevel(aclResult.getRiskLevel()); + commandRecord.applyAcl(aclResult); session.recordCommand(commandRecord); this.stateManager.getState().setLoading(false); @@ -145,26 +212,39 @@ public void createDataView(String schemaName, String tableName) { this.getConsoleLogger().warn(MessageUtils.get("ExecutionCanceled")); })); - SessionManager.getCurrentSession().getController().showDialog(dialog); + var controller = SessionManager.getCurrentSession().getController(); + DialogHandle dialogHandle = controller.showDialog(dialog, () -> { + hasNext.set(false); + countDownLatch.countDown(); + }); try { - countDownLatch.await(); + if (!countDownLatch.await(WARNING_DIALOG_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + hasNext.set(false); + dialogHandle.cancel(); + } if (!hasNext.get()) { throw new SQLException(MessageUtils.get("ExecutionCanceled")); } } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + hasNext.set(false); this.stateManager.commit(); this.getConsoleLogger().error("get result error"); + throw new SQLException(MessageUtils.get("ExecutionCanceled"), e); } finally { - SessionManager.getCurrentSession().getController().closeDialog(); + dialogHandle.close(); } } - + var plan = this.getDatasource() + .getConnectionManager() + .getSqlActuator() + .createPlan(SQL.of(sql)); plan.setSqlQueryParams(sqlQueryParams); plan.generateTargetSQL(); @@ -181,6 +261,43 @@ public void createDataView(String schemaName, String tableName) { public void onDataViewAction(DataViewAction action) { + if (DataViewAction.ACTION_SAVE_CHANGES_PREVIEW.equals(action.getAction())) { + var request = GSON.fromJson(GSON.toJson(action.getData()), SaveChangesRequest.class); + var context = new TableEditContext( + this.tableDataView.getTitle(), + this.schema, + this.table, + this.tableDataView.getData().getFields(), + this.getDatasource().getDruidDbType(), + true + ); + context.setRowRefPrimaryKeys(this.tableDataView.getRowRefPrimaryKeys()); + var result = this.tableChangesPreviewService.preview(context, action.getDataView(), request); + this.getPacketIO().sendPacket(PACKET_SAVE_CHANGES_PREVIEW_RESULT, result); + return; + } + if (DataViewAction.ACTION_SAVE_CHANGES.equals(action.getAction())) { + var request = GSON.fromJson(GSON.toJson(action.getData()), SaveChangesRequest.class); + var context = new TableEditContext( + this.tableDataView.getTitle(), + this.schema, + this.table, + this.tableDataView.getData().getFields(), + this.getDatasource().getDruidDbType(), + true + ); + context.setRowRefPrimaryKeys(this.tableDataView.getRowRefPrimaryKeys()); + var result = this.tableChangesSaveService.save( + context, + action.getDataView(), + request, + new TableBrowseSaveExecutionContext(this.getDatasource().getConnectionManager()), + SessionManager.getCurrentSession() + ); + this.getPacketIO().sendPacket(PACKET_SAVE_CHANGES_RESULT, result); + return; + } + try { this.tableDataView.getStateManager().getState().setLoading(true); this.tableDataView.getStateManager().commit(); @@ -199,5 +316,24 @@ public void onDataViewAction(DataViewAction action) { @Override public void close() { + if (!this.closed.compareAndSet(false, true)) { + return; + } + var currentSession = SessionManager.getCurrentSession(); + if (currentSession == null && this.getPacketIO().getWsSession().getAttributes() != null) { + Object token = this.getPacketIO().getWsSession().getAttributes().get("token"); + if (token instanceof String sessionToken) { + currentSession = SessionManager.getSession(sessionToken); + } + } + if (currentSession != null && currentSession.getController() != null) { + currentSession.getController().cancelDialogs(this.getPacketIO().getWsSession().getId()); + } + this.executionLock.lock(); + try { + // Wait for admitted work to observe cancellation and leave the execution section. + } finally { + this.executionLock.unlock(); + } } } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/QueryConsole.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/QueryConsole.java index ab78aa33..ca79d9ad 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/QueryConsole.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/QueryConsole.java @@ -1,74 +1,165 @@ package org.jumpserver.chen.framework.console; +import com.alibaba.druid.DbType; import com.alibaba.druid.sql.parser.ParserException; import com.google.gson.Gson; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.jumpserver.chen.framework.console.action.DataViewAction; import org.jumpserver.chen.framework.console.action.QueryConsoleAction; +import org.jumpserver.chen.framework.console.plan.ExecutionPlanService; +import org.jumpserver.chen.framework.console.plan.QueryConsolePlanContext; import org.jumpserver.chen.framework.console.dataview.DataView; +import org.jumpserver.chen.framework.console.dataview.QueryDataViewTableEditContextFactory; import org.jumpserver.chen.framework.console.dataview.UpdateDataView; +import org.jumpserver.chen.framework.console.context.ConsoleContext; import org.jumpserver.chen.framework.console.entity.request.Connect; +import org.jumpserver.chen.framework.console.entity.request.SaveChangesRequest; import org.jumpserver.chen.framework.console.entity.response.Message; +import org.jumpserver.chen.framework.console.entity.response.SaveChangesPreviewResult; +import org.jumpserver.chen.framework.console.entity.response.SaveChangesResult; import org.jumpserver.chen.framework.console.state.QueryConsoleState; import org.jumpserver.chen.framework.console.state.StateManager; +import org.jumpserver.chen.framework.console.transaction.QueryTransactionProbeResult; +import org.jumpserver.chen.framework.console.transaction.QueryTransactionStateInspector; import org.jumpserver.chen.framework.datasource.Datasource; +import org.jumpserver.chen.framework.datasource.edit.ConnectionOwnership; +import org.jumpserver.chen.framework.datasource.edit.SaveExecutionContext; +import org.jumpserver.chen.framework.datasource.edit.ServiceManagedSaveExecutionContext; +import org.jumpserver.chen.framework.datasource.edit.TableChangesPreviewService; +import org.jumpserver.chen.framework.datasource.edit.TableChangesSaveService; +import org.jumpserver.chen.framework.datasource.edit.UserManagedSaveExecutionContext; import org.jumpserver.chen.framework.datasource.sql.SQL; import org.jumpserver.chen.framework.datasource.sql.SQLActuator; import org.jumpserver.chen.framework.datasource.sql.SQLExecutePlan; +import org.jumpserver.chen.framework.datasource.sql.SQLQueryResult; +import org.jumpserver.chen.framework.datasource.plan.ConnectionInvalidatedException; +import org.jumpserver.chen.framework.datasource.plan.ExecutionPlan; +import org.jumpserver.chen.framework.datasource.plan.ExecutionPlanJson; +import org.jumpserver.chen.framework.datasource.plan.PlanCodes; +import org.jumpserver.chen.framework.datasource.plan.PlanDiagnostic; +import org.jumpserver.chen.framework.datasource.plan.PlanEffects; +import org.jumpserver.chen.framework.datasource.plan.PlanLimits; +import org.jumpserver.chen.framework.datasource.plan.PlanMode; +import org.jumpserver.chen.framework.datasource.plan.PlanStatus; import org.jumpserver.chen.framework.i18n.MessageUtils; import org.jumpserver.chen.framework.jms.acl.ACLResult; import org.jumpserver.chen.framework.jms.entity.CommandRecord; +import org.jumpserver.chen.framework.session.Session; import org.jumpserver.chen.framework.session.SessionManager; +import org.jumpserver.chen.framework.session.controller.DialogHandle; import org.jumpserver.chen.framework.session.controller.dialog.Button; import org.jumpserver.chen.framework.session.controller.dialog.Dialog; -import org.jumpserver.chen.framework.utils.TreeUtils; import org.jumpserver.chen.framework.ws.io.Packet; import org.jumpserver.wisp.Common; import org.springframework.web.socket.WebSocketSession; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.sql.Connection; +import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Statement; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.UUID; import java.util.Optional; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; @Slf4j public class QueryConsole extends AbstractConsole { + private static final String PACKET_SAVE_CHANGES_PREVIEW_RESULT = "save_changes_preview_result"; + private static final String PACKET_SAVE_CHANGES_RESULT = "save_changes_result"; + private static final long WARNING_DIALOG_TIMEOUT_SECONDS = 300; + static final String QUERY_CONSOLE_CONNECTION_UNAVAILABLE = "QUERY_CONSOLE_CONNECTION_UNAVAILABLE"; + static final String QUERY_TRANSACTION_MANUAL_IDLE = "QUERY_TRANSACTION_MANUAL_IDLE"; + static final String QUERY_TRANSACTION_FAILED = "QUERY_TRANSACTION_FAILED"; + static final String QUERY_TRANSACTION_STATE_UNKNOWN = "QUERY_TRANSACTION_STATE_UNKNOWN"; + static final String QUERY_TRANSACTION_PROBE_FAILED = "QUERY_TRANSACTION_PROBE_FAILED"; + static final String QUERY_INSERT_NOT_SUPPORTED = "QUERY_INSERT_NOT_SUPPORTED"; + static final String QUERY_DELETE_NOT_SUPPORTED = "QUERY_DELETE_NOT_SUPPORTED"; + static final String CONSOLE_DATA_VIEW_EDIT_NOT_SUPPORTED = "CONSOLE_DATA_VIEW_EDIT_NOT_SUPPORTED"; + private static final String EXECUTION_STATUS_RUNNING = "running"; + private static final String EXECUTION_STATUS_SUCCESS = "success"; + private static final String EXECUTION_STATUS_ERROR = "error"; + private static final String EXECUTION_STATUS_CANCELLED = "cancelled"; + private static final AtomicInteger CANCEL_THREAD_SEQUENCE = new AtomicInteger(); + private static final ExecutorService CANCEL_EXECUTOR = Executors.newCachedThreadPool(runnable -> { + Thread thread = new Thread(runnable, "query-console-cancel-" + CANCEL_THREAD_SEQUENCE.incrementAndGet()); + thread.setDaemon(true); + return thread; + }); private final Datasource datasource; - private Connection conn; - private volatile SQLExecutePlan currentPlan; + private final boolean consoleMode; + private final int titleSequence; + private final TableChangesPreviewService tableChangesPreviewService = new TableChangesPreviewService(); + private final TableChangesSaveService tableChangesSaveService = new TableChangesSaveService(); + private final QueryDataViewTableEditContextFactory tableEditContextFactory = new QueryDataViewTableEditContextFactory(); + // Single lifecycle lock: beginExecution() admits work before it can access the connection, + // and close() waits for admitted work to finish. Reentrancy lets the admitted handler call + // getConnection() throughout its execution without observing closed=true midway through. + private final ReentrantLock executionLock = new ReentrantLock(); + private final AtomicBoolean closed = new AtomicBoolean(false); + private volatile Connection conn; + private volatile long backendSessionId; + // Driver-specific transaction probe created alongside the physical connection. The driver's + // cached autoCommit flag does not reflect explicit BEGIN/START TRANSACTION on MySQL, Oracle, + // SQL Server, Dameng or DB2, so the server-side probe is the real source of truth for whether + // a user transaction is open before a DataView save commits or stages behind a savepoint. + private volatile QueryTransactionStateInspector transactionStateInspector; + private volatile ActiveExecution currentExecution; private StateManager stateManager; - private final Map dataViews = new HashMap<>(); + private final Map dataViews = new LinkedHashMap<>(); + // Manual context changes remain restricted to values returned by the current server-side actuator. private volatile Map allowedContexts = Map.of(); private final SQLChunkTransferManager sqlChunkTransfers = new SQLChunkTransferManager(); private static final Gson GSON = new Gson(); - public QueryConsole(Datasource datasource, WebSocketSession ws, String nodeKey) { - super(datasource, ws, nodeKey); - this.setTitle(String.format(MessageUtils.get("Query") + "-%d", generateConsoleName())); + public QueryConsole(Datasource datasource, WebSocketSession ws, ConsoleContext context) { + this(datasource, ws, context, false); + } + + public QueryConsole( + Datasource datasource, + WebSocketSession ws, + ConsoleContext context, + boolean consoleMode + ) { + super(datasource, ws, context); + this.consoleMode = consoleMode; + this.titleSequence = generateConsoleName(consoleMode); + this.setTitle(String.format( + MessageUtils.get(consoleMode ? "Console" : "Query") + "-%d", + this.titleSequence + )); this.datasource = datasource; } - private static int generateConsoleName() { + private static int generateConsoleName(boolean consoleMode) { int num = 1; var consoles = SessionManager .getCurrentSession() .getConsoles(); for (var console : consoles.values()) { - if (console instanceof QueryConsole) { - ++num; + if (console instanceof QueryConsole queryConsole && queryConsole.consoleMode == consoleMode) { + num = Math.max(num, queryConsole.titleSequence + 1); } } return num; @@ -76,8 +167,15 @@ private static int generateConsoleName() { @Override public void onInit(Connect connect) { - super.onInit(connect); - this.onConnect(connect); + if (!this.beginExecution()) { + return; + } + try { + super.onInit(connect); + this.onConnect(connect); + } finally { + this.executionLock.unlock(); + } } public void onConnect(Connect connect) { @@ -88,7 +186,7 @@ public void onConnect(Connect connect) { this.getState().setLoading(true); this.stateManager.commit(); - var context = TreeUtils.getValue(connect.getNodeKey(), this.getDatasource().getConnectionManager().getContextKey()); + var context = this.getInitialContext(); try { var currentContext = this.getSqlActuator().getCurrentSchema(); @@ -106,6 +204,7 @@ public void onConnect(Connect connect) { } catch (SQLException e) { this.getConsoleLogger().error(MessageUtils.get("ConnectError") + ": %s", e.getMessage()); + throw new IllegalStateException("Failed to initialize query console", e); } this.getState().setLoading(false); @@ -113,27 +212,111 @@ public void onConnect(Connect connect) { } + String getInitialContext() { + var contextKey = this.getDatasource().getConnectionManager().getContextKey(); + return StringUtils.equals(contextKey, "database") + ? this.getContext().database() + : this.getContext().schema(); + } + private Connection getConnection() { - if (this.conn == null) { - try { - this.conn = this.getDatasource().getConnectionManager().getPhysicalConnection(); - } catch (SQLException e) { - throw new RuntimeException(e); + if (!this.executionLock.isHeldByCurrentThread()) { + throw new IllegalStateException("Connection access requires admitted execution"); + } + return this.getOrCreateConnection(); + } + + private Connection getOrCreateConnection() { + if (this.conn != null) { + return this.conn; + } + + Connection candidate = null; + try { + var connectionManager = this.getDatasource().getConnectionManager(); + String currentContext = this.currentConnectionContext(); + if (StringUtils.isNotBlank(currentContext) && + StringUtils.equals( + connectionManager.getContextKey(), + connectionManager.getDatabaseContextKey() + )) { + connectionManager.setDatabaseContext(currentContext); + } else if (this.getContext() != null && StringUtils.isNotBlank(this.getContext().database())) { + connectionManager.setDatabaseContext(this.getContext().database()); } + + candidate = connectionManager.getPhysicalConnection(); + if (StringUtils.isNotBlank(currentContext)) { + connectionManager.getSqlActuator() + .withConnection(candidate) + .changeSchema(currentContext); + } + QueryTransactionStateInspector candidateInspector = QueryTransactionStateInspector.create( + this.getDatasource().getDatabaseType(), + candidate + ); + + this.captureBackendSessionId(candidate); + this.conn = candidate; + this.transactionStateInspector = candidateInspector; + return candidate; + } catch (SQLException e) { + closeQuietly(candidate); + throw new QueryConsoleConnectionUnavailableException(e); + } catch (RuntimeException e) { + closeQuietly(candidate); + throw e; } - return this.conn; } + private String currentConnectionContext() { + if (this.stateManager == null) { + return null; + } + return this.getState().getCurrentContext(); + } + + public String getCurrentContext() { + return this.stateManager == null ? "" : StringUtils.defaultString(this.getState().getCurrentContext()); + } @Override public void handle(Packet packet) { + if (StringUtils.equals(packet.getType(), "ping")) { + this.getPacketIO().sendPacket("pong", null); + return; + } + if (this.isCancelPacket(packet)) { + this.handleCancel(); + return; + } + if (!this.beginExecution()) { + return; + } + try { + this.handleSerialPacket(packet); + } catch (QueryConsoleConnectionUnavailableException e) { + log.warn("query console connection unavailable", e); + this.getMessager().send(Message.error( + MessageUtils.get("FetchError"), + QUERY_CONSOLE_CONNECTION_UNAVAILABLE + )); + } finally { + this.executionLock.unlock(); + } + } + + private void handleSerialPacket(Packet packet) { switch (packet.getType()) { case "ping" -> this.getPacketIO().sendPacket("pong", null); case "close_data_view" -> { - var name = (String) packet.getData(); - this.dataViews.remove(name); - log.info("close data view {}", name); + var reference = (String) packet.getData(); + var dataView = this.findDataView(reference); + if (dataView != null) { + this.dataViews.remove(dataView.getId()); + } + log.info("close data view {}", reference); } case Packet.TYPE_QUERY_CONSOLE_ACTION -> { @@ -149,13 +332,31 @@ public void handle(Packet packet) { } } + private boolean isCancelPacket(Packet packet) { + if (!StringUtils.equals(packet.getType(), Packet.TYPE_QUERY_CONSOLE_ACTION)) { + return false; + } + var action = GSON.fromJson(GSON.toJson(packet.getData()), QueryConsoleAction.class); + return StringUtils.equals(action.getAction(), QueryConsoleAction.ACTION_CANCEL); + } + + private boolean beginExecution() { + if (this.closed.get()) { + return false; + } + this.executionLock.lock(); + if (this.closed.get()) { + this.executionLock.unlock(); + return false; + } + return true; + } + private void onAction(QueryConsoleAction action) { switch (action.getAction()) { case QueryConsoleAction.ACTION_RUN_SQL -> { - this.getState().setInQuery(true); - this.stateManager.commit(); - + this.beginNewSqlSubmission(); var sql = (String) action.getData(); this.onSQL(sql); @@ -170,9 +371,7 @@ private void onAction(QueryConsoleAction action) { } case QueryConsoleAction.ACTION_RUN_SQL_FILE -> { - this.getState().setInQuery(true); - this.stateManager.commit(); - + this.beginNewSqlSubmission(); var sqlFile = (String) action.getData(); this.onSQLFile(sqlFile); @@ -180,17 +379,14 @@ private void onAction(QueryConsoleAction action) { this.stateManager.commit(); } - case QueryConsoleAction.ACTION_CANCEL -> { - this.sqlChunkTransfers.cancelAll(); - this.onCancel(); - this.getState().setInQuery(false); - this.stateManager.commit(); + this.handleCancel(); } case QueryConsoleAction.ACTION_CHANGE_CURRENT_CONTEXT -> { var schema = (String) action.getData(); this.onManualChangeContext(schema); } + case QueryConsoleAction.ACTION_GET_EXECUTION_PLAN -> this.onGetExecutionPlan(action); } } @@ -202,7 +398,7 @@ private void handleSQLChunk(QueryConsoleAction action) { this.getConsoleLogger().error("Invalid SQL chunk transfer: %s", e.getMessage()); return; } - sql.ifPresent(this::onSQL); + this.runAssembledSql(sql); } private void handleSQLComplete(QueryConsoleAction action) { @@ -213,22 +409,82 @@ private void handleSQLComplete(QueryConsoleAction action) { this.getConsoleLogger().error("Invalid SQL chunk transfer: %s", e.getMessage()); return; } - sql.ifPresent(this::onSQL); + this.runAssembledSql(sql); + } + + private void beginNewSqlSubmission() { + this.getState().setInQuery(true); + this.getState().setExecutionStatus(EXECUTION_STATUS_RUNNING); + this.getState().setCanCancel(true); + this.stateManager.commit(); + } + + private void runAssembledSql(Optional sql) { + sql.ifPresent(assembled -> { + this.beginNewSqlSubmission(); + this.onSQL(assembled); + }); } private void onDataViewAction(DataViewAction action) { - var dataView = this.dataViews.get(action.getDataView()); + if (this.consoleMode && DataViewAction.ACTION_SAVE_CHANGES_PREVIEW.equals(action.getAction())) { + this.getPacketIO().sendPacket(PACKET_SAVE_CHANGES_PREVIEW_RESULT, Map.of( + "success", false, + "allowed", false, + "reason", CONSOLE_DATA_VIEW_EDIT_NOT_SUPPORTED, + "dataView", action.getDataView() + )); + return; + } + if (this.consoleMode && DataViewAction.ACTION_SAVE_CHANGES.equals(action.getAction())) { + this.getPacketIO().sendPacket(PACKET_SAVE_CHANGES_RESULT, Map.of( + "success", false, + "allowed", false, + "reason", CONSOLE_DATA_VIEW_EDIT_NOT_SUPPORTED, + "dataView", action.getDataView() + )); + return; + } + var dataView = this.findDataView(action.getDataView()); if (dataView == null) { log.error("data view {} not found", action.getDataView()); return; } + if (DataViewAction.ACTION_SAVE_CHANGES_PREVIEW.equals(action.getAction())) { + var request = GSON.fromJson(GSON.toJson(action.getData()), SaveChangesRequest.class); + String unsupportedReason = unsupportedQueryMutation(request); + if (unsupportedReason != null) { + this.getPacketIO().sendPacket( + PACKET_SAVE_CHANGES_PREVIEW_RESULT, + this.rejectedPreview(dataView, unsupportedReason) + ); + return; + } + try { + var context = this.tableEditContextFactory.create(dataView, this.getDatasource().getDruidDbType()); + var result = this.tableChangesPreviewService.preview(context, dataView.getTitle(), request); + this.getPacketIO().sendPacket(PACKET_SAVE_CHANGES_PREVIEW_RESULT, result); + } catch (IllegalArgumentException e) { + this.getPacketIO().sendPacket(PACKET_SAVE_CHANGES_PREVIEW_RESULT, this.rejectedPreview(dataView, e.getMessage())); + } + return; + } + if (DataViewAction.ACTION_SAVE_CHANGES.equals(action.getAction())) { + var request = GSON.fromJson(GSON.toJson(action.getData()), SaveChangesRequest.class); + SaveChangesResult result = this.saveQueryChanges(dataView, dataView.getTitle(), request); + this.getPacketIO().sendPacket(PACKET_SAVE_CHANGES_RESULT, result); + return; + } try { dataView.getStateManager().getState().setLoading(true); dataView.getStateManager().commit(); dataView.doAction(action); - this.getPacketIO().sendPacket("update_data_view", new UpdateDataView(action.getDataView(), dataView.getData())); + this.getPacketIO().sendPacket( + "update_data_view", + new UpdateDataView(dataView.getId(), dataView.getTitle(), dataView.getData()) + ); } catch (SQLException e) { this.getMessager().send(Message.error(MessageUtils.get("FetchError"), e.getMessage())); @@ -238,17 +494,468 @@ private void onDataViewAction(DataViewAction action) { } } - public void onCancel() { + private SaveChangesResult saveQueryChanges( + DataView dataView, + String actionDataView, + SaveChangesRequest request + ) { + String unsupportedReason = unsupportedQueryMutation(request); + if (unsupportedReason != null) { + return this.rejectedSave(dataView, unsupportedReason); + } + + // Probe the driver-side transaction state before touching the connection. The cached + // autoCommit flag is unreliable on MySQL/Oracle/SQL Server/Dameng/DB2 (it does not flip on + // explicit BEGIN), so the server-side probe is the source of truth: + // AUTO_COMMIT -> no user tx open; service-managed transaction committed for the user + // TRANSACTION_ACTIVE -> user owns the tx; batch staged behind a savepoint, outer tx never committed + // MANUAL_COMMIT_IDLE -> reject; Chen does not start a user transaction implicitly + // TRANSACTION_FAILED -> reject; the user must rollback the failed tx first + // UNKNOWN / probeFailed -> fail closed; never risk committing or staging on an uncertain connection + Connection connection = this.getConnection(); + QueryTransactionStateInspector inspector = this.transactionStateInspector; + if (inspector == null) { + return this.rejectedSave(dataView, QUERY_CONSOLE_CONNECTION_UNAVAILABLE); + } + QueryTransactionProbeResult probeResult = inspector.probeNow(); + if (probeResult.probeFailed()) { + return this.rejectedSave(dataView, QUERY_TRANSACTION_PROBE_FAILED); + } + + SaveExecutionContext executionContext; + switch (probeResult.state()) { + case AUTO_COMMIT -> executionContext = new ServiceManagedSaveExecutionContext( + connection, + ConnectionOwnership.QUERY_CONSOLE + ); + case TRANSACTION_ACTIVE -> executionContext = new UserManagedSaveExecutionContext(connection); + case MANUAL_COMMIT_IDLE -> { + return this.rejectedSave(dataView, QUERY_TRANSACTION_MANUAL_IDLE); + } + case TRANSACTION_FAILED -> { + return this.rejectedSave(dataView, QUERY_TRANSACTION_FAILED); + } + case UNKNOWN -> { + return this.rejectedSave(dataView, QUERY_TRANSACTION_STATE_UNKNOWN); + } + default -> { + return this.rejectedSave(dataView, QUERY_TRANSACTION_STATE_UNKNOWN); + } + } + + try { + var context = this.tableEditContextFactory.create( + dataView, + this.getDatasource().getDruidDbType() + ); + SaveChangesResult result = this.tableChangesSaveService.save( + context, + actionDataView, + request, + executionContext, + SessionManager.getCurrentSession() + ); + if (result.isConnectionInvalidated()) { + this.invalidateConnection(result.getReason()); + } + return result; + } catch (IllegalArgumentException e) { + return this.rejectedSave(dataView, e.getMessage()); + } + } + + private void invalidateConnection(String reason) { + closeQuietly(this.detachConnection()); + log.warn("QueryConsole connection invalidated, reason={}", reason); + } + + private Connection detachConnection() { + Connection old = this.conn; + this.conn = null; + this.transactionStateInspector = null; + this.backendSessionId = 0; + return old; + } + + private static void closeQuietly(Connection connection) { + if (connection == null) { + return; + } + try { + connection.close(); + } catch (SQLException | RuntimeException e) { + log.warn("close query console connection failed", e); + } + } + + private static String unsupportedQueryMutation(SaveChangesRequest request) { + if (request == null) { + return null; + } + if (request.getInsertRows() != null && !request.getInsertRows().isEmpty()) { + return QUERY_INSERT_NOT_SUPPORTED; + } + if (request.getDeleteRows() != null && !request.getDeleteRows().isEmpty()) { + return QUERY_DELETE_NOT_SUPPORTED; + } + return null; + } + + private SaveChangesPreviewResult rejectedPreview(DataView dataView, String reason) { + SaveChangesPreviewResult result = new SaveChangesPreviewResult(); + result.setSuccess(false); + result.setAllowed(false); + result.setReason(reason); + result.setDataView(dataView.getId()); + return result; + } + + private SaveChangesResult rejectedSave(DataView dataView, String reason) { + SaveChangesResult result = new SaveChangesResult(); + result.setSuccess(false); + result.setAllowed(false); + result.setReason(reason); + result.setDataView(dataView.getId()); + return result; + } + + private void onGetExecutionPlan(QueryConsoleAction action) { + String requestId = StringUtils.trimToEmpty(action.getRequestId()); + String sql = action.getData() == null ? "" : String.valueOf(action.getData()); + if (StringUtils.isBlank(requestId)) { + this.getMessager().send(Message.error("Invalid plan request", PlanCodes.MISSING_REQUEST_ID)); + return; + } + + String previousStatus = this.getState().getExecutionStatus(); + QueryConsolePlanContext planContext = null; + ActiveExecution execution = null; + String serverVersion = null; try { - if (this.currentPlan != null) { - this.currentPlan.cancel(); - this.getConsoleLogger().error("cancel query: %s", this.currentPlan.getTargetSQL()); + Connection connection = this.getConnection(); + try { + serverVersion = connection.getMetaData().getDatabaseProductVersion(); + } catch (SQLException ignored) { } + planContext = new QueryConsolePlanContext( + connection, + this.transactionStateInspector, + new AtomicBoolean(false), + System.currentTimeMillis() + PlanLimits.DEFAULT_TIMEOUT_MS, + serverVersion + ); + QueryConsolePlanContext contextRef = planContext; + execution = new ActiveExecution(sql, contextRef::cancelCurrent, false); + this.currentExecution = execution; + this.getState().setCanCancel(true); + this.stateManager.commit(); + + if (this.sendCancelledPlanIfRequested(requestId, sql, serverVersion, planContext)) { + return; + } + + Session session = SessionManager.getCurrentSession(); + ACLResult aclResult = session.checkACL(sql, connection); + if (this.sendCancelledPlanIfRequested(requestId, sql, serverVersion, planContext)) { + return; + } + if (!this.canExecuteStatement(session, sql, aclResult)) { + PlanStatus status = this.planRequestCancelled(planContext) + ? PlanStatus.CANCELLED + : PlanStatus.ERROR; + String code = status == PlanStatus.CANCELLED ? PlanCodes.CANCELLED : PlanCodes.PLAN_PERMISSION_DENIED; + this.sendExecutionPlan(this.planResult( + requestId, + serverVersion, + sql, + status, + PlanEffects.unchangedReuse(), + PlanDiagnostic.of(code, MessageUtils.get("ACLRejectError")) + )); + return; + } + if (this.sendCancelledPlanIfRequested(requestId, sql, serverVersion, planContext)) { + return; + } + ExecutionPlan plan = new ExecutionPlanService().explainEstimated( + this.datasource, + planContext, + sql, + requestId + ); + if (this.connectionDetachedOrUnusable()) { + this.invalidateConnection("execution-plan-cancel"); + this.sendExecutionPlan(this.planResult( + requestId, + serverVersion, + sql, + PlanStatus.CONNECTION_INVALIDATED, + PlanEffects.discard(PlanEffects.SessionState.UNKNOWN, PlanEffects.TransactionState.UNKNOWN), + PlanDiagnostic.of(PlanCodes.CONNECTION_INVALIDATED, "Connection was discarded during cancel") + )); + return; + } + this.sendExecutionPlan(plan); + } catch (ConnectionInvalidatedException e) { + this.invalidateConnection("execution-plan"); + ExecutionPlan plan = e.partialResult() == null + ? this.planResult( + requestId, + null, + sql, + PlanStatus.CONNECTION_INVALIDATED, + PlanEffects.discard(PlanEffects.SessionState.UNKNOWN, PlanEffects.TransactionState.UNKNOWN), + e.originalError() == null + ? PlanDiagnostic.of(PlanCodes.CONNECTION_INVALIDATED, StringUtils.defaultString(e.getMessage())) + : e.originalError() + ) + : ExecutionPlan.fromDialect( + requestId, + this.datasource.getExecutionPlanDialect().database(), + PlanMode.ESTIMATED, + sql, + e.partialResult() + ); + if (plan.status() != PlanStatus.CONNECTION_INVALIDATED) { + plan = new ExecutionPlan( + plan.requestId(), + plan.database(), + plan.serverVersion(), + plan.mode(), + PlanStatus.CONNECTION_INVALIDATED, + plan.sql(), + plan.roots(), + plan.rawText(), + plan.rawFormat(), + plan.rawFormatVersion(), + plan.rawTruncated(), + plan.capabilities(), + plan.prerequisites(), + PlanEffects.discard(PlanEffects.SessionState.UNKNOWN, PlanEffects.TransactionState.UNKNOWN), + plan.error() == null + ? PlanDiagnostic.of(PlanCodes.CONNECTION_INVALIDATED, StringUtils.defaultString(e.getMessage())) + : plan.error(), + plan.warnings() + ); + } + this.sendExecutionPlan(plan); } catch (SQLException e) { + if (this.connectionDetachedOrUnusable()) { + this.invalidateConnection("execution-plan"); + this.sendExecutionPlan(this.planResult( + requestId, + serverVersion, + sql, + PlanStatus.CONNECTION_INVALIDATED, + PlanEffects.discard(PlanEffects.SessionState.UNKNOWN, PlanEffects.TransactionState.UNKNOWN), + new PlanDiagnostic( + PlanCodes.CONNECTION_INVALIDATED, + StringUtils.defaultString(e.getMessage()), + e.getSQLState(), + Integer.toString(e.getErrorCode()) + ) + )); + return; + } + boolean cancelled = this.planRequestCancelled(planContext) + || "57014".equals(e.getSQLState()) + || (e.getMessage() != null && e.getMessage().toLowerCase().contains("cancel")); + this.sendExecutionPlan(this.planResult( + requestId, + serverVersion, + sql, + cancelled ? PlanStatus.CANCELLED : PlanStatus.ERROR, + PlanEffects.unchangedReuse(), + new PlanDiagnostic( + cancelled ? PlanCodes.CANCELLED : PlanCodes.SQL_ERROR, + StringUtils.defaultString(e.getMessage()), + e.getSQLState(), + Integer.toString(e.getErrorCode()) + ) + )); + } finally { + if (this.currentExecution == execution) { + this.currentExecution = null; + } + this.getState().setCanCancel(false); + this.getState().setExecutionStatus(previousStatus); + this.stateManager.commit(); + } + } + + private boolean sendCancelledPlanIfRequested( + String requestId, + String sql, + String serverVersion, + QueryConsolePlanContext planContext + ) { + if (!this.planRequestCancelled(planContext)) { + return false; + } + if (this.connectionDetachedOrUnusable()) { + this.invalidateConnection("execution-plan-cancel"); + this.sendExecutionPlan(this.planResult( + requestId, + serverVersion, + sql, + PlanStatus.CONNECTION_INVALIDATED, + PlanEffects.discard(PlanEffects.SessionState.UNKNOWN, PlanEffects.TransactionState.UNKNOWN), + PlanDiagnostic.of(PlanCodes.CONNECTION_INVALIDATED, "Connection was discarded during cancel") + )); + return true; + } + this.sendExecutionPlan(this.planResult( + requestId, + serverVersion, + sql, + PlanStatus.CANCELLED, + PlanEffects.unchangedReuse(), + PlanDiagnostic.of(PlanCodes.CANCELLED, "Execution plan request cancelled") + )); + return true; + } + + private boolean planRequestCancelled(QueryConsolePlanContext planContext) { + return planContext != null && planContext.isCancelled(); + } + + private boolean connectionDetachedOrUnusable() { + Connection connection = this.conn; + if (connection == null) { + return true; + } + try { + return connection.isClosed(); + } catch (SQLException e) { + return true; + } + } + + private ExecutionPlan planResult( + String requestId, + String serverVersion, + String sql, + PlanStatus status, + PlanEffects effects, + PlanDiagnostic error + ) { + return new ExecutionPlan( + requestId, + this.datasource.getExecutionPlanDialect().database(), + serverVersion, + PlanMode.ESTIMATED, + status, + sql, + java.util.List.of(), + null, + null, + null, + false, + this.datasource.getExecutionPlanDialect().capabilities(), + java.util.List.of(), + effects, + error, + java.util.List.of() + ); + } + + private void sendExecutionPlan(ExecutionPlan plan) { + this.getPacketIO().sendPacket(Packet.TYPE_EXECUTION_PLAN, ExecutionPlanJson.toJsonTree(plan)); + } + + public void onCancel() { + var execution = this.currentExecution; + if (this.stateManager != null) { + this.getState().setExecutionStatus(EXECUTION_STATUS_CANCELLED); + this.getState().setCanCancel(false); + this.getState().setInQuery(false); + this.stateManager.commit(); + this.getConsoleLogger().warn("cancel query: %s", execution == null ? "" : execution.sql()); + } + CANCEL_EXECUTOR.execute(() -> this.interruptExecution(execution)); + } + + private void interruptExecution(ActiveExecution execution) { + if (execution == null) { + return; + } + if (execution.abortOnCancel()) { + Connection connectionToAbort = this.conn; + CANCEL_EXECUTOR.execute(() -> this.abortPhysicalConnection(execution, connectionToAbort)); + } + CANCEL_EXECUTOR.execute(this::tryKillServerQuery); + try { + execution.cancelAction().cancel(); + } catch (SQLException | RuntimeException e) { log.error("cancel failed ", e); } } + private void abortPhysicalConnection(ActiveExecution execution, Connection connection) { + if (execution == null || connection == null || this.currentExecution != execution) { + return; + } + try { + connection.abort(CANCEL_EXECUTOR); + } catch (SQLException | RuntimeException | AbstractMethodError e) { + log.warn("connection abort failed", e); + } + if (this.currentExecution == execution && this.conn == connection) { + this.detachConnection(); + } + } + + private void captureBackendSessionId(Connection connection) { + String sql = switch (this.datasource.getDruidDbType()) { + case mysql, mariadb -> "SELECT CONNECTION_ID()"; + case postgresql -> "SELECT pg_backend_pid()"; + default -> null; + }; + this.backendSessionId = 0; + if (sql == null) { + return; + } + try (Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery(sql)) { + if (resultSet.next()) { + this.backendSessionId = resultSet.getLong(1); + } + } catch (SQLException e) { + log.debug("capture backend session id failed", e); + } + } + + private void tryKillServerQuery() { + long sessionId = this.backendSessionId; + if (sessionId <= 0) { + return; + } + String killSql = switch (this.datasource.getDruidDbType()) { + case mysql, mariadb -> "KILL QUERY " + sessionId; + case postgresql -> "SELECT pg_cancel_backend(" + sessionId + ")"; + default -> null; + }; + if (killSql == null) { + return; + } + Connection killer = null; + try { + killer = this.datasource.getConnectionManager().getPhysicalConnection(); + try (Statement statement = killer.createStatement()) { + statement.execute(killSql); + } + } catch (SQLException e) { + log.warn("server-side query cancel failed: {}", e.getMessage()); + } finally { + closeQuietly(killer); + } + } + + public void handleCancel() { + this.sqlChunkTransfers.cancelAll(); + this.onCancel(); + } + public void onManualChangeContext(String context) { if (!this.isAllowedContext(context)) { return; @@ -262,6 +969,12 @@ public void onManualChangeContext(String context) { this.stateManager.commit(); this.getSqlActuator().changeSchema(allowedContext); + var connectionManager = this.getDatasource().getConnectionManager(); + // 只有当前 UI 上下文本身就是 JDBC database 时,才同步连接池上下文,避免 PostgreSQL schema 被当 database。 + if (StringUtils.isNotBlank(allowedContext) && + StringUtils.equals(connectionManager.getContextKey(), connectionManager.getDatabaseContextKey())) { + connectionManager.setDatabaseContext(allowedContext); + } this.getState().setCurrentContext(allowedContext); } catch (SQLException e) { @@ -290,120 +1003,292 @@ boolean isAllowedContext(String context) { } public void onSQLFile(String filename) { - if (StringUtils.isBlank(filename) - || filename.contains("/") - || filename.contains("\\") - || filename.contains("..")) { + var filePath = this.resolveSQLFileInSessionTemp(filename); + if (filePath == null) { log.warn("Rejected invalid SQL file name"); return; } - var filePath = SessionManager.getCurrentSession().getTempPath().resolve(filename); - var file = filePath.toFile(); - - if (!file.exists()) { + if (!Files.exists(filePath, LinkOption.NOFOLLOW_LINKS)) { this.getConsoleLogger().error("%s: %s", MessageUtils.get("msg.error.file_not_found"), filename); return; } - if (!file.isFile()) { + if (!Files.isRegularFile(filePath, LinkOption.NOFOLLOW_LINKS)) { this.getConsoleLogger().error("%s: %s", MessageUtils.get("msg.error.file_not_file"), filename); return; } - if (!file.canRead()) { + if (!Files.isReadable(filePath)) { this.getConsoleLogger().error("%s: %s", MessageUtils.get("msg.error.file_not_readable"), filename); return; } + var shouldDelete = false; try { - var sql = Files.readString(file.toPath()); + shouldDelete = true; + String sql; + try { + try (var inputStream = Files.newInputStream( + filePath, StandardOpenOption.READ, LinkOption.NOFOLLOW_LINKS)) { + sql = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + } + } catch (IOException | SecurityException e) { + this.getConsoleLogger().error("%s: %s", MessageUtils.get("msg.error.file_read_error"), e.getMessage()); + return; + } this.onSQL(sql); - } catch (IOException e) { - this.getConsoleLogger().error("%s: %s", MessageUtils.get("msg.error.file_read_error"), e.getMessage()); } finally { - file.delete(); + if (shouldDelete) { + try { + Files.deleteIfExists(filePath); + } catch (IOException | SecurityException e) { + log.warn("Failed to delete session SQL file", e); + } + } + } + } + + private Path resolveSQLFileInSessionTemp(String filename) { + if (StringUtils.isBlank(filename) + || StringUtils.equalsAny(filename, ".", "..") + || filename.contains("..") + || filename.contains("/") + || filename.contains("\\")) { + return null; + } + + try { + var requested = Path.of(filename); + if (requested.isAbsolute() + || requested.getNameCount() != 1 + || !requested.equals(requested.getFileName())) { + return null; + } + + var basePath = SessionManager.getCurrentSession().getTempPath() + .toAbsolutePath() + .normalize(); + var resolvedPath = basePath.resolve(requested.getFileName()).normalize(); + return resolvedPath.startsWith(basePath) ? resolvedPath : null; + } catch (InvalidPathException | SecurityException e) { + return null; } } public void onSQL(String sql) { this.getState().setInQuery(true); + if (this.isCancelled()) { + this.getState().setCanCancel(false); + this.getState().setInQuery(false); + this.stateManager.commit(); + return; + } + this.getState().setExecutionStatus(EXECUTION_STATUS_RUNNING); + this.getState().setCanCancel(true); this.stateManager.commit(); var session = SessionManager.getCurrentSession(); - var aclResult = session.checkACL(sql, this.getConnection()); - if (aclResult != null) { - if (aclResult.getRiskLevel() == Common.RiskLevel.Reject || aclResult.getRiskLevel() == Common.RiskLevel.ReviewReject) { - this.getConsoleLogger().error("%s", MessageUtils.get("ACLRejectError")); - CommandRecord commandRecord = new CommandRecord(sql); - commandRecord.setRiskLevel(aclResult.getRiskLevel()); - session.recordCommand(commandRecord); + try { + if (this.consoleMode) { + this.runRawConsoleSQL(sql, session); + } else { + this.runQuerySQL(sql, session); + } + this.ensureCurrentSchema(); + } catch (ParserException e) { + this.getState().setExecutionStatus(EXECUTION_STATUS_ERROR); + this.getConsoleLogger().error("%s: %s", MessageUtils.get("ParseError"), e.getMessage()); + this.getPacketIO().sendPacket("message", Message.error(MessageUtils.get("ParseError"), e.getMessage())); + this.sendSQLError("parse", MessageUtils.get("ParseError"), e.getMessage(), sql, null); + } catch (SQLException e) { + if (!StringUtils.equals(this.getState().getExecutionStatus(), EXECUTION_STATUS_CANCELLED)) { + this.getState().setExecutionStatus(EXECUTION_STATUS_ERROR); + this.getConsoleLogger().error("%s: %s", MessageUtils.get("ExecuteError"), e.getMessage()); + this.getPacketIO().sendPacket("message", Message.error(MessageUtils.get("ExecuteError"), e.getMessage())); + this.sendSQLError("execute", MessageUtils.get("ExecuteError"), e.getMessage(), sql, e); + } + } finally { + if (StringUtils.equals(this.getState().getExecutionStatus(), EXECUTION_STATUS_RUNNING)) { + this.getState().setExecutionStatus(EXECUTION_STATUS_SUCCESS); + } + this.getState().setInQuery(false); + this.getState().setCanCancel(false); + this.stateManager.commit(); + } + } - this.getState().setInQuery(false); - this.stateManager.commit(); - return; + private void runQuerySQL(String sql, Session session) throws SQLException { + var statements = this.getSqlActuator().parseSQL(SQL.of(sql)); + var clearOthers = true; + for (String statement : statements) { + if (this.isCancelled()) { + break; + } + var aclResult = session.checkACL(statement, this.getConnection()); + if (!this.canExecuteStatement(session, statement, aclResult)) { + break; + } + var dataView = this.runSingleSQL(statement, aclResult); + if (!dataView.isHasTable()) { + this.logAffectedRows(dataView); + } else { + this.sendDataView(dataView, clearOthers); + clearOthers = false; } + } + } - if (aclResult.isNotify()) { + private void runRawConsoleSQL(String sql, Session session) throws SQLException { + Connection connection = this.getConnection(); + for (String statement : ConsoleStatementBoundaryScanner.split(sql)) { + if (this.isCancelled()) { + break; + } + ACLResult aclResult = session.checkACL(statement, connection); + if (!this.canExecuteStatement(session, statement, aclResult)) { + break; + } + this.emitRawConsoleSQL(statement, aclResult, connection); + } + } - var dialog = new Dialog(MessageUtils.get("Warning")); - dialog.setBody(MessageUtils.get("CommandWarningDialogMessage")); - var countDownLatch = new CountDownLatch(1); - AtomicBoolean hasNext = new AtomicBoolean(true); + private void emitRawConsoleSQL(String sql, ACLResult aclResult, Connection connection) throws SQLException { + SQLQueryResult executionResult = this.executeRawConsoleSQL(sql, aclResult, connection); + if (executionResult.getResults().isEmpty()) { + this.getConsoleLogger().success("%s", MessageUtils.get("ExecuteSuccess")); + } + for (SQLQueryResult result : executionResult.getResults()) { + this.getConsoleLogger().success(result); + if (!result.isHasResultSet()) { + continue; + } - dialog.addButton(new Button(MessageUtils.get("Submit"), "submit", countDownLatch::countDown)); + DataView dataView = new DataView( + UUID.randomUUID().toString(), sql, this.getPacketIO(), this.getConsoleLogger() + ); + dataView.setSql(sql); + dataView.setLoadDataInterface((ignored) -> result); + dataView.loadData(); + this.sendDataView(dataView, false); + } + } - dialog.addButton(new Button(MessageUtils.get("Cancel"), "cancel", () -> { - hasNext.set(false); - countDownLatch.countDown(); - this.getConsoleLogger().warn(MessageUtils.get("ExecutionCanceled")); - })); + private SQLQueryResult executeRawConsoleSQL(String sql, ACLResult aclResult, Connection connection) + throws SQLException { + if (this.isCancelled()) { + throw new SQLException(MessageUtils.get("ExecutionCanceled")); + } + SQLActuator actuator = this.datasource.getConnectionManager().getSqlActuator().withConnection(connection); + SQLExecutePlan plan = actuator.createPlan(SQL.of(sql)); + plan.setAclResult(aclResult); + ActiveExecution execution = new ActiveExecution(sql, plan::cancel); + this.currentExecution = execution; + this.getState().setCanCancel(true); + this.stateManager.commit(); + try { + this.getConsoleLogger().info("execute sql: %s", sql); + return actuator.executeRawWithAudit(plan); + } finally { + if (this.currentExecution == execution) { + this.currentExecution = null; + } + this.getState().setCanCancel(false); + this.stateManager.commit(); + } + } - SessionManager.getCurrentSession().getController().showDialog(dialog); + private boolean isCancelled() { + return StringUtils.equals(this.getState().getExecutionStatus(), EXECUTION_STATUS_CANCELLED); + } - try { - countDownLatch.await(); + private void logAffectedRows(DataView dataView) { + this.getConsoleLogger().success("%s , %s: %d", + MessageUtils.get("ExecuteSuccess"), + MessageUtils.get("AffectedRows"), dataView.getUpdateCount()); + } - if (!hasNext.get()) { - this.getState().setInQuery(false); - this.stateManager.commit(); - return; - } + private void sendSQLError(String kind, String title, String message, String sql, SQLException exception) { + var error = new LinkedHashMap(); + error.put("kind", kind); + error.put("title", StringUtils.defaultString(title)); + error.put("message", StringUtils.defaultString(message)); + error.put("sql", StringUtils.defaultString(sql)); + error.put("timestamp", System.currentTimeMillis()); + if (exception != null) { + if (StringUtils.isNotBlank(exception.getSQLState())) { + error.put("sqlState", exception.getSQLState()); + } + error.put("vendorCode", exception.getErrorCode()); + } + this.getPacketIO().sendPacket("sql_error", error); + } - } catch (InterruptedException e) { - this.getState().setInQuery(false); - this.stateManager.commit(); + private boolean canExecuteStatement(Session session, String sql, ACLResult aclResult) { + if (aclResult == null) { + return true; + } + if (aclResult.getRiskLevel() == Common.RiskLevel.Reject || + aclResult.getRiskLevel() == Common.RiskLevel.ReviewReject || + aclResult.getRiskLevel() == Common.RiskLevel.ReviewCancel) { + this.getState().setExecutionStatus( + aclResult.getRiskLevel() == Common.RiskLevel.ReviewCancel + ? EXECUTION_STATUS_CANCELLED + : EXECUTION_STATUS_ERROR + ); + String rejectMessage = this.aclRejectMessage(aclResult); + this.getConsoleLogger().error("%s", rejectMessage); + this.sendSQLError("acl", rejectMessage, StringUtils.defaultString(sql), sql, null); + CommandRecord commandRecord = new CommandRecord(sql); + commandRecord.applyAcl(aclResult); + session.recordCommand(commandRecord); + return false; + } + return !aclResult.isNotify() || this.confirmStatementWarning(session); + } - this.getConsoleLogger().error("获取结果失败!"); - } finally { - SessionManager.getCurrentSession().getController().closeDialog(); - } - } + private String aclRejectMessage(ACLResult aclResult) { + if (StringUtils.isNotBlank(aclResult.getMessage())) { + return aclResult.getMessage(); } + if (aclResult.getRiskLevel() == Common.RiskLevel.ReviewCancel) { + return MessageUtils.get("UserCancelCommandReviewError"); + } + return MessageUtils.get("ACLRejectError"); + } + private boolean confirmStatementWarning(Session session) { + var dialog = new Dialog(MessageUtils.get("Warning")); + dialog.setBody(MessageUtils.get("CommandWarningDialogMessage")); + var countDownLatch = new CountDownLatch(1); + AtomicBoolean hasNext = new AtomicBoolean(true); + + dialog.addButton(new Button(MessageUtils.get("Submit"), "submit", countDownLatch::countDown)); + dialog.addButton(new Button(MessageUtils.get("Cancel"), "cancel", () -> { + hasNext.set(false); + this.getState().setExecutionStatus(EXECUTION_STATUS_CANCELLED); + countDownLatch.countDown(); + this.getConsoleLogger().warn(MessageUtils.get("ExecutionCanceled")); + })); + + var controller = session.getController(); + DialogHandle dialogHandle = controller.showDialog(dialog, () -> { + hasNext.set(false); + countDownLatch.countDown(); + }); try { - var stmts = this.getSqlActuator().parseSQL(SQL.of(sql)); - var clearOthers = true; - for (String stmt : stmts) { - var dataView = this.runSingleSQL(stmt, aclResult); - if (!dataView.isHasTable()) { - this.getConsoleLogger().success("%s , %s: %d", - MessageUtils.get("ExecuteSuccess"), - MessageUtils.get("AffectedRows"), dataView.getUpdateCount()); - } else { - this.sendDataView(dataView, clearOthers); - clearOthers = false; - } + if (!countDownLatch.await(WARNING_DIALOG_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + hasNext.set(false); + this.getState().setExecutionStatus(EXECUTION_STATUS_CANCELLED); + dialogHandle.cancel(); + this.getConsoleLogger().warn(MessageUtils.get("ExecutionCanceled")); } - this.ensureCurrentSchema(); - } catch (ParserException e) { - this.getConsoleLogger().error("%s: %s", MessageUtils.get("ParseError"), e.getMessage()); - this.getPacketIO().sendPacket("message", Message.error(MessageUtils.get("ParseError"), e.getMessage())); - } catch (SQLException e) { - this.getConsoleLogger().error("%s: %s", MessageUtils.get("ExecuteError"), e.getMessage()); - this.getPacketIO().sendPacket("message", Message.error(MessageUtils.get("ExecuteError"), e.getMessage())); + return hasNext.get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + this.getState().setExecutionStatus(EXECUTION_STATUS_ERROR); + this.getConsoleLogger().error("获取结果失败!"); + return false; } finally { - this.getState().setInQuery(false); - this.getState().setCanCancel(false); - this.stateManager.commit(); + dialogHandle.close(); } } @@ -434,79 +1319,154 @@ private void ensureCurrentSchema() { private DataView runSingleSQL(String sql, ACLResult aclResult) throws SQLException { - SQLExecutePlan plan = this.datasource - .getConnectionManager() - .getSqlActuator() - .withConnection(this.getConnection()) - .createPlan(SQL.of(sql)); - - plan.setAclResult(aclResult); - DataView dataView = new DataView(plan.getSourceSQL(), this.getPacketIO(), this.getConsoleLogger()); - dataView.setSql(plan.getSourceSQL()); + String sourceSQL = sql; + DataView dataView = new DataView( + UUID.randomUUID().toString(), + sourceSQL, + this.getPacketIO(), + this.getConsoleLogger() + ); + dataView.setSql(sourceSQL); dataView.setLoadDataInterface((sqlQueryParams) -> { sqlQueryParams.setTimeout(this.getState().getTimeout()); + SQLExecutePlan plan = this.datasource + .getConnectionManager() + .getSqlActuator() + .withConnection(this.getConnection()) + .createPlan(SQL.of(sourceSQL)); + plan.setAclResult(aclResult); plan.setSqlQueryParams(sqlQueryParams); - plan.generateTargetSQL(); - - this.getConsoleLogger().info("execute sql: %s", plan.getTargetSQL()); - - this.currentPlan = plan; - + ActiveExecution execution = new ActiveExecution(sourceSQL, plan::cancel); + this.currentExecution = execution; this.getState().setCanCancel(true); this.stateManager.commit(); - var result = plan.executeWithAudit(); - this.currentPlan = null; - - this.getConsoleLogger().success(result); - return result; + try { + plan.generateTargetSQL(); + this.getConsoleLogger().info("execute sql: %s", plan.getTargetSQL()); + var result = plan.executeWithAudit(); + this.getConsoleLogger().success(result); + return result; + } finally { + if (this.currentExecution == execution) { + this.currentExecution = null; + } + this.getState().setCanCancel(false); + this.stateManager.commit(); + } }); dataView.loadData(); - this.getState().setCanCancel(false); - this.stateManager.commit(); - return dataView; } private void sendDataView(DataView dataView, boolean clearOthers) { + if (this.consoleMode) { + this.getPacketIO().sendPacket("console_result", Map.of( + "id", dataView.getId(), + "title", dataView.getTitle(), + "data", dataView.getData(), + "state", dataView.getStateManager().getState() + )); + return; + } + if (clearOthers) { - var forDeleteDataViewTitles = new ArrayList(); - for (var title : this.dataViews.keySet()) { - if (!dataView.getTitle().equals(title) && !this.dataViews.get(title).getStateManager().getState().isPinned()) { - forDeleteDataViewTitles.add(title); + var forDeleteDataViewIds = new ArrayList(); + for (var entry : this.dataViews.entrySet()) { + if (!entry.getValue().getStateManager().getState().isPinned()) { + forDeleteDataViewIds.add(entry.getKey()); } } - forDeleteDataViewTitles.forEach(this.dataViews.keySet()::remove); - this.getPacketIO().sendPacket("close_data_view", forDeleteDataViewTitles); + forDeleteDataViewIds.forEach(this.dataViews.keySet()::remove); + this.getPacketIO().sendPacket("close_data_view", forDeleteDataViewIds); } - if (!this.dataViews.containsKey(dataView.getTitle())) { - this.getPacketIO().sendPacket("new_data_view", Map.of("title", dataView.getTitle())); - } + this.getPacketIO().sendPacket( + "new_data_view", + Map.of("id", dataView.getId(), "title", dataView.getTitle()) + ); - this.dataViews.put(dataView.getTitle(), dataView); - this.getPacketIO().sendPacket("update_data_view", new UpdateDataView(dataView.getTitle(), dataView.getData())); + this.dataViews.put(dataView.getId(), dataView); + this.getPacketIO().sendPacket( + "update_data_view", + new UpdateDataView(dataView.getId(), dataView.getTitle(), dataView.getData()) + ); dataView.getStateManager().commit(); } + private DataView findDataView(String reference) { + var dataView = this.dataViews.get(reference); + if (dataView != null) { + return dataView; + } + DataView matched = null; + for (var candidate : this.dataViews.values()) { + if (StringUtils.equals(candidate.getTitle(), reference)) { + matched = candidate; + } + } + return matched; + } + @Override public void close() { + if (!this.closed.compareAndSet(false, true)) { + return; + } this.sqlChunkTransfers.close(); - if (this.currentPlan != null) { - // flush - var session = SessionManager.getCurrentSession(); - var lastCmd = this.currentPlan.getTargetSQL(); - var cmdRecord = new CommandRecord(lastCmd); - cmdRecord.setError("Abnormal exit"); - session.recordCommand(cmdRecord); + + var currentSession = SessionManager.getCurrentSession(); + if (currentSession == null && this.getPacketIO().getWsSession().getAttributes() != null) { + Object token = this.getPacketIO().getWsSession().getAttributes().get("token"); + if (token instanceof String sessionToken) { + currentSession = SessionManager.getSession(sessionToken); + } + } + if (currentSession != null && currentSession.getController() != null) { + currentSession.getController().cancelDialogs(this.getPacketIO().getWsSession().getId()); + } + + var execution = this.currentExecution; + if (execution != null) { + try { + // flush + var session = SessionManager.getCurrentSession(); + var lastCmd = execution.sql(); + var cmdRecord = new CommandRecord(lastCmd); + cmdRecord.setError("Abnormal exit"); + if (session != null) { + session.recordCommand(cmdRecord); + } + } catch (RuntimeException e) { + log.warn("record interrupted query failed", e); + } + } + this.onCancel(); + + this.executionLock.lock(); + try { + closeQuietly(this.detachConnection()); + } finally { + this.executionLock.unlock(); } log.info("console closed"); } + + @FunctionalInterface + private interface ExecutionCancel { + void cancel() throws SQLException; + } + + private record ActiveExecution(String sql, ExecutionCancel cancelAction, boolean abortOnCancel) { + private ActiveExecution(String sql, ExecutionCancel cancelAction) { + this(sql, cancelAction, true); + } + } } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/QueryConsoleConnectionUnavailableException.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/QueryConsoleConnectionUnavailableException.java new file mode 100644 index 00000000..68a244d1 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/QueryConsoleConnectionUnavailableException.java @@ -0,0 +1,7 @@ +package org.jumpserver.chen.framework.console; + +final class QueryConsoleConnectionUnavailableException extends RuntimeException { + QueryConsoleConnectionUnavailableException(Throwable cause) { + super("Query console connection is unavailable", cause); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/action/DataViewAction.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/action/DataViewAction.java index 06d82e29..a1378d9c 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/action/DataViewAction.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/action/DataViewAction.java @@ -14,6 +14,9 @@ public class DataViewAction extends Action { public static final String ACTION_REFRESH = "refresh"; public static final String ACTION_TOGGLE_PINNED = "toggle_pinned"; public static final String ACTION_CHANGE_LIMIT = "change_limit"; + public static final String ACTION_CHANGE_FILTER = "change_filter"; public static final String ACTION_EXPORT = "export"; + public static final String ACTION_SAVE_CHANGES_PREVIEW = "save_changes_preview"; + public static final String ACTION_SAVE_CHANGES = "save_changes"; } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/action/QueryConsoleAction.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/action/QueryConsoleAction.java index 05d0a076..2131859f 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/action/QueryConsoleAction.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/action/QueryConsoleAction.java @@ -12,6 +12,7 @@ public class QueryConsoleAction extends Action { public static final String ACTION_RUN_SQL_FILE = "run_sql_file"; public static final String ACTION_CANCEL = "cancel"; public static final String ACTION_CHANGE_CURRENT_CONTEXT = "change_current_context"; + public static final String ACTION_GET_EXECUTION_PLAN = "get_execution_plan"; - + private String requestId; } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/context/ConsoleContext.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/context/ConsoleContext.java new file mode 100644 index 00000000..18c20a16 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/context/ConsoleContext.java @@ -0,0 +1,10 @@ +package org.jumpserver.chen.framework.console.context; + +public record ConsoleContext( + String nodeKey, + String nodeType, + String database, + String schema, + String table +) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/context/ConsoleContextResolutionException.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/context/ConsoleContextResolutionException.java new file mode 100644 index 00000000..187936c2 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/context/ConsoleContextResolutionException.java @@ -0,0 +1,7 @@ +package org.jumpserver.chen.framework.console.context; + +public class ConsoleContextResolutionException extends IllegalArgumentException { + public ConsoleContextResolutionException(String message) { + super(message); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/context/ConsoleContextResolver.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/context/ConsoleContextResolver.java new file mode 100644 index 00000000..611cb78d --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/context/ConsoleContextResolver.java @@ -0,0 +1,45 @@ +package org.jumpserver.chen.framework.console.context; + +import org.apache.commons.lang3.StringUtils; +import org.jumpserver.chen.framework.console.entity.request.Connect; +import org.jumpserver.chen.framework.datasource.ResourceBrowser; + +import java.util.Set; + +public class ConsoleContextResolver { + private static final Set QUERY_NODE_TYPES = Set.of("datasource", "database", "schema", "table"); + private static final Set DATA_VIEW_NODE_TYPES = Set.of("table", "view"); + + private final ResourceBrowser resourceBrowser; + + public ConsoleContextResolver(ResourceBrowser resourceBrowser) { + this.resourceBrowser = resourceBrowser; + } + + public ConsoleContext resolve(String submittedNodeKey, String consoleType) { + if (StringUtils.isBlank(submittedNodeKey) || StringUtils.isBlank(consoleType)) { + throw new ConsoleContextResolutionException("Invalid console context"); + } + + var node = this.resourceBrowser.getIndexedNode(submittedNodeKey); + if (node == null || !isAllowedNodeType(consoleType, node.type())) { + throw new ConsoleContextResolutionException("Invalid console context"); + } + + return new ConsoleContext( + node.key(), + node.type(), + node.database(), + node.schema(), + node.table() + ); + } + + private static boolean isAllowedNodeType(String consoleType, String nodeType) { + return switch (consoleType) { + case Connect.CONSOLE_TYPE_QUERY, Connect.CONSOLE_TYPE_CONSOLE -> QUERY_NODE_TYPES.contains(nodeType); + case Connect.CONSOLE_TYPE_DATA_VIEW -> DATA_VIEW_NODE_TYPES.contains(nodeType); + default -> false; + }; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/DataView.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/DataView.java index b9f4af35..fc7fda7c 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/DataView.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/DataView.java @@ -2,12 +2,14 @@ import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.ToString; import org.jumpserver.chen.framework.console.action.DataViewAction; import org.jumpserver.chen.framework.console.component.Logger; import org.jumpserver.chen.framework.console.dataview.export.DataExport; import org.jumpserver.chen.framework.console.entity.response.SQLResult; import org.jumpserver.chen.framework.console.state.DataViewState; import org.jumpserver.chen.framework.console.state.StateManager; +import org.jumpserver.chen.framework.datasource.entity.resource.Field; import org.jumpserver.chen.framework.datasource.sql.SQLQueryParams; import org.jumpserver.chen.framework.datasource.sql.SQLQueryResult; import org.jumpserver.chen.framework.i18n.MessageUtils; @@ -17,7 +19,9 @@ import org.jumpserver.chen.framework.ws.io.PacketIO; import java.io.File; +import java.security.SecureRandom; import java.sql.SQLException; +import java.util.Base64; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -25,6 +29,10 @@ @EqualsAndHashCode(callSuper = true) @Data public class DataView extends SQLResult { + public static final String ROW_REF_KEY = "__chenRowRef"; + private static final SecureRandom ROW_REF_RANDOM = new SecureRandom(); + + private final String id; private final String title; private final StateManager stateManager; private LoadDataInterface loadDataInterface; @@ -35,10 +43,18 @@ public class DataView extends SQLResult { private DataViewState state; private Logger consoleLogger; + @ToString.Exclude + @EqualsAndHashCode.Exclude + private final Map rowRefPrimaryKeys = new HashMap<>(); public DataView(String title, PacketIO packetIO, Logger logger) { + this(title, title, packetIO, logger); + } + + public DataView(String id, String title, PacketIO packetIO, Logger logger) { + this.id = id; this.title = title; - this.state = new DataViewState(title); + this.state = new DataViewState(this.id, title); this.stateManager = new StateManager<>(this.state, packetIO); this.consoleLogger = logger; } @@ -67,6 +83,9 @@ public void doAction(DataViewAction action) throws SQLException { case DataViewAction.ACTION_CHANGE_LIMIT -> { this.changeLimit(this.parseLimit(action.getData())); } + case DataViewAction.ACTION_CHANGE_FILTER -> { + this.changeFilter(this.parseFilter(action.getData())); + } case DataViewAction.ACTION_EXPORT -> { var data = (Map) action.getData(); var scope = data.get("scope"); @@ -90,10 +109,22 @@ private int parseLimit(Object data) throws SQLException { return limit; } + private String parseFilter(Object data) throws SQLException { + if (!(data instanceof String filter)) { + throw new SQLException("Invalid data view filter"); + } + filter = filter.trim(); + if (filter.length() > 10000) { + throw new SQLException("Data view filter is too long"); + } + return filter; + } + public void loadData() throws SQLException { SQLQueryParams queryParams = new SQLQueryParams(); queryParams.setLimit(this.state.getLimit()); queryParams.setOffset((this.state.getPage() - 1) * this.state.getLimit()); + queryParams.setFilter(this.state.getFilter()); var result = this.loadDataInterface .loadData(queryParams); @@ -109,18 +140,25 @@ private void fullData(SQLQueryResult result) { } this.state.setPaged(result.isPaged()); + this.state.setTruncated(result.isTruncated()); + this.state.setRowLimit(result.getRowLimit()); this.data.getFields().clear(); this.data.getData().clear(); this.getStateManager().getState().setTotal(result.getTotal()); this.fullDataViewData(this.data, result); + this.bindMaskedPrimaryKeyRowRefs(this.data, result); } private void fullDataViewData(DataViewData viewData, SQLQueryResult result) { - viewData.setFields(result.getFields()); + List fields = result.getFields(); + boolean editable = fields.stream() + .anyMatch(Field::isEditable); + viewData.setEditable(editable); + viewData.setFields(fields); Map fieldNumMap = new HashMap<>(); @@ -144,11 +182,36 @@ private void fullDataViewData(DataViewData viewData, SQLQueryResult result) { } } + private void bindMaskedPrimaryKeyRowRefs(DataViewData viewData, SQLQueryResult result) { + this.rowRefPrimaryKeys.clear(); + Field primaryKey = viewData.getFields().stream() + .filter(field -> field != null && field.isPrimaryKey()) + .findFirst() + .orElse(null); + List originals = result.getUnmaskedPrimaryKeyValues(); + if (primaryKey == null || !primaryKey.isMasked() || originals == null || + originals.size() != viewData.getData().size()) { + return; + } + for (int i = 0; i < originals.size(); i++) { + String rowRef = nextRowRef(); + this.rowRefPrimaryKeys.put(rowRef, originals.get(i)); + viewData.getData().get(i).put(ROW_REF_KEY, rowRef); + } + } + + private static String nextRowRef() { + byte[] bytes = new byte[16]; + ROW_REF_RANDOM.nextBytes(bytes); + return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); + } + public void export(String scope, String format) throws SQLException { var session = SessionManager.getCurrentSession(); - CommandRecord command = new CommandRecord(String.format("Export data: %s", this.title)); + CommandRecord command = new CommandRecord(String.format( + MessageUtils.getOrDefault("ExportDataNamed", "Export data: %s"), this.title)); try { if (!SessionManager.getCurrentSession().canDownload()) { @@ -165,16 +228,21 @@ public void export(String scope, String format) throws SQLException { switch (scope) { case "current": f = DataExport.export(format, this.data); - command.setOutput(String.format("%d rows exported", this.data.getData().size())); + command.setOutput(String.format( + MessageUtils.getOrDefault("RowsExported", "%d rows exported"), + this.data.getData().size())); break; case "all": SQLQueryParams queryParams = new SQLQueryParams(); queryParams.setLimit(-1); + queryParams.setFilter(this.state.getFilter()); var result = this.loadDataInterface.loadData(queryParams); var viewData = new DataViewData(); this.fullDataViewData(viewData, result); f = DataExport.export(format, viewData); - command.setOutput(String.format("%d rows exported", result.getData().size())); + command.setOutput(String.format( + MessageUtils.getOrDefault("RowsExported", "%d rows exported"), + result.getData().size())); break; } @@ -251,6 +319,20 @@ public void changeLimit(int limit) throws SQLException { } } + public void changeFilter(String filter) throws SQLException { + var oldFilter = this.state.getFilter(); + var oldPage = this.state.getPage(); + try { + this.state.setFilter(filter); + this.state.setPage(1); + this.loadData(); + } catch (SQLException e) { + this.state.setFilter(oldFilter); + this.state.setPage(oldPage); + throw e; + } + } + public void refresh() throws SQLException { this.loadData(); } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/DataViewData.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/DataViewData.java index 36f69ac8..6f873920 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/DataViewData.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/DataViewData.java @@ -10,6 +10,7 @@ @Data public class DataViewData { + private boolean editable; private List> data = new ArrayList<>(); private List fields = new ArrayList<>(); } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/QueryDataViewTableEditContextFactory.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/QueryDataViewTableEditContextFactory.java new file mode 100644 index 00000000..775f218e --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/QueryDataViewTableEditContextFactory.java @@ -0,0 +1,150 @@ +package org.jumpserver.chen.framework.console.dataview; + +import com.alibaba.druid.DbType; +import org.apache.commons.lang3.StringUtils; +import org.jumpserver.chen.framework.datasource.edit.TableEditContext; +import org.jumpserver.chen.framework.datasource.entity.resource.Field; + +import java.util.List; +import java.util.Objects; + +public class QueryDataViewTableEditContextFactory { + public static final String FIELDS_REQUIRED = "FIELDS_REQUIRED"; + public static final String PRIMARY_KEY_REQUIRED = "PRIMARY_KEY_REQUIRED"; + public static final String PRIMARY_KEY_SOURCE_COLUMN_REQUIRED = "PRIMARY_KEY_SOURCE_COLUMN_REQUIRED"; + public static final String EDITABLE_FIELD_REQUIRED = "EDITABLE_FIELD_REQUIRED"; + public static final String EDITABLE_FIELD_SOURCE_REQUIRED = "EDITABLE_FIELD_SOURCE_REQUIRED"; + public static final String MIXED_SOURCE_TABLE_NOT_SUPPORTED = "MIXED_SOURCE_TABLE_NOT_SUPPORTED"; + public static final String MIXED_SOURCE_SCHEMA_NOT_SUPPORTED = "MIXED_SOURCE_SCHEMA_NOT_SUPPORTED"; + public static final String SOURCE_TABLE_REQUIRED = "SOURCE_TABLE_REQUIRED"; + + public TableEditContext create(DataView dataView, DbType dbType) { + List fields = getRequiredFields(dataView); + + Field primaryKey = getRequiredPrimaryKey(fields); + validatePrimaryKey(primaryKey); + + List editableFields = getRequiredEditableFields(fields); + validateEditableFields(editableFields); + + ensureSingleSource(fields); + + String sourceTable = resolveSourceTable(primaryKey, editableFields); + String sourceSchema = resolveSourceSchema(primaryKey); + + ensureEditableFieldsMatchSource(editableFields, sourceSchema, sourceTable); + + TableEditContext context = new TableEditContext(dataView.getTitle(), sourceSchema, sourceTable, fields, dbType); + context.setRowRefPrimaryKeys(dataView.getRowRefPrimaryKeys()); + return context; + } + + private List getRequiredFields(DataView dataView) { + List fields = dataView.getData().getFields(); + if (fields == null || fields.isEmpty()) { + throw new IllegalArgumentException(FIELDS_REQUIRED); + } + return fields; + } + + private Field getRequiredPrimaryKey(List fields) { + return fields.stream() + .filter(Objects::nonNull) + .filter(Field::isPrimaryKey) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(PRIMARY_KEY_REQUIRED)); + } + + private void validatePrimaryKey(Field primaryKey) { + if (StringUtils.isBlank(primaryKey.getSourceColumn())) { + throw new IllegalArgumentException(PRIMARY_KEY_SOURCE_COLUMN_REQUIRED); + } + } + + private List getRequiredEditableFields(List fields) { + List editableFields = fields.stream() + .filter(Objects::nonNull) + .filter(Field::isEditable) + .toList(); + + if (editableFields.isEmpty()) { + throw new IllegalArgumentException(EDITABLE_FIELD_REQUIRED); + } + + return editableFields; + } + + private void validateEditableFields(List editableFields) { + for (Field field : editableFields) { + if (StringUtils.isBlank(field.getSourceTable()) || StringUtils.isBlank(field.getSourceColumn())) { + throw new IllegalArgumentException(EDITABLE_FIELD_SOURCE_REQUIRED); + } + } + } + + private void ensureSingleSource(List fields) { + ensureSingleSourceTable(fields); + ensureSingleSourceSchema(fields); + } + + private void ensureSingleSourceTable(List fields) { + long sourceTableCount = fields.stream() + .filter(Objects::nonNull) + .map(Field::getSourceTable) + .filter(StringUtils::isNotBlank) + .distinct() + .count(); + + if (sourceTableCount > 1) { + throw new IllegalArgumentException(MIXED_SOURCE_TABLE_NOT_SUPPORTED); + } + } + + private void ensureSingleSourceSchema(List fields) { + long sourceSchemaCount = fields.stream() + .filter(Objects::nonNull) + .map(Field::getSourceSchema) + .filter(StringUtils::isNotBlank) + .distinct() + .count(); + + if (sourceSchemaCount > 1) { + throw new IllegalArgumentException(MIXED_SOURCE_SCHEMA_NOT_SUPPORTED); + } + } + + private String resolveSourceTable(Field primaryKey, List editableFields) { + String sourceTable = primaryKey.getSourceTable(); + + if (StringUtils.isBlank(sourceTable)) { + sourceTable = editableFields.get(0).getSourceTable(); + } + + if (StringUtils.isBlank(sourceTable)) { + throw new IllegalArgumentException(SOURCE_TABLE_REQUIRED); + } + + return sourceTable; + } + + private String resolveSourceSchema(Field primaryKey) { + // 保持原逻辑:sourceSchema 以主键字段为准,不从 editableFields fallback。 + return primaryKey.getSourceSchema(); + } + + private void ensureEditableFieldsMatchSource( + List editableFields, + String sourceSchema, + String sourceTable + ) { + for (Field editableField : editableFields) { + if (!StringUtils.equals(sourceTable, editableField.getSourceTable())) { + throw new IllegalArgumentException(MIXED_SOURCE_TABLE_NOT_SUPPORTED); + } + + if (!StringUtils.equals(sourceSchema, editableField.getSourceSchema())) { + throw new IllegalArgumentException(MIXED_SOURCE_SCHEMA_NOT_SUPPORTED); + } + } + } +} \ No newline at end of file diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/UpdateDataView.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/UpdateDataView.java index b390f4db..e5f75597 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/UpdateDataView.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/UpdateDataView.java @@ -4,10 +4,16 @@ @Data public class UpdateDataView { + private String id; private String title; private DataViewData data; public UpdateDataView(String title, DataViewData data) { + this(title, title, data); + } + + public UpdateDataView(String id, String title, DataViewData data) { + this.id = id; this.title = title; this.data = data; } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/export/DataExport.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/export/DataExport.java index 131757d6..609e8afb 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/export/DataExport.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/export/DataExport.java @@ -17,6 +17,7 @@ import java.text.SimpleDateFormat; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; @@ -63,7 +64,6 @@ public void exportData(String path, DataViewData data) throws Exception { List fields = data.getFields().stream() .filter(f -> !f.getName().equalsIgnoreCase("ROWNUM")) .toList(); - data.setFields(fields); List> rows = data.getData().stream() .map(m -> { @@ -109,58 +109,51 @@ public void exportData(String path, DataViewData data) throws Exception { class DataExportCSV implements DataExportInterface { @Override public void exportData(String path, DataViewData data) throws Exception { - var writer = Files.newBufferedWriter(Path.of(path)); - - List fields = data.getFields().stream() - .filter(f -> !f.getName().equalsIgnoreCase("ROWNUM")) - .toList(); - data.setFields(fields); - - List> rows = data.getData().stream() - .map(m -> { - Map newMap = new LinkedHashMap<>(m); - newMap.remove("ROWNUM"); - return newMap; - }) - .toList(); - - for (Field field : fields) { - writeString(writer, field.getName()); - writer.write(","); - } - writer.newLine(); - for (Map row : rows) { - for (Field field : fields) { - var obj = row.get(field.getName()); - if (obj == null) { - writer.write("NULL"); - writer.write(","); - } else if (obj instanceof Clob clob) { - writer.write(CodeUtils.escapeCsvValue(clob.getSubString(1, (int) clob.length()))); - writer.write(","); - } else if (obj instanceof Date) { - SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - writeString(writer, fmt.format(obj)); - writer.write(","); - } else { - writeString(writer, row.get(field.getName())); - writer.write(","); + try (BufferedWriter writer = Files.newBufferedWriter(Path.of(path))) { + List fields = data.getFields().stream() + .filter(f -> !f.getName().equalsIgnoreCase("ROWNUM")) + .toList(); + + List> rows = data.getData().stream() + .map(m -> { + Map newMap = new LinkedHashMap<>(m); + newMap.remove("ROWNUM"); + return newMap; + }) + .toList(); + + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + writeCsvRow(writer, fields.stream().map(field -> CodeUtils.escapeCsvValue(field.getName())).toList()); + for (Map row : rows) { + List values = new ArrayList<>(fields.size()); + for (Field field : fields) { + values.add(formatCsvCell(row.get(field.getName()), dateFormat)); } + writeCsvRow(writer, values); } - writer.newLine(); } + } + private static void writeCsvRow(BufferedWriter writer, List values) throws IOException { + for (int i = 0; i < values.size(); i++) { + if (i > 0) { + writer.write(","); + } + writer.write(values.get(i)); + } writer.newLine(); - writer.flush(); - writer.close(); } - private static void writeString(BufferedWriter writer, Object object) throws IOException { - var str = object.toString(); - - if (str.contains("\"") || str.contains(",")) { - str = "\"" + str.replace("\"", "\"\"") + "\""; + private static String formatCsvCell(Object obj, SimpleDateFormat dateFormat) throws Exception { + if (obj == null) { + return "NULL"; + } + if (obj instanceof Clob clob) { + return CodeUtils.escapeCsvValue(clob.getSubString(1, (int) clob.length())); + } + if (obj instanceof Date) { + return CodeUtils.escapeCsvValue(dateFormat.format(obj)); } - writer.write(str); + return CodeUtils.escapeCsvValue(obj.toString()); } } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/entity/request/Connect.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/entity/request/Connect.java index f339325a..acced2dc 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/entity/request/Connect.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/entity/request/Connect.java @@ -6,6 +6,7 @@ public class Connect { public static final String CONSOLE_TYPE_QUERY = "query"; + public static final String CONSOLE_TYPE_CONSOLE = "console"; public static final String CONSOLE_TYPE_DATA_VIEW = "data_view"; private String nodeKey; diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/entity/request/SaveChangesRequest.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/entity/request/SaveChangesRequest.java new file mode 100644 index 00000000..16d593bb --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/entity/request/SaveChangesRequest.java @@ -0,0 +1,45 @@ +package org.jumpserver.chen.framework.console.entity.request; + +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +public class SaveChangesRequest { + private String schema; + private String table; + private List changes; + private List insertRows; + private List deleteRows; + + @Data + public static class ChangeItem { + private String rowRef; + private String pkColumn; + private Object pkValue; + private boolean pkValueIsNull; + private String sourceColumn; + private Object newValue; + private boolean newValueIsNull; + } + + @Data + public static class InsertRow { + private Map values; + } + + @Data + public static class DeleteRow { + private String rowRef; + private String pkColumn; + private Object pkValue; + private boolean pkValueIsNull; + } + + @Data + public static class CellValue { + private Object value; + private boolean valueIsNull; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/entity/response/SaveChangesPreviewResult.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/entity/response/SaveChangesPreviewResult.java new file mode 100644 index 00000000..4be0c962 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/entity/response/SaveChangesPreviewResult.java @@ -0,0 +1,35 @@ +package org.jumpserver.chen.framework.console.entity.response; + +import lombok.Data; +import org.jumpserver.chen.framework.console.entity.request.SaveChangesRequest; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class SaveChangesPreviewResult { + private boolean success; + private boolean allowed; + private String reason; + private Integer failedChangeIndex; + private SaveChangesRequest.ChangeItem failedChange; + private Object failedOperation; + private String dataView; + private String schema; + private String table; + private int changeCount; + private int updateCount; + private int insertCount; + private int deleteCount; + private List preparedStatements = new ArrayList<>(); + private String auditSql; + + @Data + public static class PreviewItem { + private String operation; + private String sourceColumn; + private String pkColumn; + private String preparedSql; + private List paramsPreview; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/entity/response/SaveChangesResult.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/entity/response/SaveChangesResult.java new file mode 100644 index 00000000..96a5b247 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/entity/response/SaveChangesResult.java @@ -0,0 +1,40 @@ +package org.jumpserver.chen.framework.console.entity.response; + +import lombok.Data; +import org.jumpserver.chen.framework.console.entity.request.SaveChangesRequest; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class SaveChangesResult { + private boolean success; + private boolean allowed; + private boolean databaseChangesApplied; + private boolean databaseCommitted; + private boolean auditSucceeded; + private boolean connectionInvalidated; + private String reason; + private String message; + private Integer failedChangeIndex; + private SaveChangesRequest.ChangeItem failedChange; + private Object failedOperation; + private String dataView; + private String schema; + private String table; + private int changeCount; + private int updateCount; + private int insertCount; + private int deleteCount; + private List statements = new ArrayList<>(); + private String auditSql; + + @Data + public static class ResultItem { + private String operation; + private String sourceColumn; + private String pkColumn; + private String preparedSql; + private int affectedRows; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/plan/ExecutionPlanService.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/plan/ExecutionPlanService.java new file mode 100644 index 00000000..b8627b66 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/plan/ExecutionPlanService.java @@ -0,0 +1,201 @@ +package org.jumpserver.chen.framework.console.plan; + +import com.alibaba.druid.DbType; +import org.jumpserver.chen.framework.datasource.Datasource; +import org.jumpserver.chen.framework.datasource.analysis.SqlStatementAnalysis; +import org.jumpserver.chen.framework.datasource.analysis.SqlStatementAnalyzer; +import org.jumpserver.chen.framework.datasource.plan.ConnectionInvalidatedException; +import org.jumpserver.chen.framework.datasource.plan.DialectPlanResult; +import org.jumpserver.chen.framework.datasource.plan.ExecutionPlan; +import org.jumpserver.chen.framework.datasource.plan.ExecutionPlanCapabilities; +import org.jumpserver.chen.framework.datasource.plan.ExecutionPlanDialect; +import org.jumpserver.chen.framework.datasource.plan.PlanCodes; +import org.jumpserver.chen.framework.datasource.plan.PlanDatabase; +import org.jumpserver.chen.framework.datasource.plan.PlanDiagnostic; +import org.jumpserver.chen.framework.datasource.plan.PlanEffects; +import org.jumpserver.chen.framework.datasource.plan.PlanExecutionContext; +import org.jumpserver.chen.framework.datasource.plan.PlanI18n; +import org.jumpserver.chen.framework.datasource.plan.PlanLimits; +import org.jumpserver.chen.framework.datasource.plan.PlanMode; +import org.jumpserver.chen.framework.datasource.plan.PlanStatus; +import org.jumpserver.chen.framework.datasource.plan.UnsupportedExecutionPlanDialect; + +import java.sql.SQLException; +import java.util.List; + +public final class ExecutionPlanService { + public ExecutionPlan explainEstimated( + Datasource datasource, + PlanExecutionContext context, + String sql, + String requestId + ) throws SQLException { + ExecutionPlanDialect dialect = datasource == null + ? UnsupportedExecutionPlanDialect.getInstance() + : datasource.getExecutionPlanDialect(); + PlanDatabase database = dialect.database(); + ExecutionPlanCapabilities capabilities = dialect.capabilities(); + String safeSql = sql == null ? "" : sql; + String safeRequestId = requestId == null ? "" : requestId; + + if (safeSql.length() > PlanLimits.MAX_SQL_CHARS) { + return failure( + safeRequestId, + database, + safeSql, + capabilities, + PlanStatus.UNSUPPORTED_STATEMENT, + PlanCodes.PLAN_LIMIT_REACHED, + PlanI18n.msg("Plan.SqlExceedsSizeLimit", "SQL exceeds the execution plan size limit") + ); + } + + DbType dbType = datasource == null ? null : datasource.getDruidDbType(); + SqlStatementAnalysis analysis = dbType == null + ? SqlStatementAnalyzer.analyze(com.alibaba.druid.DbType.other, safeSql) + : SqlStatementAnalyzer.analyze(dbType, safeSql); + + if (!analysis.estimable()) { + String code = analysis.rejectionCodes().isEmpty() + ? PlanCodes.UNSUPPORTED_STATEMENT + : analysis.rejectionCodes().get(0); + return failure( + safeRequestId, + database, + analysis.sql().isEmpty() ? safeSql : analysis.sql(), + capabilities, + PlanStatus.UNSUPPORTED_STATEMENT, + code, + rejectionMessage(code) + ); + } + + if (!capabilities.supported()) { + return failure( + safeRequestId, + database, + analysis.sql(), + capabilities, + PlanStatus.UNSUPPORTED_DATABASE, + PlanCodes.UNSUPPORTED_DATABASE, + PlanI18n.msg("Plan.UnsupportedDatabase", "Execution plan is not implemented for this datasource") + ); + } + + try { + DialectPlanResult result = dialect.explainEstimated(context, analysis); + return ExecutionPlan.fromDialect(safeRequestId, database, PlanMode.ESTIMATED, analysis.sql(), result); + } catch (ConnectionInvalidatedException e) { + throw e; + } catch (SQLException e) { + if (isCancelled(e) || (context != null && context.isCancelled())) { + return failure( + safeRequestId, + database, + analysis.sql(), + capabilities, + PlanStatus.CANCELLED, + PlanCodes.CANCELLED, + e.getMessage() == null ? PlanI18n.msg("Plan.RequestCancelled", "Execution plan request cancelled") : e.getMessage(), + e + ); + } + return failure( + safeRequestId, + database, + analysis.sql(), + capabilities, + PlanStatus.ERROR, + PlanCodes.SQL_ERROR, + e.getMessage() == null ? PlanI18n.msg("Plan.EstimateFailed", "Failed to estimate execution plan") : e.getMessage(), + e + ); + } + } + + static String rejectionMessage(String code) { + if (code == null || code.isBlank()) { + return PlanI18n.msg("Plan.StatementCannotBeEstimated", "This statement cannot be estimated"); + } + return switch (code) { + case SqlStatementAnalyzer.PARSE_FAILED -> + PlanI18n.msg("Plan.ParseFailed", "Chen cannot parse this SQL for an estimated plan"); + case SqlStatementAnalyzer.MULTI_STATEMENT -> + PlanI18n.msg("Plan.MultiStatement", "Multiple SQL statements cannot be estimated"); + case SqlStatementAnalyzer.NOT_SINGLE_SELECT -> + PlanI18n.msg("Plan.NotSingleSelect", "Only a single SELECT statement can be estimated"); + case SqlStatementAnalyzer.EMPTY_SQL -> + PlanI18n.msg("Plan.EmptySql", "SQL statement is empty"); + case SqlStatementAnalyzer.NESTED_WRITE -> + PlanI18n.msg("Plan.NestedWrite", "Statements with nested writes cannot be estimated"); + case SqlStatementAnalyzer.LOCKING_READ -> + PlanI18n.msg("Plan.LockingRead", "Locking reads cannot be estimated"); + case SqlStatementAnalyzer.SELECT_INTO -> + PlanI18n.msg("Plan.SelectInto", "SELECT INTO cannot be estimated"); + case SqlStatementAnalyzer.ASSIGNMENT -> + PlanI18n.msg("Plan.Assignment", "Statements with assignment cannot be estimated"); + case SqlStatementAnalyzer.EXPLAIN_WRAPPER -> + PlanI18n.msg("Plan.ExplainWrapper", "Wrapped EXPLAIN statements cannot be estimated"); + case SqlStatementAnalyzer.UNBOUND_PARAMETER -> + PlanI18n.msg("Plan.UnboundParameter", "Statements with unbound parameters cannot be estimated"); + case SqlStatementAnalyzer.DYNAMIC_EXECUTION -> + PlanI18n.msg("Plan.DynamicSql", "Dynamic SQL cannot be estimated"); + case PlanCodes.PLAN_LIMIT_REACHED -> + PlanI18n.msg("Plan.SqlExceedsSizeLimit", "SQL exceeds the execution plan size limit"); + default -> PlanI18n.msg("Plan.StatementCannotBeEstimated", "This statement cannot be estimated"); + }; + } + + private static boolean isCancelled(SQLException e) { + return "57014".equals(e.getSQLState()) + || (e.getMessage() != null && e.getMessage().toLowerCase().contains("cancel")); + } + + private static ExecutionPlan failure( + String requestId, + PlanDatabase database, + String sql, + ExecutionPlanCapabilities capabilities, + PlanStatus status, + String code, + String message + ) { + return failure(requestId, database, sql, capabilities, status, code, message, null); + } + + private static ExecutionPlan failure( + String requestId, + PlanDatabase database, + String sql, + ExecutionPlanCapabilities capabilities, + PlanStatus status, + String code, + String message, + SQLException exception + ) { + PlanDiagnostic error = new PlanDiagnostic( + code, + message, + exception == null ? null : exception.getSQLState(), + exception == null ? null : Integer.toString(exception.getErrorCode()) + ); + return new ExecutionPlan( + requestId, + database, + null, + PlanMode.ESTIMATED, + status, + sql, + List.of(), + null, + null, + null, + false, + capabilities, + List.of(), + PlanEffects.unchangedReuse(), + error, + List.of() + ); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/plan/QueryConsolePlanContext.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/plan/QueryConsolePlanContext.java new file mode 100644 index 00000000..0312b83f --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/plan/QueryConsolePlanContext.java @@ -0,0 +1,147 @@ +package org.jumpserver.chen.framework.console.plan; + +import org.jumpserver.chen.framework.console.transaction.QueryTransactionProbeResult; +import org.jumpserver.chen.framework.console.transaction.QueryTransactionState; +import org.jumpserver.chen.framework.console.transaction.QueryTransactionStateInspector; +import org.jumpserver.chen.framework.datasource.plan.PlanExecutionContext; +import org.jumpserver.chen.framework.datasource.plan.PlanLimits; +import org.jumpserver.chen.framework.datasource.plan.PlanTransactionState; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.SQLTimeoutException; +import java.sql.Statement; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +public final class QueryConsolePlanContext implements PlanExecutionContext { + private final Connection connection; + private final QueryTransactionStateInspector inspector; + private final AtomicBoolean cancelled; + private final AtomicReference currentStatement = new AtomicReference<>(); + private final long deadlineMillis; + private final int maxRawBytes; + private final int maxNodes; + private final int maxDepth; + private final String serverVersion; + private QueryTransactionProbeResult probeSnapshot; + + public QueryConsolePlanContext( + Connection connection, + QueryTransactionStateInspector inspector, + AtomicBoolean cancelled, + long deadlineMillis, + String serverVersion + ) { + this.connection = connection; + this.inspector = inspector; + this.cancelled = cancelled == null ? new AtomicBoolean(false) : cancelled; + this.deadlineMillis = deadlineMillis; + this.maxRawBytes = PlanLimits.MAX_RAW_BYTES; + this.maxNodes = PlanLimits.MAX_NODES; + this.maxDepth = PlanLimits.MAX_DEPTH; + this.serverVersion = serverVersion; + } + + @Override + public Connection connection() { + return connection; + } + + @Override + public PlanTransactionState transactionState() { + return map(snapshot().state()); + } + + @Override + public boolean transactionProbeFailed() { + return snapshot().probeFailed(); + } + + @Override + public String transactionProbeDetail() { + return snapshot().detail(); + } + + @Override + public void registerStatement(Statement statement) { + currentStatement.set(statement); + } + + @Override + public void unregisterStatement(Statement statement) { + currentStatement.compareAndSet(statement, null); + } + + public void cancelCurrent() throws SQLException { + cancelled.set(true); + Statement statement = currentStatement.get(); + if (statement != null) { + statement.cancel(); + } + } + + @Override + public boolean isCancelled() { + return cancelled.get() || System.currentTimeMillis() >= deadlineMillis; + } + + @Override + public void throwIfCancelled() throws SQLException { + if (cancelled.get()) { + throw new SQLException("Execution plan request cancelled", "57014"); + } + if (System.currentTimeMillis() >= deadlineMillis) { + throw new SQLTimeoutException("Execution plan request timed out"); + } + } + + @Override + public long deadlineMillis() { + return deadlineMillis; + } + + @Override + public int maxRawBytes() { + return maxRawBytes; + } + + @Override + public int maxNodes() { + return maxNodes; + } + + @Override + public int maxDepth() { + return maxDepth; + } + + @Override + public String serverVersion() { + return serverVersion; + } + + private QueryTransactionProbeResult snapshot() { + if (probeSnapshot == null) { + if (inspector == null) { + probeSnapshot = new QueryTransactionProbeResult(QueryTransactionState.UNKNOWN, true, "inspector not attached"); + } else { + probeSnapshot = inspector.probeNow(); + } + } + return probeSnapshot; + } + + private static PlanTransactionState map(QueryTransactionState state) { + if (state == null) { + return PlanTransactionState.UNKNOWN; + } + return switch (state) { + case AUTO_COMMIT -> PlanTransactionState.AUTO_COMMIT; + case MANUAL_COMMIT_IDLE -> PlanTransactionState.MANUAL_COMMIT_IDLE; + case TRANSACTION_ACTIVE -> PlanTransactionState.TRANSACTION_ACTIVE; + case TRANSACTION_FAILED -> PlanTransactionState.TRANSACTION_FAILED; + case UNKNOWN -> PlanTransactionState.UNKNOWN; + }; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/state/DataViewState.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/state/DataViewState.java index 4f75ac6a..59f30b47 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/state/DataViewState.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/state/DataViewState.java @@ -8,19 +8,25 @@ @EqualsAndHashCode(callSuper = true) @Data public class DataViewState extends State { + private String id; private int page; private int limit; private int total; + private boolean truncated; + private int rowLimit; private boolean pinned; private boolean paged; + private String filter; - public DataViewState(String title) { + public DataViewState(String id, String title) { super(title); + this.id = id; this.paged = true; this.pinned = false; this.total = 0; this.page = 1; this.limit = 50; + this.filter = ""; } } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/state/QueryConsoleState.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/state/QueryConsoleState.java index 40a6679d..bd1a7795 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/state/QueryConsoleState.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/state/QueryConsoleState.java @@ -9,12 +9,13 @@ @EqualsAndHashCode(callSuper = true) @Data public class QueryConsoleState extends State { - private String currentContext; + private volatile String currentContext; private boolean inQuery; private List contexts; private int timeout; private boolean editorLoading; private boolean canCancel; + private volatile String executionStatus; public QueryConsoleState(String title) { super(title); @@ -23,6 +24,7 @@ public QueryConsoleState(String title) { this.timeout = 30; this.editorLoading = false; this.canCancel = false; + this.executionStatus = ""; } } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/DamengTransactionStateProbe.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/DamengTransactionStateProbe.java new file mode 100644 index 00000000..b3ecac41 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/DamengTransactionStateProbe.java @@ -0,0 +1,65 @@ +package org.jumpserver.chen.framework.console.transaction; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Locale; + +final class DamengTransactionStateProbe implements TransactionStateProbe { + private static final String DRIVER_CONNECTION = "dm.jdbc.driver.DmdbConnection"; + private static final int STATE_MASK = 0x0FFF; + private static final int NOT_STARTED = 0; + private static final int COMMITTED = 32; + private static final int ROLLED_BACK = 64; + private static final String STATE_SQL = + "SELECT STATUS FROM V$TRX WHERE SESS_ID = SESSID"; + + @Override + public QueryTransactionState inspect(Connection connection) { + try { + Class driverClass = Class.forName( + DRIVER_CONNECTION, + false, + connection.getClass().getClassLoader() + ); + Object driverConnection = driverClass.isInstance(connection) + ? connection + : connection.unwrap(driverClass); + int transactionStatus = driverClass.getField("trxStatus").getInt(driverConnection); + QueryTransactionState driverState = map(transactionStatus, connection.getAutoCommit()); + if (driverState != QueryTransactionState.TRANSACTION_ACTIVE) { + return driverState; + } + try (var statement = connection.createStatement(); + var resultSet = statement.executeQuery(STATE_SQL)) { + if (!resultSet.next()) { + return QueryTransactionState.UNKNOWN; + } + return mapServerStatus(resultSet.getString(1)); + } + } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | + SQLException e) { + throw new TransactionStateProbeException(e); + } + } + + static QueryTransactionState map(int transactionStatus, boolean autoCommit) { + int state = transactionStatus & STATE_MASK; + if (state == NOT_STARTED || state == COMMITTED || state == ROLLED_BACK) { + return autoCommit + ? QueryTransactionState.AUTO_COMMIT + : QueryTransactionState.MANUAL_COMMIT_IDLE; + } + return QueryTransactionState.TRANSACTION_ACTIVE; + } + + static QueryTransactionState mapServerStatus(String status) { + if (status == null) { + return QueryTransactionState.UNKNOWN; + } + return switch (status.toUpperCase(Locale.ROOT).trim()) { + case "ACTIVE", "LOCK WAIT" -> QueryTransactionState.TRANSACTION_ACTIVE; + case "ROLLING", "ROLLING_SUSPEND" -> QueryTransactionState.TRANSACTION_FAILED; + default -> QueryTransactionState.UNKNOWN; + }; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/Db2TransactionStateProbe.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/Db2TransactionStateProbe.java new file mode 100644 index 00000000..8b875834 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/Db2TransactionStateProbe.java @@ -0,0 +1,116 @@ +package org.jumpserver.chen.framework.console.transaction; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.sql.Connection; +import java.sql.SQLException; + +final class Db2TransactionStateProbe implements TransactionStateProbe { + private static final String DRIVER_CONNECTION = "com.ibm.db2.jcc.DB2Connection"; + private static final int MAX_UNWRAP_DEPTH = 8; + + @Override + public QueryTransactionState inspect(Connection connection) { + try { + Object driverConnection = resolveDriverConnection(connection); + Class driverClass = Class.forName( + DRIVER_CONNECTION, + false, + driverConnection.getClass().getClassLoader() + ); + boolean inUnitOfWork = (boolean) driverClass + .getMethod("isInDB2UnitOfWork") + .invoke(driverConnection); + return map(inUnitOfWork, connection.getAutoCommit()); + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | + InvocationTargetException | SQLException e) { + throw new TransactionStateProbeException(e); + } + } + + /** + * QueryConsole can expose Druid's statement connection while JCC lives in an isolated + * DriverClassLoader. Peel wrappers before loading the driver interface so reflection uses + * the same loader as the physical connection. + */ + static Object resolveDriverConnection(Connection connection) throws SQLException, ClassNotFoundException { + if (connection == null) { + throw new ClassNotFoundException(DRIVER_CONNECTION + " from null connection"); + } + Connection current = connection; + ClassNotFoundException lastNotFound = null; + for (int depth = 0; depth < MAX_UNWRAP_DEPTH; depth++) { + ClassLoader classLoader = current.getClass().getClassLoader(); + if (classLoader != null) { + try { + Class driverClass = Class.forName(DRIVER_CONNECTION, false, classLoader); + if (driverClass.isInstance(current)) { + return current; + } + if (current.isWrapperFor(driverClass)) { + return current.unwrap(driverClass); + } + } catch (ClassNotFoundException e) { + lastNotFound = e; + } catch (SQLException ignored) { + // This wrapper cannot unwrap with the driver class visible to its loader. + } + } + Connection next = peel(current); + if (next == null || next == current) { + break; + } + current = next; + } + throw lastNotFound != null + ? lastNotFound + : new ClassNotFoundException( + DRIVER_CONNECTION + " from " + connection.getClass().getName() + + " classLoader=" + classLoaderName(connection)); + } + + private static Connection peel(Connection connection) { + Connection viaGetter = invokeConnectionGetter(connection, "getConnection"); + if (viaGetter != null) { + return viaGetter; + } + Connection viaPhysical = invokeConnectionGetter(connection, "getPhysicalConnection"); + if (viaPhysical != null) { + return viaPhysical; + } + try { + Connection unwrapped = connection.unwrap(Connection.class); + if (unwrapped != null && unwrapped != connection) { + return unwrapped; + } + } catch (SQLException ignored) { + } + return connection; + } + + private static Connection invokeConnectionGetter(Connection connection, String methodName) { + try { + Method method = connection.getClass().getMethod(methodName); + Object value = method.invoke(connection); + if (value instanceof Connection next && next != connection) { + return next; + } + } catch (ReflectiveOperationException ignored) { + } + return null; + } + + private static String classLoaderName(Connection connection) { + ClassLoader classLoader = connection.getClass().getClassLoader(); + return classLoader == null ? "null" : classLoader.getClass().getName(); + } + + static QueryTransactionState map(boolean inUnitOfWork, boolean autoCommit) { + if (inUnitOfWork) { + return QueryTransactionState.TRANSACTION_ACTIVE; + } + return autoCommit + ? QueryTransactionState.AUTO_COMMIT + : QueryTransactionState.MANUAL_COMMIT_IDLE; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/MysqlDriverTransactionStateProbe.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/MysqlDriverTransactionStateProbe.java new file mode 100644 index 00000000..570f1352 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/MysqlDriverTransactionStateProbe.java @@ -0,0 +1,119 @@ +package org.jumpserver.chen.framework.console.transaction; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.sql.Connection; +import java.sql.SQLException; + +final class MysqlDriverTransactionStateProbe implements TransactionStateProbe { + private static final String JDBC_CONNECTION = "com.mysql.cj.jdbc.JdbcConnection"; + private static final String SESSION = "com.mysql.cj.Session"; + private static final String SERVER_SESSION = "com.mysql.cj.protocol.ServerSession"; + private static final int MAX_UNWRAP_DEPTH = 8; + + @Override + public QueryTransactionState inspect(Connection connection) { + try { + Object jdbcConnection = resolveDriverConnection(connection); + ClassLoader classLoader = jdbcConnection.getClass().getClassLoader(); + Class jdbcConnectionClass = Class.forName(JDBC_CONNECTION, false, classLoader); + Object session = jdbcConnectionClass.getMethod("getSession").invoke(jdbcConnection); + Class sessionClass = Class.forName(SESSION, false, classLoader); + Object serverSession = sessionClass.getMethod("getServerSession").invoke(session); + Class serverSessionClass = Class.forName(SERVER_SESSION, false, classLoader); + boolean inTransaction = (boolean) serverSessionClass + .getMethod("inTransactionOnServer") + .invoke(serverSession); + return map(inTransaction, connection.getAutoCommit()); + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | + InvocationTargetException | SQLException e) { + throw new TransactionStateProbeException(e); + } + } + + /** + * QueryConsole can expose Druid's statement connection while Connector/J lives in an + * isolated DriverClassLoader. Peel wrappers before loading the driver interface so every + * reflected Connector/J type comes from the physical connection's loader. + */ + static Object resolveDriverConnection(Connection connection) throws SQLException, ClassNotFoundException { + if (connection == null) { + throw new ClassNotFoundException(JDBC_CONNECTION + " from null connection"); + } + Connection current = connection; + ClassNotFoundException lastNotFound = null; + for (int depth = 0; depth < MAX_UNWRAP_DEPTH; depth++) { + ClassLoader classLoader = current.getClass().getClassLoader(); + if (classLoader != null) { + try { + Class jdbcConnectionClass = Class.forName(JDBC_CONNECTION, false, classLoader); + if (jdbcConnectionClass.isInstance(current)) { + return current; + } + if (current.isWrapperFor(jdbcConnectionClass)) { + return current.unwrap(jdbcConnectionClass); + } + } catch (ClassNotFoundException e) { + lastNotFound = e; + } catch (SQLException ignored) { + // This wrapper cannot unwrap with the driver class visible to its loader. + } + } + Connection next = peel(current); + if (next == null || next == current) { + break; + } + current = next; + } + throw lastNotFound != null + ? lastNotFound + : new ClassNotFoundException( + JDBC_CONNECTION + " from " + connection.getClass().getName() + + " classLoader=" + classLoaderName(connection)); + } + + private static Connection peel(Connection connection) { + Connection viaGetter = invokeConnectionGetter(connection, "getConnection"); + if (viaGetter != null) { + return viaGetter; + } + Connection viaPhysical = invokeConnectionGetter(connection, "getPhysicalConnection"); + if (viaPhysical != null) { + return viaPhysical; + } + try { + Connection unwrapped = connection.unwrap(Connection.class); + if (unwrapped != null && unwrapped != connection) { + return unwrapped; + } + } catch (SQLException ignored) { + } + return connection; + } + + private static Connection invokeConnectionGetter(Connection connection, String methodName) { + try { + Method method = connection.getClass().getMethod(methodName); + Object value = method.invoke(connection); + if (value instanceof Connection next && next != connection) { + return next; + } + } catch (ReflectiveOperationException ignored) { + } + return null; + } + + private static String classLoaderName(Connection connection) { + ClassLoader classLoader = connection.getClass().getClassLoader(); + return classLoader == null ? "null" : classLoader.getClass().getName(); + } + + static QueryTransactionState map(boolean inTransaction, boolean autoCommit) { + if (inTransaction) { + return QueryTransactionState.TRANSACTION_ACTIVE; + } + return autoCommit + ? QueryTransactionState.AUTO_COMMIT + : QueryTransactionState.MANUAL_COMMIT_IDLE; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/MysqlTransactionStateProbe.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/MysqlTransactionStateProbe.java new file mode 100644 index 00000000..574d3ef5 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/MysqlTransactionStateProbe.java @@ -0,0 +1,27 @@ +package org.jumpserver.chen.framework.console.transaction; + +import java.sql.Connection; +import java.sql.SQLException; + +final class MysqlTransactionStateProbe implements TransactionStateProbe { + private static final String STATE_SQL = + "SELECT @@session.autocommit, @@session.in_transaction"; + + @Override + public QueryTransactionState inspect(Connection connection) { + try (var statement = connection.createStatement(); + var resultSet = statement.executeQuery(STATE_SQL)) { + if (!resultSet.next()) { + return QueryTransactionState.UNKNOWN; + } + if (resultSet.getBoolean(2)) { + return QueryTransactionState.TRANSACTION_ACTIVE; + } + return resultSet.getBoolean(1) + ? QueryTransactionState.AUTO_COMMIT + : QueryTransactionState.MANUAL_COMMIT_IDLE; + } catch (SQLException e) { + throw new TransactionStateProbeException(e); + } + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/OracleTransactionStateProbe.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/OracleTransactionStateProbe.java new file mode 100644 index 00000000..9bbc5391 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/OracleTransactionStateProbe.java @@ -0,0 +1,28 @@ +package org.jumpserver.chen.framework.console.transaction; + +import java.sql.Connection; +import java.sql.SQLException; + +final class OracleTransactionStateProbe implements TransactionStateProbe { + private static final String STATE_SQL = + "SELECT DBMS_TRANSACTION.LOCAL_TRANSACTION_ID(FALSE) FROM DUAL"; + + @Override + public QueryTransactionState inspect(Connection connection) { + try (var statement = connection.createStatement(); + var resultSet = statement.executeQuery(STATE_SQL)) { + if (!resultSet.next()) { + return QueryTransactionState.UNKNOWN; + } + String transactionId = resultSet.getString(1); + if (transactionId != null && !transactionId.isBlank()) { + return QueryTransactionState.TRANSACTION_ACTIVE; + } + return connection.getAutoCommit() + ? QueryTransactionState.AUTO_COMMIT + : QueryTransactionState.MANUAL_COMMIT_IDLE; + } catch (SQLException e) { + throw new TransactionStateProbeException(e); + } + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/PostgresqlTransactionStateProbe.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/PostgresqlTransactionStateProbe.java new file mode 100644 index 00000000..0e1cfd95 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/PostgresqlTransactionStateProbe.java @@ -0,0 +1,125 @@ +package org.jumpserver.chen.framework.console.transaction; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.sql.Connection; +import java.sql.SQLException; + +final class PostgresqlTransactionStateProbe implements TransactionStateProbe { + private static final String BASE_CONNECTION = "org.postgresql.core.BaseConnection"; + private static final int MAX_UNWRAP_DEPTH = 8; + + @Override + public QueryTransactionState inspect(Connection connection) { + try { + Object baseConnection = resolveBaseConnection(connection); + Class baseConnectionClass = baseConnection.getClass(); + ClassLoader driverClassLoader = baseConnectionClass.getClassLoader(); + Class type = Class.forName(BASE_CONNECTION, false, driverClassLoader); + if (!type.isInstance(baseConnection)) { + baseConnection = ((Connection) baseConnection).unwrap(type); + } + Object transactionState = type.getMethod("getTransactionState").invoke(baseConnection); + return map(String.valueOf(transactionState), connection.getAutoCommit()); + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | + InvocationTargetException | SQLException e) { + throw new TransactionStateProbeException(e); + } + } + + /** + * Chen's QueryConsole "physical" connection is Druid's {@code DruidStatementConnection}, + * whose classloader cannot see the isolated PostgreSQL DriverClassLoader. Walk to the + * raw {@code org.postgresql} connection and load BaseConnection from that loader. + */ + static Object resolveBaseConnection(Connection connection) throws SQLException, ClassNotFoundException { + if (connection == null) { + throw new ClassNotFoundException(BASE_CONNECTION + " from null connection"); + } + Connection current = connection; + ClassNotFoundException lastNotFound = null; + for (int depth = 0; depth < MAX_UNWRAP_DEPTH; depth++) { + ClassLoader classLoader = current.getClass().getClassLoader(); + if (classLoader != null) { + try { + Class baseConnectionClass = Class.forName(BASE_CONNECTION, false, classLoader); + if (baseConnectionClass.isInstance(current)) { + return current; + } + if (current.isWrapperFor(baseConnectionClass)) { + return current.unwrap(baseConnectionClass); + } + } catch (ClassNotFoundException e) { + lastNotFound = e; + } catch (SQLException ignored) { + // This wrapper cannot unwrap with this loader's BaseConnection. Peel and retry. + } + } + Connection next = peel(current); + if (next == null || next == current) { + break; + } + current = next; + } + throw lastNotFound != null + ? lastNotFound + : new ClassNotFoundException( + BASE_CONNECTION + " from " + connection.getClass().getName() + + " classLoader=" + classLoaderName(connection)); + } + + static Connection peel(Connection connection) { + Connection viaGetter = invokeConnectionGetter(connection, "getConnection"); + if (viaGetter != null) { + return viaGetter; + } + Connection viaPhysical = invokeConnectionGetter(connection, "getPhysicalConnection"); + if (viaPhysical != null) { + return viaPhysical; + } + try { + Connection unwrapped = connection.unwrap(Connection.class); + if (unwrapped != null && unwrapped != connection) { + return unwrapped; + } + } catch (SQLException ignored) { + } + return connection; + } + + private static Connection invokeConnectionGetter(Connection connection, String methodName) { + try { + Method method = connection.getClass().getMethod(methodName); + Object value = method.invoke(connection); + if (value instanceof Connection next && next != connection) { + return next; + } + } catch (ReflectiveOperationException ignored) { + } + return null; + } + + static String classLoaderName(Connection connection) { + if (connection == null) { + return "null"; + } + ClassLoader classLoader = connection.getClass().getClassLoader(); + return classLoader == null ? "null" : classLoader.getClass().getName(); + } + + static QueryTransactionState map(String driverState, boolean autoCommit) { + String state = driverState == null ? "" : driverState.trim(); + int dot = state.lastIndexOf('.'); + if (dot >= 0 && dot < state.length() - 1) { + state = state.substring(dot + 1); + } + return switch (state) { + case "OPEN" -> QueryTransactionState.TRANSACTION_ACTIVE; + case "FAILED" -> QueryTransactionState.TRANSACTION_FAILED; + case "IDLE" -> autoCommit + ? QueryTransactionState.AUTO_COMMIT + : QueryTransactionState.MANUAL_COMMIT_IDLE; + default -> QueryTransactionState.UNKNOWN; + }; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/QueryTransactionProbeResult.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/QueryTransactionProbeResult.java new file mode 100644 index 00000000..2abce34f --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/QueryTransactionProbeResult.java @@ -0,0 +1,29 @@ +package org.jumpserver.chen.framework.console.transaction; + +import java.util.Objects; + +public record QueryTransactionProbeResult( + QueryTransactionState state, + boolean probeFailed, + String detail +) { + public QueryTransactionProbeResult { + Objects.requireNonNull(state, "state"); + } + + public QueryTransactionProbeResult(QueryTransactionState state, boolean probeFailed) { + this(state, probeFailed, null); + } + + static QueryTransactionProbeResult observed(QueryTransactionState state) { + return new QueryTransactionProbeResult(state, false, null); + } + + static QueryTransactionProbeResult failed() { + return failed(null); + } + + static QueryTransactionProbeResult failed(String detail) { + return new QueryTransactionProbeResult(QueryTransactionState.UNKNOWN, true, detail); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/QueryTransactionState.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/QueryTransactionState.java new file mode 100644 index 00000000..bedf72dc --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/QueryTransactionState.java @@ -0,0 +1,9 @@ +package org.jumpserver.chen.framework.console.transaction; + +public enum QueryTransactionState { + AUTO_COMMIT, + MANUAL_COMMIT_IDLE, + TRANSACTION_ACTIVE, + TRANSACTION_FAILED, + UNKNOWN +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/QueryTransactionStateInspector.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/QueryTransactionStateInspector.java new file mode 100644 index 00000000..e0f01f31 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/QueryTransactionStateInspector.java @@ -0,0 +1,61 @@ +package org.jumpserver.chen.framework.console.transaction; + +import lombok.extern.slf4j.Slf4j; +import org.jumpserver.chen.framework.datasource.plan.PlanDatabase; + +import java.sql.Connection; + +@Slf4j +public final class QueryTransactionStateInspector { + private final Connection connection; + private final TransactionStateProbe probe; + + public static QueryTransactionStateInspector create(PlanDatabase database, Connection connection) { + return new QueryTransactionStateInspector(connection, probeFor(database)); + } + + static TransactionStateProbe probeFor(PlanDatabase database) { + if (database == null) { + return ignored -> QueryTransactionState.UNKNOWN; + } + return switch (database) { + case postgresql -> new PostgresqlTransactionStateProbe(); + case mysql -> new MysqlDriverTransactionStateProbe(); + case mariadb -> new MysqlTransactionStateProbe(); + case oracle -> new OracleTransactionStateProbe(); + case sqlserver -> new SqlServerTransactionStateProbe(); + case dm -> new DamengTransactionStateProbe(); + case db2 -> new Db2TransactionStateProbe(); + default -> ignored -> QueryTransactionState.UNKNOWN; + }; + } + + QueryTransactionStateInspector(Connection connection, TransactionStateProbe probe) { + this.connection = connection; + this.probe = probe; + } + + public QueryTransactionProbeResult probeNow() { + try { + return QueryTransactionProbeResult.observed(this.probe.inspect(this.connection)); + } catch (TransactionStateProbeException e) { + // Only explicit probe failures (SQLException / driver-compat reflection errors wrapped + // by the probe) become a probeFailed result. Program errors such as NPE, + // ClassCastException or IllegalStateException are NOT caught here and must propagate. + Throwable cause = e.getCause() == null ? e : e.getCause(); + log.warn( + "probe transaction state failed: connectionClass={}, classLoader={}, cause={}", + this.connection == null ? "null" : this.connection.getClass().getName(), + this.connection == null ? "null" : PostgresqlTransactionStateProbe.classLoaderName(this.connection), + cause.toString() + ); + return QueryTransactionProbeResult.failed(summarize(cause)); + } + } + + private static String summarize(Throwable cause) { + String type = cause.getClass().getSimpleName(); + String message = cause.getMessage(); + return message == null || message.isBlank() ? type : type + ": " + message; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/SqlServerTransactionStateProbe.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/SqlServerTransactionStateProbe.java new file mode 100644 index 00000000..eb29009c --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/SqlServerTransactionStateProbe.java @@ -0,0 +1,145 @@ +package org.jumpserver.chen.framework.console.transaction; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.sql.Connection; +import java.sql.SQLException; + +final class SqlServerTransactionStateProbe implements TransactionStateProbe { + private static final String DRIVER_CONNECTION = "com.microsoft.sqlserver.jdbc.SQLServerConnection"; + private static final String STATE_SQL = "SELECT XACT_STATE()"; + private static final int MAX_UNWRAP_DEPTH = 8; + + @Override + public QueryTransactionState inspect(Connection connection) { + try { + if (!hasTransactionDescriptor(connection)) { + return connection.getAutoCommit() + ? QueryTransactionState.AUTO_COMMIT + : QueryTransactionState.MANUAL_COMMIT_IDLE; + } + try (var statement = connection.createStatement(); + var resultSet = statement.executeQuery(STATE_SQL)) { + if (!resultSet.next()) { + return QueryTransactionState.UNKNOWN; + } + return mapXactState(resultSet.getInt(1)); + } + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | + InvocationTargetException | SQLException e) { + throw new TransactionStateProbeException(e); + } + } + + static QueryTransactionState mapXactState(int state) { + return switch (state) { + case 1 -> QueryTransactionState.TRANSACTION_ACTIVE; + case -1 -> QueryTransactionState.TRANSACTION_FAILED; + default -> QueryTransactionState.UNKNOWN; + }; + } + + private static boolean hasTransactionDescriptor(Connection connection) + throws ClassNotFoundException, SQLException, NoSuchMethodException, + InvocationTargetException, IllegalAccessException { + Object driverConnection = resolveDriverConnection(connection); + Class driverClass = Class.forName( + DRIVER_CONNECTION, + false, + driverConnection.getClass().getClassLoader() + ); + var method = driverClass.getDeclaredMethod("getTransactionDescriptor"); + method.setAccessible(true); + byte[] descriptor = (byte[]) method.invoke(driverConnection); + return hasTransactionDescriptor(descriptor); + } + + /** + * QueryConsole exposes Druid's statement connection, whose application classloader cannot + * see the isolated Microsoft JDBC driver. Walk through wrappers until the driver connection + * and its classloader are available. + */ + static Object resolveDriverConnection(Connection connection) throws SQLException, ClassNotFoundException { + if (connection == null) { + throw new ClassNotFoundException(DRIVER_CONNECTION + " from null connection"); + } + Connection current = connection; + ClassNotFoundException lastNotFound = null; + for (int depth = 0; depth < MAX_UNWRAP_DEPTH; depth++) { + ClassLoader classLoader = current.getClass().getClassLoader(); + if (classLoader != null) { + try { + Class driverClass = Class.forName(DRIVER_CONNECTION, false, classLoader); + if (driverClass.isInstance(current)) { + return current; + } + if (current.isWrapperFor(driverClass)) { + return current.unwrap(driverClass); + } + } catch (ClassNotFoundException e) { + lastNotFound = e; + } catch (SQLException ignored) { + // This wrapper cannot unwrap with this loader's driver class. Peel and retry. + } + } + Connection next = peel(current); + if (next == null || next == current) { + break; + } + current = next; + } + throw lastNotFound != null + ? lastNotFound + : new ClassNotFoundException( + DRIVER_CONNECTION + " from " + connection.getClass().getName() + + " classLoader=" + classLoaderName(connection)); + } + + private static Connection peel(Connection connection) { + Connection viaGetter = invokeConnectionGetter(connection, "getConnection"); + if (viaGetter != null) { + return viaGetter; + } + Connection viaPhysical = invokeConnectionGetter(connection, "getPhysicalConnection"); + if (viaPhysical != null) { + return viaPhysical; + } + try { + Connection unwrapped = connection.unwrap(Connection.class); + if (unwrapped != null && unwrapped != connection) { + return unwrapped; + } + } catch (SQLException ignored) { + } + return connection; + } + + private static Connection invokeConnectionGetter(Connection connection, String methodName) { + try { + Method method = connection.getClass().getMethod(methodName); + Object value = method.invoke(connection); + if (value instanceof Connection next && next != connection) { + return next; + } + } catch (ReflectiveOperationException ignored) { + } + return null; + } + + private static String classLoaderName(Connection connection) { + ClassLoader classLoader = connection.getClass().getClassLoader(); + return classLoader == null ? "null" : classLoader.getClass().getName(); + } + + static boolean hasTransactionDescriptor(byte[] descriptor) { + if (descriptor == null) { + return false; + } + for (byte value : descriptor) { + if (value != 0) { + return true; + } + } + return false; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/TransactionStateProbe.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/TransactionStateProbe.java new file mode 100644 index 00000000..9e340d49 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/TransactionStateProbe.java @@ -0,0 +1,8 @@ +package org.jumpserver.chen.framework.console.transaction; + +import java.sql.Connection; + +@FunctionalInterface +interface TransactionStateProbe { + QueryTransactionState inspect(Connection connection); +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/TransactionStateProbeException.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/TransactionStateProbeException.java new file mode 100644 index 00000000..ae4a9862 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/TransactionStateProbeException.java @@ -0,0 +1,7 @@ +package org.jumpserver.chen.framework.console.transaction; + +final class TransactionStateProbeException extends RuntimeException { + TransactionStateProbeException(Throwable cause) { + super(cause); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/ConnectionManager.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/ConnectionManager.java index 1978374a..3d0d94b6 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/ConnectionManager.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/ConnectionManager.java @@ -20,6 +20,8 @@ public interface ConnectionManager { void setDatabaseContext(String database); + T withDatabaseContext(String database, DatabaseContextAction action) throws SQLException; + Datasource getDatasource(); DBConnectInfo getConnectInfo(); @@ -30,7 +32,16 @@ public interface ConnectionManager { SQLActuator getSqlActuator(); + // 对象树/编辑器当前上下文使用的 key,例如 schema。 String getContextKey(); + // JDBC URL/连接池默认库使用的 key,不能和对象树上下文混用。 + String getDatabaseContextKey(); + void close(); + + @FunctionalInterface + interface DatabaseContextAction { + T run() throws SQLException; + } } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/Datasource.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/Datasource.java index c0bc5231..7be0abad 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/Datasource.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/Datasource.java @@ -8,6 +8,10 @@ import org.jumpserver.chen.framework.datasource.entity.resource.TreeNode; import org.jumpserver.chen.framework.datasource.entity.action.Action; import org.jumpserver.chen.framework.datasource.entity.form.FormData; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; +import org.jumpserver.chen.framework.datasource.plan.ExecutionPlanDialect; +import org.jumpserver.chen.framework.datasource.plan.PlanDatabase; +import org.jumpserver.chen.framework.datasource.plan.UnsupportedExecutionPlanDialect; import java.sql.SQLException; @@ -35,6 +39,22 @@ public interface Datasource { ConnectionManager getConnectionManager(); ResourceBrowser getResourceBrowser(); + MetadataCatalog getMetadataCatalog(); + + default ExecutionPlanDialect getExecutionPlanDialect() { + return UnsupportedExecutionPlanDialect.getInstance(); + } + + /** + * The actual database product, independent of the Druid SQL dialect returned by + * {@link #getDruidDbType()}. A datasource may intentionally reuse another product's + * parser dialect (for example MariaDB uses Druid's MySQL dialect) while still requiring + * product-specific runtime behavior. + */ + default PlanDatabase getDatabaseType() { + ExecutionPlanDialect dialect = this.getExecutionPlanDialect(); + return dialect == null ? null : dialect.database(); + } void close(); } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/DatasourceFactory.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/DatasourceFactory.java index e3e512fa..5082cd79 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/DatasourceFactory.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/DatasourceFactory.java @@ -3,6 +3,7 @@ import lombok.extern.slf4j.Slf4j; import org.jumpserver.chen.framework.datasource.entity.DBConnectInfo; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -11,10 +12,26 @@ public class DatasourceFactory { private static final Map> DATASOURCE_MAP = new ConcurrentHashMap<>(); + private static final Map DATASOURCE_CLASS_NAMES = new HashMap<>(); + + static { + DATASOURCE_CLASS_NAMES.put("clickhouse", "org.jumpserver.chen.modules.clickhouse.ClickHouseDatasource"); + DATASOURCE_CLASS_NAMES.put("postgresql", "org.jumpserver.chen.modules.postgresql.PostgresqlDatasource"); + DATASOURCE_CLASS_NAMES.put("mariadb", "org.jumpserver.chen.modules.mariadb.MariaDBDatasource"); + DATASOURCE_CLASS_NAMES.put("oracle", "org.jumpserver.chen.modules.oracle.OracleDatasource"); + DATASOURCE_CLASS_NAMES.put("dameng", "org.jumpserver.chen.modules.dameng.DMDatasource"); + DATASOURCE_CLASS_NAMES.put("mysql", "org.jumpserver.chen.modules.mysql.MysqlDatasource"); + DATASOURCE_CLASS_NAMES.put("sqlserver", "org.jumpserver.chen.modules.sqlserver.SQLServerDatasource"); + DATASOURCE_CLASS_NAMES.put("db2", "org.jumpserver.chen.modules.db2.DB2Datasource"); + } public static Datasource fromConnectInfo(DBConnectInfo info) { var dbType = info.getDbType(); - var datasource = DATASOURCE_MAP.get(info.getDbType()); + var datasource = DATASOURCE_MAP.get(dbType); + if (datasource == null) { + lazyRegister(dbType); + datasource = DATASOURCE_MAP.get(dbType); + } if (datasource == null) { throw new RuntimeException("Unsupported dbType: " + dbType); } @@ -31,4 +48,18 @@ public static void Register(Class datasource) { log.info("Register datasource for dbType: {}", datasource.getName()); DATASOURCE_MAP.put(dbType, datasource); } + + @SuppressWarnings("unchecked") + private static void lazyRegister(String dbType) { + var className = DATASOURCE_CLASS_NAMES.get(dbType); + if (className == null) { + return; + } + try { + var datasourceClass = (Class) Class.forName(className); + Register(datasourceClass); + } catch (ClassNotFoundException e) { + log.warn("Datasource class not found for dbType {}: {}", dbType, className); + } + } } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/ResourceBrowser.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/ResourceBrowser.java index 6e2c05de..5824730d 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/ResourceBrowser.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/ResourceBrowser.java @@ -1,9 +1,7 @@ package org.jumpserver.chen.framework.datasource; import org.jumpserver.chen.framework.datasource.entity.resource.*; -import org.jumpserver.chen.framework.datasource.hints.SQLHintsHandler; -import org.jumpserver.chen.framework.datasource.sql.SQL; -import org.jumpserver.chen.framework.datasource.sql.SQLActuator; +import org.jumpserver.chen.framework.datasource.metadata.RelationScope; import java.sql.SQLException; import java.util.List; @@ -17,14 +15,7 @@ public interface ResourceBrowser { List getChildren(TreeNode node, boolean fromCache) throws SQLException; - List getSchemas(SQL sql) throws SQLException; + RelationScope resolveScope(ResourceNodeSnapshot node, String context) throws SQLException; - List getTables(SQL sql) throws SQLException; - - List getViews(SQL sql) throws SQLException; - - List getFields(SQL sql) throws SQLException; - - SQLActuator getSQLActuator(); - SQLHintsHandler getSQLHintsHandler(); + ResourceNodeSnapshot getIndexedNode(String key); } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/analysis/SqlStatementAnalysis.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/analysis/SqlStatementAnalysis.java new file mode 100644 index 00000000..36fa6e19 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/analysis/SqlStatementAnalysis.java @@ -0,0 +1,42 @@ +package org.jumpserver.chen.framework.datasource.analysis; + +import com.alibaba.druid.sql.ast.SQLStatement; + +import java.util.List; +import java.util.Objects; + +public record SqlStatementAnalysis( + String sql, + StatementKind kind, + SQLStatement ast, + boolean singleStatement, + boolean nestedWrite, + boolean lockingRead, + boolean selectInto, + boolean assignment, + boolean explainWrapper, + boolean unboundParameter, + boolean dynamicExecution, + List functionNames, + List rejectionCodes +) { + public SqlStatementAnalysis { + Objects.requireNonNull(sql, "sql"); + Objects.requireNonNull(kind, "kind"); + functionNames = functionNames == null ? List.of() : List.copyOf(functionNames); + rejectionCodes = rejectionCodes == null ? List.of() : List.copyOf(rejectionCodes); + } + + public boolean estimable() { + return rejectionCodes.isEmpty() + && singleStatement + && kind == StatementKind.SELECT + && !nestedWrite + && !lockingRead + && !selectInto + && !assignment + && !explainWrapper + && !unboundParameter + && !dynamicExecution; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/analysis/SqlStatementAnalyzer.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/analysis/SqlStatementAnalyzer.java new file mode 100644 index 00000000..69eaf7ac --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/analysis/SqlStatementAnalyzer.java @@ -0,0 +1,315 @@ +package org.jumpserver.chen.framework.datasource.analysis; + +import com.alibaba.druid.DbType; +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; +import com.alibaba.druid.sql.ast.expr.SQLVariantRefExpr; +import com.alibaba.druid.sql.ast.statement.SQLAssignItem; +import com.alibaba.druid.sql.ast.statement.SQLDeleteStatement; +import com.alibaba.druid.sql.ast.statement.SQLExplainStatement; +import com.alibaba.druid.sql.ast.statement.SQLInsertStatement; +import com.alibaba.druid.sql.ast.statement.SQLMergeStatement; +import com.alibaba.druid.sql.ast.statement.SQLReplaceStatement; +import com.alibaba.druid.sql.ast.SQLObject; +import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import com.alibaba.druid.sql.dialect.oracle.visitor.OracleASTVisitor; +import com.alibaba.druid.sql.dialect.postgresql.ast.stmt.PGSelectQueryBlock; +import com.alibaba.druid.sql.ast.statement.SQLSetStatement; +import com.alibaba.druid.sql.ast.statement.SQLUpdateStatement; +import com.alibaba.druid.sql.visitor.SQLASTVisitorAdapter; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.regex.Pattern; + +/** + * Read-only Druid AST analysis. No Connection, catalog lookup, execution, or SQL rewrite. + */ +public final class SqlStatementAnalyzer { + public static final String MULTI_STATEMENT = "MULTI_STATEMENT"; + public static final String PARSE_FAILED = "PARSE_FAILED"; + public static final String EMPTY_SQL = "EMPTY_SQL"; + public static final String NOT_SINGLE_SELECT = "NOT_SINGLE_SELECT"; + public static final String NESTED_WRITE = "NESTED_WRITE"; + public static final String LOCKING_READ = "LOCKING_READ"; + public static final String SELECT_INTO = "SELECT_INTO"; + public static final String ASSIGNMENT = "ASSIGNMENT"; + public static final String EXPLAIN_WRAPPER = "EXPLAIN_WRAPPER"; + public static final String UNBOUND_PARAMETER = "UNBOUND_PARAMETER"; + public static final String DYNAMIC_EXECUTION = "DYNAMIC_EXECUTION"; + + private static final Pattern TRAILING_DELIMITER = Pattern.compile(";\\s*$"); + private static final Pattern EXECUTABLE_COMMENT = Pattern.compile("/\\*!\\d*.*?\\*/", Pattern.DOTALL); + private static final Pattern PREPARE_EXECUTE = Pattern.compile( + "(?i)\\b(execute\\s+immediate|execute\\s+procedure|prepare\\s+\\S+\\s+as|call\\s+\\S+\\s*\\()\\b" + ); + // PostgreSQL CTE materialization (AS [NOT] MATERIALIZED (...)) is not accepted by Druid 1.2.28 + // after AS. Strip only that optional clause for parse; keep the original SQL for EXPLAIN. + private static final Pattern CTE_MATERIALIZED = Pattern.compile( + "(?i)(\\bAS)\\s+(NOT\\s+)?MATERIALIZED\\s*(\\()" + ); + + private SqlStatementAnalyzer() { + } + + public static SqlStatementAnalysis analyze(DbType dbType, String sql) { + if (StringUtils.isBlank(sql)) { + return rejected("", StatementKind.OTHER, null, false, List.of(), List.of(EMPTY_SQL)); + } + + String original = stripTrailingDelimiter(sql.strip()); + if (original.isEmpty()) { + return rejected("", StatementKind.OTHER, null, false, List.of(), List.of(EMPTY_SQL)); + } + + List statements; + try { + statements = parseStatements(dbType, original); + } catch (RuntimeException e) { + return rejected(original, StatementKind.OTHER, null, false, List.of(), List.of(PARSE_FAILED)); + } + + if (statements == null || statements.isEmpty()) { + return rejected(original, StatementKind.OTHER, null, false, List.of(), List.of(EMPTY_SQL)); + } + if (statements.size() != 1) { + return rejected(original, kindOf(statements.get(0)), statements.get(0), false, List.of(), List.of(MULTI_STATEMENT)); + } + + SQLStatement statement = statements.get(0); + StatementKind kind = kindOf(statement); + FactsVisitor visitor = new FactsVisitor(); + statement.accept(visitor); + + List codes = new ArrayList<>(); + boolean explainWrapper = statement instanceof SQLExplainStatement || visitor.explainWrapper; + if (kind != StatementKind.SELECT) { + codes.add(kind == StatementKind.EXPLAIN ? EXPLAIN_WRAPPER : NOT_SINGLE_SELECT); + } + if (explainWrapper) { + codes.add(EXPLAIN_WRAPPER); + } + if (visitor.nestedWrite) { + codes.add(NESTED_WRITE); + } + if (visitor.lockingRead) { + codes.add(LOCKING_READ); + } + if (visitor.selectInto) { + codes.add(SELECT_INTO); + } + if (visitor.assignment) { + codes.add(ASSIGNMENT); + } + if (visitor.unboundParameter) { + codes.add(UNBOUND_PARAMETER); + } + if (visitor.dynamicExecution || looksDynamic(original) || hasExecutableComment(original)) { + codes.add(DYNAMIC_EXECUTION); + } + + return new SqlStatementAnalysis( + original, + kind, + statement, + true, + visitor.nestedWrite, + visitor.lockingRead, + visitor.selectInto, + visitor.assignment, + explainWrapper, + visitor.unboundParameter, + codes.contains(DYNAMIC_EXECUTION), + List.copyOf(visitor.functionNames), + List.copyOf(new LinkedHashSet<>(codes)) + ); + } + + static String stripTrailingDelimiter(String sql) { + return TRAILING_DELIMITER.matcher(sql).replaceFirst(""); + } + + static String sqlForParse(String sql) { + return CTE_MATERIALIZED.matcher(sql).replaceAll("$1 $3"); + } + + private static List parseStatements(DbType dbType, String sql) { + try { + return SQLUtils.parseStatements(sql, dbType); + } catch (RuntimeException originalError) { + String parseSql = sqlForParse(sql); + if (parseSql.equals(sql)) { + throw originalError; + } + return SQLUtils.parseStatements(parseSql, dbType); + } + } + + private static boolean hasExecutableComment(String sql) { + return EXECUTABLE_COMMENT.matcher(sql).find(); + } + + private static boolean looksDynamic(String sql) { + return PREPARE_EXECUTE.matcher(sql).find(); + } + + private static StatementKind kindOf(SQLStatement statement) { + if (statement instanceof SQLExplainStatement) { + return StatementKind.EXPLAIN; + } + if (statement instanceof SQLSelectStatement) { + return StatementKind.SELECT; + } + if (statement instanceof SQLInsertStatement + || statement instanceof SQLUpdateStatement + || statement instanceof SQLDeleteStatement + || statement instanceof SQLMergeStatement + || statement instanceof SQLReplaceStatement) { + return StatementKind.DML; + } + String type = statement.getClass().getSimpleName().toLowerCase(Locale.ROOT); + if (type.contains("create") || type.contains("alter") || type.contains("drop") || type.contains("truncate")) { + return StatementKind.DDL; + } + return StatementKind.OTHER; + } + + private static SqlStatementAnalysis rejected( + String sql, + StatementKind kind, + SQLStatement ast, + boolean singleStatement, + List functionNames, + List codes + ) { + return new SqlStatementAnalysis( + sql, + kind, + ast, + singleStatement, + false, + false, + false, + false, + codes.contains(EXPLAIN_WRAPPER), + false, + false, + functionNames, + codes + ); + } + + private static final class FactsVisitor extends SQLASTVisitorAdapter implements OracleASTVisitor { + private boolean nestedWrite; + private boolean lockingRead; + private boolean selectInto; + private boolean assignment; + private boolean explainWrapper; + private boolean unboundParameter; + private boolean dynamicExecution; + private final Set functionNames = new LinkedHashSet<>(); + + @Override + public boolean visit(SQLExplainStatement x) { + explainWrapper = true; + return true; + } + + @Override + public boolean visit(SQLInsertStatement x) { + nestedWrite = true; + return true; + } + + @Override + public boolean visit(SQLUpdateStatement x) { + nestedWrite = true; + return true; + } + + @Override + public boolean visit(SQLDeleteStatement x) { + nestedWrite = true; + return true; + } + + @Override + public boolean visit(SQLMergeStatement x) { + nestedWrite = true; + return true; + } + + @Override + public boolean visit(SQLReplaceStatement x) { + nestedWrite = true; + return true; + } + + @Override + public boolean visit(SQLSetStatement x) { + assignment = true; + return true; + } + + @Override + public boolean visit(SQLAssignItem x) { + assignment = true; + return true; + } + + @Override + public void preVisit(SQLObject x) { + if (x instanceof SQLSelectQueryBlock block) { + inspectQueryBlock(block); + } + } + + @Override + public boolean visit(SQLSelectQueryBlock x) { + inspectQueryBlock(x); + return true; + } + + private void inspectQueryBlock(SQLSelectQueryBlock x) { + if (x.isForUpdate() || x.isForShare() || x.getForUpdateOfSize() > 0) { + lockingRead = true; + } + if (x.getInto() != null) { + selectInto = true; + } + if (x instanceof PGSelectQueryBlock pg) { + if (pg.getForClause() != null) { + lockingRead = true; + } + if (pg.getIntoOption() != null) { + selectInto = true; + } + } + } + + @Override + public boolean visit(SQLMethodInvokeExpr x) { + String name = x.getMethodName(); + if (StringUtils.isNotBlank(name)) { + String owner = x.getOwner() == null ? "" : x.getOwner().toString(); + functionNames.add(owner.isBlank() ? name : owner + "." + name); + } + return true; + } + + @Override + public boolean visit(SQLVariantRefExpr x) { + String name = x.getName(); + if (StringUtils.isBlank(name) || "?".equals(name) || name.startsWith(":") || name.startsWith("$")) { + unboundParameter = true; + } + return true; + } + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/analysis/StatementKind.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/analysis/StatementKind.java new file mode 100644 index 00000000..aea6196c --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/analysis/StatementKind.java @@ -0,0 +1,9 @@ +package org.jumpserver.chen.framework.datasource.analysis; + +public enum StatementKind { + SELECT, + EXPLAIN, + DML, + DDL, + OTHER +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseActionHandler.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseActionHandler.java index f2fa7bd7..32c11fd6 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseActionHandler.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseActionHandler.java @@ -8,7 +8,11 @@ import org.jumpserver.chen.framework.datasource.entity.dialog.detail.DetailDialog; import org.jumpserver.chen.framework.datasource.entity.dialog.detail.DetailItem; import org.jumpserver.chen.framework.datasource.entity.resource.TreeNode; -import org.jumpserver.chen.framework.datasource.sql.SQL; +import org.jumpserver.chen.framework.datasource.metadata.ObjectRef; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; +import org.jumpserver.chen.framework.datasource.metadata.RelationScope; +import org.jumpserver.chen.framework.datasource.metadata.ScopeKind; +import org.jumpserver.chen.framework.datasource.metadata.ScopeRef; import org.jumpserver.chen.framework.i18n.MessageUtils; import org.jumpserver.chen.framework.session.SessionManager; import org.jumpserver.chen.framework.utils.LangUtils; @@ -99,6 +103,11 @@ public List getTableActions(TreeNode node) { @Override public List getViewActions(TreeNode node) { return List.of( + Action.builder() + .label(MessageUtils.get("ViewData")) + .key("view_data") + .icon("el-icon-view") + .build(), Action.builder() .label(MessageUtils.get("Refresh")) .key("refresh_node") @@ -159,22 +168,60 @@ public EventEmitter onShowProperties(TreeNode node) { } } - public EventEmitter onShowObjectProperties(String type, String sql, TreeNode node) throws SQLException { - var sqlActuator = this.getDatasource().getConnectionManager().getSqlActuator(); - var objName = TreeUtils.getValue(node.getKey(), type); - var command = SQL.of(sql, objName.replace("'", "''")); - log.info("resource action show_properties: type={}, node={}", type, node.getKey()); - var result = sqlActuator.execute(command); + public EventEmitter onTableProperties(TreeNode node) throws SQLException { + return this.showRelationProperties(node); + } + + public EventEmitter onViewProperties(TreeNode node) throws SQLException { + return this.showRelationProperties(node); + } + + private EventEmitter showRelationProperties(TreeNode node) throws SQLException { + var database = TreeUtils.getValue(node.getKey(), "database"); + var schema = TreeUtils.getValue(node.getKey(), "schema"); + var table = TreeUtils.getValue(node.getKey(), node.getType()); + var kind = "view".equals(node.getType()) ? RelationKind.VIEW : RelationKind.TABLE; + var ref = new ObjectRef(database.isEmpty() ? null : database, schema, table, kind); + var properties = this.getDatasource().getMetadataCatalog().objectProperties(ref); + + var detailDialog = new DetailDialog(node.getKey(), node.getType() + MessageUtils.get("Properties")); + detailDialog.setWidth("50%"); + for (var item : properties.items()) { + detailDialog.addItem(DetailItem.builder() + .name(item.name()) + .label(item.name()) + .type("text") + .value(item.value() == null ? "" : item.value()) + .build()); + } + return EventEmitter.of("new_dialog", detailDialog); + } + + public EventEmitter onSchemaProperties(TreeNode node) throws SQLException { + return this.showScopeProperties(node, ScopeKind.SCHEMA); + } + + public EventEmitter onDatabaseProperties(TreeNode node) throws SQLException { + return this.showScopeProperties(node, ScopeKind.CATALOG); + } - var detailDialog = new DetailDialog(node.getKey(), type + MessageUtils.get("Properties")); + private EventEmitter showScopeProperties(TreeNode node, ScopeKind kind) throws SQLException { + var datasource = this.getDatasource(); + var browser = datasource.getResourceBrowser(); + var snapshot = browser.getIndexedNode(node.getKey()); + var scope = browser.resolveScope(snapshot, null); + if (kind == ScopeKind.CATALOG) { + scope = new RelationScope(scope.catalog(), null); + } + var properties = datasource.getMetadataCatalog().scopeProperties(new ScopeRef(scope, kind)); + var detailDialog = new DetailDialog(node.getKey(), node.getType() + MessageUtils.get("Properties")); detailDialog.setWidth("50%"); - for (int i = 0; i < result.getFields().size(); i++) { - var column = result.getFields().get(i); + for (var item : properties.items()) { detailDialog.addItem(DetailItem.builder() - .name(column.getName()) - .label(column.getName()) + .name(item.name()) + .label(item.name()) .type("text") - .value(result.getData().get(0).get(i) == null ? "" : result.getData().get(0).get(i).toString()) + .value(item.value() == null ? "" : item.value()) .build()); } return EventEmitter.of("new_dialog", detailDialog); @@ -230,16 +277,36 @@ public EventEmitter onDatasourceProperties(TreeNode node) { } public EventEmitter onRefreshNode(TreeNode node) throws SQLException { - var resourceBrowser = this.getDatasource().getResourceBrowser(); + var datasource = this.getDatasource(); + var resourceBrowser = datasource.getResourceBrowser(); var n = TreeUtils.getNode(resourceBrowser.getTree(), node.getKey()); if (n != null) { - var children = resourceBrowser.getChildren(n, false); - n.setChildren(children); + this.invalidateMetadata(n); + resourceBrowser.getChildren(n, false); } return EventEmitter.of("refresh_node", node.getKey()); } + private void invalidateMetadata(TreeNode node) throws SQLException { + var datasource = this.getDatasource(); + var browser = datasource.getResourceBrowser(); + var catalog = datasource.getMetadataCatalog(); + var snapshot = browser.getIndexedNode(node.getKey()); + switch (node.getType()) { + case "datasource" -> catalog.invalidateAll(); + case "database" -> catalog.invalidateCatalog(snapshot.database()); + case "schema", "folder" -> catalog.invalidate(browser.resolveScope(snapshot, null)); + case "table", "view" -> { + var scope = browser.resolveScope(snapshot, null); + var kind = "view".equals(node.getType()) ? RelationKind.VIEW : RelationKind.TABLE; + catalog.invalidate(new ObjectRef(scope.catalog(), scope.schema(), snapshot.table(), kind)); + } + default -> { + } + } + } + public EventEmitter onNewQuery(TreeNode node) { return EventEmitter.of("new_query", node.getKey()); } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseConnectionManager.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseConnectionManager.java index b3d1dfce..96c80f4f 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseConnectionManager.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseConnectionManager.java @@ -76,7 +76,26 @@ public Driver getDriver() { @Override public void setDatabaseContext(String database) { - this.currentDatabase.set(database); + if (StringUtils.isBlank(database)) { + this.currentDatabase.remove(); + } else { + this.currentDatabase.set(database); + } + } + + @Override + public T withDatabaseContext(String database, DatabaseContextAction action) throws SQLException { + var previousDatabase = this.currentDatabase.get(); + try { + this.setDatabaseContext(database); + return action.run(); + } finally { + if (previousDatabase == null) { + this.currentDatabase.remove(); + } else { + this.currentDatabase.set(previousDatabase); + } + } } @Override @@ -84,6 +103,12 @@ public String getContextKey() { return "schema"; } + @Override + public String getDatabaseContextKey() { + // 大多数数据库的连接上下文是 database;MySQL/MariaDB 会覆盖为 schema。 + return "database"; + } + @Override public DBConnectInfo getConnectInfo() { return this.connectInfo; diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseDatasource.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseDatasource.java index 21b01d39..ddfae1f6 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseDatasource.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseDatasource.java @@ -10,6 +10,9 @@ import org.jumpserver.chen.framework.datasource.entity.resource.TreeNode; import org.jumpserver.chen.framework.datasource.entity.action.Action; import org.jumpserver.chen.framework.datasource.entity.form.FormData; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; +import org.jumpserver.chen.framework.datasource.plan.ExecutionPlanDialect; +import org.jumpserver.chen.framework.datasource.plan.UnsupportedExecutionPlanDialect; import org.jumpserver.chen.framework.session.SessionManager; import java.sql.SQLException; @@ -22,6 +25,7 @@ public abstract class BaseDatasource implements Datasource { protected ResourceBrowser resourceBrowser; protected ActionHandler actionHandler; protected DatasourceInfo datasourceInfo; + protected MetadataCatalog metadataCatalog; public void ping() { try { @@ -84,6 +88,15 @@ public ResourceBrowser getResourceBrowser() { return this.resourceBrowser; } + public MetadataCatalog getMetadataCatalog() { + return this.metadataCatalog; + } + + @Override + public ExecutionPlanDialect getExecutionPlanDialect() { + return UnsupportedExecutionPlanDialect.getInstance(); + } + public DBConnectInfo getConnectInfo() { return this.connectionManager.getConnectInfo(); } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseResourceBrowser.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseResourceBrowser.java index 7bc48327..986f4357 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseResourceBrowser.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseResourceBrowser.java @@ -2,21 +2,25 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.jumpserver.chen.framework.datasource.ConnectionManager; import org.jumpserver.chen.framework.datasource.ResourceBrowser; -import org.jumpserver.chen.framework.datasource.hints.SQLHintsHandler; -import org.jumpserver.chen.framework.datasource.sql.SQL; import org.jumpserver.chen.framework.datasource.entity.resource.*; -import org.jumpserver.chen.framework.datasource.sql.SQLActuator; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; +import org.jumpserver.chen.framework.datasource.metadata.RelationScope; import org.jumpserver.chen.framework.session.SessionManager; +import org.jumpserver.chen.framework.utils.SqlIdentifierUtils; import org.jumpserver.chen.framework.utils.TreeUtils; import java.lang.reflect.InvocationTargetException; import java.sql.SQLException; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; @Slf4j @@ -26,17 +30,10 @@ public abstract class BaseResourceBrowser implements ResourceBrowser { @Getter private final ConnectionManager connectionManager; - private final SQLHintsHandler sqlHintsHandler; + private final ConcurrentHashMap nodeIndex = new ConcurrentHashMap<>(); - - @Override - public SQLHintsHandler getSQLHintsHandler() { - return this.sqlHintsHandler; - } - - public BaseResourceBrowser(ConnectionManager connectionManager, SQLHintsHandler sqlHintsHandler) { + public BaseResourceBrowser(ConnectionManager connectionManager) { this.connectionManager = connectionManager; - this.sqlHintsHandler = sqlHintsHandler; } @Override @@ -44,7 +41,7 @@ public void buildTree() throws SQLException { var root = new Root(); root.setName(SessionManager.getCurrentSession().getDatasourceName()); - this.root = root.toResourceNode(null); + this.resetNodeIndex(root.toResourceNode(null)); var parents = List.of(this.root); while (!parents.isEmpty()) { @@ -77,6 +74,16 @@ public List getChildren(TreeNode node) throws SQLException { @Override public List getChildren(TreeNode node, boolean fromCache) throws SQLException { + return this.getChildren(node, fromCache, null); + } + + /** + * Reloads {@code node} from the source. Previously loaded descendants (children != null) + * are fetched again so a schema/database refresh keeps expanded tables/views populated. + * Never-loaded descendants stay {@code children == null} and are fetched on the next expand. + */ + private List getChildren(TreeNode node, boolean fromCache, Set loadedDescendantKeys) + throws SQLException { if (node == null) { return List.of(this.root); } @@ -86,13 +93,48 @@ public List getChildren(TreeNode node, boolean fromCache) throws SQLEx return n.getChildren(); } } + var cachedNode = TreeUtils.getNode(this.root, node.getKey()); + var loadedKeys = loadedDescendantKeys != null + ? loadedDescendantKeys + : this.collectLoadedDescendantKeys(cachedNode); var children = this.getChildNodes(node); - if (!children.isEmpty()) { - this.saveTreeNode(node, children); + this.saveTreeNode(node, children); + if (!fromCache) { + this.reloadLoadedChildren(children, loadedKeys); } return children; } + private Set collectLoadedDescendantKeys(TreeNode node) { + var keys = new HashSet(); + this.collectLoadedDescendantKeys(node, keys); + return keys; + } + + private void collectLoadedDescendantKeys(TreeNode node, Set keys) { + if (node == null || node.getChildren() == null) { + return; + } + for (var child : node.getChildren()) { + if (child.getChildren() != null) { + keys.add(child.getKey()); + } + this.collectLoadedDescendantKeys(child, keys); + } + } + + private void reloadLoadedChildren(List children, Set loadedKeys) throws SQLException { + if (children == null || children.isEmpty() || loadedKeys.isEmpty()) { + return; + } + for (var child : children) { + if (!loadedKeys.contains(child.getKey())) { + continue; + } + child.setChildren(this.getChildren(child, false, loadedKeys)); + } + } + public List getChildNodes(TreeNode node) throws SQLException { if (node == null) { return List.of(this.root); @@ -113,9 +155,12 @@ public List getChildNodes(TreeNode node) throws SQLException { } public List getDatasourceChildNodes(TreeNode parent) throws SQLException { - return this.getSchemas() - .stream() - .map(schema -> schema.toResourceNode(parent)) + return this.metadataCatalog().listSchemas(null).stream() + .map(schema -> { + var entity = new Schema(); + entity.setName(schema.name()); + return entity.toResourceNode(parent); + }) .toList(); } @@ -129,72 +174,150 @@ public List getSchemaChildNodes(TreeNode parent) throws SQLException { public List getFolderChildNodes(TreeNode parent) throws SQLException { String folder = TreeUtils.getValue(parent.getKey(), "folder"); String schema = TreeUtils.getValue(parent.getKey(), "schema"); + String database = TreeUtils.getValue(parent.getKey(), "database"); + var scope = new RelationScope(database.isEmpty() ? null : database, schema); return switch (Objects.requireNonNull(folder)) { - case "tables" -> this.getTables(schema) + case "tables" -> this.metadataCatalog().listRelations(scope, Set.of(RelationKind.TABLE)) .stream() - .map(table -> table.toResourceNode(parent)) + .map(relation -> { + var table = new Table(); + table.setName(relation.ref().name()); + table.setSchema(relation.ref().schema()); + return table.toResourceNode(parent); + }) .toList(); - case "views" -> this.getViews(schema) + case "views" -> this.metadataCatalog().listRelations( + scope, Set.of(RelationKind.VIEW, RelationKind.MATERIALIZED_VIEW)) .stream() - .map(view -> view.toResourceNode(parent)) + .map(relation -> { + var view = new View(); + view.setName(relation.ref().name()); + view.setSchema(relation.ref().schema()); + view.setRelationKind(relation.ref().kind()); + return view.toResourceNode(parent); + }) .toList(); default -> List.of(); }; } - public void saveTreeNode(TreeNode node, List children) { + public synchronized void saveTreeNode(TreeNode node, List children) { var n = TreeUtils.getNode(this.root, node.getKey()); if (n != null) { n.setChildren(children); + this.removeIndexedDescendants(n.getKey()); + var parent = this.nodeIndex.get(n.getKey()); + for (var child : children) { + this.registerNode(child, parent); + } } } - public abstract List getSchemas() throws SQLException; + protected synchronized void resetNodeIndex(TreeNode root) { + this.root = root; + this.nodeIndex.clear(); + this.registerNode(root, null); + } @Override - public List getSchemas(SQL sql) throws SQLException { - var currentSchema = ""; - List schemas = new ArrayList<>(); - schemas.addAll(this.getSQLActuator().getObjects(sql.getSql(), Schema.class, Map.of("name", 1))); - schemas.sort((o1, o2) -> { - if (o1.getName().equalsIgnoreCase(currentSchema)) { - return -1; - } else if (o2.getName().equalsIgnoreCase(currentSchema)) { - return 1; - } else { - return 0; - } - }); - return schemas; + public synchronized ResourceNodeSnapshot getIndexedNode(String key) { + return key == null ? null : this.nodeIndex.get(key); } - public abstract List
getTables(String schema) throws SQLException; + private void registerNode(TreeNode node, ResourceNodeSnapshot parent) { + String database = parent == null ? null : parent.database(); + String schema = parent == null ? null : parent.schema(); + String table = parent == null ? null : parent.table(); - @Override - public List
getTables(SQL sql) throws SQLException { - return new ArrayList<>(this.getSQLActuator().getObjects(sql.getSql(), Table.class, Map.of("name", 1))); + switch (node.getType()) { + case "database" -> { + database = node.getLabel(); + schema = null; + table = null; + } + case "schema" -> { + schema = node.getLabel(); + if (this.connectionManager != null && + Objects.equals(this.connectionManager.getDatabaseContextKey(), "schema")) { + database = node.getLabel(); + } + table = null; + } + case "table", "view" -> table = node.getLabel(); + default -> { + } + } + + this.nodeIndex.put(node.getKey(), new ResourceNodeSnapshot( + node.getKey(), + node.getType(), + database, + schema, + table, + node.getLabel(), + relationKindForNode(node) + )); + if (node.getChildren() != null) { + var snapshot = this.nodeIndex.get(node.getKey()); + for (var child : node.getChildren()) { + this.registerNode(child, snapshot); + } + } } - public abstract List getViews(String schema) throws SQLException; + private RelationKind relationKindForNode(TreeNode node) { + if (StringUtils.isNotBlank(node.getRelationKind())) { + return RelationKind.fromCode(node.getRelationKind()); + } + return switch (node.getType()) { + case "table" -> RelationKind.TABLE; + case "view" -> RelationKind.VIEW; + default -> null; + }; + } - @Override - public List getViews(SQL sql) throws SQLException { - return new ArrayList<>(this.getSQLActuator().getObjects(sql.getSql(), View.class, Map.of("name", 1))); + private void removeIndexedDescendants(String parentKey) { + String prefix = parentKey + ","; + this.nodeIndex.keySet().removeIf(key -> key.startsWith(prefix)); } - public abstract List getFields(String schema, String table) throws SQLException; @Override - public List getFields(SQL sql) throws SQLException { - Map fieldMapping = Map.of( - "name", 1, - "type", 2, - "nullable", 3); - return new ArrayList<>(this.getSQLActuator().getObjects(sql.getSql(), Field.class, fieldMapping)); + public RelationScope resolveScope(ResourceNodeSnapshot node, String context) throws SQLException { + if (node == null) { + throw new IllegalArgumentException("Invalid metadata context"); + } + var contextKey = this.connectionManager.getContextKey(); + var databaseContextKey = this.connectionManager.getDatabaseContextKey(); + var currentContext = StringUtils.defaultString(context).trim(); + var catalog = node.database(); + if (StringUtils.equals(contextKey, databaseContextKey) && StringUtils.isNotBlank(currentContext)) { + var allowedContexts = this.connectionManager.getSqlActuator().getSchemas(); + if (!allowedContexts.contains(currentContext)) { + throw new IllegalArgumentException("Unknown database metadata context"); + } + catalog = currentContext; + } + if (StringUtils.equals(databaseContextKey, "schema")) { + var schema = StringUtils.defaultIfBlank(currentContext, node.schema()); + SqlIdentifierUtils.validateDatabaseName(schema); + return new RelationScope(null, schema); + } + SqlIdentifierUtils.validateDatabaseName(catalog); + String schema = null; + if (StringUtils.equals(contextKey, "schema")) { + schema = StringUtils.defaultIfBlank(currentContext, node.schema()); + if (StringUtils.isNotBlank(catalog) && schema.startsWith(catalog + ".")) { + schema = schema.substring(catalog.length() + 1); + } + } else if (StringUtils.equals(node.database(), catalog)) { + schema = node.schema(); + } + return new RelationScope(catalog, schema); } - public SQLActuator getSQLActuator() { - return this.connectionManager.getSqlActuator(); + protected MetadataCatalog metadataCatalog() { + return this.connectionManager.getDatasource().getMetadataCatalog(); } } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseSQLActuator.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseSQLActuator.java index 99b0c187..95e8b248 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseSQLActuator.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseSQLActuator.java @@ -13,6 +13,8 @@ import org.jumpserver.chen.framework.datasource.ConnectionManager; import org.jumpserver.chen.framework.datasource.entity.resource.Field; import org.jumpserver.chen.framework.datasource.sql.*; +import org.jumpserver.chen.framework.datasource.edit.pk.JdbcPrimaryKeyResolver; +import org.jumpserver.chen.framework.datasource.edit.analyzer.QueryResultEditabilityAnalyzer; import org.jumpserver.chen.framework.jms.exception.CommandRejectException; import org.jumpserver.chen.framework.session.SessionManager; import org.jumpserver.chen.framework.utils.HexUtils; @@ -38,9 +40,12 @@ import java.time.temporal.ChronoField; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.StringJoiner; +import java.util.UUID; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; @@ -51,6 +56,7 @@ public abstract class BaseSQLActuator implements SQLActuator { private final DbType druidDbType; // Keep large JDBC text values bounded so one cell cannot fail or stall the whole result view. private static final int MAX_TEXT_DISPLAY_LENGTH = 1024 * 1024; + private static final int RAW_RESULT_ROW_LIMIT = 1000; private static final String TRUNCATED_SUFFIX = "...[truncated]"; private static final DateTimeFormatter LOCAL_DATE_TIME_DISPLAY_FORMATTER = new DateTimeFormatterBuilder() .appendPattern("uuuu-MM-dd HH:mm:ss") @@ -119,7 +125,7 @@ public int getAffectedRows(SQL sql) throws SQLException { @Override public List parseSQL(SQL sql) { - return SQLUtils.parseStatements(sql.getSql(), this.druidDbType).stream() + return SqlValidator.parse(this.druidDbType, sql.getSql()).stream() .map(stmt -> SQLUtils.toSQLString(stmt, this.druidDbType)) .toList(); } @@ -147,6 +153,33 @@ public List getObjects(String sql, Class clazz, Map f return objects; } + @Override + public List> queryRows(String sql, List parameters) throws SQLException { + var rows = new ArrayList>(); + try (Connection conn = this.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + for (int index = 0; index < parameters.size(); index++) { + stmt.setObject(index + 1, parameters.get(index)); + } + try (ResultSet rs = stmt.executeQuery()) { + var metadata = rs.getMetaData(); + while (rs.next()) { + var row = new LinkedHashMap(); + for (int index = 1; index <= metadata.getColumnCount(); index++) { + row.put( + metadata.getColumnLabel(index).toLowerCase(Locale.ROOT), + this.normalizeJdbcValue(rs, index) + ); + } + rows.add(row); + } + } + } catch (SQLException e) { + throw new SQLException("run metadata sql error, %s".formatted(e.getMessage()), e); + } + return rows; + } + @Override public SQLQueryResult execute(SQL sql) throws SQLException { var plan = this.createPlan(sql); @@ -156,12 +189,32 @@ public SQLQueryResult execute(SQL sql) throws SQLException { @Override public SQLQueryResult execute(SQLExecutePlan plan) throws SQLException { + return this.execute(plan, true); + } + + @Override + public SQLQueryResult executeRaw(SQLExecutePlan plan) throws SQLException { + SQLQueryResult executionResult = new SQLQueryResult(plan.getTargetSQL()); + executionResult.setAclResult(plan.getAclResult()); + executionResult.setHasResultSet(false); + try { + Statement statement = plan.createStatement(); + this.executeRawStatement(plan, statement, executionResult); + } finally { + if (plan.getConnection() instanceof DruidPooledConnection) { + plan.getConnection().close(); + } + } + return executionResult; + } + + private SQLQueryResult execute(SQLExecutePlan plan, boolean enrichResult) throws SQLException { String sql = plan.getTargetSQL(); SQLQueryResult result = new SQLQueryResult(sql); result.setAclResult(plan.getAclResult()); try { Statement statement = plan.createStatement(); - this.executeStatement(plan, statement, result); + this.executeStatement(plan, statement, result, enrichResult); } finally { if (plan.getConnection() instanceof DruidPooledConnection) { plan.getConnection().close(); @@ -170,7 +223,12 @@ public SQLQueryResult execute(SQLExecutePlan plan) throws SQLException { return result; } - private void executeStatement(SQLExecutePlan plan, Statement statement, SQLQueryResult result) throws SQLException { + private void executeStatement( + SQLExecutePlan plan, + Statement statement, + SQLQueryResult result, + boolean enrichResult + ) throws SQLException { try (statement) { result.setStartTime(new Time(System.currentTimeMillis())); @@ -180,57 +238,250 @@ private void executeStatement(SQLExecutePlan plan, Statement statement, SQLQuery result.setQueryFinishedTime(new Time(System.currentTimeMillis())); if (hasResult) { - var resultSet = statement.getResultSet(); - var metaData = resultSet.getMetaData(); - var columnCount = metaData.getColumnCount(); - - for (int i = 1; i <= columnCount; i++) { - Field field = new Field(); - - var fieldName = StringUtils.isNotEmpty(metaData.getColumnLabel(i)) ? - metaData.getColumnLabel(i) : metaData.getColumnName(i); - field.setName(fieldName); - field.setColumnName(metaData.getColumnName(i)); - field.setLabel(metaData.getColumnLabel(i)); - result.getFields().add(field); + try (ResultSet resultSet = statement.getResultSet()) { + this.readResultSet(resultSet, result, Integer.MAX_VALUE); } - - while (resultSet.next()) { - List fs = new ArrayList<>(); - for (int i = 1; i <= columnCount; i++) { - try { - fs.add(this.normalizeJdbcValue(resultSet, i)); - } catch (NoClassDefFoundError e) { - log.error(e.getMessage()); - } + result.setFetchFinishedTime(new Time(System.currentTimeMillis())); + if (enrichResult) { + markGeneratedColumns(plan.getConnection(), this.getDruidDbType(), result.getFields()); + this.analyzeResultEditability(plan, result); + } + this.handleDataMasking(result); + if (enrichResult) { + var total = this.count(plan); + if (total < 0) { + result.setTotal(result.getData().size()); + } else { + result.setPaged(true); + result.setTotal(total); } - result.getData().add(fs); + } else { + result.setTotal(result.getData().size()); } - resultSet.close(); - result.setFetchFinishedTime(new Time(System.currentTimeMillis())); + } else { + result.setUpdateCount(statement.getUpdateCount()); + } + result.setEndTime(new Time(System.currentTimeMillis())); + } catch (SQLException e) { + throw e; + } catch (Exception e) { + throw new SQLException(e.getMessage(), e); + } + } - // 数据脱敏 - this.handleDataMasking(result); + private void executeRawStatement( + SQLExecutePlan plan, + Statement statement, + SQLQueryResult executionResult + ) throws SQLException { + long executionStartedAt = System.currentTimeMillis(); + executionResult.setStartTime(new Time(executionStartedAt)); + + try (statement) { + try { + // Ask the driver for one sentinel row so truncation can be detected without rewriting SQL. + statement.setMaxRows(RAW_RESULT_ROW_LIMIT + 1); + } catch (SQLException e) { + log.debug("JDBC driver does not support Statement.setMaxRows", e); + } - var total = this.count(plan); - if (total < 0) { + long resultStartedAt = executionStartedAt; + boolean hasResultSet = statement.execute(plan.getTargetSQL()); + while (true) { + long queryFinishedAt = System.currentTimeMillis(); + if (hasResultSet) { + SQLQueryResult result = newRawResult(plan, resultStartedAt, queryFinishedAt, true); + try (ResultSet resultSet = statement.getResultSet()) { + result.setTruncated(this.readResultSet(resultSet, result, RAW_RESULT_ROW_LIMIT)); + } + result.setRowLimit(RAW_RESULT_ROW_LIMIT); result.setTotal(result.getData().size()); + result.setFetchFinishedTime(new Time(System.currentTimeMillis())); + result.setEndTime(result.getFetchFinishedTime()); + this.handleDataMasking(result); + executionResult.getResults().add(result); } else { - result.setPaged(true); - result.setTotal(total); + int updateCount = statement.getUpdateCount(); + if (updateCount == -1) { + break; + } + SQLQueryResult result = newRawResult(plan, resultStartedAt, queryFinishedAt, false); + result.setUpdateCount(updateCount); + result.setEndTime(new Time(System.currentTimeMillis())); + executionResult.getResults().add(result); } - } else { - result.setUpdateCount(statement.getUpdateCount()); + resultStartedAt = System.currentTimeMillis(); + hasResultSet = statement.getMoreResults(Statement.CLOSE_CURRENT_RESULT); } - result.setEndTime(new Time(System.currentTimeMillis())); + + Time finishedAt = new Time(System.currentTimeMillis()); + executionResult.setQueryFinishedTime(finishedAt); + executionResult.setFetchFinishedTime(finishedAt); + executionResult.setEndTime(finishedAt); + } catch (SQLException e) { + throw e; } catch (Exception e) { - throw new SQLException(e.getMessage()); + throw new SQLException(e.getMessage(), e); } } + private SQLQueryResult newRawResult( + SQLExecutePlan plan, + long startedAt, + long queryFinishedAt, + boolean hasResultSet + ) { + SQLQueryResult result = new SQLQueryResult(plan.getTargetSQL()); + result.setAclResult(plan.getAclResult()); + result.setHasResultSet(hasResultSet); + result.setStartTime(new Time(startedAt)); + result.setQueryFinishedTime(new Time(queryFinishedAt)); + return result; + } + + private boolean readResultSet(ResultSet resultSet, SQLQueryResult result, int rowLimit) throws SQLException { + var metaData = resultSet.getMetaData(); + var columnCount = metaData.getColumnCount(); + for (int index = 1; index <= columnCount; index++) { + result.getFields().add(buildField(metaData, index, this.getDruidDbType())); + } + while (resultSet.next()) { + if (result.getData().size() >= rowLimit) { + return true; + } + List row = new ArrayList<>(); + for (int index = 1; index <= columnCount; index++) { + try { + row.add(this.normalizeJdbcValue(resultSet, index)); + } catch (NoClassDefFoundError e) { + log.error(e.getMessage()); + } + } + result.getData().add(row); + } + return false; + } + + private void analyzeResultEditability(SQLExecutePlan plan, SQLQueryResult result) { + var analyzer = new QueryResultEditabilityAnalyzer( + this.getDruidDbType(), + new JdbcPrimaryKeyResolver(plan.getConnection(), this.getDruidDbType()) + ); + analyzer.analyze(plan.getSourceSQL(), result.getFields()); + } + + static Field buildField(ResultSetMetaData metaData, int columnIndex) throws SQLException { + return buildField(metaData, columnIndex, null); + } + + private static Field buildField(ResultSetMetaData metaData, int columnIndex, DbType dbType) throws SQLException { + Field field = new Field(); + + String columnLabel = metaData.getColumnLabel(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + String fieldName = StringUtils.isNotEmpty(columnLabel) ? columnLabel : columnName; + if (StringUtils.isEmpty(fieldName)) { + fieldName = "column_" + columnIndex; + } + field.setName(fieldName); + field.setColumnName(columnName); + field.setLabel(columnLabel); + fillOptionalFieldMetadata(field, metaData, columnIndex, dbType); + return field; + } + + private static void fillOptionalFieldMetadata( + Field field, + ResultSetMetaData metaData, + int columnIndex, + DbType dbType + ) { + //后续还要靠 SQL AST 和主键解析再判断 + String schema = getNullableMetadataValue(() -> metaData.getSchemaName(columnIndex), "schema", columnIndex); + if (StringUtils.isBlank(schema) && (dbType == DbType.mysql || dbType == DbType.mariadb)) { + // Connector/J exposes the MySQL database through catalog metadata by default. + schema = getNullableMetadataValue(() -> metaData.getCatalogName(columnIndex), "catalog", columnIndex); + } + field.setSchema(schema); + field.setTable(getNullableMetadataValue(() -> metaData.getTableName(columnIndex), "table", columnIndex)); + field.setType(getNullableMetadataValue(() -> metaData.getColumnTypeName(columnIndex), "type", columnIndex)); + try { + field.setJdbcType(metaData.getColumnType(columnIndex)); + } catch (SQLException e) { + log.debug("read result set jdbc type metadata failed for column {}", columnIndex, e); + } + + try { + // Field.nullable is boolean, so failed/unknown nullable metadata remains false. + field.setNullable(metaData.isNullable(columnIndex) == ResultSetMetaData.columnNullable); + } catch (SQLException e) { + log.debug("read result set nullable metadata failed for column {}", columnIndex, e); + } + try { + field.setAutoIncrement(metaData.isAutoIncrement(columnIndex)); + } catch (Exception e) { + log.debug("read result set auto increment metadata failed for column {}", columnIndex, e); + } + // ResultSetMetaData.isReadOnly/isWritable describe whether this ResultSet cursor can be + // updated through ResultSet.updateXXX(). Chen does not update that cursor: it resolves a + // base table and primary key, then issues a separate prepared UPDATE statement. In + // particular, the default Statement created by JDBC has CONCUR_READ_ONLY concurrency, so + // cursor writability must not be copied to Field.readOnly. + } + + private static String getNullableMetadataValue(MetadataValueReader reader, String name, int columnIndex) { + try { + String value = reader.read(); + return StringUtils.isNotBlank(value) ? value : null; + } catch (SQLException e) { + log.debug("read result set {} metadata failed for column {}", name, columnIndex, e); + return null; + } + } + + @FunctionalInterface + private interface MetadataValueReader { + String read() throws SQLException; + } + // Normalize JDBC driver objects before Gson sees them in update_data_view packets. protected Object normalizeJdbcValue(ResultSet resultSet, int columnIndex) throws SQLException { + int jdbcType = Types.OTHER; + String typeName = null; + try { + ResultSetMetaData metaData = resultSet.getMetaData(); + try { + jdbcType = metaData.getColumnType(columnIndex); + } catch (SQLException e) { + log.debug("read result set jdbc type failed for column {}", columnIndex, e); + } + try { + typeName = metaData.getColumnTypeName(columnIndex); + } catch (SQLException e) { + log.debug("read result set type name failed for column {}", columnIndex, e); + } + } catch (SQLException e) { + log.debug("read result set metadata failed for column {}", columnIndex, e); + } + + if (JdbcDisplayValue.isDecimalType(jdbcType, typeName)) { + try { + BigDecimal decimal = resultSet.getBigDecimal(columnIndex); + return decimal == null ? null : decimal.toPlainString(); + } catch (SQLException | AbstractMethodError e) { + log.debug("read decimal value failed for column {}", columnIndex, e); + } + } + + if (JdbcDisplayValue.isGeometryType(jdbcType, typeName)) { + return this.normalizeGeometryValue(resultSet, columnIndex); + } + + if (JdbcDisplayValue.isBlobType(jdbcType, typeName)) { + return this.normalizeBlobValue(resultSet, columnIndex); + } + return this.normalizeJdbcValue(resultSet.getObject(columnIndex)); } @@ -273,7 +524,7 @@ protected Object normalizeJdbcValue(Object value) throws SQLException { } if (value instanceof Blob blob) { - return HexUtils.bytesToHex(blob.getBytes(1, (int) blob.length())); + return this.summarizeBlob(blob); } if (value.getClass().getSimpleName().equalsIgnoreCase("pgobject")) { @@ -287,6 +538,78 @@ protected Object normalizeJdbcValue(Object value) throws SQLException { return value; } + private Object normalizeGeometryValue(ResultSet resultSet, int columnIndex) throws SQLException { + Object value; + boolean objectReadFailed = false; + try { + value = resultSet.getObject(columnIndex); + } catch (SQLException | AbstractMethodError e) { + log.debug("read geometry object failed for column {}", columnIndex, e); + value = null; + objectReadFailed = true; + } + if (!objectReadFailed && value == null) { + return null; + } + String display = JdbcDisplayValue.toGeometryDisplay(value, this.getDruidDbType()); + if (display != null && JdbcDisplayValue.looksLikeWkt(display)) { + return display; + } + try { + String text = resultSet.getString(columnIndex); + if (JdbcDisplayValue.looksLikeWkt(text)) { + return text.trim(); + } + } catch (SQLException | AbstractMethodError e) { + log.debug("read geometry as string failed for column {}", columnIndex, e); + } + if (display != null) { + return display; + } + return JdbcDisplayValue.geometrySummary(null); + } + + private Object normalizeBlobValue(ResultSet resultSet, int columnIndex) throws SQLException { + try { + Blob blob = resultSet.getBlob(columnIndex); + if (blob != null) { + return this.summarizeBlob(blob); + } + if (resultSet.wasNull()) { + return null; + } + } catch (SQLException | AbstractMethodError e) { + log.debug("read blob locator failed for column {}", columnIndex, e); + } + + Object value = resultSet.getObject(columnIndex); + if (value == null) { + return null; + } + if (value instanceof Blob blob) { + return this.summarizeBlob(blob); + } + if (value instanceof byte[] bytes) { + return JdbcDisplayValue.blobSummary(bytes.length); + } + return this.normalizeJdbcValue(value); + } + + private String summarizeBlob(Blob blob) throws SQLException { + try { + return JdbcDisplayValue.blobSummary(blob.length()); + } catch (SQLException e) { + log.debug("read blob length failed", e); + return JdbcDisplayValue.blobSummary(); + } finally { + try { + blob.free(); + } catch (SQLException e) { + log.debug("free blob failed", e); + } + } + } + private String normalizeOracleTimestampWithTimeZone(Object value) throws SQLException { try { var offsetDateTime = value.getClass().getMethod("toOffsetDateTime").invoke(value); @@ -338,6 +661,164 @@ private String formatTemporalValue(Object value) { return null; } + static void markGeneratedColumns(Connection connection, DbType dbType, List fields) { + if (connection == null || fields == null || fields.isEmpty()) { + return; + } + boolean postgresql = dbType == DbType.postgresql; + boolean mysqlFamily = dbType == DbType.mysql || dbType == DbType.mariadb; + if (!postgresql && !mysqlFamily) { + markGeneratedColumnsFromJdbcMetadata(connection, fields); + return; + } + String sql = postgresql + ? """ + SELECT is_generated, generation_expression + FROM information_schema.columns + WHERE table_schema = ? + AND table_name = ? + AND column_name = ? + """ + : """ + SELECT extra, generation_expression + FROM information_schema.columns + WHERE table_schema = ? + AND table_name = ? + AND column_name = ? + """; + for (Field field : fields) { + if (field == null || + StringUtils.isBlank(field.getSchema()) || + StringUtils.isBlank(field.getTable()) || + StringUtils.isBlank(field.getColumnName())) { + continue; + } + try (PreparedStatement statement = connection.prepareStatement(sql)) { + statement.setString(1, field.getSchema()); + statement.setString(2, field.getTable()); + statement.setString(3, field.getColumnName()); + try (ResultSet resultSet = statement.executeQuery()) { + if (!resultSet.next()) { + continue; + } + String generationExpression = resultSet.getString("generation_expression"); + boolean generated; + if (postgresql) { + generated = "ALWAYS".equalsIgnoreCase(resultSet.getString("is_generated")) || + StringUtils.isNotBlank(generationExpression); + } else { + String extra = resultSet.getString("extra"); + generated = StringUtils.isNotBlank(generationExpression) || + StringUtils.containsIgnoreCase(extra, "STORED GENERATED") || + StringUtils.containsIgnoreCase(extra, "VIRTUAL GENERATED"); + } + if (generated) { + field.setGenerated(true); + field.setReadOnly(true); + } + } + } catch (SQLException e) { + log.debug("read {} generated column metadata failed for {}.{}.{}", + dbType, + field.getSchema(), + field.getTable(), + field.getColumnName(), + e); + } + } + } + + private static void markGeneratedColumnsFromJdbcMetadata( + Connection connection, + List fields + ) { + DatabaseMetaData metadata; + try { + metadata = connection.getMetaData(); + } catch (SQLException e) { + log.debug("read database metadata for generated columns failed", e); + return; + } + if (metadata == null) { + return; + } + + Map> fieldsByTable = new LinkedHashMap<>(); + for (Field field : fields) { + if (field == null || + StringUtils.isBlank(field.getSchema()) || + StringUtils.isBlank(field.getTable()) || + StringUtils.isBlank(field.getColumnName())) { + continue; + } + fieldsByTable.computeIfAbsent( + new ColumnMetadataTable(field.getSchema(), field.getTable()), + ignored -> new ArrayList<>() + ).add(field); + } + + for (Map.Entry> entry : fieldsByTable.entrySet()) { + ColumnMetadataTable table = entry.getKey(); + try (ResultSet resultSet = metadata.getColumns( + null, + table.schema(), + table.table(), + "%" + )) { + while (resultSet.next()) { + String metadataTable = getMetadataValue(resultSet, "TABLE_NAME"); + String metadataColumn = getMetadataValue(resultSet, "COLUMN_NAME"); + if (!metadataIdentifierEquals(table.table(), metadataTable)) { + continue; + } + for (Field field : entry.getValue()) { + if (!metadataIdentifierEquals(field.getColumnName(), metadataColumn)) { + continue; + } + if (isYesMetadataValue(resultSet, "IS_AUTOINCREMENT")) { + field.setAutoIncrement(true); + } + if (isYesMetadataValue(resultSet, "IS_GENERATEDCOLUMN")) { + field.setGenerated(true); + field.setReadOnly(true); + } + break; + } + } + } catch (SQLException e) { + log.debug("read JDBC generated column metadata failed for {}.{}", + table.schema(), + table.table(), + e); + } + } + } + + private static String getMetadataValue(ResultSet resultSet, String columnLabel) { + try { + return StringUtils.trimToNull(resultSet.getString(columnLabel)); + } catch (SQLException e) { + log.debug("read JDBC column metadata attribute {} failed", columnLabel, e); + return null; + } + } + + private static boolean metadataIdentifierEquals(String expected, String actual) { + return StringUtils.equalsIgnoreCase(StringUtils.trim(expected), StringUtils.trim(actual)); + } + + private static boolean isYesMetadataValue(ResultSet resultSet, String columnLabel) { + try { + return StringUtils.equalsIgnoreCase("YES", resultSet.getString(columnLabel)); + } catch (SQLException e) { + log.debug("read JDBC column metadata attribute {} failed", columnLabel, e); + return false; + } + } + + private record ColumnMetadataTable(String schema, String table) { + } + private String toDisplayArray(java.sql.Array jdbcArray) throws SQLException { try { var text = jdbcArray.toString(); @@ -457,22 +938,27 @@ private void handleDataMasking(SQLQueryResult result) { var maskIndexes = new ArrayList<>(); var maskRules = new HashMap(); for (var i = 0; i < result.getFields().size(); i++) { + var field = result.getFields().get(i); for (Common.DataMaskingRule rule : rules) { - if (this.matchField(result.getFields().get(i), rule.getFieldsPattern())) { + if (this.matchField(field, rule.getFieldsPattern())) { + field.setMasked(true); maskIndexes.add(i); maskRules.put(i, rule); } } } + this.captureUnmaskedPrimaryKeyValues(result, maskIndexes); + for (var i = 0; i < result.getData().size(); i++) { for (var j = 0; j < result.getData().get(i).size(); j++) { if (maskIndexes.contains(j)) { var rule = maskRules.get(j); var val = result.getData().get(i).get(j); - if (val instanceof String) { - var rep = this.replaceColumnVal(rule, (String) val); - result.getData().get(i).set(j, rep); + if (val instanceof String text) { + result.getData().get(i).set(j, this.replaceColumnVal(rule, text)); + } else if (isValueBasedMaskingMethod(rule.getMaskingMethod()) && isScalarMaskingValue(val)) { + result.getData().get(i).set(j, this.replaceColumnVal(rule, val.toString())); } else { result.getData().get(i).set(j, rule.getMaskPattern()); } @@ -481,11 +967,49 @@ private void handleDataMasking(SQLQueryResult result) { } } + private boolean isValueBasedMaskingMethod(String method) { + return switch (method) { + case "hide_middle", "keep_prefix", "keep_suffix" -> true; + default -> false; + }; + } + + private boolean isScalarMaskingValue(Object value) { + return value instanceof Number + || value instanceof Boolean + || value instanceof Character + || value instanceof CharSequence + || value instanceof UUID + || value instanceof Enum; + } + + private void captureUnmaskedPrimaryKeyValues(SQLQueryResult result, java.util.List maskIndexes) { + int pkIndex = -1; + for (int i = 0; i < result.getFields().size(); i++) { + if (result.getFields().get(i).isPrimaryKey()) { + pkIndex = i; + break; + } + } + if (pkIndex < 0 || !maskIndexes.contains(pkIndex)) { + result.setUnmaskedPrimaryKeyValues(null); + return; + } + java.util.List values = new java.util.ArrayList<>(result.getData().size()); + for (java.util.List row : result.getData()) { + values.add(pkIndex < row.size() ? row.get(pkIndex) : null); + } + result.setUnmaskedPrimaryKeyValues(values); + } + private boolean matchField(Field field, String pattern) { - List names = List.of(field.getColumnName(), field.getLabel()); + String[] names = {field.getColumnName(), field.getLabel()}; String[] ps = pattern.split(","); for (String name : names) { + if (name == null) { + continue; + } for (String p : ps) { p = p.trim(); if (p.isEmpty()) continue; @@ -535,12 +1059,18 @@ private String replaceColumnVal(Common.DataMaskingRule rule, String val) { case "hide_middle": // 隐藏中间 - if (val == null || val.length() < 3) { + if (val == null) { return pattern.isEmpty() ? "####" : pattern; } - return val.charAt(0) - + "*".repeat(val.length() - 2) - + val.substring(val.length() - 1); + int codePointCount = val.codePointCount(0, val.length()); + if (codePointCount < 3) { + return pattern.isEmpty() ? "####" : pattern; + } + int firstCodePointEnd = val.offsetByCodePoints(0, 1); + int lastCodePointStart = val.offsetByCodePoints(0, codePointCount - 1); + return val.substring(0, firstCodePointEnd) + + "*".repeat(codePointCount - 2) + + val.substring(lastCodePointStart); case "keep_prefix": // 保留前缀 @@ -576,7 +1106,17 @@ public SQLQueryResult executeWithAudit(SQL sql) throws SQLException { public SQLQueryResult executeWithAudit(SQLExecutePlan plan) throws SQLException { var sess = SessionManager.getCurrentSession(); try { - return sess.withAudit(plan.getTargetSQL(), () -> this.execute(plan)); + return sess.withAudit(plan.getTargetSQL(), plan.getAclResult(), () -> this.execute(plan)); + } catch (CommandRejectException e) { + throw new SQLException(e.getMessage()); + } + } + + @Override + public SQLQueryResult executeRawWithAudit(SQLExecutePlan plan) throws SQLException { + var sess = SessionManager.getCurrentSession(); + try { + return sess.withAudit(plan.getTargetSQL(), plan.getAclResult(), () -> this.executeRaw(plan)); } catch (CommandRejectException e) { throw new SQLException(e.getMessage()); } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseSQLHintsHandler.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseSQLHintsHandler.java deleted file mode 100644 index d6e132c9..00000000 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseSQLHintsHandler.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.jumpserver.chen.framework.datasource.base; - -import org.jumpserver.chen.framework.datasource.entity.resource.Field; -import org.jumpserver.chen.framework.datasource.entity.resource.Table; -import org.jumpserver.chen.framework.datasource.hints.SQLHintsHandler; -import org.jumpserver.chen.framework.session.SessionManager; - -import java.sql.SQLException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public abstract class BaseSQLHintsHandler implements SQLHintsHandler { - - public abstract List
getALlTables(String schema) throws SQLException; - - public abstract List getAllFields(String schema) throws SQLException; - - @Override - public Map> getHints(String nodeKey, String context) throws SQLException { - Map> suggestions = new HashMap<>(); - - var session = SessionManager.getCurrentSession(); - if (!session.enableAutoComplete()) { - return suggestions; - } - - var tables = this.getALlTables(context); - var tableNames = tables.stream().map(Table::getName).toList(); - - suggestions.put(context, tableNames); - var fields = this.getAllFields(context); - - tables.forEach(table -> { - var fieldsOfTable = fields.stream().filter(field -> field.getTable().equals(table.getName())).toList(); - suggestions.put(table.getName(), fieldsOfTable.stream().map(Field::getName).toList()); - }); - return suggestions; - } -} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/ConnectionOwnership.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/ConnectionOwnership.java new file mode 100644 index 00000000..6bcedebf --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/ConnectionOwnership.java @@ -0,0 +1,6 @@ +package org.jumpserver.chen.framework.datasource.edit; + +public enum ConnectionOwnership { + EXECUTION_CONTEXT, + QUERY_CONSOLE +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/SaveExecutionContext.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/SaveExecutionContext.java new file mode 100644 index 00000000..2b6d75c5 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/SaveExecutionContext.java @@ -0,0 +1,15 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import java.sql.Connection; +import java.sql.SQLException; + +public interface SaveExecutionContext extends AutoCloseable { + Connection connection() throws SQLException; + + TransactionMode transactionMode(); + + ConnectionOwnership connectionOwnership(); + + @Override + void close() throws SQLException; +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/SavepointController.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/SavepointController.java new file mode 100644 index 00000000..4747993c --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/SavepointController.java @@ -0,0 +1,14 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import java.sql.Connection; +import java.sql.SQLException; + +interface SavepointController { + void create(Connection connection, String name) throws SQLException; + + void rollbackTo(Connection connection, String name) throws SQLException; + + default void release(Connection connection, String name) throws SQLException { + // A dialect without RELEASE SAVEPOINT support may intentionally leave it active. + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/SavepointControllers.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/SavepointControllers.java new file mode 100644 index 00000000..f8c68a83 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/SavepointControllers.java @@ -0,0 +1,41 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import com.alibaba.druid.DbType; + +final class SavepointControllers { + private static final SavepointController STANDARD = new SqlSavepointController( + "SAVEPOINT %s", + "ROLLBACK TO SAVEPOINT %s", + "RELEASE SAVEPOINT %s" + ); + private static final SavepointController ORACLE = new SqlSavepointController( + "SAVEPOINT %s", + "ROLLBACK TO SAVEPOINT %s", + null + ); + private static final SavepointController SQL_SERVER = new SqlSavepointController( + "SAVE TRANSACTION %s", + "ROLLBACK TRANSACTION %s", + null + ); + private static final SavepointController DB2 = new SqlSavepointController( + "SAVEPOINT %s ON ROLLBACK RETAIN CURSORS", + "ROLLBACK TO SAVEPOINT %s", + "RELEASE SAVEPOINT %s" + ); + + private SavepointControllers() { + } + + static SavepointController forDbType(DbType dbType) { + return switch (dbType) { + case postgresql, mysql, mariadb, dm -> STANDARD; + case oracle -> ORACLE; + case sqlserver -> SQL_SERVER; + case db2 -> DB2; + default -> throw new IllegalArgumentException( + "USER_MANAGED DataView savepoints are not supported for database: " + dbType + ); + }; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/ServiceManagedSaveExecutionContext.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/ServiceManagedSaveExecutionContext.java new file mode 100644 index 00000000..a756d873 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/ServiceManagedSaveExecutionContext.java @@ -0,0 +1,37 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Objects; + +public final class ServiceManagedSaveExecutionContext implements SaveExecutionContext { + private final Connection connection; + private final ConnectionOwnership connectionOwnership; + + public ServiceManagedSaveExecutionContext(Connection connection, ConnectionOwnership connectionOwnership) { + this.connection = Objects.requireNonNull(connection, "connection"); + this.connectionOwnership = Objects.requireNonNull(connectionOwnership, "connectionOwnership"); + } + + @Override + public Connection connection() { + return this.connection; + } + + @Override + public TransactionMode transactionMode() { + return TransactionMode.SERVICE_MANAGED; + } + + @Override + public ConnectionOwnership connectionOwnership() { + return this.connectionOwnership; + } + + @Override + public void close() throws SQLException { + if (this.connectionOwnership == ConnectionOwnership.EXECUTION_CONTEXT) { + this.connection.close(); + } + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/ServiceManagedTransactionBoundary.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/ServiceManagedTransactionBoundary.java new file mode 100644 index 00000000..40484e1e --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/ServiceManagedTransactionBoundary.java @@ -0,0 +1,142 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import lombok.extern.slf4j.Slf4j; +import org.jumpserver.chen.framework.datasource.edit.exception.CommitOutcomeUnknownException; +import org.jumpserver.chen.framework.datasource.edit.exception.RollbackFailedException; +import org.jumpserver.chen.framework.datasource.edit.exception.RolledBackConnectionUnavailableException; + +import java.sql.Connection; +import java.sql.SQLException; + +/** + * Runs a DataView save batch on a connection the service owns (autoCommit=true at entry). + * + * A commit failure is treated as an unknown outcome: the database may have committed despite + * the client never receiving the acknowledgement. Rolling back would either mask a real commit + * or fail meaninglessly, so the connection is discarded and the caller reports the uncertainty + * rather than pretending the batch was rolled back. Failures that occur before commit (a + * statement error, a runtime bug in binding, etc.) have committed nothing and are rolled back + * normally. + */ +@Slf4j +final class ServiceManagedTransactionBoundary implements TransactionBoundary { + @Override + public TransactionOutcome execute( + Connection connection, + TableChangesPlan plan, + TransactionWork work + ) throws SQLException { + boolean originalAutoCommit = connection.getAutoCommit(); + if (!originalAutoCommit) { + throw new SQLException("SERVICE_MANAGED transaction requires autoCommit=true"); + } + + connection.setAutoCommit(false); + T result; + try { + result = work.execute(); + } catch (SQLException | RuntimeException e) { + try { + this.rollback(connection, plan, e); + } catch (RollbackFailedException rollbackFailure) { + throw rollbackFailure; + } + try { + this.restoreAutoCommit(connection, plan, originalAutoCommit); + } catch (SQLException restoreFailure) { + RolledBackConnectionUnavailableException failure = + new RolledBackConnectionUnavailableException(restoreFailure, e); + this.discardConnection(connection, failure); + throw failure; + } + throw e; + } + + try { + this.commit(connection, plan); + } catch (CommitOutcomeUnknownException e) { + this.discardConnection(connection, e); + throw e; + } + + try { + this.restoreAutoCommit(connection, plan, originalAutoCommit); + return TransactionOutcome.success(result, true); + } catch (SQLException restoreFailure) { + this.discardConnection(connection, restoreFailure); + return new TransactionOutcome<>( + result, + true, + true, + TableChangesSaveService.SAVE_CHANGES_COMMITTED_CONNECTION_UNAVAILABLE + ); + } + } + + private void commit(Connection connection, TableChangesPlan plan) throws SQLException { + try { + connection.commit(); + } catch (SQLException e) { + log.error( + "save changes commit failed, table={}.{}, sqlState={}, vendorCode={}, message={}", + plan.getSchema(), + plan.getTable(), + e.getSQLState(), + e.getErrorCode(), + e.getMessage(), + e + ); + throw new CommitOutcomeUnknownException(e); + } + } + + private void rollback(Connection connection, TableChangesPlan plan, Throwable primaryException) + throws RollbackFailedException { + try { + connection.rollback(); + } catch (SQLException e) { + log.warn( + "rollback save changes transaction failed, table={}.{}, sqlState={}, vendorCode={}, message={}", + plan.getSchema(), + plan.getTable(), + e.getSQLState(), + e.getErrorCode(), + e.getMessage(), + e + ); + RollbackFailedException failure = new RollbackFailedException(e, primaryException); + this.discardConnection(connection, failure); + throw failure; + } + } + + private void restoreAutoCommit( + Connection connection, + TableChangesPlan plan, + boolean originalAutoCommit + ) throws SQLException { + try { + connection.setAutoCommit(originalAutoCommit); + } catch (SQLException e) { + log.warn( + "restore save changes connection autoCommit failed, table={}.{}, originalAutoCommit={}, sqlState={}, vendorCode={}, message={}", + plan.getSchema(), + plan.getTable(), + originalAutoCommit, + e.getSQLState(), + e.getErrorCode(), + e.getMessage(), + e + ); + throw e; + } + } + + private void discardConnection(Connection connection, Throwable failure) { + try { + connection.close(); + } catch (SQLException | RuntimeException e) { + failure.addSuppressed(e); + } + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/SqlSavepointController.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/SqlSavepointController.java new file mode 100644 index 00000000..5e3386d2 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/SqlSavepointController.java @@ -0,0 +1,51 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.regex.Pattern; + +final class SqlSavepointController implements SavepointController { + private static final Pattern SAFE_NAME = Pattern.compile("[A-Za-z][A-Za-z0-9_]{0,62}"); + + private final String createSql; + private final String rollbackSql; + private final String releaseSql; + + SqlSavepointController(String createSql, String rollbackSql, String releaseSql) { + this.createSql = createSql; + this.rollbackSql = rollbackSql; + this.releaseSql = releaseSql; + } + + @Override + public void create(Connection connection, String name) throws SQLException { + execute(connection, this.createSql, name); + } + + @Override + public void rollbackTo(Connection connection, String name) throws SQLException { + execute(connection, this.rollbackSql, name); + } + + @Override + public void release(Connection connection, String name) throws SQLException { + validateName(name); + if (this.releaseSql != null) { + execute(connection, this.releaseSql, name); + } + } + + static void validateName(String name) throws SQLException { + if (name == null || !SAFE_NAME.matcher(name).matches()) { + throw new SQLException("Invalid server-generated savepoint name"); + } + } + + private void execute(Connection connection, String template, String name) throws SQLException { + validateName(name); + try (Statement statement = connection.createStatement()) { + statement.execute(template.formatted(name)); + } + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableBrowseSaveExecutionContext.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableBrowseSaveExecutionContext.java new file mode 100644 index 00000000..be08fd68 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableBrowseSaveExecutionContext.java @@ -0,0 +1,41 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import org.jumpserver.chen.framework.datasource.ConnectionManager; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Objects; + +public final class TableBrowseSaveExecutionContext implements SaveExecutionContext { + private final ConnectionManager connectionManager; + private Connection connection; + + public TableBrowseSaveExecutionContext(ConnectionManager connectionManager) { + this.connectionManager = Objects.requireNonNull(connectionManager, "connectionManager"); + } + + @Override + public Connection connection() throws SQLException { + if (this.connection == null) { + this.connection = this.connectionManager.getConnection(); + } + return this.connection; + } + + @Override + public TransactionMode transactionMode() { + return TransactionMode.SERVICE_MANAGED; + } + + @Override + public ConnectionOwnership connectionOwnership() { + return ConnectionOwnership.EXECUTION_CONTEXT; + } + + @Override + public void close() throws SQLException { + if (this.connection != null) { + this.connection.close(); + } + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesPlan.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesPlan.java new file mode 100644 index 00000000..1fbf2f55 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesPlan.java @@ -0,0 +1,21 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import lombok.Data; +import org.jumpserver.chen.framework.datasource.edit.command.PreparedTableChangeCommand; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class TableChangesPlan { + private String dataView; + private String schema; + private String table; + private int changeCount; + private int updateCount; + private int insertCount; + private int deleteCount; + private List commands = new ArrayList<>(); + private List auditSqlList = new ArrayList<>(); + private String auditSql; +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesPlanBuildResult.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesPlanBuildResult.java new file mode 100644 index 00000000..45329aac --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesPlanBuildResult.java @@ -0,0 +1,35 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import lombok.Data; +import org.jumpserver.chen.framework.console.entity.request.SaveChangesRequest; + +@Data +public class TableChangesPlanBuildResult { + private boolean success; + private String reason; + private Integer failedChangeIndex; + private SaveChangesRequest.ChangeItem failedChange; + private Object failedOperation; + private TableChangesPlan plan; + + public static TableChangesPlanBuildResult success(TableChangesPlan plan) { + TableChangesPlanBuildResult result = new TableChangesPlanBuildResult(); + result.setSuccess(true); + result.setPlan(plan); + return result; + } + + public static TableChangesPlanBuildResult failure(String reason, Integer failedChangeIndex, SaveChangesRequest.ChangeItem failedChange) { + return failure(reason, failedChangeIndex, failedChange, failedChange); + } + + public static TableChangesPlanBuildResult failure(String reason, Integer failedChangeIndex, SaveChangesRequest.ChangeItem failedChange, Object failedOperation) { + TableChangesPlanBuildResult result = new TableChangesPlanBuildResult(); + result.setSuccess(false); + result.setReason(reason); + result.setFailedChangeIndex(failedChangeIndex); + result.setFailedChange(failedChange); + result.setFailedOperation(failedOperation); + return result; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesPlanBuilder.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesPlanBuilder.java new file mode 100644 index 00000000..acfda3bf --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesPlanBuilder.java @@ -0,0 +1,589 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import org.apache.commons.lang3.StringUtils; +import org.jumpserver.chen.framework.console.entity.request.SaveChangesRequest; +import org.jumpserver.chen.framework.datasource.edit.analyzer.EditabilityReason; +import org.jumpserver.chen.framework.datasource.edit.bind.TableEditTypeCodecs; +import org.jumpserver.chen.framework.datasource.edit.bind.TableEditValueConverter; +import org.jumpserver.chen.framework.datasource.edit.command.PreparedTableChangeCommand; +import org.jumpserver.chen.framework.datasource.edit.dialect.TableEditDialect; +import org.jumpserver.chen.framework.datasource.edit.dialect.TableEditDialects; +import org.jumpserver.chen.framework.datasource.entity.resource.Field; + +import lombok.extern.slf4j.Slf4j; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +@Slf4j +public class TableChangesPlanBuilder { + public static final int MAX_CHANGES = 500; + public static final String DATABASE_NOT_SUPPORTED_FOR_EDIT = "DATABASE_NOT_SUPPORTED_FOR_EDIT"; + public static final String DATA_VIEW_MISMATCH = "DATA_VIEW_MISMATCH"; + public static final String SCHEMA_TABLE_MISMATCH = "SCHEMA_TABLE_MISMATCH"; + public static final String EMPTY_CHANGES = "EMPTY_CHANGES"; + public static final String TOO_MANY_CHANGES = "TOO_MANY_CHANGES"; + public static final String NO_PRIMARY_KEY = "NO_PRIMARY_KEY"; + public static final String COMPOSITE_PRIMARY_KEY_NOT_SUPPORTED = "COMPOSITE_PRIMARY_KEY_NOT_SUPPORTED"; + public static final String PK_COLUMN_MISMATCH = "PK_COLUMN_MISMATCH"; + public static final String PRIMARY_KEY_VALUE_REQUIRED = "PRIMARY_KEY_VALUE_REQUIRED"; + public static final String PRIMARY_KEY_COLUMN_NOT_EDITABLE = "PRIMARY_KEY_COLUMN_NOT_EDITABLE"; + public static final String SOURCE_COLUMN_NOT_FOUND = "SOURCE_COLUMN_NOT_FOUND"; + public static final String SOURCE_COLUMN_AMBIGUOUS = "SOURCE_COLUMN_AMBIGUOUS"; + public static final String PRIMARY_KEY_SOURCE_COLUMN_MISSING = "PRIMARY_KEY_SOURCE_COLUMN_MISSING"; + public static final String SOURCE_SCHEMA_MISMATCH = "SOURCE_SCHEMA_MISMATCH"; + public static final String SOURCE_TABLE_MISMATCH = "SOURCE_TABLE_MISMATCH"; + public static final String SOURCE_COLUMN_NOT_EDITABLE = "SOURCE_COLUMN_NOT_EDITABLE"; + public static final String NULL_VALUE_NOT_ALLOWED = "NULL_VALUE_NOT_ALLOWED"; + public static final String TYPE_CONVERSION_FAILED = "TYPE_CONVERSION_FAILED"; + public static final String ROW_OPERATIONS_TABLE_BROWSE_ONLY = "ROW_OPERATIONS_TABLE_BROWSE_ONLY"; + public static final String INSERT_VALUES_REQUIRED = "INSERT_VALUES_REQUIRED"; + public static final String INSERT_COLUMN_NOT_WRITABLE = "INSERT_COLUMN_NOT_WRITABLE"; + public static final String ROW_REF_REQUIRED = "ROW_REF_REQUIRED"; + public static final String ROW_REF_NOT_FOUND = "ROW_REF_NOT_FOUND"; + + public TableChangesPlanBuildResult build(TableEditContext context, String actionDataView, SaveChangesRequest request) { + TableEditDialect dialect = TableEditDialects.find(context.getDbType()).orElse(null); + if (dialect == null) { + return TableChangesPlanBuildResult.failure(DATABASE_NOT_SUPPORTED_FOR_EDIT, null, null); + } + if (!StringUtils.equals(actionDataView, context.getDataViewTitle())) { + return TableChangesPlanBuildResult.failure(DATA_VIEW_MISMATCH, null, null); + } + if (request == null || + !StringUtils.equals(request.getSchema(), context.getSchema()) || + !StringUtils.equals(request.getTable(), context.getTable())) { + return TableChangesPlanBuildResult.failure(SCHEMA_TABLE_MISMATCH, null, null); + } + + int updateCount = size(request.getChanges()); + int insertCount = size(request.getInsertRows()); + int deleteCount = size(request.getDeleteRows()); + int totalCount = updateCount + insertCount + deleteCount; + if (totalCount == 0) { + return TableChangesPlanBuildResult.failure(EMPTY_CHANGES, null, null); + } + if (totalCount > MAX_CHANGES) { + return TableChangesPlanBuildResult.failure(TOO_MANY_CHANGES, null, null); + } + if ((insertCount > 0 || deleteCount > 0) && !context.isTableBrowse()) { + return TableChangesPlanBuildResult.failure(ROW_OPERATIONS_TABLE_BROWSE_ONLY, null, null); + } + if (insertCount > 0 && !TableInsertability.isInsertable(context.getFields())) { + return TableChangesPlanBuildResult.failure( + INSERT_COLUMN_NOT_WRITABLE, + deleteCount + updateCount, + null, + request.getInsertRows().get(0) + ); + } + + Field primaryKey = resolveSinglePrimaryKey(context.getFields()); + if (primaryKey == null) { + return TableChangesPlanBuildResult.failure(NO_PRIMARY_KEY, null, null); + } + if (primaryKey == MultiplePrimaryKeys.FIELD) { + return TableChangesPlanBuildResult.failure(COMPOSITE_PRIMARY_KEY_NOT_SUPPORTED, null, null); + } + + String pkColumn = primaryKey.getSourceColumn(); + if (StringUtils.isBlank(pkColumn)) { + return TableChangesPlanBuildResult.failure(PRIMARY_KEY_SOURCE_COLUMN_MISSING, null, null); + } + if (!TableEditTypeCodecs.supports(primaryKey, context.getDbType())) { + return TableChangesPlanBuildResult.failure(EditabilityReason.TYPE_NOT_SUPPORTED_FOR_EDIT, null, null); + } + + TableChangesPlan plan = new TableChangesPlan(); + plan.setDataView(context.getDataViewTitle()); + plan.setSchema(context.getSchema()); + plan.setTable(context.getTable()); + plan.setChangeCount(totalCount); + plan.setUpdateCount(updateCount); + plan.setInsertCount(insertCount); + plan.setDeleteCount(deleteCount); + List renderedSqlList = new ArrayList<>(); + + int commandIndex = 0; + BuildCommandsResult deleteResult = buildDeleteCommands(context, request, primaryKey, dialect, renderedSqlList, commandIndex, plan); + if (!deleteResult.success()) { + return deleteResult.failure(); + } + commandIndex += deleteCount; + + BuildCommandsResult updateResult = buildUpdateCommands(context, request, primaryKey, dialect, renderedSqlList, commandIndex, plan); + if (!updateResult.success()) { + return updateResult.failure(); + } + commandIndex += updateCount; + + BuildCommandsResult insertResult = buildInsertCommands(context, request, dialect, renderedSqlList, commandIndex, plan); + if (!insertResult.success()) { + return insertResult.failure(); + } + + plan.setAuditSqlList(List.copyOf(renderedSqlList)); + plan.setAuditSql(String.join("\n", renderedSqlList)); + return TableChangesPlanBuildResult.success(plan); + } + + private BuildCommandsResult buildDeleteCommands( + TableEditContext context, + SaveChangesRequest request, + Field primaryKey, + TableEditDialect dialect, + List renderedSqlList, + int commandIndexOffset, + TableChangesPlan plan + ) { + if (request.getDeleteRows() == null) { + return BuildCommandsResult.ok(); + } + String pkColumn = primaryKey.getSourceColumn(); + for (int i = 0; i < request.getDeleteRows().size(); i++) { + SaveChangesRequest.DeleteRow deleteRow = request.getDeleteRows().get(i); + if (deleteRow == null) { + return BuildCommandsResult.failure(SOURCE_COLUMN_NOT_FOUND, commandIndexOffset + i, null); + } + if (!StringUtils.equals(deleteRow.getPkColumn(), pkColumn)) { + return BuildCommandsResult.failure(PK_COLUMN_MISMATCH, commandIndexOffset + i, deleteRow); + } + PrimaryKeyValue pkValue = resolvePrimaryKeyValue( + context, primaryKey, deleteRow.getRowRef(), deleteRow.getPkValue(), deleteRow.isPkValueIsNull() + ); + if (pkValue.reason() != null) { + return BuildCommandsResult.failure(pkValue.reason(), commandIndexOffset + i, deleteRow); + } + + PreparedTableChangeCommand command = new PreparedTableChangeCommand(); + command.setOperation(PreparedTableChangeCommand.Operation.DELETE); + command.setPkColumn(pkColumn); + command.setDeleteRow(deleteRow); + command.setPreparedSql(dialect.buildPreparedDeleteSql(context.getSchema(), context.getTable(), pkColumn)); + try { + PreparedTableChangeCommand.Parameter pkParameter = convertedParameter( + "pkValue", pkColumn, primaryKey, pkValue.value(), pkValue.valueIsNull(), context.getDbType() + ); + command.getParameters().add(pkParameter); + renderedSqlList.add(dialect.buildAuditDeleteSql( + context.getSchema(), + context.getTable(), + pkColumn, + primaryKey, + pkParameter.getValue(), + pkValue.valueIsNull() + )); + plan.getCommands().add(command); + } catch (SQLException e) { + return BuildCommandsResult.failure(TYPE_CONVERSION_FAILED, commandIndexOffset + i, deleteRow); + } + } + return BuildCommandsResult.ok(); + } + + private BuildCommandsResult buildUpdateCommands( + TableEditContext context, + SaveChangesRequest request, + Field primaryKey, + TableEditDialect dialect, + List renderedSqlList, + int commandIndexOffset, + TableChangesPlan plan + ) { + if (request.getChanges() == null) { + return BuildCommandsResult.ok(); + } + String pkColumn = primaryKey.getSourceColumn(); + for (int i = 0; i < request.getChanges().size(); i++) { + SaveChangesRequest.ChangeItem change = request.getChanges().get(i); + ChangeValidation validation = validateChange(context, change, primaryKey); + if (validation.reason() != null) { + return BuildCommandsResult.failure(validation.reason(), commandIndexOffset + i, change); + } + + Field targetField = validation.field(); + String sourceColumn = targetField.getSourceColumn(); + PreparedTableChangeCommand command = new PreparedTableChangeCommand(); + command.setOperation(PreparedTableChangeCommand.Operation.UPDATE); + command.setSourceColumn(sourceColumn); + command.setPkColumn(pkColumn); + command.setChange(change); + command.setPreparedSql(dialect.buildPreparedUpdateSql( + context.getSchema(), + context.getTable(), + sourceColumn, + pkColumn + )); + try { + PreparedTableChangeCommand.Parameter newParameter = convertedParameter( + "newValue", sourceColumn, targetField, change.getNewValue(), change.isNewValueIsNull(), context.getDbType() + ); + PreparedTableChangeCommand.Parameter pkParameter = convertedParameter( + "pkValue", pkColumn, primaryKey, validation.pkValue(), validation.pkValueIsNull(), context.getDbType() + ); + command.getParameters().add(newParameter); + command.getParameters().add(pkParameter); + renderedSqlList.add(dialect.buildAuditUpdateSql( + context.getSchema(), + context.getTable(), + sourceColumn, + targetField, + newParameter.getValue(), + newParameter.isValueIsNull(), + pkColumn, + primaryKey, + pkParameter.getValue(), + validation.pkValueIsNull() + )); + plan.getCommands().add(command); + } catch (SQLException e) { + return BuildCommandsResult.failure(TYPE_CONVERSION_FAILED, commandIndexOffset + i, change); + } + } + return BuildCommandsResult.ok(); + } + + private BuildCommandsResult buildInsertCommands( + TableEditContext context, + SaveChangesRequest request, + TableEditDialect dialect, + List renderedSqlList, + int commandIndexOffset, + TableChangesPlan plan + ) { + if (request.getInsertRows() == null) { + return BuildCommandsResult.ok(); + } + for (int i = 0; i < request.getInsertRows().size(); i++) { + SaveChangesRequest.InsertRow insertRow = request.getInsertRows().get(i); + if (insertRow == null || insertRow.getValues() == null || insertRow.getValues().isEmpty()) { + return BuildCommandsResult.failure(INSERT_VALUES_REQUIRED, commandIndexOffset + i, insertRow); + } + + List sourceColumns = new ArrayList<>(); + List fields = new ArrayList<>(); + List values = new ArrayList<>(); + List valueIsNulls = new ArrayList<>(); + Map requestedValues = insertRow.getValues(); + for (Field field : context.getFields()) { + if (field == null || StringUtils.isBlank(field.getSourceColumn())) { + continue; + } + if (!requestedValues.containsKey(field.getSourceColumn())) { + continue; + } + SaveChangesRequest.CellValue cellValue = requestedValues.get(field.getSourceColumn()); + if (cellValue == null) { + return BuildCommandsResult.failure(INSERT_VALUES_REQUIRED, commandIndexOffset + i, insertRow); + } + InsertValidation validation = validateInsertField( + context, + field, + cellValue.getValue(), + cellValue.isValueIsNull() + ); + if (validation.reason() != null) { + return BuildCommandsResult.failure(validation.reason(), commandIndexOffset + i, insertRow); + } + sourceColumns.add(field.getSourceColumn()); + fields.add(field); + try { + values.add(convertValue(cellValue.getValue(), cellValue.isValueIsNull(), field, context.getDbType())); + } catch (SQLException e) { + return BuildCommandsResult.failure(TYPE_CONVERSION_FAILED, commandIndexOffset + i, insertRow); + } + valueIsNulls.add(isNullValue(cellValue.getValue(), cellValue.isValueIsNull())); + } + + if (sourceColumns.size() != requestedValues.size()) { + return BuildCommandsResult.failure(SOURCE_COLUMN_NOT_FOUND, commandIndexOffset + i, insertRow); + } + if (sourceColumns.isEmpty()) { + return BuildCommandsResult.failure(INSERT_VALUES_REQUIRED, commandIndexOffset + i, insertRow); + } + + PreparedTableChangeCommand command = new PreparedTableChangeCommand(); + command.setOperation(PreparedTableChangeCommand.Operation.INSERT); + command.setInsertRow(insertRow); + command.setPreparedSql(dialect.buildPreparedInsertSql(context.getSchema(), context.getTable(), sourceColumns)); + for (int paramIndex = 0; paramIndex < sourceColumns.size(); paramIndex++) { + command.getParameters().add(new PreparedTableChangeCommand.Parameter( + "insertValue", + sourceColumns.get(paramIndex), + fields.get(paramIndex), + values.get(paramIndex), + valueIsNulls.get(paramIndex), + context.getDbType() + )); + } + plan.getCommands().add(command); + try { + renderedSqlList.add(dialect.buildAuditInsertSql( + context.getSchema(), + context.getTable(), + sourceColumns, + fields, + values, + valueIsNulls + )); + } catch (SQLException e) { + return BuildCommandsResult.failure(TYPE_CONVERSION_FAILED, commandIndexOffset + i, insertRow); + } + } + return BuildCommandsResult.ok(); + } + + private ChangeValidation validateChange(TableEditContext context, SaveChangesRequest.ChangeItem change, Field primaryKey) { + if (change == null) { + return ChangeValidation.failure(SOURCE_COLUMN_NOT_FOUND); + } + if (!StringUtils.equals(change.getPkColumn(), primaryKey.getSourceColumn())) { + return ChangeValidation.failure(PK_COLUMN_MISMATCH); + } + PrimaryKeyValue pkValue = resolvePrimaryKeyValue( + context, primaryKey, change.getRowRef(), change.getPkValue(), change.isPkValueIsNull() + ); + if (pkValue.reason() != null) { + return ChangeValidation.failure(pkValue.reason()); + } + if (StringUtils.equals(change.getSourceColumn(), primaryKey.getSourceColumn())) { + return ChangeValidation.failure(PRIMARY_KEY_COLUMN_NOT_EDITABLE); + } + + Field field = resolveUniqueSourceField(context.getFields(), change.getSourceColumn()); + if (field == null) { + return ChangeValidation.failure(SOURCE_COLUMN_NOT_FOUND); + } + if (field == MultiplePrimaryKeys.FIELD) { + return ChangeValidation.failure(SOURCE_COLUMN_AMBIGUOUS); + } + + String sourceReason = validateSourceField(context, field); + if (sourceReason != null) { + return ChangeValidation.failure(sourceReason); + } + if (field.isAutoIncrement() || field.isReadOnly() || field.isGenerated()) { + return ChangeValidation.failure(SOURCE_COLUMN_NOT_EDITABLE); + } + if (field.isPrimaryKey()) { + return ChangeValidation.failure(PRIMARY_KEY_COLUMN_NOT_EDITABLE); + } + if (!field.isEditable()) { + return ChangeValidation.failure(StringUtils.defaultIfBlank(field.getEditReason(), SOURCE_COLUMN_NOT_EDITABLE)); + } + if (isNullValue(change.getNewValue(), change.isNewValueIsNull()) && !field.isNullable()) { + return ChangeValidation.failure(NULL_VALUE_NOT_ALLOWED); + } + if (!TableEditTypeCodecs.supports(field, context.getDbType())) { + return ChangeValidation.failure(EditabilityReason.TYPE_NOT_SUPPORTED_FOR_EDIT); + } + return ChangeValidation.success(field, pkValue.value(), pkValue.valueIsNull()); + } + + private InsertValidation validateInsertField( + TableEditContext context, + Field field, + Object value, + boolean valueIsNull + ) { + String sourceReason = validateSourceField(context, field); + if (sourceReason != null) { + return InsertValidation.failure(sourceReason); + } + if (!field.isInsertable()) { + return InsertValidation.failure(StringUtils.defaultIfBlank(field.getInsertReason(), INSERT_COLUMN_NOT_WRITABLE)); + } + if (field.isAutoIncrement() || field.isReadOnly() || field.isGenerated()) { + return InsertValidation.failure(INSERT_COLUMN_NOT_WRITABLE); + } + if (isNullValue(value, valueIsNull) && !field.isNullable()) { + return InsertValidation.failure(NULL_VALUE_NOT_ALLOWED); + } + if (!TableEditTypeCodecs.supports(field, context.getDbType())) { + return InsertValidation.failure(EditabilityReason.TYPE_NOT_SUPPORTED_FOR_EDIT); + } + return InsertValidation.success(); + } + + private PrimaryKeyValue resolvePrimaryKeyValue( + TableEditContext context, + Field primaryKey, + String rowRef, + Object pkValue, + boolean pkValueIsNull + ) { + if (primaryKey.isMasked()) { + if (StringUtils.isBlank(rowRef)) { + return PrimaryKeyValue.failure(ROW_REF_REQUIRED); + } + Map rowRefs = context.getRowRefPrimaryKeys(); + if (rowRefs == null || !rowRefs.containsKey(rowRef)) { + return PrimaryKeyValue.failure(ROW_REF_NOT_FOUND); + } + Object realPkValue = rowRefs.get(rowRef); + if (realPkValue == null) { + return PrimaryKeyValue.failure(PRIMARY_KEY_VALUE_REQUIRED); + } + return PrimaryKeyValue.success(realPkValue, false); + } + if (pkValueIsNull || pkValue == null) { + return PrimaryKeyValue.failure(PRIMARY_KEY_VALUE_REQUIRED); + } + return PrimaryKeyValue.success(pkValue, false); + } + + private PreparedTableChangeCommand.Parameter convertedParameter( + String name, + String column, + Field field, + Object value, + boolean valueIsNull, + com.alibaba.druid.DbType dbType + ) throws SQLException { + boolean nullValue = isNullValue(value, valueIsNull); + return new PreparedTableChangeCommand.Parameter( + name, + column, + field, + convertValue(value, nullValue, field, dbType), + nullValue, + field != null ? field.getJdbcType() : null, + dbType + ); + } + + private Object convertValue(Object value, boolean valueIsNull, Field field, com.alibaba.druid.DbType dbType) throws SQLException { + if (isNullValue(value, valueIsNull)) { + return null; + } + return TableEditValueConverter.coerce(value, field, dbType); + } + + private boolean isNullValue(Object value, boolean valueIsNull) { + return valueIsNull || value == null; + } + + private String validateSourceField(TableEditContext context, Field field) { + if (StringUtils.isNotBlank(field.getSourceSchema()) + && !StringUtils.equals(field.getSourceSchema(), context.getSchema())) { + log.warn( + "SOURCE_SCHEMA_MISMATCH contextSchema={}, sourceSchema={}, contextTable={}, sourceTable={}, sourceColumn={}", + context.getSchema(), + field.getSourceSchema(), + context.getTable(), + field.getSourceTable(), + field.getSourceColumn() + ); + return SOURCE_SCHEMA_MISMATCH; + } + + if (StringUtils.isNotBlank(field.getSourceTable()) + && !StringUtils.equals(field.getSourceTable(), context.getTable())) { + log.warn( + "SOURCE_TABLE_MISMATCH contextSchema={}, sourceSchema={}, contextTable={}, sourceTable={}, sourceColumn={}", + context.getSchema(), + field.getSourceSchema(), + context.getTable(), + field.getSourceTable(), + field.getSourceColumn() + ); + return SOURCE_TABLE_MISMATCH; + } + + return null; + } + private Field resolveSinglePrimaryKey(List fields) { + if (fields == null) { + return null; + } + List primaryKeys = fields.stream() + .filter(Objects::nonNull) + .filter(Field::isPrimaryKey) + .toList(); + if (primaryKeys.isEmpty()) { + return null; + } + if (primaryKeys.size() > 1) { + return MultiplePrimaryKeys.FIELD; + } + return primaryKeys.get(0); + } + + private Field resolveUniqueSourceField(List fields, String sourceColumn) { + if (fields == null) { + return null; + } + List matchedFields = fields.stream() + .filter(Objects::nonNull) + .filter(field -> StringUtils.equals(field.getSourceColumn(), sourceColumn)) + .toList(); + if (matchedFields.isEmpty()) { + return null; + } + if (matchedFields.size() > 1) { + return MultiplePrimaryKeys.FIELD; + } + return matchedFields.get(0); + } + + private int size(List list) { + return list == null ? 0 : list.size(); + } + + private record ChangeValidation(String reason, Field field, Object pkValue, boolean pkValueIsNull) { + static ChangeValidation success(Field field, Object pkValue, boolean pkValueIsNull) { + return new ChangeValidation(null, field, pkValue, pkValueIsNull); + } + + static ChangeValidation failure(String reason) { + return new ChangeValidation(reason, null, null, false); + } + } + + private record PrimaryKeyValue(String reason, Object value, boolean valueIsNull) { + static PrimaryKeyValue success(Object value, boolean valueIsNull) { + return new PrimaryKeyValue(null, value, valueIsNull); + } + + static PrimaryKeyValue failure(String reason) { + return new PrimaryKeyValue(reason, null, false); + } + } + + private record InsertValidation(String reason) { + static InsertValidation success() { + return new InsertValidation(null); + } + + static InsertValidation failure(String reason) { + return new InsertValidation(reason); + } + } + + private record BuildCommandsResult(boolean successful, TableChangesPlanBuildResult failureResult) { + boolean success() { + return successful; + } + + TableChangesPlanBuildResult failure() { + return failureResult; + } + + static BuildCommandsResult ok() { + return new BuildCommandsResult(true, null); + } + + static BuildCommandsResult failure(String reason, Integer failedChangeIndex, Object failedOperation) { + SaveChangesRequest.ChangeItem failedChange = failedOperation instanceof SaveChangesRequest.ChangeItem change ? change : null; + return new BuildCommandsResult(false, + TableChangesPlanBuildResult.failure(reason, failedChangeIndex, failedChange, failedOperation)); + } + } + + private static final class MultiplePrimaryKeys { + private static final Field FIELD = new Field(); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesPreviewService.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesPreviewService.java new file mode 100644 index 00000000..a97ab4bb --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesPreviewService.java @@ -0,0 +1,118 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import org.jumpserver.chen.framework.console.entity.request.SaveChangesRequest; +import org.jumpserver.chen.framework.console.entity.response.SaveChangesPreviewResult; +import org.jumpserver.chen.framework.datasource.edit.command.PreparedTableChangeCommand; + +import java.math.BigDecimal; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.UUID; + +public class TableChangesPreviewService { + public static final int MAX_CHANGES = TableChangesPlanBuilder.MAX_CHANGES; + public static final String DATABASE_NOT_SUPPORTED_FOR_EDIT = TableChangesPlanBuilder.DATABASE_NOT_SUPPORTED_FOR_EDIT; + public static final String DATA_VIEW_MISMATCH = TableChangesPlanBuilder.DATA_VIEW_MISMATCH; + public static final String SCHEMA_TABLE_MISMATCH = TableChangesPlanBuilder.SCHEMA_TABLE_MISMATCH; + public static final String EMPTY_CHANGES = TableChangesPlanBuilder.EMPTY_CHANGES; + public static final String TOO_MANY_CHANGES = TableChangesPlanBuilder.TOO_MANY_CHANGES; + public static final String NO_PRIMARY_KEY = TableChangesPlanBuilder.NO_PRIMARY_KEY; + public static final String COMPOSITE_PRIMARY_KEY_NOT_SUPPORTED = TableChangesPlanBuilder.COMPOSITE_PRIMARY_KEY_NOT_SUPPORTED; + public static final String PK_COLUMN_MISMATCH = TableChangesPlanBuilder.PK_COLUMN_MISMATCH; + public static final String PRIMARY_KEY_VALUE_REQUIRED = TableChangesPlanBuilder.PRIMARY_KEY_VALUE_REQUIRED; + public static final String PRIMARY_KEY_COLUMN_NOT_EDITABLE = TableChangesPlanBuilder.PRIMARY_KEY_COLUMN_NOT_EDITABLE; + public static final String SOURCE_COLUMN_NOT_FOUND = TableChangesPlanBuilder.SOURCE_COLUMN_NOT_FOUND; + public static final String SOURCE_COLUMN_AMBIGUOUS = TableChangesPlanBuilder.SOURCE_COLUMN_AMBIGUOUS; + public static final String PRIMARY_KEY_SOURCE_COLUMN_MISSING = TableChangesPlanBuilder.PRIMARY_KEY_SOURCE_COLUMN_MISSING; + public static final String SOURCE_SCHEMA_MISMATCH = TableChangesPlanBuilder.SOURCE_SCHEMA_MISMATCH; + public static final String SOURCE_TABLE_MISMATCH = TableChangesPlanBuilder.SOURCE_TABLE_MISMATCH; + public static final String SOURCE_COLUMN_NOT_EDITABLE = TableChangesPlanBuilder.SOURCE_COLUMN_NOT_EDITABLE; + public static final String NULL_VALUE_NOT_ALLOWED = TableChangesPlanBuilder.NULL_VALUE_NOT_ALLOWED; + public static final String TYPE_CONVERSION_FAILED = TableChangesPlanBuilder.TYPE_CONVERSION_FAILED; + public static final String ROW_OPERATIONS_TABLE_BROWSE_ONLY = TableChangesPlanBuilder.ROW_OPERATIONS_TABLE_BROWSE_ONLY; + public static final String INSERT_VALUES_REQUIRED = TableChangesPlanBuilder.INSERT_VALUES_REQUIRED; + public static final String INSERT_COLUMN_NOT_WRITABLE = TableChangesPlanBuilder.INSERT_COLUMN_NOT_WRITABLE; + public static final String ROW_REF_REQUIRED = TableChangesPlanBuilder.ROW_REF_REQUIRED; + public static final String ROW_REF_NOT_FOUND = TableChangesPlanBuilder.ROW_REF_NOT_FOUND; + + private final TableChangesPlanBuilder planBuilder = new TableChangesPlanBuilder(); + + public SaveChangesPreviewResult preview(TableEditContext context, String actionDataView, SaveChangesRequest request) { + SaveChangesPreviewResult result = baseResult(context); + TableChangesPlanBuildResult buildResult = this.planBuilder.build(context, actionDataView, request); + if (!buildResult.isSuccess()) { + result.setReason(buildResult.getReason()); + result.setFailedChangeIndex(buildResult.getFailedChangeIndex()); + result.setFailedChange(buildResult.getFailedChange()); + result.setFailedOperation(buildResult.getFailedOperation()); + return result; + } + + TableChangesPlan plan = buildResult.getPlan(); + boolean redactPrimaryKey = context.isMaskedPrimaryKey(); + result.setSuccess(true); + result.setAllowed(true); + result.setChangeCount(plan.getChangeCount()); + result.setUpdateCount(plan.getUpdateCount()); + result.setInsertCount(plan.getInsertCount()); + result.setDeleteCount(plan.getDeleteCount()); + if (!redactPrimaryKey) { + result.setAuditSql(plan.getAuditSql()); + } + for (PreparedTableChangeCommand command : plan.getCommands()) { + SaveChangesPreviewResult.PreviewItem previewItem = new SaveChangesPreviewResult.PreviewItem(); + previewItem.setOperation(command.getOperation().name()); + previewItem.setSourceColumn(command.getSourceColumn()); + previewItem.setPkColumn(command.getPkColumn()); + previewItem.setPreparedSql(command.getPreparedSql()); + ArrayList paramsPreview = new ArrayList<>(); + for (PreparedTableChangeCommand.Parameter parameter : command.getParameters()) { + if (redactPrimaryKey && "pkValue".equals(parameter.getName())) { + continue; + } + paramsPreview.add(paramPreviewValue(parameter.getValue(), parameter.isValueIsNull())); + } + previewItem.setParamsPreview(paramsPreview); + result.getPreparedStatements().add(previewItem); + } + return result; + } + + private SaveChangesPreviewResult baseResult(TableEditContext context) { + SaveChangesPreviewResult result = new SaveChangesPreviewResult(); + result.setSuccess(false); + result.setAllowed(false); + result.setDataView(context.getDataViewTitle()); + result.setSchema(context.getSchema()); + result.setTable(context.getTable()); + return result; + } + + private Object paramPreviewValue(Object value, boolean isNull) { + if (isNull) { + return null; + } + if (value instanceof BigDecimal decimal) { + return decimal.toPlainString(); + } + if (value instanceof Date date) { + return date.toLocalDate().toString(); + } + if (value instanceof Time time) { + return time.toLocalTime().toString(); + } + if (value instanceof Timestamp timestamp) { + return timestamp.toLocalDateTime().toString(); + } + if (value instanceof OffsetDateTime offsetDateTime) { + return offsetDateTime.toString(); + } + if (value instanceof UUID uuid) { + return uuid.toString(); + } + return value; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesSaveService.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesSaveService.java new file mode 100644 index 00000000..fb968e8a --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesSaveService.java @@ -0,0 +1,619 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import com.alibaba.druid.DbType; +import lombok.extern.slf4j.Slf4j; +import org.jumpserver.chen.framework.console.entity.request.SaveChangesRequest; +import org.jumpserver.chen.framework.console.entity.response.SaveChangesResult; +import org.jumpserver.chen.framework.datasource.edit.bind.PreparedStatementBinder; +import org.jumpserver.chen.framework.datasource.edit.command.PreparedTableChangeCommand; +import org.jumpserver.chen.framework.datasource.edit.exception.CommitOutcomeUnknownException; +import org.jumpserver.chen.framework.datasource.edit.exception.RollbackFailedException; +import org.jumpserver.chen.framework.datasource.edit.exception.RolledBackConnectionUnavailableException; +import org.jumpserver.chen.framework.datasource.edit.exception.RowNotFoundOrNotUniqueException; +import org.jumpserver.chen.framework.datasource.edit.exception.SavepointRollbackFailedException; +import org.jumpserver.chen.framework.datasource.edit.exception.UnexpectedAffectedRowsException; +import org.jumpserver.chen.framework.datasource.sql.SQLQueryResult; +import org.jumpserver.chen.framework.jms.acl.ACLCommandContext; +import org.jumpserver.chen.framework.jms.acl.ACLResult; +import org.jumpserver.chen.framework.jms.entity.CommandRecord; +import org.jumpserver.chen.framework.jms.exception.CommandRejectException; +import org.jumpserver.chen.framework.session.Session; +import org.jumpserver.wisp.Common; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +@Slf4j +public class TableChangesSaveService { + public static final String ACL_REJECTED = "ACL_REJECTED"; + public static final String ACL_RISK_LEVEL_UNRECOGNIZED = "ACL_RISK_LEVEL_UNRECOGNIZED"; + public static final String ROW_NOT_FOUND_OR_NOT_UNIQUE = "ROW_NOT_FOUND_OR_NOT_UNIQUE"; + public static final String AFFECTED_ROWS_UNEXPECTED = "AFFECTED_ROWS_UNEXPECTED"; + public static final String SAVE_CHANGES_EXECUTE_FAILED = "SAVE_CHANGES_EXECUTE_FAILED"; + public static final String SAVE_CHANGES_COMMIT_OUTCOME_UNKNOWN = "SAVE_CHANGES_COMMIT_OUTCOME_UNKNOWN"; + public static final String SAVE_CHANGES_COMMITTED_CONNECTION_UNAVAILABLE = + "SAVE_CHANGES_COMMITTED_CONNECTION_UNAVAILABLE"; + public static final String SAVE_CHANGES_ROLLED_BACK_CONNECTION_UNAVAILABLE = + "SAVE_CHANGES_ROLLED_BACK_CONNECTION_UNAVAILABLE"; + public static final String SAVE_CHANGES_ROLLBACK_FAILED = "SAVE_CHANGES_ROLLBACK_FAILED"; + public static final String SAVE_CHANGES_SAVEPOINT_ROLLBACK_FAILED = "SAVE_CHANGES_SAVEPOINT_ROLLBACK_FAILED"; + public static final String SAVE_CHANGES_AUDIT_REJECTED = "SAVE_CHANGES_AUDIT_REJECTED"; + public static final String SAVE_CHANGES_AUDIT_FAILED_AFTER_COMMIT = "SAVE_CHANGES_AUDIT_FAILED_AFTER_COMMIT"; + public static final String SAVE_CHANGES_AUDIT_FAILED_AFTER_APPLY = "SAVE_CHANGES_AUDIT_FAILED_AFTER_APPLY"; + + private final TableChangesPlanBuilder planBuilder; + private final PreparedStatementBinder binder; + private final ServiceManagedTransactionBoundary serviceManagedTransactionBoundary; + + public TableChangesSaveService() { + this(new TableChangesPlanBuilder(), new PreparedStatementBinder()); + } + + TableChangesSaveService(TableChangesPlanBuilder planBuilder, PreparedStatementBinder binder) { + this.planBuilder = planBuilder; + this.binder = binder; + this.serviceManagedTransactionBoundary = new ServiceManagedTransactionBoundary(); + } + + public SaveChangesResult save( + TableEditContext context, + String actionDataView, + SaveChangesRequest request, + SaveExecutionContext executionContext, + Session session + ) { + SaveChangesResult result = baseResult(context); + TableChangesPlanBuildResult buildResult = this.planBuilder.build(context, actionDataView, request); + if (!buildResult.isSuccess()) { + log.warn( + "save changes plan rejected, reason={}, dataView={}, actionDataView={}, table={}.{}, failedChangeIndex={}", + buildResult.getReason(), + context.getDataViewTitle(), + actionDataView, + context.getSchema(), + context.getTable(), + buildResult.getFailedChangeIndex() + ); + return reject(result, buildResult.getReason(), buildResult.getFailedChangeIndex(), + buildResult.getFailedChange(), buildResult.getFailedOperation()); + } + + TableChangesPlan plan = buildResult.getPlan(); + fillPlanResult(result, plan, context.isMaskedPrimaryKey()); + + try { + TransactionBoundary transactionBoundary = transactionBoundary(executionContext, context.getDbType()); + if (executionContext.transactionMode() == TransactionMode.SERVICE_MANAGED) { + try (executionContext) { + return executeAcceptedSave(result, plan, executionContext.connection(), + executionContext.connectionOwnership(), + session, transactionBoundary); + } + } + return executeAcceptedSave(result, plan, executionContext.connection(), + executionContext.connectionOwnership(), + session, transactionBoundary); + } catch (UnexpectedAffectedRowsException e) { + PreparedTableChangeCommand command = commandAt(plan, e.getChangeIndex()); + log.error( + "save changes affected rows unexpected, dataView={}, table={}.{}, changeIndex={}, sourceColumn={}, pkColumn={}, affectedRows={}", + plan.getDataView(), + plan.getSchema(), + plan.getTable(), + e.getChangeIndex(), + command != null ? command.getSourceColumn() : null, + command != null ? command.getPkColumn() : null, + e.getAffectedRows() + ); + return reject(result, AFFECTED_ROWS_UNEXPECTED, e.getChangeIndex(), + command != null ? command.getChange() : null, clientFailedOperation(command)); + } catch (RowNotFoundOrNotUniqueException e) { + PreparedTableChangeCommand command = commandAt(plan, e.getChangeIndex()); + log.warn( + "save changes row not found or not unique, dataView={}, table={}.{}, changeIndex={}, operation={}, pkColumn={}", + plan.getDataView(), + plan.getSchema(), + plan.getTable(), + e.getChangeIndex(), + command != null ? command.getOperation() : null, + command != null ? command.getPkColumn() : null + ); + return reject(result, ROW_NOT_FOUND_OR_NOT_UNIQUE, e.getChangeIndex(), + command != null ? command.getChange() : null, clientFailedOperation(command)); + } catch (RollbackFailedException e) { + log.error( + "save changes rollback failed and connection was invalidated, dataView={}, table={}.{}", + plan.getDataView(), + plan.getSchema(), + plan.getTable(), + e + ); + return rejectWithInvalidatedConnection(result, SAVE_CHANGES_ROLLBACK_FAILED); + } catch (SavepointRollbackFailedException e) { + log.error( + "save changes rollback to savepoint failed and connection was invalidated, dataView={}, table={}.{}", + plan.getDataView(), + plan.getSchema(), + plan.getTable(), + e + ); + return rejectWithInvalidatedConnection(result, SAVE_CHANGES_SAVEPOINT_ROLLBACK_FAILED); + } catch (RolledBackConnectionUnavailableException e) { + log.error( + "save changes rolled back but connection could not be restored, dataView={}, table={}.{}", + plan.getDataView(), + plan.getSchema(), + plan.getTable(), + e + ); + return rejectWithInvalidatedConnection( + result, + SAVE_CHANGES_ROLLED_BACK_CONNECTION_UNAVAILABLE + ); + } catch (CommitOutcomeUnknownException e) { + // A commit failure is an unknown outcome: the database may have committed despite + // the client error. The connection has already been discarded by the boundary; the + // caller must not let the user treat this as a retriable failure. + log.error( + "save changes commit outcome unknown, dataView={}, table={}.{}", + plan.getDataView(), + plan.getSchema(), + plan.getTable() + ); + return rejectWithInvalidatedConnection(result, SAVE_CHANGES_COMMIT_OUTCOME_UNKNOWN); + } catch (CommandRejectException e) { + log.warn( + "save changes audit rejected, dataView={}, table={}.{}, message={}", + plan.getDataView(), + plan.getSchema(), + plan.getTable(), + e.getMessage(), + e + ); + return reject(result, SAVE_CHANGES_AUDIT_REJECTED, null, null); + } catch (SQLException e) { + // Statement-level detail (changeIndex, columns, sqlState) was already logged by + // executeCommand; record only the save-level summary here. + String sqlMessage = sqlExceptionMessage(e); + log.warn( + "save changes failed, reason={}, dataView={}, table={}.{}, sqlState={}, vendorCode={}, message={}", + SAVE_CHANGES_EXECUTE_FAILED, + plan.getDataView(), + plan.getSchema(), + plan.getTable(), + e.getSQLState(), + e.getErrorCode(), + sqlMessage + ); + SaveChangesResult rejected = reject(result, SAVE_CHANGES_EXECUTE_FAILED, null, null); + rejected.setMessage(sqlMessage); + return rejected; + } + } + + private SaveChangesResult executeAcceptedSave( + SaveChangesResult result, + TableChangesPlan plan, + Connection connection, + ConnectionOwnership connectionOwnership, + Session session, + TransactionBoundary transactionBoundary + ) throws SQLException, CommandRejectException { + log.debug( + "save changes plan accepted, dataView={}, table={}.{}, changeCount={}", + plan.getDataView(), + plan.getSchema(), + plan.getTable(), + plan.getChangeCount() + ); + + ACLResult aclResult = checkBatchACL(plan, session, connection, connectionOwnership); + if (aclResult.getRiskLevel() == Common.RiskLevel.UNRECOGNIZED) { + log.error( + "save changes acl returned unrecognized risk level, dataView={}, table={}.{}", + plan.getDataView(), + plan.getSchema(), + plan.getTable() + ); + return reject(result, ACL_RISK_LEVEL_UNRECOGNIZED, null, null); + } + if (isRejected(aclResult)) { + log.warn( + "save changes acl rejected, dataView={}, table={}.{}, riskLevel={}", + plan.getDataView(), + plan.getSchema(), + plan.getTable(), + aclResult.getRiskLevel() + ); + recordRejectedCommand(session, plan, aclResult); + return reject(result, ACL_REJECTED, null, null); + } + ACLResult finalAclResult = aclResult; + AtomicReference> outcomeRef = new AtomicReference<>(); + try { + session.withAudit( + plan.getAuditSql(), + finalAclResult, + () -> { + TransactionOutcome outcome = executeTransaction( + connection, + plan, + finalAclResult, + transactionBoundary + ); + outcomeRef.set(outcome); + return outcome.value(); + } + ); + } catch (RuntimeException auditFailure) { + TransactionOutcome outcome = outcomeRef.get(); + if (outcome == null) { + throw auditFailure; + } + log.error( + "save changes database work completed but audit failed, dataView={}, table={}.{}, databaseCommitted={}", + plan.getDataView(), + plan.getSchema(), + plan.getTable(), + outcome.databaseCommitted(), + auditFailure + ); + return appliedResult( + result, + plan, + outcome.databaseCommitted(), + false, + outcome.connectionInvalidated(), + outcome.reason() != null + ? outcome.reason() + : outcome.databaseCommitted() + ? SAVE_CHANGES_AUDIT_FAILED_AFTER_COMMIT + : SAVE_CHANGES_AUDIT_FAILED_AFTER_APPLY + ); + } + TransactionOutcome outcome = outcomeRef.get(); + return appliedResult( + result, + plan, + outcome.databaseCommitted(), + true, + outcome.connectionInvalidated(), + outcome.reason() + ); + } + + private SaveChangesResult appliedResult( + SaveChangesResult result, + TableChangesPlan plan, + boolean databaseCommitted, + boolean auditSucceeded, + boolean connectionInvalidated, + String reason + ) { + result.setSuccess(true); + result.setAllowed(true); + result.setDatabaseChangesApplied(true); + result.setDatabaseCommitted(databaseCommitted); + result.setAuditSucceeded(auditSucceeded); + result.setConnectionInvalidated(connectionInvalidated); + result.setReason(reason); + result.setChangeCount(plan.getChangeCount()); + // executeTransaction has already verified every command affected exactly one row. + result.getStatements().forEach(item -> item.setAffectedRows(1)); + + if (auditSucceeded) { + log.info( + "save changes succeeded, dataView={}, table={}.{}, changeCount={}, databaseCommitted={}", + plan.getDataView(), + plan.getSchema(), + plan.getTable(), + plan.getChangeCount(), + databaseCommitted + ); + } + return result; + } + + private void recordRejectedCommand(Session session, TableChangesPlan plan, ACLResult aclResult) { + CommandRecord commandRecord = new CommandRecord(plan.getAuditSql()); + commandRecord.applyAcl(aclResult); + commandRecord.setError(ACL_REJECTED); + try { + session.recordCommand(commandRecord); + } catch (RuntimeException e) { + log.error( + "record rejected save changes command failed, dataView={}, table={}.{}", + plan.getDataView(), + plan.getSchema(), + plan.getTable(), + e + ); + } + } + + private TransactionBoundary transactionBoundary(SaveExecutionContext executionContext, DbType dbType) { + if (executionContext.transactionMode() == TransactionMode.SERVICE_MANAGED) { + return this.serviceManagedTransactionBoundary; + } + if (executionContext.transactionMode() != TransactionMode.USER_MANAGED || + executionContext.connectionOwnership() != ConnectionOwnership.QUERY_CONSOLE) { + throw new IllegalArgumentException("USER_MANAGED transactions require QUERY_CONSOLE connection ownership"); + } + return new UserManagedTransactionBoundary(SavepointControllers.forDbType(dbType)); + } + + TransactionOutcome executeTransaction( + Connection connection, + TableChangesPlan plan, + ACLResult aclResult, + TransactionBoundary transactionBoundary + ) throws SQLException { + return transactionBoundary.execute(connection, plan, () -> { + for (int i = 0; i < plan.getCommands().size(); i++) { + executeCommand(connection, plan, plan.getCommands().get(i), i); + } + return successQueryResult(plan, aclResult); + }); + } + + private SaveChangesResult rejectWithInvalidatedConnection( + SaveChangesResult result, + String reason + ) { + result.setConnectionInvalidated(true); + return reject(result, reason, null, null); + } + + private void executeCommand( + Connection connection, + TableChangesPlan plan, + PreparedTableChangeCommand command, + int changeIndex + ) throws SQLException { + try (PreparedStatement statement = connection.prepareStatement(command.getPreparedSql())) { + this.binder.bind(statement, command); + + int affectedRows; + try { + affectedRows = statement.executeUpdate(); + } catch (SQLException e) { + log.warn( + "save changes statement execute failed, table={}.{}, changeIndex={}, operation={}, sourceColumn={}, pkColumn={}, sqlState={}, vendorCode={}, message={}", + plan.getSchema(), + plan.getTable(), + changeIndex, + command.getOperation(), + command.getSourceColumn(), + command.getPkColumn(), + e.getSQLState(), + e.getErrorCode(), + e.getMessage(), + e + ); + throw e; + } + + handleAffectedRows(connection, plan, command, changeIndex, affectedRows); + } + } + + private void handleAffectedRows( + Connection connection, + TableChangesPlan plan, + PreparedTableChangeCommand command, + int changeIndex, + int affectedRows + ) throws SQLException { + if (affectedRows == 0) { + log.warn( + "save changes affected zero rows, table={}.{}, changeIndex={}, operation={}, sourceColumn={}, pkColumn={}", + plan.getSchema(), + plan.getTable(), + changeIndex, + command.getOperation(), + command.getSourceColumn(), + command.getPkColumn() + ); + if (command.getOperation() == PreparedTableChangeCommand.Operation.DELETE) { + throw new RowNotFoundOrNotUniqueException(changeIndex); + } + if (command.getOperation() == PreparedTableChangeCommand.Operation.INSERT) { + throw new UnexpectedAffectedRowsException(changeIndex, affectedRows); + } + throw new RowNotFoundOrNotUniqueException(changeIndex); + } + if (affectedRows > 1) { + log.error( + "save changes affected unexpected rows, table={}.{}, changeIndex={}, operation={}, sourceColumn={}, pkColumn={}, affectedRows={}", + plan.getSchema(), + plan.getTable(), + changeIndex, + command.getOperation(), + command.getSourceColumn(), + command.getPkColumn(), + affectedRows + ); + throw new UnexpectedAffectedRowsException(changeIndex, affectedRows); + } + } + + private SQLQueryResult successQueryResult(TableChangesPlan plan, ACLResult aclResult) { + SQLQueryResult result = new SQLQueryResult(plan.getAuditSql()); + result.setHasResultSet(false); + result.setUpdateCount(plan.getChangeCount()); + result.setAclResult(aclResult); + return result; + } + + private boolean isRejected(ACLResult aclResult) { + return aclResult != null && + (aclResult.getRiskLevel() == Common.RiskLevel.Reject || + aclResult.getRiskLevel() == Common.RiskLevel.ReviewReject || + aclResult.getRiskLevel() == Common.RiskLevel.ReviewCancel); + } + + private ACLResult checkBatchACL( + TableChangesPlan plan, + Session session, + Connection connection, + ConnectionOwnership connectionOwnership + ) { + List results = new ArrayList<>(plan.getAuditSqlList().size()); + boolean batch = plan.getAuditSqlList().size() > 1; + String reviewBatchSql = batch ? plan.getAuditSql() : null; + for (String sql : plan.getAuditSqlList()) { + results.add(session.checkACLWithContext( + sql, + ACLCommandContext.planned( + connection, + connectionOwnership, + plan.getChangeCount(), + reviewBatchSql + ) + )); + } + return aggregateACLResults(results); + } + + static ACLResult aggregateACLResults(List results) { + ACLResult aggregate = normalACLResult(); + boolean notify = false; + boolean hasEffectiveResult = false; + for (ACLResult result : results) { + if (result == null) { + continue; + } + notify |= result.isNotify(); + int resultPriority = riskPriority(result.getRiskLevel()); + int aggregatePriority = riskPriority(aggregate.getRiskLevel()); + if (!hasEffectiveResult || resultPriority > aggregatePriority || + (resultPriority == aggregatePriority && result.isNotify() && !aggregate.isNotify())) { + aggregate = copyACLResult(result); + hasEffectiveResult = true; + } + } + aggregate.setNotify(notify); + return aggregate; + } + + private static int riskPriority(Common.RiskLevel riskLevel) { + if (riskLevel == null) { + return 0; + } + return switch (riskLevel) { + case UNRECOGNIZED -> 7; + case ReviewReject -> 6; + case ReviewCancel -> 5; + case Reject -> 4; + case ReviewAccept -> 3; + case Warning -> 2; + case Normal -> 1; + }; + } + + private static ACLResult normalACLResult() { + ACLResult result = new ACLResult(); + result.setRiskLevel(Common.RiskLevel.Normal); + return result; + } + + private static ACLResult copyACLResult(ACLResult source) { + ACLResult result = new ACLResult(); + result.setRiskLevel(source.getRiskLevel()); + result.setCmdAclId(source.getCmdAclId()); + result.setCmdGroupId(source.getCmdGroupId()); + result.setNotify(source.isNotify()); + result.setMessage(source.getMessage()); + return result; + } + + private SaveChangesResult baseResult(TableEditContext context) { + SaveChangesResult result = new SaveChangesResult(); + result.setSuccess(false); + result.setAllowed(false); + result.setDataView(context.getDataViewTitle()); + result.setSchema(context.getSchema()); + result.setTable(context.getTable()); + return result; + } + + private void fillPlanResult(SaveChangesResult result, TableChangesPlan plan, boolean redactPrimaryKey) { + result.setDataView(plan.getDataView()); + result.setSchema(plan.getSchema()); + result.setTable(plan.getTable()); + result.setChangeCount(plan.getChangeCount()); + result.setUpdateCount(plan.getUpdateCount()); + result.setInsertCount(plan.getInsertCount()); + result.setDeleteCount(plan.getDeleteCount()); + if (!redactPrimaryKey) { + result.setAuditSql(plan.getAuditSql()); + } + for (PreparedTableChangeCommand command : plan.getCommands()) { + SaveChangesResult.ResultItem item = new SaveChangesResult.ResultItem(); + item.setOperation(command.getOperation().name()); + item.setSourceColumn(command.getSourceColumn()); + item.setPkColumn(command.getPkColumn()); + item.setPreparedSql(command.getPreparedSql()); + result.getStatements().add(item); + } + } + + private Object clientFailedOperation(PreparedTableChangeCommand command) { + if (command == null) { + return null; + } + if (command.getChange() != null) { + return command.getChange(); + } + if (command.getDeleteRow() != null) { + return command.getDeleteRow(); + } + return command.getInsertRow(); + } + + private PreparedTableChangeCommand commandAt(TableChangesPlan plan, int index) { + if (index < 0 || index >= plan.getCommands().size()) { + return null; + } + return plan.getCommands().get(index); + } + + private static String sqlExceptionMessage(SQLException e) { + for (Throwable current = e; current != null; current = current.getCause()) { + String message = current.getMessage(); + if (message != null && !message.isBlank()) { + return message.trim(); + } + } + return null; + } + + private SaveChangesResult reject( + SaveChangesResult result, + String reason, + Integer failedChangeIndex, + SaveChangesRequest.ChangeItem failedChange + ) { + return reject(result, reason, failedChangeIndex, failedChange, failedChange); + } + + private SaveChangesResult reject( + SaveChangesResult result, + String reason, + Integer failedChangeIndex, + SaveChangesRequest.ChangeItem failedChange, + Object failedOperation + ) { + result.setSuccess(false); + result.setAllowed(false); + result.setReason(reason); + result.setFailedChangeIndex(failedChangeIndex); + result.setFailedChange(failedChange); + result.setFailedOperation(failedOperation); + return result; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableEditContext.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableEditContext.java new file mode 100644 index 00000000..41c2ef6a --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableEditContext.java @@ -0,0 +1,52 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import com.alibaba.druid.DbType; +import lombok.Data; +import org.jumpserver.chen.framework.datasource.entity.resource.Field; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +@Data +public class TableEditContext { + private String dataViewTitle; + private String schema; + private String table; + private List fields; + private DbType dbType; + private boolean tableBrowse; + private Map rowRefPrimaryKeys = Collections.emptyMap(); + + public TableEditContext(String dataViewTitle, String schema, String table, List fields, DbType dbType) { + this(dataViewTitle, schema, table, fields, dbType, false); + } + + public TableEditContext( + String dataViewTitle, + String schema, + String table, + List fields, + DbType dbType, + boolean tableBrowse + ) { + this.dataViewTitle = dataViewTitle; + this.schema = schema; + this.table = table; + this.fields = fields; + this.dbType = dbType; + this.tableBrowse = tableBrowse; + } + + public boolean isMaskedPrimaryKey() { + if (this.fields == null) { + return false; + } + for (Field field : this.fields) { + if (field != null && field.isPrimaryKey() && field.isMasked()) { + return true; + } + } + return false; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableInsertability.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableInsertability.java new file mode 100644 index 00000000..24ff75ed --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableInsertability.java @@ -0,0 +1,31 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import org.jumpserver.chen.framework.datasource.entity.resource.Field; + +import java.util.List; + +public final class TableInsertability { + private TableInsertability() { + } + + public static boolean isInsertable(List fields) { + if (fields == null || fields.isEmpty()) { + return false; + } + + boolean hasOrdinaryField = false; + for (Field field : fields) { + if (field == null) { + return false; + } + if (field.isAutoIncrement() || field.isGenerated()) { + continue; + } + hasOrdinaryField = true; + if (!field.isInsertable()) { + return false; + } + } + return hasOrdinaryField; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TransactionBoundary.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TransactionBoundary.java new file mode 100644 index 00000000..af4b3724 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TransactionBoundary.java @@ -0,0 +1,12 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import java.sql.Connection; +import java.sql.SQLException; + +interface TransactionBoundary { + TransactionOutcome execute( + Connection connection, + TableChangesPlan plan, + TransactionWork work + ) throws SQLException; +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TransactionMode.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TransactionMode.java new file mode 100644 index 00000000..b3bb0c1f --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TransactionMode.java @@ -0,0 +1,6 @@ +package org.jumpserver.chen.framework.datasource.edit; + +public enum TransactionMode { + SERVICE_MANAGED, + USER_MANAGED +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TransactionOutcome.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TransactionOutcome.java new file mode 100644 index 00000000..5e3004b5 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TransactionOutcome.java @@ -0,0 +1,12 @@ +package org.jumpserver.chen.framework.datasource.edit; + +record TransactionOutcome( + T value, + boolean databaseCommitted, + boolean connectionInvalidated, + String reason +) { + static TransactionOutcome success(T value, boolean databaseCommitted) { + return new TransactionOutcome<>(value, databaseCommitted, false, null); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TransactionWork.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TransactionWork.java new file mode 100644 index 00000000..b36e22c1 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TransactionWork.java @@ -0,0 +1,9 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import java.sql.SQLException; + +@FunctionalInterface +interface TransactionWork { + T execute() throws SQLException; +} + diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/UserManagedSaveExecutionContext.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/UserManagedSaveExecutionContext.java new file mode 100644 index 00000000..a537b7a4 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/UserManagedSaveExecutionContext.java @@ -0,0 +1,32 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import java.sql.Connection; +import java.util.Objects; + +public final class UserManagedSaveExecutionContext implements SaveExecutionContext { + private final Connection connection; + + public UserManagedSaveExecutionContext(Connection connection) { + this.connection = Objects.requireNonNull(connection, "connection"); + } + + @Override + public Connection connection() { + return this.connection; + } + + @Override + public TransactionMode transactionMode() { + return TransactionMode.USER_MANAGED; + } + + @Override + public ConnectionOwnership connectionOwnership() { + return ConnectionOwnership.QUERY_CONSOLE; + } + + @Override + public void close() { + // The surrounding user transaction owns the connection lifecycle. + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/UserManagedTransactionBoundary.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/UserManagedTransactionBoundary.java new file mode 100644 index 00000000..1a3769af --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/UserManagedTransactionBoundary.java @@ -0,0 +1,67 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import lombok.extern.slf4j.Slf4j; +import org.jumpserver.chen.framework.datasource.edit.exception.SavepointRollbackFailedException; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.concurrent.atomic.AtomicLong; + +@Slf4j +final class UserManagedTransactionBoundary implements TransactionBoundary { + private static final AtomicLong SAVEPOINT_SEQUENCE = new AtomicLong(); + + private final SavepointController savepointController; + + UserManagedTransactionBoundary(SavepointController savepointController) { + this.savepointController = savepointController; + } + + @Override + public TransactionOutcome execute( + Connection connection, + TableChangesPlan plan, + TransactionWork work + ) throws SQLException { + String savepointName = "chen_dataview_" + SAVEPOINT_SEQUENCE.incrementAndGet(); + this.savepointController.create(connection, savepointName); + try { + T result = work.execute(); + this.savepointController.release(connection, savepointName); + return TransactionOutcome.success(result, false); + } catch (SQLException | RuntimeException e) { + rollbackToSavepoint(connection, plan, savepointName, e); + throw e; + } + } + + private void rollbackToSavepoint( + Connection connection, + TableChangesPlan plan, + String savepointName, + Throwable primaryException + ) throws SavepointRollbackFailedException { + try { + this.savepointController.rollbackTo(connection, savepointName); + } catch (SQLException rollbackException) { + log.warn( + "rollback DataView savepoint failed, table={}.{}, savepoint={}, sqlState={}, vendorCode={}, message={}", + plan.getSchema(), + plan.getTable(), + savepointName, + rollbackException.getSQLState(), + rollbackException.getErrorCode(), + rollbackException.getMessage(), + rollbackException + ); + SavepointRollbackFailedException failure = + new SavepointRollbackFailedException(rollbackException, primaryException); + try { + connection.close(); + } catch (SQLException | RuntimeException closeException) { + failure.addSuppressed(closeException); + } + throw failure; + } + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/analyzer/EditabilityReason.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/analyzer/EditabilityReason.java new file mode 100644 index 00000000..544cd4be --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/analyzer/EditabilityReason.java @@ -0,0 +1,33 @@ +package org.jumpserver.chen.framework.datasource.edit.analyzer; + +public final class EditabilityReason { + public static final String NOT_SELECT = "NOT_SELECT"; + public static final String MULTIPLE_STATEMENTS_NOT_SUPPORTED = "MULTIPLE_STATEMENTS_NOT_SUPPORTED"; + public static final String QUERY_BLOCK_NOT_SUPPORTED = "QUERY_BLOCK_NOT_SUPPORTED"; + public static final String CTE_NOT_SUPPORTED = "CTE_NOT_SUPPORTED"; + public static final String SET_OPERATION_NOT_SUPPORTED = "SET_OPERATION_NOT_SUPPORTED"; + public static final String JOIN_NOT_SUPPORTED = "JOIN_NOT_SUPPORTED"; + public static final String SUBQUERY_NOT_SUPPORTED = "SUBQUERY_NOT_SUPPORTED"; + public static final String TABLE_SOURCE_NOT_SUPPORTED = "TABLE_SOURCE_NOT_SUPPORTED"; + public static final String VIEW_NOT_SUPPORTED = "VIEW_NOT_SUPPORTED"; + public static final String DISTINCT_NOT_SUPPORTED = "DISTINCT_NOT_SUPPORTED"; + public static final String GROUP_BY_NOT_SUPPORTED = "GROUP_BY_NOT_SUPPORTED"; + public static final String HAVING_NOT_SUPPORTED = "HAVING_NOT_SUPPORTED"; + public static final String EXPRESSION_COLUMN = "EXPRESSION_COLUMN"; + public static final String AGGREGATE_OR_FUNCTION_COLUMN = "AGGREGATE_OR_FUNCTION_COLUMN"; + public static final String ALL_COLUMNS_NOT_SUPPORTED = "ALL_COLUMNS_NOT_SUPPORTED"; + public static final String COLUMN_OWNER_NOT_SUPPORTED = "COLUMN_OWNER_NOT_SUPPORTED"; + public static final String UNKNOWN_COLUMN_SOURCE = "UNKNOWN_COLUMN_SOURCE"; + public static final String SELECT_LIST_MISMATCH = "SELECT_LIST_MISMATCH"; + public static final String PRIMARY_KEY_RESOLUTION_NOT_IMPLEMENTED = "PRIMARY_KEY_RESOLUTION_NOT_IMPLEMENTED"; + public static final String PRIMARY_KEY_RESOLUTION_FAILED = "PRIMARY_KEY_RESOLUTION_FAILED"; + public static final String NO_PRIMARY_KEY = "NO_PRIMARY_KEY"; + public static final String COMPOSITE_PRIMARY_KEY_NOT_SUPPORTED = "COMPOSITE_PRIMARY_KEY_NOT_SUPPORTED"; + public static final String PRIMARY_KEY_NOT_IN_RESULT = "PRIMARY_KEY_NOT_IN_RESULT"; + public static final String PRIMARY_KEY_COLUMN_NOT_EDITABLE = "PRIMARY_KEY_COLUMN_NOT_EDITABLE"; + public static final String DATA_MASKED = "DATA_MASKED"; + public static final String TYPE_NOT_SUPPORTED_FOR_EDIT = "TYPE_NOT_SUPPORTED_FOR_EDIT"; + + private EditabilityReason() { + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/analyzer/QueryResultEditabilityAnalyzer.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/analyzer/QueryResultEditabilityAnalyzer.java new file mode 100644 index 00000000..83857041 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/analyzer/QueryResultEditabilityAnalyzer.java @@ -0,0 +1,498 @@ +package org.jumpserver.chen.framework.datasource.edit.analyzer; + +import com.alibaba.druid.DbType; +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.expr.SQLAggregateExpr; +import com.alibaba.druid.sql.ast.expr.SQLAllColumnExpr; +import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; +import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; +import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr; +import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; +import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource; +import com.alibaba.druid.sql.ast.statement.SQLSelectGroupByClause; +import com.alibaba.druid.sql.ast.statement.SQLSelectItem; +import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import com.alibaba.druid.sql.ast.statement.SQLSubqueryTableSource; +import com.alibaba.druid.sql.ast.statement.SQLTableSource; +import com.alibaba.druid.sql.ast.statement.SQLUnionQuery; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.jumpserver.chen.framework.datasource.edit.TableChangesPlanBuilder; +import org.jumpserver.chen.framework.datasource.edit.bind.TableEditTypeCodecs; +import org.jumpserver.chen.framework.datasource.entity.resource.Field; +import org.jumpserver.chen.framework.datasource.edit.pk.PrimaryKeyResolution; +import org.jumpserver.chen.framework.datasource.edit.pk.PrimaryKeyResolver; + +import java.sql.SQLException; +import java.util.List; +import java.util.Objects; + +@Slf4j +public class QueryResultEditabilityAnalyzer { + private final DbType dbType; + private final PrimaryKeyResolver primaryKeyResolver; + + public QueryResultEditabilityAnalyzer(DbType dbType, PrimaryKeyResolver primaryKeyResolver) { + this.dbType = dbType; + this.primaryKeyResolver = primaryKeyResolver; + } + + public void analyze(String sourceSQL, List fields) { + if (fields == null || fields.isEmpty()) { + return; + } + + try { + List statements = SQLUtils.parseStatements(sourceSQL, this.dbType); + if (statements.size() != 1) { + this.markAllReadOnly(fields, EditabilityReason.MULTIPLE_STATEMENTS_NOT_SUPPORTED); + return; + } + if (!(statements.get(0) instanceof SQLSelectStatement selectStatement)) { + this.markAllReadOnly(fields, EditabilityReason.NOT_SELECT); + return; + } + this.analyzeSelect(selectStatement, fields); + } catch (Exception e) { + log.debug("analyze query result editability failed", e); + this.markAllReadOnly(fields, EditabilityReason.UNKNOWN_COLUMN_SOURCE); + } + } + + private void analyzeSelect(SQLSelectStatement selectStatement, List fields) { + SQLSelectQueryBlock queryBlock = this.resolveSupportedQueryBlock(selectStatement, fields); + if (queryBlock == null) { + return; + } + + SourceTable source = this.resolveSourceTable(queryBlock, fields); + + if (!this.mapSelectItems( + queryBlock.getSelectList(), + fields, + source.schema(), + source.table(), + source.alias() + )) { + return; + } + + this.applyPrimaryKeyEditability(fields, source.schema(), source.table()); + } + + private SQLSelectQueryBlock resolveSupportedQueryBlock(SQLSelectStatement selectStatement, List fields) { + var select = selectStatement.getSelect(); + if (select.getWithSubQuery() != null) { + this.markAllReadOnly(fields, EditabilityReason.CTE_NOT_SUPPORTED); + return null; + } + if (select.getQuery() instanceof SQLUnionQuery) { + this.markAllReadOnly(fields, EditabilityReason.SET_OPERATION_NOT_SUPPORTED); + return null; + } + if (!(select.getQuery() instanceof SQLSelectQueryBlock queryBlock)) { + this.markAllReadOnly(fields, EditabilityReason.QUERY_BLOCK_NOT_SUPPORTED); + return null; + } + + if (!this.validateQueryBlock(queryBlock, fields)) { + return null; + } + return queryBlock; + } + + private SourceTable resolveSourceTable(SQLSelectQueryBlock queryBlock, List fields) { + var tableSource = (SQLExprTableSource) queryBlock.getFrom(); + String rawSchema = tableSource.getSchema(); + String rawTable = tableSource.getTableName(); + String schema = normalizeIdentifier(rawSchema); + String table = normalizeIdentifier(rawTable); + String tableAlias = normalizeIdentifier(tableSource.getAlias()); + schema = this.canonicalizeSourceSchema(schema, fields, isQuotedIdentifier(rawSchema)); + table = this.canonicalizeSourceTable(table, fields, isQuotedIdentifier(rawTable)); + + return new SourceTable(schema, table, tableAlias); + } + + private boolean validateQueryBlock(SQLSelectQueryBlock queryBlock, List fields) { + if (queryBlock.isDistinct()) { + this.markAllReadOnly(fields, EditabilityReason.DISTINCT_NOT_SUPPORTED); + return false; + } + + SQLSelectGroupByClause groupBy = queryBlock.getGroupBy(); + if (groupBy != null && !groupBy.getItems().isEmpty()) { + this.markAllReadOnly(fields, EditabilityReason.GROUP_BY_NOT_SUPPORTED); + return false; + } + if (groupBy != null && groupBy.getHaving() != null) { + this.markAllReadOnly(fields, EditabilityReason.HAVING_NOT_SUPPORTED); + return false; + } + + SQLTableSource from = queryBlock.getFrom(); + if (from instanceof SQLJoinTableSource) { + this.markAllReadOnly(fields, EditabilityReason.JOIN_NOT_SUPPORTED); + return false; + } + if (from instanceof SQLSubqueryTableSource) { + this.markAllReadOnly(fields, EditabilityReason.SUBQUERY_NOT_SUPPORTED); + return false; + } + if (!(from instanceof SQLExprTableSource)) { + this.markAllReadOnly(fields, EditabilityReason.TABLE_SOURCE_NOT_SUPPORTED); + return false; + } + if (!containsAllColumn(queryBlock.getSelectList()) && queryBlock.getSelectList().size() != fields.size()) { + this.markAllReadOnly(fields, EditabilityReason.SELECT_LIST_MISMATCH); + return false; + } + return true; + } + + private boolean mapSelectItems(List selectItems, + List fields, + String schema, + String table, + String tableAlias) { + if (containsAllColumn(selectItems)) { + return this.mapAllColumns(selectItems, fields, schema, table, tableAlias); + } + + for (int i = 0; i < selectItems.size(); i++) { + var field = fields.get(i); + var column = resolveColumn(selectItems.get(i).getExpr(), table, tableAlias); + if (column.reason() != null) { + this.markAllReadOnly(fields, column.reason()); + return false; + } + + field.setSourceSchema(schema); + field.setSourceTable(table); + field.setSourceColumn(this.canonicalizeSourceColumn(field, column.name(), column.quoted())); + } + return true; + } + + private boolean mapAllColumns(List selectItems, + List fields, + String schema, + String table, + String tableAlias) { + if (selectItems.size() != 1) { + this.markAllReadOnly(fields, EditabilityReason.ALL_COLUMNS_NOT_SUPPORTED); + return false; + } + + var star = selectItems.get(0).getExpr(); + String owner = starOwner(star); + if (owner == null) { + this.markAllReadOnly(fields, EditabilityReason.ALL_COLUMNS_NOT_SUPPORTED); + return false; + } + if (StringUtils.isNotBlank(owner) && !matchesTableOwner(owner, table, tableAlias)) { + this.markAllReadOnly(fields, EditabilityReason.COLUMN_OWNER_NOT_SUPPORTED); + return false; + } + + for (Field field : fields) { + String sourceColumn = this.canonicalizeSourceColumn(field, normalizeIdentifier(field.getColumnName()), false); + if (StringUtils.isBlank(sourceColumn)) { + this.markAllReadOnly(fields, EditabilityReason.UNKNOWN_COLUMN_SOURCE); + return false; + } + field.setSourceSchema(schema); + field.setSourceTable(table); + field.setSourceColumn(sourceColumn); + } + return true; + } + + private ColumnResolution resolveColumn(SQLExpr expr, String table, String tableAlias) { + if (expr instanceof SQLIdentifierExpr identifierExpr) { + String rawName = identifierExpr.getName(); + return ColumnResolution.column(normalizeIdentifier(rawName), isQuotedIdentifier(rawName)); + } + if (expr instanceof SQLPropertyExpr propertyExpr) { + String owner = normalizeIdentifier(propertyExpr.getOwnerName()); + if (!matchesTableOwner(owner, table, tableAlias)) { + return ColumnResolution.reason(EditabilityReason.COLUMN_OWNER_NOT_SUPPORTED); + } + String rawName = propertyExpr.getName(); + return ColumnResolution.column(normalizeIdentifier(rawName), isQuotedIdentifier(rawName)); + } + if (expr instanceof SQLAggregateExpr || expr instanceof SQLMethodInvokeExpr) { + return ColumnResolution.reason(EditabilityReason.AGGREGATE_OR_FUNCTION_COLUMN); + } + return ColumnResolution.reason(EditabilityReason.EXPRESSION_COLUMN); + } + + private static boolean containsAllColumn(List selectItems) { + return selectItems.stream() + .map(SQLSelectItem::getExpr) + .anyMatch(QueryResultEditabilityAnalyzer::isAllColumn); + } + + private static boolean isAllColumn(SQLExpr expr) { + if (expr instanceof SQLAllColumnExpr) { + return true; + } + if (expr instanceof SQLPropertyExpr propertyExpr) { + return Objects.equals(normalizeIdentifier(propertyExpr.getName()), "*"); + } + return false; + } + + private static String starOwner(SQLExpr expr) { + if (expr instanceof SQLAllColumnExpr allColumnExpr) { + SQLExpr owner = allColumnExpr.getOwner(); + return owner == null ? "" : normalizeIdentifier(owner.toString()); + } + if (expr instanceof SQLPropertyExpr propertyExpr && Objects.equals(normalizeIdentifier(propertyExpr.getName()), "*")) { + return normalizeIdentifier(propertyExpr.getOwnerName()); + } + return null; + } + + private void applyPrimaryKeyEditability(List fields, String schema, String table) { + PrimaryKeyResolution resolution; + try { + resolution = this.primaryKeyResolver.resolvePrimaryKeys(schema, table); + } catch (SQLException e) { + log.debug("resolve primary key failed for {}.{}", schema, table, e); + this.markAllReadOnly(fields, EditabilityReason.PRIMARY_KEY_RESOLUTION_FAILED); + return; + } + + if (resolution == null) { + this.markAllReadOnly(fields, EditabilityReason.PRIMARY_KEY_RESOLUTION_NOT_IMPLEMENTED); + return; + } + if (StringUtils.isNotBlank(resolution.getReadOnlyReason())) { + this.markAllReadOnly(fields, resolution.getReadOnlyReason()); + return; + } + + List primaryKeys = resolution.getPrimaryKeys(); + if (primaryKeys.isEmpty()) { + this.markAllReadOnly(fields, EditabilityReason.NO_PRIMARY_KEY); + return; + } + if (primaryKeys.size() > 1) { + this.markAllReadOnly(fields, EditabilityReason.COMPOSITE_PRIMARY_KEY_NOT_SUPPORTED); + return; + } + + String primaryKey = normalizeIdentifier(primaryKeys.get(0)); + var primaryKeyField = fields.stream() + .filter(field -> identifierEquals(field.getSourceColumn(), primaryKey)) + .findFirst(); + if (primaryKeyField.isEmpty()) { + this.markAllReadOnly(fields, EditabilityReason.PRIMARY_KEY_NOT_IN_RESULT); + return; + } + + fields.forEach(field -> { + this.applyColumnEditability(field, primaryKey); + this.applyInsertability(field); + }); + } + + private void applyColumnEditability(Field field, String primaryKey) { + if (identifierEquals(field.getSourceColumn(), primaryKey)) { + field.setPrimaryKey(true); + field.setEditable(false); + field.setEditReason(EditabilityReason.PRIMARY_KEY_COLUMN_NOT_EDITABLE); + return; + } + if (field.isAutoIncrement() || field.isReadOnly() || field.isGenerated()) { + field.setEditable(false); + field.setEditReason(TableChangesPlanBuilder.SOURCE_COLUMN_NOT_EDITABLE); + return; + } + if (!TableEditTypeCodecs.supports(field, this.dbType)) { + field.setEditable(false); + field.setEditReason(EditabilityReason.TYPE_NOT_SUPPORTED_FOR_EDIT); + return; + } + field.setEditable(true); + field.setEditReason(null); + } + + private void markAllReadOnly(List fields, String reason) { + fields.forEach(field -> { + field.setEditable(false); + field.setEditReason(reason); + field.setInsertable(false); + field.setInsertReason(reason); + field.setRequiredOnInsert(false); + }); + } + + private void applyInsertability(Field field) { + if (!TableEditTypeCodecs.supports(field, this.dbType)) { + this.markNotInsertable(field, EditabilityReason.TYPE_NOT_SUPPORTED_FOR_EDIT); + return; + } + if (field.isAutoIncrement() || field.isReadOnly() || field.isGenerated()) { + this.markNotInsertable(field, TableChangesPlanBuilder.INSERT_COLUMN_NOT_WRITABLE); + return; + } + if (StringUtils.isBlank(field.getSourceColumn())) { + this.markNotInsertable(field, EditabilityReason.UNKNOWN_COLUMN_SOURCE); + return; + } + field.setInsertable(true); + field.setInsertReason(null); + field.setRequiredOnInsert(!field.isNullable()); + } + + private void markNotInsertable(Field field, String reason) { + field.setInsertable(false); + field.setInsertReason(reason); + field.setRequiredOnInsert(false); + } + + private static boolean matchesTableOwner(String owner, String table, String tableAlias) { + if (StringUtils.isBlank(owner)) { + return true; + } + return identifierEquals(owner, tableAlias) || identifierEquals(owner, table); + } + + private static boolean identifierEquals(String left, String right) { + return StringUtils.equalsIgnoreCase(normalizeIdentifier(left), normalizeIdentifier(right)); + } + + private static String normalizeIdentifier(String identifier) { + if (identifier == null) { + return null; + } + String value = identifier.trim(); + if ((value.startsWith("\"") && value.endsWith("\"")) || + (value.startsWith("`") && value.endsWith("`")) || + (value.startsWith("[") && value.endsWith("]"))) { + value = value.substring(1, value.length() - 1); + } + return value; + } + + private String canonicalizeSourceSchema(String schema, List fields, boolean quoted) { + if (!this.shouldCanonicalizeFromMetadata()) { + return schema; + } + return this.canonicalizeFromFieldMetadata(schema, fields, MetadataKind.SCHEMA, quoted); + } + + private String canonicalizeSourceTable(String table, List fields, boolean quoted) { + if (!this.shouldCanonicalizeFromMetadata()) { + return table; + } + return this.canonicalizeFromFieldMetadata(table, fields, MetadataKind.TABLE, quoted); + } + + private String canonicalizeSourceColumn(Field field, String column, boolean quoted) { + if (!this.shouldCanonicalizeFromMetadata() || field == null) { + return column; + } + if (quoted && this.isDamengOrDb2()) { + return column; + } + String metadataColumn = normalizeIdentifier(field.getColumnName()); + if (StringUtils.isBlank(metadataColumn)) { + return column; + } + if (StringUtils.isBlank(column) || identifierEquals(metadataColumn, column)) { + return metadataColumn; + } + return this.canonicalizeUnquotedIdentifier(column, quoted); + } + + private String canonicalizeFromFieldMetadata(String identifier, List fields, MetadataKind kind, boolean quoted) { + if (quoted && this.isDamengOrDb2()) { + return identifier; + } + if (fields == null || fields.isEmpty()) { + return this.canonicalizeUnquotedIdentifier(identifier, quoted); + } + String normalizedIdentifier = normalizeIdentifier(identifier); + String fallback = null; + for (Field field : fields) { + if (field == null) { + continue; + } + String metadataIdentifier = switch (kind) { + case SCHEMA -> normalizeIdentifier(field.getSchema()); + case TABLE -> normalizeIdentifier(field.getTable()); + }; + if (StringUtils.isBlank(metadataIdentifier)) { + continue; + } + if (fallback == null) { + fallback = metadataIdentifier; + } + if (StringUtils.isBlank(normalizedIdentifier) || identifierEquals(metadataIdentifier, normalizedIdentifier)) { + return metadataIdentifier; + } + } + if (StringUtils.isNotBlank(fallback) && StringUtils.isBlank(normalizedIdentifier)) { + return fallback; + } + return this.canonicalizeUnquotedIdentifier(normalizedIdentifier, quoted); + } + + private String canonicalizeUnquotedIdentifier(String identifier, boolean quoted) { + if (quoted || StringUtils.isBlank(identifier)) { + return identifier; + } + if (this.dbType != DbType.oracle && this.dbType != DbType.dm && this.dbType != DbType.db2) { + return identifier; + } + return identifier.toUpperCase(java.util.Locale.ROOT); + } + + private static boolean isQuotedIdentifier(String identifier) { + if (identifier == null) { + return false; + } + String value = identifier.trim(); + return (value.startsWith("\"") && value.endsWith("\"")) || + (value.startsWith("`") && value.endsWith("`")) || + (value.startsWith("[") && value.endsWith("]")); + } + + private boolean shouldCanonicalizeFromMetadata() { + return this.dbType == DbType.oracle || + this.dbType == DbType.sqlserver || + this.dbType == DbType.dm || + this.dbType == DbType.db2; + } + + private boolean isDamengOrDb2() { + return this.dbType == DbType.dm || this.dbType == DbType.db2; + } + + private enum MetadataKind { + SCHEMA, + TABLE + } + + private record ColumnResolution(String name, boolean quoted, String reason) { + static ColumnResolution column(String name, boolean quoted) { + if (StringUtils.isBlank(name) || Objects.equals(name, "*")) { + return reason(EditabilityReason.UNKNOWN_COLUMN_SOURCE); + } + return new ColumnResolution(name, quoted, null); + } + + static ColumnResolution reason(String reason) { + return new ColumnResolution(null, false, reason); + } + } + + private record SourceTable(String schema, String table, String alias) { + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/bind/PreparedStatementBinder.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/bind/PreparedStatementBinder.java new file mode 100644 index 00000000..8cafbaf6 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/bind/PreparedStatementBinder.java @@ -0,0 +1,68 @@ +package org.jumpserver.chen.framework.datasource.edit.bind; + +import com.alibaba.druid.DbType; +import lombok.extern.slf4j.Slf4j; +import org.jumpserver.chen.framework.datasource.edit.command.PreparedTableChangeCommand; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; + +@Slf4j +public class PreparedStatementBinder { + + public void bind(PreparedStatement statement, PreparedTableChangeCommand command) throws SQLException { + for (int i = 0; i < command.getParameters().size(); i++) { + PreparedTableChangeCommand.Parameter parameter = command.getParameters().get(i); + bindConvertedValue( + statement, + i + 1, + parameter + ); + } + } + + private void bindConvertedValue( + PreparedStatement statement, + int index, + PreparedTableChangeCommand.Parameter parameter + ) throws SQLException { + try { + if (parameter.isValueIsNull() || parameter.getValue() == null) { + statement.setNull(index, parameter.getJdbcType() != null ? parameter.getJdbcType() : Types.NULL); + return; + } + if (isJsonOther(parameter)) { + statement.setObject(index, parameter.getValue(), Types.OTHER); + return; + } + statement.setObject(index, parameter.getValue()); + } catch (SQLException e) { + log.warn( + "bind save changes parameter failed, index={}, paramName={}, column={}, isNull={}, jdbcType={}, dbType={}, valueClass={}, sqlState={}, vendorCode={}, message={}", + index, + parameter.getName(), + parameter.getColumn(), + parameter.isValueIsNull(), + parameter.getJdbcType(), + parameter.getDbType(), + parameter.getValue() != null ? parameter.getValue().getClass().getName() : null, + e.getSQLState(), + e.getErrorCode(), + e.getMessage(), + e + ); + throw e; + } + } + + private boolean isJsonOther(PreparedTableChangeCommand.Parameter parameter) { + if (parameter.getDbType() != DbType.postgresql || + parameter.getJdbcType() == null || + parameter.getJdbcType() != Types.OTHER) { + return false; + } + String type = TableEditTypeCodecs.normalizeType(parameter.getField()); + return "json".equals(type) || "jsonb".equals(type); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/bind/TableEditTypeCodecs.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/bind/TableEditTypeCodecs.java new file mode 100644 index 00000000..cbc73c64 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/bind/TableEditTypeCodecs.java @@ -0,0 +1,560 @@ +package org.jumpserver.chen.framework.datasource.edit.bind; + +import com.alibaba.druid.DbType; +import com.google.gson.Gson; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import org.apache.commons.lang3.StringUtils; +import org.jumpserver.chen.framework.datasource.entity.resource.Field; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Date; +import java.sql.SQLException; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.util.UUID; + +public final class TableEditTypeCodecs { + private static final Gson GSON = new Gson(); + private static final int SQLSERVER_GUID = -145; + private static final int SQLSERVER_SMALLDATETIME = -150; + private static final int SQLSERVER_DATETIME = -151; + private static final int SQLSERVER_DATETIMEOFFSET = -155; + private static final int ORACLE_TIMESTAMPNS = -100; + private static final int ORACLE_TIMESTAMPTZ = -101; + private static final int ORACLE_TIMESTAMPLTZ = -102; + private static final int ORACLE_JSON = 2016; + private static final BigInteger UNSIGNED_TINYINT_MAX = BigInteger.valueOf(255); + private static final BigInteger UNSIGNED_SMALLINT_MAX = BigInteger.valueOf(65_535); + private static final BigInteger UNSIGNED_MEDIUMINT_MAX = BigInteger.valueOf(16_777_215); + private static final BigInteger UNSIGNED_INT_MAX = BigInteger.valueOf(4_294_967_295L); + private static final BigInteger UNSIGNED_BIGINT_MAX = new BigInteger("18446744073709551615"); + + private TableEditTypeCodecs() { + } + + public static boolean supports(Field field) { + return supports(field, null); + } + + public static boolean supports(Field field, DbType dbType) { + return kind(field, dbType) != Kind.UNSUPPORTED; + } + + public static Object coerce(Object value, Field field) throws SQLException { + return coerce(value, field, null); + } + + public static Object coerce(Object value, Field field, DbType dbType) throws SQLException { + Kind kind = kind(field, dbType); + String type = normalizeType(field); + if (kind == Kind.UNSUPPORTED) { + throw new SQLException("unsupported table edit type, column=" + + (field != null ? field.getSourceColumn() : null) + ", type=" + type); + } + if (value == null) { + return null; + } + + try { + return switch (kind) { + case TINYINT -> toByte(value); + case SMALLINT -> toShort(value); + case INTEGER -> toInteger(value); + case BIGINT -> toLong(value); + case UNSIGNED_TINYINT -> exactInteger(value, BigInteger.ZERO, UNSIGNED_TINYINT_MAX).intValueExact(); + case UNSIGNED_SMALLINT -> exactInteger(value, BigInteger.ZERO, UNSIGNED_SMALLINT_MAX).intValueExact(); + case UNSIGNED_MEDIUMINT -> exactInteger(value, BigInteger.ZERO, UNSIGNED_MEDIUMINT_MAX).intValueExact(); + case UNSIGNED_INT -> exactInteger(value, BigInteger.ZERO, UNSIGNED_INT_MAX).longValueExact(); + case UNSIGNED_BIGINT -> exactInteger(value, BigInteger.ZERO, UNSIGNED_BIGINT_MAX); + case DECIMAL -> toBigDecimal(value); + case FLOATING -> toFiniteDouble(value); + case BOOLEAN -> toBoolean(value); + case STRING -> value.toString(); + case DATE -> toDate(value); + case TIME -> toTime(value); + case TIMESTAMP -> toTimestamp(value); + case OFFSET_TIMESTAMP -> toOffsetDateTime(value); + case UUID -> toUuid(value); + case JSON -> toJsonString(value); + case UNSUPPORTED -> throw new IllegalArgumentException("unsupported type"); + }; + } catch (RuntimeException e) { + String column = field != null ? field.getSourceColumn() : null; + throw new SQLException("convert value failed, column=" + column + ", type=" + type, e); + } + } + + public static String renderLiteral(Object value, boolean isNull, Field field) throws SQLException { + return renderLiteral(value, isNull, field, null); + } + + public static String renderLiteral(Object value, boolean isNull, Field field, DbType dbType) throws SQLException { + if (isNull || value == null) { + return "NULL"; + } + Object coercedValue = coerce(value, field, dbType); + if (coercedValue == null) { + return "NULL"; + } + if (coercedValue instanceof Number) { + return coercedValue.toString(); + } + if (coercedValue instanceof Boolean bool) { + return bool ? "TRUE" : "FALSE"; + } + return "'" + coercedValue.toString().replace("'", "''") + "'"; + } + + public static String normalizeType(Field field) { + if (field == null || StringUtils.isBlank(field.getType())) { + return null; + } + String type = unwrapKnownTypeWrappers(field.getType().trim().toLowerCase()); + return type.replaceAll("\\([^)]*\\)", "").trim(); + } + + private static Kind kind(Field field, DbType dbType) { + if (field == null) { + return Kind.UNSUPPORTED; + } + String type = normalizeType(field); + Integer jdbcType = field.getJdbcType(); + if (isUnsignedType(type)) { + return mysqlMariaUnsignedIntegerKind(type, dbType); + } + if (jdbcType != null) { + if (jdbcType == SQLSERVER_GUID) { + return dbType == DbType.sqlserver ? Kind.UUID : Kind.UNSUPPORTED; + } + if (jdbcType == SQLSERVER_DATETIMEOFFSET) { + return dbType == DbType.sqlserver ? Kind.OFFSET_TIMESTAMP : Kind.UNSUPPORTED; + } + if (jdbcType == ORACLE_TIMESTAMPTZ) { + return dbType == DbType.oracle ? Kind.OFFSET_TIMESTAMP : Kind.UNSUPPORTED; + } + if (jdbcType == ORACLE_TIMESTAMPLTZ) { + return Kind.UNSUPPORTED; + } + if (jdbcType == ORACLE_JSON) { + return dbType == DbType.oracle ? Kind.JSON : Kind.UNSUPPORTED; + } + if (jdbcType == Types.TIMESTAMP_WITH_TIMEZONE) { + return timestampWithTimezoneKind(type, dbType); + } + if (jdbcType == Types.BOOLEAN) { + return booleanKind(type, dbType); + } + if (jdbcType == Types.BIT) { + return bitKind(type, dbType); + } + if (jdbcType == Types.OTHER && StringUtils.isNotBlank(type)) { + if (isTimestampWithTimezone(type)) { + return timestampWithTimezoneKind(type, dbType); + } + if (isUuid(type)) { + return uuidKind(type, dbType); + } + if (isJson(type)) { + return jsonKind(type, dbType); + } + if (isBoolean(type)) { + return booleanKind(type, dbType); + } + } + if (jdbcType == SQLSERVER_DATETIME) { + return dbType == DbType.sqlserver ? Kind.TIMESTAMP : Kind.UNSUPPORTED; + } + if (jdbcType == SQLSERVER_SMALLDATETIME) { + return dbType == DbType.sqlserver ? Kind.TIMESTAMP : Kind.UNSUPPORTED; + } + if (jdbcType == ORACLE_TIMESTAMPNS) { + return dbType == DbType.oracle ? Kind.TIMESTAMP : Kind.UNSUPPORTED; + } + } + if (jdbcType != null && jdbcType != Types.OTHER) { + return switch (jdbcType) { + case Types.TINYINT -> Kind.TINYINT; + case Types.SMALLINT -> Kind.SMALLINT; + case Types.INTEGER -> Kind.INTEGER; + case Types.BIGINT -> Kind.BIGINT; + case Types.NUMERIC, Types.DECIMAL -> Kind.DECIMAL; + case Types.REAL, Types.FLOAT, Types.DOUBLE -> Kind.FLOATING; + case Types.CHAR, Types.VARCHAR, Types.LONGVARCHAR, + Types.NCHAR, Types.NVARCHAR, Types.LONGNVARCHAR -> Kind.STRING; + case Types.DATE -> Kind.DATE; + case Types.TIME -> Kind.TIME; + case Types.TIMESTAMP -> Kind.TIMESTAMP; + default -> Kind.UNSUPPORTED; + }; + } + if (StringUtils.isBlank(type)) { + return Kind.UNSUPPORTED; + } + if (isBitString(type)) { + return Kind.UNSUPPORTED; + } + if (isBigInt(type)) { + return Kind.BIGINT; + } + if (isInteger(type)) { + return Kind.INTEGER; + } + if (isTinyInt(type)) { + return Kind.TINYINT; + } + if (isSmallInt(type)) { + return Kind.SMALLINT; + } + if (isDecimal(type)) { + return Kind.DECIMAL; + } + if (isFloating(type)) { + return Kind.FLOATING; + } + if (isBoolean(type)) { + return booleanKind(type, dbType); + } + if (isString(type)) { + return Kind.STRING; + } + if (isDate(type)) { + return Kind.DATE; + } + if (isTime(type)) { + return Kind.TIME; + } + if (isTimestamp(type)) { + return Kind.TIMESTAMP; + } + if (isTimestampWithTimezone(type)) { + return timestampWithTimezoneKind(type, dbType); + } + if (isUuid(type)) { + return uuidKind(type, dbType); + } + if (isJson(type)) { + return jsonKind(type, dbType); + } + return Kind.UNSUPPORTED; + } + + private static Kind booleanKind(String type, DbType dbType) { + if (dbType == DbType.postgresql || dbType == DbType.mysql || dbType == DbType.mariadb) { + return StringUtils.isBlank(type) || isBoolean(type) ? Kind.BOOLEAN : Kind.UNSUPPORTED; + } + return Kind.UNSUPPORTED; + } + + private static Kind bitKind(String type, DbType dbType) { + if (dbType == DbType.postgresql && isBoolean(type)) { + return Kind.BOOLEAN; + } + if (dbType == DbType.sqlserver && (StringUtils.isBlank(type) || StringUtils.equals(type, "bit"))) { + return Kind.BOOLEAN; + } + return Kind.UNSUPPORTED; + } + + private static Kind uuidKind(String type, DbType dbType) { + if (dbType == DbType.postgresql && StringUtils.equals(type, "uuid")) { + return Kind.UUID; + } + if (dbType == DbType.sqlserver && StringUtils.equals(type, "uniqueidentifier")) { + return Kind.UUID; + } + return Kind.UNSUPPORTED; + } + + private static Kind jsonKind(String type, DbType dbType) { + if (dbType == DbType.postgresql && StringUtils.equalsAny(type, "json", "jsonb")) { + return Kind.JSON; + } + if ((dbType == DbType.mysql || dbType == DbType.mariadb || dbType == DbType.oracle) && + StringUtils.equals(type, "json")) { + return Kind.JSON; + } + return Kind.UNSUPPORTED; + } + + private static Kind timestampWithTimezoneKind(String type, DbType dbType) { + if (dbType == DbType.postgresql && (StringUtils.isBlank(type) || StringUtils.equals(type, "timestamptz") || + StringUtils.equals(type, "timestamp with time zone"))) { + return Kind.OFFSET_TIMESTAMP; + } + if (dbType == DbType.sqlserver && (StringUtils.isBlank(type) || StringUtils.equals(type, "datetimeoffset"))) { + return Kind.OFFSET_TIMESTAMP; + } + if (dbType == DbType.oracle && (StringUtils.isBlank(type) || + StringUtils.equals(type, "timestamp with time zone"))) { + return Kind.OFFSET_TIMESTAMP; + } + return Kind.UNSUPPORTED; + } + + private static String unwrapKnownTypeWrappers(String type) { + String unwrapped = type; + boolean changed; + do { + changed = false; + if (unwrapped.startsWith("nullable(") && unwrapped.endsWith(")")) { + unwrapped = unwrapped.substring("nullable(".length(), unwrapped.length() - 1).trim(); + changed = true; + } + if (unwrapped.startsWith("lowcardinality(") && unwrapped.endsWith(")")) { + unwrapped = unwrapped.substring("lowcardinality(".length(), unwrapped.length() - 1).trim(); + changed = true; + } + } while (changed); + return unwrapped; + } + + private static boolean isBigInt(String type) { + return StringUtils.equalsAny(type, "bigserial", "bigint", "int8"); + } + + private static boolean isInteger(String type) { + return StringUtils.equalsAny(type, "serial", "integer", "int", "int4", "mediumint"); + } + + private static boolean isTinyInt(String type) { + return StringUtils.equalsAny(type, "tinyint", "int1"); + } + + private static boolean isSmallInt(String type) { + return StringUtils.equalsAny(type, "smallint", "int2"); + } + + private static boolean isDecimal(String type) { + return StringUtils.equalsAny(type, "numeric", "decimal", "number"); + } + + private static boolean isFloating(String type) { + return StringUtils.equalsAny(type, "real", "float", "float4", "float8", "double", "double precision"); + } + + private static Kind mysqlMariaUnsignedIntegerKind(String type, DbType dbType) { + if (dbType != DbType.mysql && dbType != DbType.mariadb) { + return Kind.UNSUPPORTED; + } + if (StringUtils.equals(type, "tinyint unsigned")) { + return Kind.UNSIGNED_TINYINT; + } + if (StringUtils.equals(type, "smallint unsigned")) { + return Kind.UNSIGNED_SMALLINT; + } + if (StringUtils.equals(type, "mediumint unsigned")) { + return Kind.UNSIGNED_MEDIUMINT; + } + if (StringUtils.equalsAny(type, "int unsigned", "integer unsigned")) { + return Kind.UNSIGNED_INT; + } + if (StringUtils.equals(type, "bigint unsigned")) { + return Kind.UNSIGNED_BIGINT; + } + return Kind.UNSUPPORTED; + } + + private static boolean isUnsignedType(String type) { + return StringUtils.endsWith(type, " unsigned") || StringUtils.startsWith(type, "uint"); + } + + private static boolean isBoolean(String type) { + return StringUtils.equalsAny(type, "bool", "boolean"); + } + + private static boolean isBitString(String type) { + return StringUtils.equalsAny(type, "bit", "bit varying", "varbit"); + } + + private static boolean isString(String type) { + return StringUtils.equalsAny(type, "varchar", "varchar2", "character varying", "text", "tinytext", + "mediumtext", "longtext", "char", "character", "bpchar", "nvarchar", "nvarchar2", "nchar", + "longnvarchar"); + } + + private static boolean isDate(String type) { + return StringUtils.equals(type, "date"); + } + + private static boolean isTime(String type) { + return StringUtils.equalsAny(type, "time", "time without time zone"); + } + + private static boolean isTimestamp(String type) { + return StringUtils.equalsAny(type, "timestamp", "timestamp without time zone", "datetime", "datetime2", + "smalldatetime"); + } + + private static boolean isTimestampWithTimezone(String type) { + return StringUtils.equalsAny(type, "timestamptz", "timestamp with time zone", "datetimeoffset"); + } + + private static boolean isUuid(String type) { + return StringUtils.equalsAny(type, "uuid", "uniqueidentifier"); + } + + private static boolean isJson(String type) { + return StringUtils.equalsAny(type, "json", "jsonb"); + } + + private static Byte toByte(Object value) { + return exactInteger(value, BigInteger.valueOf(Byte.MIN_VALUE), BigInteger.valueOf(Byte.MAX_VALUE)).byteValueExact(); + } + + private static Short toShort(Object value) { + return exactInteger(value, BigInteger.valueOf(Short.MIN_VALUE), BigInteger.valueOf(Short.MAX_VALUE)).shortValueExact(); + } + + private static Integer toInteger(Object value) { + return exactInteger(value, BigInteger.valueOf(Integer.MIN_VALUE), BigInteger.valueOf(Integer.MAX_VALUE)).intValueExact(); + } + + private static Long toLong(Object value) { + return exactInteger(value, BigInteger.valueOf(Long.MIN_VALUE), BigInteger.valueOf(Long.MAX_VALUE)).longValueExact(); + } + + private static BigInteger exactInteger(Object value, BigInteger minimum, BigInteger maximum) { + BigInteger integer = toBigDecimal(value).toBigIntegerExact(); + if (integer.compareTo(minimum) < 0 || integer.compareTo(maximum) > 0) { + throw new ArithmeticException("integer out of range"); + } + return integer; + } + + private static BigDecimal toBigDecimal(Object value) { + if (value instanceof BigDecimal bigDecimal) { + return bigDecimal; + } + if (value instanceof Number || value instanceof CharSequence) { + return new BigDecimal(value.toString()); + } + throw new IllegalArgumentException("value is not numeric"); + } + + private static Double toFiniteDouble(Object value) { + double converted; + if (value instanceof Number number) { + converted = number.doubleValue(); + } else { + converted = Double.parseDouble(value.toString().trim()); + } + if (!Double.isFinite(converted)) { + throw new IllegalArgumentException("value is not finite"); + } + return converted; + } + + private static Boolean toBoolean(Object value) { + if (value instanceof Boolean bool) { + return bool; + } + String text = value.toString().trim(); + if (StringUtils.equalsAnyIgnoreCase(text, "true", "t", "1", "yes", "y")) { + return true; + } + if (StringUtils.equalsAnyIgnoreCase(text, "false", "f", "0", "no", "n")) { + return false; + } + throw new IllegalArgumentException("value is not boolean"); + } + + private static Date toDate(Object value) { + if (value instanceof Date date) { + return date; + } + if (value instanceof java.util.Date date) { + return new Date(date.getTime()); + } + String text = value.toString().trim(); + int timeSeparator = Math.max(text.indexOf('T'), text.indexOf(' ')); + if (timeSeparator > -1) { + text = text.substring(0, timeSeparator); + } + return Date.valueOf(LocalDate.parse(text)); + } + + private static Time toTime(Object value) { + if (value instanceof Time time) { + return time; + } + if (value instanceof java.util.Date date) { + return new Time(date.getTime()); + } + String text = value.toString().trim(); + if (text.endsWith("Z") || text.matches(".*[+-]\\d{2}:\\d{2}$")) { + return Time.valueOf(OffsetDateTime.parse(text).toLocalTime()); + } + if (text.contains("T") || text.contains(" ")) { + return Time.valueOf(LocalDateTime.parse(text.replace(' ', 'T')).toLocalTime()); + } + return Time.valueOf(LocalTime.parse(text)); + } + + private static Timestamp toTimestamp(Object value) { + if (value instanceof Timestamp timestamp) { + return timestamp; + } + if (value instanceof java.util.Date date) { + return new Timestamp(date.getTime()); + } + String text = value.toString().trim(); + if (text.endsWith("Z") || text.matches(".*[+-]\\d{2}:\\d{2}$")) { + return Timestamp.from(OffsetDateTime.parse(text).toInstant()); + } + return Timestamp.valueOf(LocalDateTime.parse(text.replace(' ', 'T'))); + } + + private static OffsetDateTime toOffsetDateTime(Object value) { + if (value instanceof OffsetDateTime offsetDateTime) { + return offsetDateTime; + } + return OffsetDateTime.parse(value.toString().trim()); + } + + private static UUID toUuid(Object value) { + if (value instanceof UUID uuid) { + return uuid; + } + return UUID.fromString(value.toString().trim()); + } + + private static String toJsonString(Object value) { + String text = value instanceof CharSequence ? value.toString() : GSON.toJson(value); + try { + JsonParser.parseString(text); + } catch (JsonParseException e) { + throw new IllegalArgumentException("value is not valid json", e); + } + return text; + } + + private enum Kind { + TINYINT, + SMALLINT, + INTEGER, + BIGINT, + UNSIGNED_TINYINT, + UNSIGNED_SMALLINT, + UNSIGNED_MEDIUMINT, + UNSIGNED_INT, + UNSIGNED_BIGINT, + DECIMAL, + FLOATING, + BOOLEAN, + STRING, + DATE, + TIME, + TIMESTAMP, + OFFSET_TIMESTAMP, + UUID, + JSON, + UNSUPPORTED + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/bind/TableEditValueConverter.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/bind/TableEditValueConverter.java new file mode 100644 index 00000000..c74bc6c3 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/bind/TableEditValueConverter.java @@ -0,0 +1,19 @@ +package org.jumpserver.chen.framework.datasource.edit.bind; + +import com.alibaba.druid.DbType; +import org.jumpserver.chen.framework.datasource.entity.resource.Field; + +import java.sql.SQLException; + +public final class TableEditValueConverter { + private TableEditValueConverter() { + } + + public static Object coerce(Object value, Field field) throws SQLException { + return TableEditTypeCodecs.coerce(value, field); + } + + public static Object coerce(Object value, Field field, DbType dbType) throws SQLException { + return TableEditTypeCodecs.coerce(value, field, dbType); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/command/PreparedTableChangeCommand.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/command/PreparedTableChangeCommand.java new file mode 100644 index 00000000..be493e64 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/command/PreparedTableChangeCommand.java @@ -0,0 +1,60 @@ +package org.jumpserver.chen.framework.datasource.edit.command; + +import com.alibaba.druid.DbType; +import lombok.Data; +import org.jumpserver.chen.framework.console.entity.request.SaveChangesRequest; +import org.jumpserver.chen.framework.datasource.entity.resource.Field; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class PreparedTableChangeCommand { + private Operation operation; + private String preparedSql; + private String sourceColumn; + private String pkColumn; + private SaveChangesRequest.ChangeItem change; + private SaveChangesRequest.InsertRow insertRow; + private SaveChangesRequest.DeleteRow deleteRow; + private List parameters = new ArrayList<>(); + + public enum Operation { + UPDATE, + DELETE, + INSERT + } + + @Data + public static class Parameter { + private final String name; + private final String column; + private final Field field; + private final Object value; + private final boolean valueIsNull; + private final Integer jdbcType; + private final DbType dbType; + + public Parameter(String name, String column, Field field, Object value, boolean valueIsNull) { + this(name, column, field, value, valueIsNull, field != null ? field.getJdbcType() : null, null); + } + + public Parameter(String name, String column, Field field, Object value, boolean valueIsNull, DbType dbType) { + this(name, column, field, value, valueIsNull, field != null ? field.getJdbcType() : null, dbType); + } + + public Parameter(String name, String column, Field field, Object value, boolean valueIsNull, Integer jdbcType) { + this(name, column, field, value, valueIsNull, jdbcType, null); + } + + public Parameter(String name, String column, Field field, Object value, boolean valueIsNull, Integer jdbcType, DbType dbType) { + this.name = name; + this.column = column; + this.field = field; + this.value = value; + this.valueIsNull = valueIsNull; + this.jdbcType = jdbcType; + this.dbType = dbType; + } + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/AbstractTableEditDialect.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/AbstractTableEditDialect.java new file mode 100644 index 00000000..60d625d5 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/AbstractTableEditDialect.java @@ -0,0 +1,122 @@ +package org.jumpserver.chen.framework.datasource.edit.dialect; + +import com.alibaba.druid.DbType; +import org.apache.commons.lang3.StringUtils; +import org.jumpserver.chen.framework.datasource.entity.resource.Field; +import org.jumpserver.chen.framework.datasource.edit.bind.TableEditTypeCodecs; +import org.jumpserver.chen.framework.datasource.sql.SQLIdentifier; + +import java.sql.SQLException; +import java.util.List; +import java.util.stream.Collectors; + +abstract class AbstractTableEditDialect implements TableEditDialect { + private final DbType dbType; + + AbstractTableEditDialect(DbType dbType) { + this.dbType = dbType; + } + + @Override + public String quoteIdentifier(String identifier) { + return SQLIdentifier.quote(this.dbType, identifier); + } + + @Override + public String buildPreparedUpdateSql(String schema, String table, String sourceColumn, String pkColumn) { + return "UPDATE " + this.qualifiedTable(schema, table) + "\n" + + "SET " + this.quoteIdentifier(sourceColumn) + " = ?\n" + + "WHERE " + this.quoteIdentifier(pkColumn) + " = ?"; + } + + @Override + public String buildPreparedDeleteSql(String schema, String table, String pkColumn) { + return "DELETE FROM " + this.qualifiedTable(schema, table) + "\n" + + "WHERE " + this.quoteIdentifier(pkColumn) + " = ?"; + } + + @Override + public String buildPreparedInsertSql(String schema, String table, List sourceColumns) { + String columns = sourceColumns.stream() + .map(this::quoteIdentifier) + .collect(Collectors.joining(", ")); + String placeholders = sourceColumns.stream() + .map(column -> "?") + .collect(Collectors.joining(", ")); + return "INSERT INTO " + this.qualifiedTable(schema, table) + " (" + columns + ")\n" + + "VALUES (" + placeholders + ")"; + } + + @Override + public String renderLiteral(Object value, boolean isNull, Field field) throws SQLException { + return TableEditTypeCodecs.renderLiteral(value, isNull, field, this.dbType); + } + + @Override + public String buildAuditUpdateSql( + String schema, + String table, + String sourceColumn, + Field targetField, + Object newValue, + boolean newValueIsNull, + String pkColumn, + Field pkField, + Object pkValue, + boolean pkValueIsNull + ) throws SQLException { + if (targetField != null && StringUtils.isNotBlank(targetField.getSourceColumn()) && + !StringUtils.equals(targetField.getSourceColumn(), sourceColumn)) { + throw new SQLException("sourceColumn does not match targetField.sourceColumn"); + } + return "UPDATE " + this.qualifiedTable(schema, table) + "\n" + + "SET " + this.quoteIdentifier(sourceColumn) + " = " + + this.renderLiteral(newValue, newValueIsNull, targetField) + "\n" + + "WHERE " + this.quoteIdentifier(pkColumn) + " = " + + this.renderLiteral(pkValue, pkValueIsNull, pkField) + ";"; + } + + @Override + public String buildAuditDeleteSql( + String schema, + String table, + String pkColumn, + Field pkField, + Object pkValue, + boolean pkValueIsNull + ) throws SQLException { + return "DELETE FROM " + this.qualifiedTable(schema, table) + "\n" + + "WHERE " + this.quoteIdentifier(pkColumn) + " = " + + this.renderLiteral(pkValue, pkValueIsNull, pkField) + ";"; + } + + @Override + public String buildAuditInsertSql( + String schema, + String table, + List sourceColumns, + List fields, + List values, + List valueIsNulls + ) throws SQLException { + String columns = sourceColumns.stream() + .map(this::quoteIdentifier) + .collect(Collectors.joining(", ")); + StringBuilder renderedValues = new StringBuilder(); + for (int i = 0; i < sourceColumns.size(); i++) { + if (i > 0) { + renderedValues.append(", "); + } + renderedValues.append(this.renderLiteral(values.get(i), valueIsNulls.get(i), fields.get(i))); + } + return "INSERT INTO " + this.qualifiedTable(schema, table) + " (" + columns + ")\n" + + "VALUES (" + renderedValues + ");"; + } + + protected String qualifiedTable(String schema, String table) { + if (StringUtils.isBlank(schema)) { + return this.quoteIdentifier(table); + } + return this.quoteIdentifier(schema) + "." + this.quoteIdentifier(table); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/DamengTableEditDialect.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/DamengTableEditDialect.java new file mode 100644 index 00000000..fbb8ac5a --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/DamengTableEditDialect.java @@ -0,0 +1,9 @@ +package org.jumpserver.chen.framework.datasource.edit.dialect; + +import com.alibaba.druid.DbType; + +class DamengTableEditDialect extends AbstractTableEditDialect { + DamengTableEditDialect() { + super(DbType.dm); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/Db2TableEditDialect.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/Db2TableEditDialect.java new file mode 100644 index 00000000..6ddff815 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/Db2TableEditDialect.java @@ -0,0 +1,9 @@ +package org.jumpserver.chen.framework.datasource.edit.dialect; + +import com.alibaba.druid.DbType; + +class Db2TableEditDialect extends AbstractTableEditDialect { + Db2TableEditDialect() { + super(DbType.db2); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/MysqlTableEditDialect.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/MysqlTableEditDialect.java new file mode 100644 index 00000000..741d3682 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/MysqlTableEditDialect.java @@ -0,0 +1,9 @@ +package org.jumpserver.chen.framework.datasource.edit.dialect; + +import com.alibaba.druid.DbType; + +class MysqlTableEditDialect extends AbstractTableEditDialect { + MysqlTableEditDialect() { + super(DbType.mysql); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/OracleTableEditDialect.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/OracleTableEditDialect.java new file mode 100644 index 00000000..08946a93 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/OracleTableEditDialect.java @@ -0,0 +1,9 @@ +package org.jumpserver.chen.framework.datasource.edit.dialect; + +import com.alibaba.druid.DbType; + +class OracleTableEditDialect extends AbstractTableEditDialect { + OracleTableEditDialect() { + super(DbType.oracle); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/PostgresqlTableEditDialect.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/PostgresqlTableEditDialect.java new file mode 100644 index 00000000..0a82018d --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/PostgresqlTableEditDialect.java @@ -0,0 +1,9 @@ +package org.jumpserver.chen.framework.datasource.edit.dialect; + +import com.alibaba.druid.DbType; + +class PostgresqlTableEditDialect extends AbstractTableEditDialect { + PostgresqlTableEditDialect() { + super(DbType.postgresql); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/SqlServerTableEditDialect.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/SqlServerTableEditDialect.java new file mode 100644 index 00000000..0e7902b2 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/SqlServerTableEditDialect.java @@ -0,0 +1,9 @@ +package org.jumpserver.chen.framework.datasource.edit.dialect; + +import com.alibaba.druid.DbType; + +class SqlServerTableEditDialect extends AbstractTableEditDialect { + SqlServerTableEditDialect() { + super(DbType.sqlserver); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/TableEditDialect.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/TableEditDialect.java new file mode 100644 index 00000000..f6d0e545 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/TableEditDialect.java @@ -0,0 +1,49 @@ +package org.jumpserver.chen.framework.datasource.edit.dialect; + +import org.jumpserver.chen.framework.datasource.entity.resource.Field; + +import java.sql.SQLException; +import java.util.List; + +public interface TableEditDialect { + String quoteIdentifier(String identifier); + + String buildPreparedUpdateSql(String schema, String table, String sourceColumn, String pkColumn); + + String buildPreparedDeleteSql(String schema, String table, String pkColumn); + + String buildPreparedInsertSql(String schema, String table, List sourceColumns); + + String renderLiteral(Object value, boolean isNull, Field field) throws SQLException; + + String buildAuditUpdateSql( + String schema, + String table, + String sourceColumn, + Field targetField, + Object newValue, + boolean newValueIsNull, + String pkColumn, + Field pkField, + Object pkValue, + boolean pkValueIsNull + ) throws SQLException; + + String buildAuditDeleteSql( + String schema, + String table, + String pkColumn, + Field pkField, + Object pkValue, + boolean pkValueIsNull + ) throws SQLException; + + String buildAuditInsertSql( + String schema, + String table, + List sourceColumns, + List fields, + List values, + List valueIsNulls + ) throws SQLException; +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/TableEditDialects.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/TableEditDialects.java new file mode 100644 index 00000000..34f2feea --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/TableEditDialects.java @@ -0,0 +1,39 @@ +package org.jumpserver.chen.framework.datasource.edit.dialect; + +import com.alibaba.druid.DbType; + +import java.util.Optional; + +public final class TableEditDialects { + private static final TableEditDialect POSTGRESQL = new PostgresqlTableEditDialect(); + private static final TableEditDialect MYSQL = new MysqlTableEditDialect(); + private static final TableEditDialect ORACLE = new OracleTableEditDialect(); + private static final TableEditDialect SQLSERVER = new SqlServerTableEditDialect(); + private static final TableEditDialect DAMENG = new DamengTableEditDialect(); + private static final TableEditDialect DB2 = new Db2TableEditDialect(); + + private TableEditDialects() { + } + + public static Optional find(DbType dbType) { + if (dbType == DbType.postgresql) { + return Optional.of(POSTGRESQL); + } + if (dbType == DbType.mysql || dbType == DbType.mariadb) { + return Optional.of(MYSQL); + } + if (dbType == DbType.oracle) { + return Optional.of(ORACLE); + } + if (dbType == DbType.sqlserver) { + return Optional.of(SQLSERVER); + } + if (dbType == DbType.dm) { + return Optional.of(DAMENG); + } + if (dbType == DbType.db2) { + return Optional.of(DB2); + } + return Optional.empty(); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/CommitOutcomeUnknownException.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/CommitOutcomeUnknownException.java new file mode 100644 index 00000000..98efae58 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/CommitOutcomeUnknownException.java @@ -0,0 +1,11 @@ +package org.jumpserver.chen.framework.datasource.edit.exception; + +import org.jumpserver.chen.framework.datasource.edit.TableChangesSaveService; + +import java.sql.SQLException; + +public class CommitOutcomeUnknownException extends TableEditException { + public CommitOutcomeUnknownException(SQLException cause) { + super(TableChangesSaveService.SAVE_CHANGES_COMMIT_OUTCOME_UNKNOWN, cause); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/RollbackFailedException.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/RollbackFailedException.java new file mode 100644 index 00000000..5685fe4e --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/RollbackFailedException.java @@ -0,0 +1,12 @@ +package org.jumpserver.chen.framework.datasource.edit.exception; + +import org.jumpserver.chen.framework.datasource.edit.TableChangesSaveService; + +import java.sql.SQLException; + +public class RollbackFailedException extends TableEditException { + public RollbackFailedException(SQLException cause, Throwable primaryException) { + super(TableChangesSaveService.SAVE_CHANGES_ROLLBACK_FAILED, cause); + this.addSuppressed(primaryException); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/RolledBackConnectionUnavailableException.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/RolledBackConnectionUnavailableException.java new file mode 100644 index 00000000..1f5c3767 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/RolledBackConnectionUnavailableException.java @@ -0,0 +1,12 @@ +package org.jumpserver.chen.framework.datasource.edit.exception; + +import org.jumpserver.chen.framework.datasource.edit.TableChangesSaveService; + +import java.sql.SQLException; + +public class RolledBackConnectionUnavailableException extends TableEditException { + public RolledBackConnectionUnavailableException(SQLException cause, Throwable primaryException) { + super(TableChangesSaveService.SAVE_CHANGES_ROLLED_BACK_CONNECTION_UNAVAILABLE, cause); + this.addSuppressed(primaryException); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/RowNotFoundOrNotUniqueException.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/RowNotFoundOrNotUniqueException.java new file mode 100644 index 00000000..022dc8bd --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/RowNotFoundOrNotUniqueException.java @@ -0,0 +1,18 @@ +package org.jumpserver.chen.framework.datasource.edit.exception; + +import org.jumpserver.chen.framework.datasource.edit.TableChangesSaveService; + +import java.sql.SQLException; + +public class RowNotFoundOrNotUniqueException extends SQLException { + private final int changeIndex; + + public RowNotFoundOrNotUniqueException(int changeIndex) { + super(TableChangesSaveService.ROW_NOT_FOUND_OR_NOT_UNIQUE); + this.changeIndex = changeIndex; + } + + public int getChangeIndex() { + return changeIndex; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/SavepointRollbackFailedException.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/SavepointRollbackFailedException.java new file mode 100644 index 00000000..286b6eb8 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/SavepointRollbackFailedException.java @@ -0,0 +1,12 @@ +package org.jumpserver.chen.framework.datasource.edit.exception; + +import org.jumpserver.chen.framework.datasource.edit.TableChangesSaveService; + +import java.sql.SQLException; + +public class SavepointRollbackFailedException extends TableEditException { + public SavepointRollbackFailedException(SQLException cause, Throwable primaryException) { + super(TableChangesSaveService.SAVE_CHANGES_SAVEPOINT_ROLLBACK_FAILED, cause); + this.addSuppressed(primaryException); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/TableEditException.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/TableEditException.java new file mode 100644 index 00000000..1288e9e8 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/TableEditException.java @@ -0,0 +1,13 @@ +package org.jumpserver.chen.framework.datasource.edit.exception; + +import java.sql.SQLException; + +public class TableEditException extends SQLException { + public TableEditException(String reason) { + super(reason); + } + + public TableEditException(String reason, Throwable cause) { + super(reason, cause); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/UnexpectedAffectedRowsException.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/UnexpectedAffectedRowsException.java new file mode 100644 index 00000000..a099ae37 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/UnexpectedAffectedRowsException.java @@ -0,0 +1,22 @@ +package org.jumpserver.chen.framework.datasource.edit.exception; + +import org.jumpserver.chen.framework.datasource.edit.TableChangesSaveService; + +public class UnexpectedAffectedRowsException extends TableEditException { + private final int changeIndex; + private final int affectedRows; + + public UnexpectedAffectedRowsException(int changeIndex, int affectedRows) { + super(TableChangesSaveService.AFFECTED_ROWS_UNEXPECTED); + this.changeIndex = changeIndex; + this.affectedRows = affectedRows; + } + + public int getChangeIndex() { + return changeIndex; + } + + public int getAffectedRows() { + return affectedRows; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/pk/JdbcPrimaryKeyResolver.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/pk/JdbcPrimaryKeyResolver.java new file mode 100644 index 00000000..83d3be5c --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/pk/JdbcPrimaryKeyResolver.java @@ -0,0 +1,252 @@ +package org.jumpserver.chen.framework.datasource.edit.pk; + +import com.alibaba.druid.DbType; +import org.apache.commons.lang3.StringUtils; +import org.jumpserver.chen.framework.datasource.edit.analyzer.EditabilityReason; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +public class JdbcPrimaryKeyResolver implements PrimaryKeyResolver { + private final Connection connection; + private final DbType dbType; + + public JdbcPrimaryKeyResolver(Connection connection, DbType dbType) { + this.connection = connection; + this.dbType = dbType; + } + + @Override + public PrimaryKeyResolution resolvePrimaryKeys(String schema, String table) throws SQLException { + return switch (this.dbType) { + case postgresql -> this.resolvePostgresqlPrimaryKeysWithTableCheck(schema, table); + case mysql, mariadb -> this.resolveMysqlPrimaryKeysWithTableCheck(schema, table); + case oracle -> this.resolveOraclePrimaryKeysWithTableCheck(schema, table); + case sqlserver -> this.resolveSqlServerPrimaryKeysWithTableCheck(schema, table); + case dm -> this.resolveDamengPrimaryKeysWithTableCheck(schema, table); + case db2 -> this.resolveDb2PrimaryKeysWithTableCheck(schema, table); + default -> null; + }; + } + + private PrimaryKeyResolution resolvePostgresqlPrimaryKeysWithTableCheck(String schema, String table) throws SQLException { + if (this.isPostgresqlView(schema, table)) { + return PrimaryKeyResolution.readOnly(EditabilityReason.VIEW_NOT_SUPPORTED); + } + return PrimaryKeyResolution.primaryKeys(this.resolvePostgresqlPrimaryKeys(schema, table)); + } + + private PrimaryKeyResolution resolveMysqlPrimaryKeysWithTableCheck(String schema, String table) throws SQLException { + if (this.isMysqlView(schema, table)) { + return PrimaryKeyResolution.readOnly(EditabilityReason.VIEW_NOT_SUPPORTED); + } + return PrimaryKeyResolution.primaryKeys(this.resolveMysqlPrimaryKeys(schema, table)); + } + + private PrimaryKeyResolution resolveOraclePrimaryKeysWithTableCheck(String schema, String table) throws SQLException { + if (this.isOracleView(schema, table)) { + return PrimaryKeyResolution.readOnly(EditabilityReason.VIEW_NOT_SUPPORTED); + } + return PrimaryKeyResolution.primaryKeys(this.resolveOraclePrimaryKeys(schema, table)); + } + + private PrimaryKeyResolution resolveSqlServerPrimaryKeysWithTableCheck(String schema, String table) throws SQLException { + if (this.isSqlServerView(schema, table)) { + return PrimaryKeyResolution.readOnly(EditabilityReason.VIEW_NOT_SUPPORTED); + } + return PrimaryKeyResolution.primaryKeys(this.resolveSqlServerPrimaryKeys(schema, table)); + } + + private PrimaryKeyResolution resolveDamengPrimaryKeysWithTableCheck(String schema, String table) throws SQLException { + var metadata = this.connection.getMetaData(); + String schemaPattern = StringUtils.trimToNull(schema); + try (var resultSet = metadata.getTables(null, schemaPattern, table, new String[]{"VIEW"})) { + if (resultSet.next()) { + return PrimaryKeyResolution.readOnly(EditabilityReason.VIEW_NOT_SUPPORTED); + } + } + + List primaryKeys = new ArrayList<>(); + try (var resultSet = metadata.getPrimaryKeys(null, schemaPattern, table)) { + while (resultSet.next()) { + primaryKeys.add(resultSet.getString("COLUMN_NAME")); + } + } + return PrimaryKeyResolution.primaryKeys(primaryKeys); + } + + private PrimaryKeyResolution resolveDb2PrimaryKeysWithTableCheck(String schema, String table) throws SQLException { + if (this.isDb2View(schema, table)) { + return PrimaryKeyResolution.readOnly(EditabilityReason.VIEW_NOT_SUPPORTED); + } + return PrimaryKeyResolution.primaryKeys(this.resolveDb2PrimaryKeys(schema, table)); + } + + private boolean isPostgresqlView(String schema, String table) throws SQLException { + String sql = """ + SELECT table_type + FROM information_schema.tables + WHERE table_schema = COALESCE(?, current_schema()) + AND table_name = ? + """; + return this.isView(sql, schema, table); + } + + private boolean isMysqlView(String schema, String table) throws SQLException { + String sql = """ + SELECT table_type + FROM information_schema.tables + WHERE table_schema = COALESCE(?, DATABASE()) + AND table_name = ? + """; + return this.isView(sql, schema, table); + } + + private boolean isOracleView(String schema, String table) throws SQLException { + String sql = """ + SELECT 'VIEW' + FROM all_views + WHERE owner = COALESCE(?, SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA')) + AND view_name = ? + """; + return this.isView(sql, schema, table); + } + + private boolean isSqlServerView(String schema, String table) throws SQLException { + String sql = """ + SELECT 'VIEW' + FROM sys.views v + JOIN sys.schemas s + ON v.schema_id = s.schema_id + WHERE s.name = COALESCE(?, 'dbo') + AND v.name = ? + """; + return this.isView(sql, schema, table); + } + + private boolean isDb2View(String schema, String table) throws SQLException { + String sql = """ + SELECT 'VIEW' + FROM syscat.tables + WHERE tabschema = COALESCE(?, CURRENT SCHEMA) + AND tabname = ? + AND type = 'V' + """; + return this.isView(sql, schema, table); + } + + private boolean isView(String sql, String schema, String table) throws SQLException { + try (var statement = this.connection.prepareStatement(sql)) { + bindSchemaAndTable(statement, schema, table); + try (var resultSet = statement.executeQuery()) { + return resultSet.next() && StringUtils.equalsIgnoreCase(resultSet.getString(1), "VIEW"); + } + } + } + + private List resolvePostgresqlPrimaryKeys(String schema, String table) throws SQLException { + String sql = """ + SELECT kcu.column_name + FROM information_schema.table_constraints tc + JOIN information_schema.key_column_usage kcu + ON tc.constraint_name = kcu.constraint_name + AND tc.table_schema = kcu.table_schema + AND tc.table_name = kcu.table_name + WHERE tc.constraint_type = 'PRIMARY KEY' + AND tc.table_schema = COALESCE(?, current_schema()) + AND tc.table_name = ? + ORDER BY kcu.ordinal_position + """; + return this.queryPrimaryKeys(sql, schema, table); + } + + private List resolveMysqlPrimaryKeys(String schema, String table) throws SQLException { + String sql = """ + SELECT column_name + FROM information_schema.key_column_usage + WHERE constraint_name = 'PRIMARY' + AND table_schema = COALESCE(?, DATABASE()) + AND table_name = ? + ORDER BY ordinal_position + """; + return this.queryPrimaryKeys(sql, schema, table); + } + + private List resolveOraclePrimaryKeys(String schema, String table) throws SQLException { + String sql = """ + SELECT acc.column_name + FROM all_constraints ac + JOIN all_cons_columns acc + ON ac.owner = acc.owner + AND ac.constraint_name = acc.constraint_name + AND ac.table_name = acc.table_name + WHERE ac.constraint_type = 'P' + AND ac.owner = COALESCE(?, SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA')) + AND ac.table_name = ? + ORDER BY acc.position + """; + return this.queryPrimaryKeys(sql, schema, table); + } + + private List resolveSqlServerPrimaryKeys(String schema, String table) throws SQLException { + String sql = """ + SELECT c.name + FROM sys.key_constraints kc + JOIN sys.index_columns ic + ON kc.parent_object_id = ic.object_id + AND kc.unique_index_id = ic.index_id + JOIN sys.columns c + ON ic.object_id = c.object_id + AND ic.column_id = c.column_id + JOIN sys.tables t + ON kc.parent_object_id = t.object_id + JOIN sys.schemas s + ON t.schema_id = s.schema_id + WHERE kc.type = 'PK' + AND s.name = COALESCE(?, 'dbo') + AND t.name = ? + ORDER BY ic.key_ordinal + """; + return this.queryPrimaryKeys(sql, schema, table); + } + + private List resolveDb2PrimaryKeys(String schema, String table) throws SQLException { + String sql = """ + SELECT kcu.colname + FROM syscat.tabconst tc + JOIN syscat.keycoluse kcu + ON tc.constname = kcu.constname + AND tc.tabschema = kcu.tabschema + AND tc.tabname = kcu.tabname + WHERE tc.type = 'P' + AND tc.tabschema = COALESCE(?, CURRENT SCHEMA) + AND tc.tabname = ? + ORDER BY kcu.colseq + """; + return this.queryPrimaryKeys(sql, schema, table); + } + + private List queryPrimaryKeys(String sql, String schema, String table) throws SQLException { + List primaryKeys = new ArrayList<>(); + try (var statement = this.connection.prepareStatement(sql)) { + bindSchemaAndTable(statement, schema, table); + try (var resultSet = statement.executeQuery()) { + while (resultSet.next()) { + primaryKeys.add(resultSet.getString(1)); + } + } + } + return primaryKeys; + } + + private static void bindSchemaAndTable(java.sql.PreparedStatement statement, String schema, String table) throws SQLException { + if (StringUtils.isBlank(schema)) { + statement.setNull(1, java.sql.Types.VARCHAR); + } else { + statement.setString(1, schema); + } + statement.setString(2, table); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/pk/PrimaryKeyResolution.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/pk/PrimaryKeyResolution.java new file mode 100644 index 00000000..13e3e14f --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/pk/PrimaryKeyResolution.java @@ -0,0 +1,24 @@ +package org.jumpserver.chen.framework.datasource.edit.pk; + +import lombok.Getter; + +import java.util.List; + +@Getter +public class PrimaryKeyResolution { + private final List primaryKeys; + private final String readOnlyReason; + + private PrimaryKeyResolution(List primaryKeys, String readOnlyReason) { + this.primaryKeys = primaryKeys; + this.readOnlyReason = readOnlyReason; + } + + public static PrimaryKeyResolution primaryKeys(List primaryKeys) { + return new PrimaryKeyResolution(primaryKeys, null); + } + + public static PrimaryKeyResolution readOnly(String reason) { + return new PrimaryKeyResolution(List.of(), reason); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/pk/PrimaryKeyResolver.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/pk/PrimaryKeyResolver.java new file mode 100644 index 00000000..f36634c7 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/pk/PrimaryKeyResolver.java @@ -0,0 +1,8 @@ +package org.jumpserver.chen.framework.datasource.edit.pk; + +import java.sql.SQLException; + +@FunctionalInterface +public interface PrimaryKeyResolver { + PrimaryKeyResolution resolvePrimaryKeys(String schema, String table) throws SQLException; +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/entity/resource/Field.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/entity/resource/Field.java index 34d3ba46..6d93f0b9 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/entity/resource/Field.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/entity/resource/Field.java @@ -12,8 +12,21 @@ public class Field implements ResourceNode { private String schema; private String table; private String type; + private transient Integer jdbcType; + private String sourceSchema; + private String sourceTable; + private String sourceColumn; private boolean nullable; + private boolean editable; + private String editReason; private boolean isPrimaryKey; + private boolean masked; + private boolean autoIncrement; + private boolean readOnly; + private transient boolean generated; + private boolean insertable; + private boolean requiredOnInsert; + private String insertReason; public void setNullable(String nullable) { String[] trueAlias = {"YES", "Y"}; diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/entity/resource/ResourceNodeSnapshot.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/entity/resource/ResourceNodeSnapshot.java new file mode 100644 index 00000000..405a5a3d --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/entity/resource/ResourceNodeSnapshot.java @@ -0,0 +1,35 @@ +package org.jumpserver.chen.framework.datasource.entity.resource; + +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; + +public record ResourceNodeSnapshot( + String key, + String type, + String database, + String schema, + String table, + String name, + RelationKind relationKind +) { + public ResourceNodeSnapshot( + String key, + String type, + String database, + String schema, + String table, + String name + ) { + this(key, type, database, schema, table, name, relationKindForLegacyType(type)); + } + + private static RelationKind relationKindForLegacyType(String type) { + if (type == null) { + return null; + } + return switch (type) { + case "table" -> RelationKind.TABLE; + case "view" -> RelationKind.VIEW; + default -> null; + }; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/entity/resource/Table.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/entity/resource/Table.java index 82a859ae..38205922 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/entity/resource/Table.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/entity/resource/Table.java @@ -1,6 +1,7 @@ package org.jumpserver.chen.framework.datasource.entity.resource; import lombok.Data; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; import org.jumpserver.chen.framework.utils.TreeUtils; @Data @@ -14,6 +15,7 @@ public TreeNode toResourceNode(TreeNode parent) { TreeNode treeNode = new TreeNode(); treeNode.setLabel(this.name); treeNode.setType("table"); + treeNode.setRelationKind(RelationKind.TABLE.code()); treeNode.setKey(TreeUtils.generateNodeKey(parent, treeNode.getType(), name)); treeNode.setHasChildren(false); return treeNode; diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/entity/resource/TreeNode.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/entity/resource/TreeNode.java index b3ea2349..1dfae767 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/entity/resource/TreeNode.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/entity/resource/TreeNode.java @@ -9,6 +9,7 @@ public class TreeNode { private String key; private String type; + private String relationKind; private String label; private Map meta; private boolean hasChildren = true; diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/entity/resource/View.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/entity/resource/View.java index 2b1b59bc..1166f5dc 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/entity/resource/View.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/entity/resource/View.java @@ -1,15 +1,26 @@ package org.jumpserver.chen.framework.datasource.entity.resource; import lombok.Data; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; import org.jumpserver.chen.framework.utils.TreeUtils; @Data public class View extends Table { + private RelationKind relationKind = RelationKind.VIEW; + + public void setRelationKind(RelationKind relationKind) { + if (relationKind != RelationKind.VIEW && relationKind != RelationKind.MATERIALIZED_VIEW) { + throw new IllegalArgumentException("View nodes require a view relation kind"); + } + this.relationKind = relationKind; + } + @Override public TreeNode toResourceNode(TreeNode parent) { TreeNode treeNode = new TreeNode(); treeNode.setLabel(this.name); treeNode.setType("view"); + treeNode.setRelationKind(this.relationKind.code()); treeNode.setHasChildren(false); treeNode.setKey(TreeUtils.generateNodeKey(parent, treeNode.getType(), name)); return treeNode; diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/hints/SQLHintsHandler.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/hints/SQLHintsHandler.java deleted file mode 100644 index f9efa2d4..00000000 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/hints/SQLHintsHandler.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.jumpserver.chen.framework.datasource.hints; - -import java.sql.SQLException; -import java.util.List; -import java.util.Map; - -public interface SQLHintsHandler { - Map> getHints(String nodeKey, String context) throws SQLException; -} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/BaseDatabaseMetadataProvider.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/BaseDatabaseMetadataProvider.java new file mode 100644 index 00000000..014d37f6 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/BaseDatabaseMetadataProvider.java @@ -0,0 +1,568 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +import org.jumpserver.chen.framework.datasource.ConnectionManager; +import org.jumpserver.chen.framework.session.SessionManager; + +import java.math.BigDecimal; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +/** + * Shared plumbing for dialect providers: the parameterized query entry point, + * dialect identifier quoting, IN-list chunking, and canonical row-mapping helpers. + */ +public abstract class BaseDatabaseMetadataProvider implements DatabaseMetadataProvider { + + protected final ConnectionManager connectionManager; + + protected BaseDatabaseMetadataProvider(ConnectionManager connectionManager) { + this.connectionManager = connectionManager; + } + + /** + * The only SQL execution primitive providers should use. All identifiers in + * value position must be passed as {@code ?} parameters; identifiers in + * identifier position must be {@link #quoteIdentifier(String)}-quoted and + * already canonical-validated by the caller. + */ + protected List> query(String sql, List parameters) throws SQLException { + var query = this.connectionManager.getSqlActuator(); + if (!MetadataQueryAuditContext.isActive()) { + return query.queryRows(sql, parameters); + } + var session = SessionManager.getCurrentSession(); + if (session == null) { + return query.queryRows(sql, parameters); + } + return session.withMetadataQueryAudit(sql, () -> query.queryRows(sql, parameters)); + } + + /** + * Dialect-specific identifier quoting. Default is SQL-standard double quotes. + * Override for dialects that use a different quote (e.g. backticks). + */ + protected String quoteIdentifier(String value) { + return "\"" + value.replace("\"", "\"\"") + "\""; + } + + /** Split a name list into sub-batches that respect an IN-clause bound. */ + protected static List> chunk(List values, int bound) { + var chunks = new ArrayList>(); + for (int i = 0; i < values.size(); i += bound) { + chunks.add(values.subList(i, Math.min(values.size(), i + bound))); + } + return chunks; + } + + // -- shared row mapping ------------------------------------------------- + + protected static String stringValue(Map row, String key) { + var value = row.get(key); + return value == null ? null : String.valueOf(value); + } + + /** Canonical {@code comment} alias, with Dameng's reserved-word-safe {@code object_comment}. */ + protected static String commentValue(Map row) { + var value = stringValue(row, "comment"); + return value != null ? value : stringValue(row, "object_comment"); + } + + protected static Long longValue(Map row, String key) { + var value = row.get(key); + if (value instanceof Number number) { + return number.longValue(); + } + if (value == null || String.valueOf(value).isBlank()) { + return null; + } + try { + return new BigDecimal(String.valueOf(value)).longValue(); + } catch (NumberFormatException e) { + return null; + } + } + + protected static Integer integerValue(Map row, String key) { + var value = longValue(row, key); + return value == null ? null : value.intValue(); + } + + protected static Boolean booleanValue(Map row, String key) { + var value = row.get(key); + if (value instanceof Boolean bool) { + return bool; + } + if (value instanceof Number number) { + return number.intValue() != 0; + } + if (value == null) { + return null; + } + return switch (String.valueOf(value).toLowerCase(Locale.ROOT)) { + case "true", "t", "yes", "y", "1" -> true; + case "false", "f", "no", "n", "0" -> false; + default -> null; + }; + } + + /** Shared vendor type-name to JDBC type mapping used by all providers. */ + protected static int jdbcType(String nativeType) { + if (nativeType == null || nativeType.isBlank()) { + return Types.OTHER; + } + var type = nativeType.trim().toUpperCase(Locale.ROOT); + while ((type.startsWith("NULLABLE(") || type.startsWith("LOWCARDINALITY(")) && type.endsWith(")")) { + type = type.substring(type.indexOf('(') + 1, type.length() - 1).trim(); + } + var parameter = type.indexOf('('); + if (parameter >= 0) { + type = type.substring(0, parameter).trim(); + } + return switch (type) { + case "BIT" -> Types.BIT; + case "BOOL", "BOOLEAN" -> Types.BOOLEAN; + case "TINYINT", "UINT8" -> Types.TINYINT; + case "SMALLINT", "INT2", "SMALLSERIAL", "INT16", "UINT16" -> Types.SMALLINT; + case "INT", "INTEGER", "INT4", "MEDIUMINT", "SERIAL", "INT32", "UINT32" -> Types.INTEGER; + case "BIGINT", "INT8", "BIGSERIAL", "INT64", "UINT64", "INT128", "UINT128", "INT256", "UINT256" -> Types.BIGINT; + case "REAL", "FLOAT4", "BINARY_FLOAT" -> Types.REAL; + case "FLOAT" -> Types.FLOAT; + case "DOUBLE", "DOUBLE PRECISION", "FLOAT8", "BINARY_DOUBLE", "FLOAT32", "FLOAT64" -> Types.DOUBLE; + case "DECIMAL", "DEC", "NUMBER", "NUMERIC", "DECFLOAT" -> Types.DECIMAL; + case "CHAR", "CHARACTER", "BPCHAR" -> Types.CHAR; + case "NCHAR" -> Types.NCHAR; + case "VARCHAR", "VARCHAR2", "CHARACTER VARYING", "STRING", "FIXEDSTRING", "ENUM", "SET", "UUID" -> Types.VARCHAR; + case "NVARCHAR", "NVARCHAR2" -> Types.NVARCHAR; + case "TEXT", "TINYTEXT", "MEDIUMTEXT", "LONGTEXT", "JSON", "JSONB", "XML" -> Types.LONGVARCHAR; + case "CLOB" -> Types.CLOB; + case "NCLOB" -> Types.NCLOB; + case "BINARY" -> Types.BINARY; + case "VARBINARY", "RAW", "BYTEA" -> Types.VARBINARY; + case "BLOB", "LONGBLOB", "MEDIUMBLOB", "TINYBLOB", "IMAGE" -> Types.BLOB; + case "DATE", "DATE32" -> Types.DATE; + case "YEAR" -> Types.DATE; + case "TIME", "TIME WITHOUT TIME ZONE" -> Types.TIME; + case "TIME WITH TIME ZONE", "TIMETZ" -> Types.TIME_WITH_TIMEZONE; + case "TIMESTAMP", "DATETIME", "DATETIME2", "SMALLDATETIME", "TIMESTAMP WITHOUT TIME ZONE", "DATETIME64" -> Types.TIMESTAMP; + case "TIMESTAMP WITH TIME ZONE", "TIMESTAMPTZ", "DATETIMEOFFSET" -> Types.TIMESTAMP_WITH_TIMEZONE; + case "ARRAY" -> Types.ARRAY; + case "ROWID", "UROWID" -> Types.ROWID; + case "SQLXML" -> Types.SQLXML; + default -> Types.OTHER; + }; + } + + /** + * Groups flat index rows (one row per column) into {@link IndexMetadata}, + * preserving column order and folding the shared definition onto the index. + */ + protected List groupIndexRows(List> rows, RelationScope scope) { + var grouped = new LinkedHashMap(); + for (var row : rows) { + var name = stringValue(row, "name"); + var table = stringValue(row, "table_name"); + var key = table + "\u0000" + name; + var index = grouped.computeIfAbsent(key, ignored -> new MutableIndex( + name, + table, + booleanValue(row, "is_unique"), + stringValue(row, "method"), + stringValue(row, "definition") + )); + var column = stringValue(row, "column_name"); + var expression = stringValue(row, "expression"); + if (column != null && !column.isBlank()) { + var included = Boolean.TRUE.equals(booleanValue(row, "included")); + index.parts.add(new IndexPart( + index.parts.size(), column, null, stringValue(row, "sort_order"), included + )); + } else if (expression != null && !expression.isBlank()) { + index.parts.add(new IndexPart( + index.parts.size(), null, expression, stringValue(row, "sort_order"), false + )); + } + if (index.definition == null) { + index.definition = stringValue(row, "definition"); + } + } + var result = new ArrayList(grouped.size()); + for (var index : grouped.values()) { + result.add(new IndexMetadata( + index.name, + new ObjectRef(scope.catalog(), scope.schema(), index.table, RelationKind.TABLE), + Boolean.TRUE.equals(index.unique), + index.method, + List.copyOf(index.parts), + index.definition + )); + } + return result; + } + + /** + * Shared batched column load. Groups relations by schema, chunks the name + * list to respect IN-clause limits, and reassociates each column with its + * owner via the {@code table_name} projection in {@code sqlTemplate}. + * + *

{@code sqlTemplate} must contain a {@code __IN__} marker where the + * (comma-separated) {@code ?} placeholders are inserted, and project the + * canonical aliases {@code name}/{@code table_name}/{@code ordinal}/ + * {@code native_type}/{@code jdbc_type_name}/{@code size}/{@code scale}/ + * {@code nullable}/{@code default_value}/{@code comment}. Dameng projects + * comments as {@code object_comment} because {@code COMMENT} is reserved.

+ */ + protected List loadColumns(String sqlTemplate, List relations) throws SQLException { + if (relations.isEmpty()) { + return List.of(); + } + var result = new ArrayList(); + var bySchema = new LinkedHashMap>(); + for (var relation : relations) { + bySchema.computeIfAbsent(relation.schema(), ignored -> new ArrayList<>()).add(relation); + } + for (var entry : bySchema.entrySet()) { + var schema = entry.getKey(); + var refs = entry.getValue(); + var refByName = new HashMap(); + for (var ref : refs) { + refByName.put(ref.name(), ref); + } + var names = refs.stream().map(ObjectRef::name).toList(); + for (var batch : chunk(names, 500)) { + var placeholders = String.join(",", Collections.nCopies(batch.size(), "?")); + var params = new ArrayList(); + params.add(schema); + params.addAll(batch); + var sql = sqlTemplate.replace("__IN__", placeholders); + for (var row : query(sql, params)) { + var owner = refByName.get(stringValue(row, "table_name")); + if (owner == null) { + continue; + } + var ordinal = integerValue(row, "ordinal"); + result.add(new ColumnMetadata( + owner, + stringValue(row, "name"), + ordinal == null ? 0 : ordinal, + stringValue(row, "native_type"), + jdbcType(stringValue(row, "jdbc_type_name") == null + ? stringValue(row, "native_type") + : stringValue(row, "jdbc_type_name")), + integerValue(row, "size"), + integerValue(row, "scale"), + Boolean.TRUE.equals(booleanValue(row, "nullable")), + stringValue(row, "default_value"), + commentValue(row) + )); + } + } + } + return result; + } + + // -- defaults ----------------------------------------------------------- + + @Override + public List listCatalogs() throws SQLException { + return List.of(); + } + + @Override + public List listSchemas(String catalog) throws SQLException { + return List.of(); + } + + @Override + public List listRelations(RelationScope scope, Set kinds) throws SQLException { + throw unsupported("relations"); + } + + @Override + public List searchRelations(RelationScope scope, Set kinds, String prefix, int limit) + throws SQLException { + var relations = this.listRelations(scope, kinds); + var normalizedPrefix = prefix == null ? "" : prefix.trim().toLowerCase(Locale.ROOT); + var stream = relations.stream() + .filter(relation -> normalizedPrefix.isEmpty() + || relation.ref().name().toLowerCase(Locale.ROOT).startsWith(normalizedPrefix) + || (relation.ref().schema() + "." + relation.ref().name()) + .toLowerCase(Locale.ROOT).startsWith(normalizedPrefix)); + if (limit > 0) { + stream = stream.limit(limit); + } + return stream.toList(); + } + + @Override + public List listColumns(List relations) throws SQLException { + throw unsupported("columns"); + } + + @Override + public List listIndexes(RelationScope scope) throws SQLException { + throw unsupported("indexes"); + } + + @Override + public List listIndexes(List relations) throws SQLException { + throw unsupported("object indexes"); + } + + @Override + public List listStatistics(RelationScope scope) throws SQLException { + throw unsupported("statistics"); + } + + @Override + public List listPrimaryKeys(List relations) throws SQLException { + throw unsupported("primary keys"); + } + + @Override + public List listForeignKeys(List relations) throws SQLException { + throw unsupported("foreign keys"); + } + + @Override + public List listConstraints(List relations) throws SQLException { + throw unsupported("constraints"); + } + + @Override + public String getSchemaDefinition(RelationScope scope) throws SQLException { + return null; + } + + @Override + public String getTableDefinition(ObjectRef relation) throws SQLException { + return null; + } + + @Override + public ObjectProperties objectProperties(ObjectRef ref) throws SQLException { + return new ObjectProperties(ref, List.of()); + } + + @Override + public ScopeProperties scopeProperties(ScopeRef ref) throws SQLException { + return new ScopeProperties(ref, List.of()); + } + + /** + * Runs a single-row vendor properties query and maps its first row to + * {@link PropertyItem}s in column order. + */ + protected ObjectProperties loadObjectProperties(ObjectRef ref, String sql) throws SQLException { + var rows = query(sql, List.of(ref.schema(), ref.name())); + if (rows.isEmpty()) { + return new ObjectProperties(ref, List.of()); + } + var items = new ArrayList(); + for (var entry : rows.get(0).entrySet()) { + var value = entry.getValue(); + items.add(new PropertyItem(entry.getKey(), value == null ? null : String.valueOf(value))); + } + return new ObjectProperties(ref, items); + } + + protected ScopeProperties loadScopeProperties(ScopeRef ref, String sql, List parameters) throws SQLException { + var rows = query(sql, parameters); + if (rows.isEmpty()) { + return new ScopeProperties(ref, List.of()); + } + var items = new ArrayList(); + for (var entry : rows.get(0).entrySet()) { + var value = entry.getValue(); + items.add(new PropertyItem(entry.getKey(), value == null ? null : String.valueOf(value))); + } + return new ScopeProperties(ref, items); + } + + /** + * Runs a key query whose rows are (table_name, column_name, name[, referenced_*]) + * restricted to {@code table IN (…)}, and groups them by owner. + */ + protected List> queryKeys(String sqlTemplate, List relations, String schema) + throws SQLException { + if (relations.isEmpty()) { + return List.of(); + } + var result = new ArrayList>(); + for (var batch : chunk(relations.stream().map(ObjectRef::name).toList(), 500)) { + var placeholders = String.join(",", Collections.nCopies(batch.size(), "?")); + var params = new ArrayList(); + params.add(schema); + params.addAll(batch); + result.addAll(query(sqlTemplate.replace("__IN__", placeholders), params)); + } + return result; + } + + protected List groupPrimaryKeys(List> rows, RelationScope scope) { + var grouped = new LinkedHashMap(); + for (var row : rows) { + var table = stringValue(row, "table_name"); + var pk = grouped.computeIfAbsent(table, ignored -> new MutablePrimaryKey(stringValue(row, "name"), table)); + var column = stringValue(row, "column_name"); + if (column != null && !column.isBlank()) { + pk.columns.add(column); + } + } + var result = new ArrayList(grouped.size()); + for (var pk : grouped.values()) { + result.add(new PrimaryKeyMetadata( + new ObjectRef(scope.catalog(), scope.schema(), pk.table, RelationKind.TABLE), + pk.name, + List.copyOf(pk.columns) + )); + } + return result; + } + + protected List groupForeignKeys(List> rows, RelationScope scope) { + var grouped = new LinkedHashMap(); + for (var row : rows) { + var table = stringValue(row, "table_name"); + var name = stringValue(row, "name"); + var fk = grouped.computeIfAbsent(table + "\u0000" + name, ignored -> new MutableForeignKey(name, table)); + fk.columns.add(stringValue(row, "column_name")); + fk.referencedSchema = stringValue(row, "referenced_schema"); + fk.referencedTable = stringValue(row, "referenced_table"); + fk.referencedColumns.add(stringValue(row, "referenced_column")); + } + var result = new ArrayList(grouped.size()); + for (var fk : grouped.values()) { + result.add(new ForeignKeyMetadata( + new ObjectRef(scope.catalog(), scope.schema(), fk.table, RelationKind.TABLE), + fk.name, + List.copyOf(fk.columns), + new ObjectRef(scope.catalog(), fk.referencedSchema, fk.referencedTable, RelationKind.TABLE), + List.copyOf(fk.referencedColumns) + )); + } + return result; + } + + /** Groups canonical constraint rows, preserving key-column order. */ + protected List groupConstraints(List> rows, RelationScope scope) { + var grouped = new LinkedHashMap(); + for (var row : rows) { + var table = stringValue(row, "table_name"); + var name = stringValue(row, "name"); + var typeValue = stringValue(row, "constraint_type"); + if (table == null || name == null || typeValue == null) { + continue; + } + ConstraintType type; + try { + type = ConstraintType.fromDatabaseValue(typeValue); + } catch (IllegalArgumentException ignored) { + continue; + } + var constraint = grouped.computeIfAbsent(table + "\u0000" + name, + ignored -> new MutableConstraint(name, table, type)); + addNonBlank(constraint.columns, stringValue(row, "column_name")); + constraint.referencedSchema = stringValue(row, "referenced_schema"); + constraint.referencedTable = stringValue(row, "referenced_table"); + addNonBlank(constraint.referencedColumns, stringValue(row, "referenced_column")); + if (constraint.definition == null) { + constraint.definition = stringValue(row, "definition"); + } + } + var result = new ArrayList(grouped.size()); + for (var constraint : grouped.values()) { + ObjectRef referenced = null; + if (constraint.referencedTable != null && !constraint.referencedTable.isBlank()) { + referenced = new ObjectRef(scope.catalog(), constraint.referencedSchema, + constraint.referencedTable, RelationKind.TABLE); + } + result.add(new ConstraintMetadata( + new ObjectRef(scope.catalog(), scope.schema(), constraint.table, RelationKind.TABLE), + constraint.name, + constraint.type, + List.copyOf(constraint.columns), + referenced, + List.copyOf(constraint.referencedColumns), + constraint.definition + )); + } + return result; + } + + private static void addNonBlank(List values, String value) { + if (value != null && !value.isBlank()) { + values.add(value); + } + } + + private static final class MutableConstraint { + private final String name; + private final String table; + private final ConstraintType type; + private final List columns = new ArrayList<>(); + private final List referencedColumns = new ArrayList<>(); + private String referencedSchema; + private String referencedTable; + private String definition; + + private MutableConstraint(String name, String table, ConstraintType type) { + this.name = name; + this.table = table; + this.type = type; + } + } + + private static final class MutablePrimaryKey { + private final String name; + private final String table; + private final List columns = new ArrayList<>(); + + private MutablePrimaryKey(String name, String table) { + this.name = name; + this.table = table; + } + } + + private static final class MutableForeignKey { + private final String name; + private final String table; + private final List columns = new ArrayList<>(); + private final List referencedColumns = new ArrayList<>(); + private String referencedSchema; + private String referencedTable; + + private MutableForeignKey(String name, String table) { + this.name = name; + this.table = table; + } + } + + private static UnsupportedOperationException unsupported(String category) { + return new UnsupportedOperationException("Metadata category not supported: " + category); + } + + private static final class MutableIndex { + private final String name; + private final String table; + private final Boolean unique; + private final String method; + private String definition; + private final List parts = new ArrayList<>(); + + private MutableIndex(String name, String table, Boolean unique, String method, String definition) { + this.name = name; + this.table = table; + this.unique = unique; + this.method = method; + this.definition = definition; + } + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/CatalogMetadata.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/CatalogMetadata.java new file mode 100644 index 00000000..c9aa2487 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/CatalogMetadata.java @@ -0,0 +1,4 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +public record CatalogMetadata(String name) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ColumnMetadata.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ColumnMetadata.java new file mode 100644 index 00000000..d31ad1b0 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ColumnMetadata.java @@ -0,0 +1,27 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +/** + * Canonical column metadata. {@code owner} carries the owning relation so batch + * results can be reassociated. {@code nativeType} is the SQL type name; + * {@code jdbcType} is the {@link java.sql.Types} code ({@code OTHER} when unknown). + */ +public record ColumnMetadata( + ObjectRef owner, + String name, + int ordinal, + String nativeType, + int jdbcType, + Integer size, + Integer scale, + boolean nullable, + String defaultValue, + String comment +) { + /** Compatibility constructor retained for existing completion/catalog callers. */ + public ColumnMetadata( + ObjectRef owner, String name, int ordinal, String nativeType, int jdbcType, + boolean nullable, String defaultValue, String comment + ) { + this(owner, name, ordinal, nativeType, jdbcType, null, null, nullable, defaultValue, comment); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ConstraintMetadata.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ConstraintMetadata.java new file mode 100644 index 00000000..0082a06b --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ConstraintMetadata.java @@ -0,0 +1,15 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +import java.util.List; + +/** Canonical table constraint metadata for PK, FK, UNIQUE and CHECK constraints. */ +public record ConstraintMetadata( + ObjectRef owner, + String name, + ConstraintType type, + List columns, + ObjectRef referenced, + List referencedColumns, + String definition +) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ConstraintType.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ConstraintType.java new file mode 100644 index 00000000..35ee3fcb --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ConstraintType.java @@ -0,0 +1,33 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +import java.util.Locale; + +public enum ConstraintType { + PRIMARY_KEY("PRIMARY KEY"), + FOREIGN_KEY("FOREIGN KEY"), + UNIQUE("UNIQUE"), + CHECK("CHECK"); + + private final String code; + + ConstraintType(String code) { + this.code = code; + } + + public String code() { + return this.code; + } + + public static ConstraintType fromDatabaseValue(String value) { + if (value == null) { + throw new IllegalArgumentException("Constraint type is required"); + } + var normalized = value.trim().replace('_', ' ').toUpperCase(Locale.ROOT); + for (var type : values()) { + if (type.code.equals(normalized)) { + return type; + } + } + throw new IllegalArgumentException("Unknown constraint type: " + value); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/DatabaseMetadataProvider.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/DatabaseMetadataProvider.java new file mode 100644 index 00000000..14c3e293 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/DatabaseMetadataProvider.java @@ -0,0 +1,69 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +import java.sql.SQLException; +import java.util.List; +import java.util.Set; + +/** + * Canonical metadata source for one SQL dialect. Providers are stateless with + * respect to cache; they hold a {@link org.jumpserver.chen.framework.datasource.ConnectionManager} + * reference and issue parameterized metadata queries. + * + *

The catalog never calls a category whose {@link #capabilities()} flag is + * {@code false}. Unsupported categories therefore either return an empty/natural + * default (containers, schema DDL, vendor properties) or throw + * {@link UnsupportedOperationException} from the base implementation.

+ */ +public interface DatabaseMetadataProvider { + + MetadataCapabilities capabilities(); + + List listCatalogs() throws SQLException; + + List listSchemas(String catalog) throws SQLException; + + /** + * Full relation list for a scope. Cacheable; {@code kinds} restricts which + * relation kinds are returned. + */ + List listRelations(RelationScope scope, Set kinds) throws SQLException; + + /** + * Completion-oriented search. Prefix and limit are applied by the caller + * (base default filters in memory); a dialect may override to push the + * predicate down. Not cached by the catalog. + */ + List searchRelations(RelationScope scope, Set kinds, String prefix, int limit) + throws SQLException; + + /** + * Batched column load. Every returned {@link ColumnMetadata} carries its + * owning {@link ObjectRef} so the caller can reassociate batch results. + */ + List listColumns(List relations) throws SQLException; + + List listIndexes(RelationScope scope) throws SQLException; + + /** Object-scoped index load. Implementations must push the relation filter into SQL. */ + List listIndexes(List relations) throws SQLException; + + List listStatistics(RelationScope scope) throws SQLException; + + List listPrimaryKeys(List relations) throws SQLException; + + List listForeignKeys(List relations) throws SQLException; + + List listConstraints(List relations) throws SQLException; + + /** + * Schema/database DDL, or {@code null} when the dialect does not provide one. + */ + String getSchemaDefinition(RelationScope scope) throws SQLException; + + /** Complete table DDL, or {@code null} when the dialect does not provide it. */ + String getTableDefinition(ObjectRef relation) throws SQLException; + + ObjectProperties objectProperties(ObjectRef ref) throws SQLException; + + ScopeProperties scopeProperties(ScopeRef ref) throws SQLException; +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ForeignKeyMetadata.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ForeignKeyMetadata.java new file mode 100644 index 00000000..59339f19 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ForeignKeyMetadata.java @@ -0,0 +1,12 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +import java.util.List; + +public record ForeignKeyMetadata( + ObjectRef owner, + String name, + List columns, + ObjectRef referenced, + List referencedColumns +) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/IndexMetadata.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/IndexMetadata.java new file mode 100644 index 00000000..0fc2a93b --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/IndexMetadata.java @@ -0,0 +1,13 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +import java.util.List; + +public record IndexMetadata( + String name, + ObjectRef owner, + boolean unique, + String method, + List parts, + String definition +) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/IndexPart.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/IndexPart.java new file mode 100644 index 00000000..dd914977 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/IndexPart.java @@ -0,0 +1,14 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +/** + * One column or expression participating in an index. + * Exactly one of {@code columnName} / {@code expression} is non-null. + */ +public record IndexPart( + int ordinal, + String columnName, + String expression, + String sortOrder, + boolean included +) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/MetadataCapabilities.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/MetadataCapabilities.java new file mode 100644 index 00000000..c0914049 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/MetadataCapabilities.java @@ -0,0 +1,39 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +/** + * Per-dialect capability flags. Coarse category flags drive whether the catalog + * may call a provider method; fine field-level flags drive which relation/ + * statistics columns a consumer (e.g. schema overview) may render. + */ +public record MetadataCapabilities( + boolean catalogs, + boolean schemas, + boolean relations, + boolean columns, + boolean indexes, + boolean primaryKeys, + boolean foreignKeys, + boolean statistics, + boolean definitions, + boolean tableRows, + boolean tableSize, + boolean tableEngine, + boolean tableCharacterSet, + boolean tableCollation, + boolean tableComment, + boolean viewComment, + boolean constraints, + boolean tableDefinitions +) { + /** Compatibility constructor for callers that only know the original capability set. */ + public MetadataCapabilities( + boolean catalogs, boolean schemas, boolean relations, boolean columns, + boolean indexes, boolean primaryKeys, boolean foreignKeys, boolean statistics, + boolean definitions, boolean tableRows, boolean tableSize, boolean tableEngine, + boolean tableCharacterSet, boolean tableCollation, boolean tableComment, boolean viewComment + ) { + this(catalogs, schemas, relations, columns, indexes, primaryKeys, foreignKeys, statistics, + definitions, tableRows, tableSize, tableEngine, tableCharacterSet, tableCollation, + tableComment, viewComment, false, false); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/MetadataCatalog.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/MetadataCatalog.java new file mode 100644 index 00000000..efe489f9 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/MetadataCatalog.java @@ -0,0 +1,321 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +import org.jumpserver.chen.framework.datasource.ConnectionManager; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Session-scoped metadata facade: cache + invalidation + database-context + * switching over a {@link DatabaseMetadataProvider}. It manages metadata only, + * not the tree children cache, and does not depend on {@code ResourceBrowser}. + * + *

Object-scoped categories (columns, keys) are cached per relation and batch + * the misses into a single provider call; schema-scoped categories (relations, + * indexes, statistics, definitions) are cached per scope.

+ */ +public class MetadataCatalog { + + private static final long DEFAULT_TTL_MILLIS = 30 * 60 * 1000L; + private static final long STATISTICS_TTL_MILLIS = 5 * 60 * 1000L; + + private static final Set ALL_KINDS = EnumSet.allOf(RelationKind.class); + + private final ConnectionManager connectionManager; + private final DatabaseMetadataProvider provider; + private final Map cache = new ConcurrentHashMap<>(); + + public MetadataCatalog(ConnectionManager connectionManager, DatabaseMetadataProvider provider) { + this.connectionManager = Objects.requireNonNull(connectionManager); + this.provider = Objects.requireNonNull(provider); + } + + public MetadataCapabilities capabilities() { + return this.provider.capabilities(); + } + + // -- containers --------------------------------------------------------- + + public List listCatalogs() throws SQLException { + var key = new CacheKey(null, null, null, Category.CATALOGS); + return cachedList(key, DEFAULT_TTL_MILLIS, provider.capabilities().catalogs(), () -> + provider.listCatalogs()); + } + + public List listSchemas(String catalog) throws SQLException { + var key = new CacheKey(catalog, null, null, Category.SCHEMAS); + return cachedList(key, DEFAULT_TTL_MILLIS, provider.capabilities().schemas(), () -> + connectionManager.withDatabaseContext(catalog, () -> provider.listSchemas(catalog))); + } + + // -- relations ---------------------------------------------------------- + + public List listRelations(RelationScope scope, Set kinds) throws SQLException { + var key = new CacheKey(scope.catalog(), scope.schema(), null, Category.RELATIONS); + var all = cachedList(key, DEFAULT_TTL_MILLIS, provider.capabilities().relations(), () -> + connectionManager.withDatabaseContext(scope.catalog(), () -> provider.listRelations(scope, ALL_KINDS))); + if (all.isEmpty()) { + return List.of(); + } + return all.stream().filter(relation -> kinds.contains(relation.ref().kind())).toList(); + } + + public List searchRelations(RelationScope scope, Set kinds, String prefix, int limit) + throws SQLException { + if (!provider.capabilities().relations()) { + return List.of(); + } + return connectionManager.withDatabaseContext(scope.catalog(), () -> + provider.searchRelations(scope, kinds, prefix, limit)); + } + + // -- columns / keys (object-scoped, batch of misses) -------------------- + + public List listColumns(List relations) throws SQLException { + if (relations.isEmpty() || !provider.capabilities().columns()) { + return List.of(); + } + return loadObjectScoped(relations, Category.COLUMNS, provider::listColumns, DEFAULT_TTL_MILLIS); + } + + public List listPrimaryKeys(List relations) throws SQLException { + if (relations.isEmpty() || !provider.capabilities().primaryKeys()) { + return List.of(); + } + return loadObjectScoped(relations, Category.PRIMARY_KEYS, provider::listPrimaryKeys, DEFAULT_TTL_MILLIS); + } + + public List listForeignKeys(List relations) throws SQLException { + if (relations.isEmpty() || !provider.capabilities().foreignKeys()) { + return List.of(); + } + return loadObjectScoped(relations, Category.FOREIGN_KEYS, provider::listForeignKeys, DEFAULT_TTL_MILLIS); + } + + public List listIndexes(List relations) throws SQLException { + if (relations.isEmpty() || !provider.capabilities().indexes()) { + return List.of(); + } + return loadObjectScoped(relations, Category.OBJECT_INDEXES, provider::listIndexes, DEFAULT_TTL_MILLIS); + } + + public List listConstraints(List relations) throws SQLException { + if (relations.isEmpty() || !provider.capabilities().constraints()) { + return List.of(); + } + return loadObjectScoped(relations, Category.CONSTRAINTS, provider::listConstraints, DEFAULT_TTL_MILLIS); + } + + public String getTableDefinition(ObjectRef relation) throws SQLException { + var key = new CacheKey(relation.catalog(), relation.schema(), relation.name(), Category.TABLE_DEFINITIONS); + return cachedNullable(key, DEFAULT_TTL_MILLIS, provider.capabilities().tableDefinitions(), () -> + connectionManager.withDatabaseContext(relation.catalog(), () -> provider.getTableDefinition(relation))); + } + + // -- schema-scoped ------------------------------------------------------ + + public List listIndexes(RelationScope scope) throws SQLException { + var key = new CacheKey(scope.catalog(), scope.schema(), null, Category.INDEXES); + return cachedList(key, DEFAULT_TTL_MILLIS, provider.capabilities().indexes(), () -> + connectionManager.withDatabaseContext(scope.catalog(), () -> provider.listIndexes(scope))); + } + + public List listStatistics(RelationScope scope) throws SQLException { + var key = new CacheKey(scope.catalog(), scope.schema(), null, Category.STATISTICS); + return cachedList(key, STATISTICS_TTL_MILLIS, provider.capabilities().statistics(), () -> + connectionManager.withDatabaseContext(scope.catalog(), () -> provider.listStatistics(scope))); + } + + public String getSchemaDefinition(RelationScope scope) throws SQLException { + var key = new CacheKey(scope.catalog(), scope.schema(), null, Category.DEFINITIONS); + return cachedNullable(key, DEFAULT_TTL_MILLIS, provider.capabilities().definitions(), () -> + connectionManager.withDatabaseContext(scope.catalog(), () -> provider.getSchemaDefinition(scope))); + } + + public ObjectProperties objectProperties(ObjectRef ref) throws SQLException { + if (!provider.capabilities().relations()) { + return new ObjectProperties(ref, List.of()); + } + return connectionManager.withDatabaseContext(ref.catalog(), () -> provider.objectProperties(ref)); + } + + public ScopeProperties scopeProperties(ScopeRef ref) throws SQLException { + return connectionManager.withDatabaseContext(ref.scope().catalog(), () -> provider.scopeProperties(ref)); + } + + // -- invalidation ------------------------------------------------------- + + public void invalidate(RelationScope scope) { + invalidatePrefix(scope.catalog(), scope.schema()); + } + + public void invalidateCatalog(String catalog) { + cache.keySet().removeIf(key -> Objects.equals(key.catalog(), catalog)); + } + + public void invalidate(ObjectRef ref) { + invalidatePrefix(ref.catalog(), ref.schema(), ref.name()); + } + + public void invalidateAll() { + cache.clear(); + } + + // -- internals ---------------------------------------------------------- + + private List cachedList(CacheKey key, long ttlMillis, boolean supported, Loader> loader) + throws SQLException { + return cached(key, ttlMillis, supported, loader, List.of()); + } + + private T cachedNullable(CacheKey key, long ttlMillis, boolean supported, Loader loader) + throws SQLException { + return cached(key, ttlMillis, supported, loader, null); + } + + private T cached(CacheKey key, long ttlMillis, boolean supported, Loader loader, T emptyValue) + throws SQLException { + var now = System.currentTimeMillis(); + var existing = cache.get(key); + if (existing != null && now - existing.loadedAt() < ttlMillis) { + if (existing.state() == State.UNSUPPORTED) { + return emptyValue; + } + return cast(existing.value()); + } + if (!supported) { + cache.put(key, new CacheEntry(State.UNSUPPORTED, null, now)); + return emptyValue; + } + var value = loader.load(); + var state = isEmpty(value) ? State.LOADED_EMPTY : State.LOADED; + cache.put(key, new CacheEntry(state, value, now)); + return value; + } + + private List loadObjectScoped(List relations, Category category, ObjectLoader loader, long ttlMillis) + throws SQLException { + var result = new ArrayList(); + var misses = new ArrayList(); + var now = System.currentTimeMillis(); + for (var ref : relations) { + var key = new CacheKey(ref.catalog(), ref.schema(), ref.name(), category); + var existing = cache.get(key); + if (existing != null && existing.state() != State.UNSUPPORTED && now - existing.loadedAt() < ttlMillis) { + result.addAll(castList(existing.value())); + } else { + misses.add(ref); + } + } + if (misses.isEmpty()) { + return result; + } + + var missesByScope = new LinkedHashMap>(); + for (var ref : misses) { + missesByScope.computeIfAbsent(new RelationScope(ref.catalog(), ref.schema()), ignored -> new ArrayList<>()).add(ref); + } + + for (var entry : missesByScope.entrySet()) { + var scope = entry.getKey(); + var scopeRefs = entry.getValue(); + var fetched = connectionManager.withDatabaseContext(scope.catalog(), () -> loader.load(scopeRefs)); + var byOwner = new LinkedHashMap>(); + for (var item : fetched) { + var owner = ownerOf(item); + if (owner != null) { + byOwner.computeIfAbsent(owner, ignored -> new ArrayList<>()).add(item); + } + } + for (var ref : scopeRefs) { + var items = byOwner.getOrDefault(ref, List.of()); + var key = new CacheKey(ref.catalog(), ref.schema(), ref.name(), category); + cache.put(key, new CacheEntry( + items.isEmpty() ? State.LOADED_EMPTY : State.LOADED, + items, + System.currentTimeMillis() + )); + result.addAll(items); + } + } + return result; + } + + @SuppressWarnings("unchecked") + private static ObjectRef ownerOf(T item) { + if (item instanceof ColumnMetadata column) { + return column.owner(); + } + if (item instanceof PrimaryKeyMetadata pk) { + return pk.owner(); + } + if (item instanceof ForeignKeyMetadata fk) { + return fk.owner(); + } + if (item instanceof IndexMetadata index) { + return index.owner(); + } + if (item instanceof ConstraintMetadata constraint) { + return constraint.owner(); + } + return null; + } + + private static boolean isEmpty(Object value) { + return value instanceof Collection collection && collection.isEmpty(); + } + + private void invalidatePrefix(String catalog, String schema) { + invalidatePrefix(catalog, schema, null); + } + + private void invalidatePrefix(String catalog, String schema, String object) { + cache.keySet().removeIf(key -> + (object == null || key.object() == null || key.object().equals(object)) + && Objects.equals(key.catalog(), catalog) + && Objects.equals(key.schema(), schema)); + } + + @SuppressWarnings("unchecked") + private static T cast(Object value) { + return (T) value; + } + + @SuppressWarnings("unchecked") + private static List castList(Object value) { + return value instanceof List list ? (List) list : List.of(); + } + + private enum Category { + CATALOGS, SCHEMAS, RELATIONS, COLUMNS, INDEXES, STATISTICS, + PRIMARY_KEYS, FOREIGN_KEYS, DEFINITIONS, OBJECT_INDEXES, CONSTRAINTS, TABLE_DEFINITIONS + } + + private enum State { + LOADED, LOADED_EMPTY, UNSUPPORTED + } + + private record CacheKey(String catalog, String schema, String object, Category category) { + } + + private record CacheEntry(State state, Object value, long loadedAt) { + } + + @FunctionalInterface + private interface Loader { + T load() throws SQLException; + } + + @FunctionalInterface + private interface ObjectLoader { + List load(List relations) throws SQLException; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/MetadataQueryAuditContext.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/MetadataQueryAuditContext.java new file mode 100644 index 00000000..903cb184 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/MetadataQueryAuditContext.java @@ -0,0 +1,31 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +/** + * Narrow marker enabled by the table-property and schema-overview services when + * provider SQL belongs to the user-facing metadata request. + */ +public final class MetadataQueryAuditContext implements AutoCloseable { + private static final ThreadLocal DEPTH = ThreadLocal.withInitial(() -> 0); + + private MetadataQueryAuditContext() { + DEPTH.set(DEPTH.get() + 1); + } + + public static MetadataQueryAuditContext open() { + return new MetadataQueryAuditContext(); + } + + public static boolean isActive() { + return DEPTH.get() > 0; + } + + @Override + public void close() { + var depth = DEPTH.get() - 1; + if (depth <= 0) { + DEPTH.remove(); + } else { + DEPTH.set(depth); + } + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ObjectProperties.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ObjectProperties.java new file mode 100644 index 00000000..7bfcea93 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ObjectProperties.java @@ -0,0 +1,10 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +import java.util.List; + +/** + * Generic vendor-specific properties not representable by the canonical model. + * Order-preserving; used by Show Properties to avoid silently dropping fields. + */ +public record ObjectProperties(ObjectRef ref, List items) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ObjectRef.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ObjectRef.java new file mode 100644 index 00000000..547047f9 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ObjectRef.java @@ -0,0 +1,14 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +/** + * Canonical reference to a relation (or, by extension, a schema/catalog object). + * JSON-compatible with the previous {@code QualifiedRelation} shape + * (catalog is nullable, kind is the lowercase {@link RelationKind#code()} wire value). + */ +public record ObjectRef( + String catalog, + String schema, + String name, + RelationKind kind +) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ObjectStatistics.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ObjectStatistics.java new file mode 100644 index 00000000..c984ed6c --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ObjectStatistics.java @@ -0,0 +1,8 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +/** + * Estimated row count and total storage size for a relation. Estimates only; + * never produced by {@code COUNT(*)}. + */ +public record ObjectStatistics(ObjectRef ref, Long estimatedRows, Long totalSizeBytes) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/PrimaryKeyMetadata.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/PrimaryKeyMetadata.java new file mode 100644 index 00000000..ab0c4f07 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/PrimaryKeyMetadata.java @@ -0,0 +1,6 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +import java.util.List; + +public record PrimaryKeyMetadata(ObjectRef owner, String name, List columns) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/PropertyItem.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/PropertyItem.java new file mode 100644 index 00000000..972af5fd --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/PropertyItem.java @@ -0,0 +1,4 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +public record PropertyItem(String name, String value) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/RelationKind.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/RelationKind.java new file mode 100644 index 00000000..cf8e0aca --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/RelationKind.java @@ -0,0 +1,32 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +/** + * Canonical relation kind. The {@link #code()} value is the wire form used in + * {@link ObjectRef}; keep it stable so it stays JSON-compatible with the + * completion and table-metadata client contract + * ("table" / "view" / "materialized_view"). + */ +public enum RelationKind { + TABLE("table"), + VIEW("view"), + MATERIALIZED_VIEW("materialized_view"); + + private final String code; + + RelationKind(String code) { + this.code = code; + } + + public String code() { + return this.code; + } + + public static RelationKind fromCode(String code) { + for (var kind : values()) { + if (kind.code.equals(code)) { + return kind; + } + } + throw new IllegalArgumentException("Unknown relation kind: " + code); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/RelationMetadata.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/RelationMetadata.java new file mode 100644 index 00000000..17425ba6 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/RelationMetadata.java @@ -0,0 +1,14 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +/** + * Structural metadata about a relation, excluding statistics (rows/size), which + * have a different refresh frequency and live in {@link ObjectStatistics}. + */ +public record RelationMetadata( + ObjectRef ref, + String comment, + String engine, + String characterSet, + String collation +) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/RelationScope.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/RelationScope.java new file mode 100644 index 00000000..fe07348c --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/RelationScope.java @@ -0,0 +1,7 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +/** + * The (catalog, schema) context for schema-scoped batch queries. + */ +public record RelationScope(String catalog, String schema) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/SchemaMetadata.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/SchemaMetadata.java new file mode 100644 index 00000000..9d4c908f --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/SchemaMetadata.java @@ -0,0 +1,4 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +public record SchemaMetadata(String catalog, String name) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ScopeKind.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ScopeKind.java new file mode 100644 index 00000000..9b75e053 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ScopeKind.java @@ -0,0 +1,6 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +public enum ScopeKind { + CATALOG, + SCHEMA +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ScopeProperties.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ScopeProperties.java new file mode 100644 index 00000000..9cc73985 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ScopeProperties.java @@ -0,0 +1,6 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +import java.util.List; + +public record ScopeProperties(ScopeRef ref, List items) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ScopeRef.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ScopeRef.java new file mode 100644 index 00000000..5743a535 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ScopeRef.java @@ -0,0 +1,4 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +public record ScopeRef(RelationScope scope, ScopeKind kind) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/BaseExecutionPlanDialect.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/BaseExecutionPlanDialect.java new file mode 100644 index 00000000..43e4a7b3 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/BaseExecutionPlanDialect.java @@ -0,0 +1,135 @@ +package org.jumpserver.chen.framework.datasource.plan; + +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.List; + +public abstract class BaseExecutionPlanDialect implements ExecutionPlanDialect { + protected String readFirstColumn(ResultSet resultSet, int maxRawBytes) throws SQLException { + if (resultSet == null || !resultSet.next()) { + return ""; + } + String value = resultSet.getString(1); + return boundRaw(value, maxRawBytes).text(); + } + + protected BoundedRaw boundRaw(String value, int maxRawBytes) { + if (value == null) { + return new BoundedRaw("", false); + } + byte[] bytes = value.getBytes(StandardCharsets.UTF_8); + if (bytes.length <= maxRawBytes) { + return new BoundedRaw(value, false); + } + return new BoundedRaw(new String(bytes, 0, utf8PrefixLength(bytes, maxRawBytes), StandardCharsets.UTF_8), true); + } + + protected static int utf8ByteLength(CharSequence value) { + if (value == null || value.isEmpty()) { + return 0; + } + return value.toString().getBytes(StandardCharsets.UTF_8).length; + } + + protected static int utf8PrefixLength(byte[] bytes, int maxBytes) { + int length = Math.min(bytes.length, Math.max(0, maxBytes)); + if (length >= bytes.length) { + return length; + } + while (length > 0 && (bytes[length - 1] & 0xC0) == 0x80) { + length--; + } + if (length > 0 && (bytes[length - 1] & 0x80) != 0) { + length--; + } + return length; + } + + protected void throwIfConnectionUnusable( + PlanExecutionContext context, + SQLException error, + ExecutionPlanCapabilities capabilities + ) throws SQLException { + if (!connectionUnusable(context, error)) { + return; + } + String message = error.getMessage() == null + ? PlanI18n.msg("Plan.ConnectionUnusable", "Connection became unusable during execution plan") + : error.getMessage(); + PlanDiagnostic diagnostic = new PlanDiagnostic( + PlanCodes.CONNECTION_INVALIDATED, + message, + error.getSQLState(), + Integer.toString(error.getErrorCode()) + ); + throw new ConnectionInvalidatedException( + message, + error, + diagnostic, + null, + new DialectPlanResult( + context.serverVersion(), + PlanStatus.CONNECTION_INVALIDATED, + List.of(), + null, + null, + null, + false, + capabilities, + List.of(), + PlanEffects.discard(PlanEffects.SessionState.UNKNOWN, PlanEffects.TransactionState.UNKNOWN), + diagnostic, + List.of() + ) + ); + } + + protected static boolean connectionUnusable(PlanExecutionContext context, SQLException error) { + String state = error.getSQLState(); + if (state != null && state.startsWith("08")) { + return true; + } + Connection connection = context.connection(); + if (connection == null) { + return true; + } + try { + if (connection.isClosed()) { + return true; + } + } catch (SQLException e) { + return true; + } + try { + return !connection.isValid(1); + } catch (SQLException | AbstractMethodError | RuntimeException ignored) { + return false; + } + } + + protected void closeQuietly(Statement statement) { + if (statement == null) { + return; + } + try { + statement.close(); + } catch (SQLException ignored) { + } + } + + protected void closeQuietly(ResultSet resultSet) { + if (resultSet == null) { + return; + } + try { + resultSet.close(); + } catch (SQLException ignored) { + } + } + + public record BoundedRaw(String text, boolean truncated) { + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/ConnectionInvalidatedException.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/ConnectionInvalidatedException.java new file mode 100644 index 00000000..8109cc97 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/ConnectionInvalidatedException.java @@ -0,0 +1,48 @@ +package org.jumpserver.chen.framework.datasource.plan; + +import java.sql.SQLException; +import java.util.List; + +public class ConnectionInvalidatedException extends SQLException { + private final PlanDiagnostic originalError; + private final PlanDiagnostic cleanupError; + private final DialectPlanResult partialResult; + + public ConnectionInvalidatedException( + String message, + Throwable cause, + PlanDiagnostic originalError, + PlanDiagnostic cleanupError, + DialectPlanResult partialResult + ) { + super(message, cause); + this.originalError = originalError; + this.cleanupError = cleanupError; + this.partialResult = partialResult; + } + + public PlanDiagnostic originalError() { + return originalError; + } + + public PlanDiagnostic cleanupError() { + return cleanupError; + } + + public DialectPlanResult partialResult() { + return partialResult; + } + + public List diagnostics() { + if (originalError == null && cleanupError == null) { + return List.of(); + } + if (originalError == null) { + return List.of(cleanupError); + } + if (cleanupError == null) { + return List.of(originalError); + } + return List.of(originalError, cleanupError); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/DialectPlanResult.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/DialectPlanResult.java new file mode 100644 index 00000000..58060fe6 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/DialectPlanResult.java @@ -0,0 +1,28 @@ +package org.jumpserver.chen.framework.datasource.plan; + +import java.util.List; +import java.util.Objects; + +public record DialectPlanResult( + String serverVersion, + PlanStatus status, + List roots, + String rawText, + PlanRawFormat rawFormat, + String rawFormatVersion, + boolean rawTruncated, + ExecutionPlanCapabilities capabilities, + List prerequisites, + PlanEffects effects, + PlanDiagnostic error, + List warnings +) { + public DialectPlanResult { + Objects.requireNonNull(status, "status"); + Objects.requireNonNull(capabilities, "capabilities"); + Objects.requireNonNull(effects, "effects"); + roots = roots == null ? List.of() : List.copyOf(roots); + prerequisites = prerequisites == null ? List.of() : List.copyOf(prerequisites); + warnings = warnings == null ? List.of() : List.copyOf(warnings); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/ExecutionPlan.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/ExecutionPlan.java new file mode 100644 index 00000000..5e81a7be --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/ExecutionPlan.java @@ -0,0 +1,62 @@ +package org.jumpserver.chen.framework.datasource.plan; + +import java.util.List; +import java.util.Objects; + +public record ExecutionPlan( + String requestId, + PlanDatabase database, + String serverVersion, + PlanMode mode, + PlanStatus status, + String sql, + List roots, + String rawText, + PlanRawFormat rawFormat, + String rawFormatVersion, + boolean rawTruncated, + ExecutionPlanCapabilities capabilities, + List prerequisites, + PlanEffects effects, + PlanDiagnostic error, + List warnings +) { + public ExecutionPlan { + Objects.requireNonNull(requestId, "requestId"); + Objects.requireNonNull(mode, "mode"); + Objects.requireNonNull(status, "status"); + Objects.requireNonNull(sql, "sql"); + Objects.requireNonNull(capabilities, "capabilities"); + Objects.requireNonNull(effects, "effects"); + roots = roots == null ? List.of() : List.copyOf(roots); + prerequisites = prerequisites == null ? List.of() : List.copyOf(prerequisites); + warnings = warnings == null ? List.of() : List.copyOf(warnings); + } + + public static ExecutionPlan fromDialect( + String requestId, + PlanDatabase database, + PlanMode mode, + String sql, + DialectPlanResult result + ) { + return new ExecutionPlan( + requestId, + database, + result.serverVersion(), + mode, + result.status(), + sql, + result.roots(), + result.rawText(), + result.rawFormat(), + result.rawFormatVersion(), + result.rawTruncated(), + result.capabilities(), + result.prerequisites(), + result.effects(), + result.error(), + result.warnings() + ); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/ExecutionPlanCapabilities.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/ExecutionPlanCapabilities.java new file mode 100644 index 00000000..1206136b --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/ExecutionPlanCapabilities.java @@ -0,0 +1,83 @@ +package org.jumpserver.chen.framework.datasource.plan; + +import java.util.Objects; + +public record ExecutionPlanCapabilities( + boolean supported, + boolean structured, + boolean raw, + boolean cost, + boolean estimatedRows, + boolean predicates, + boolean twoStep, + boolean requiresSessionState, + boolean requiresPlanTable, + PlanTransactionPolicy transactionPolicy +) { + public ExecutionPlanCapabilities { + Objects.requireNonNull(transactionPolicy, "transactionPolicy"); + } + + public static ExecutionPlanCapabilities unsupported() { + return new ExecutionPlanCapabilities( + false, false, false, false, false, false, false, false, false, + PlanTransactionPolicy.STATEMENT_ONLY + ); + } + + public static ExecutionPlanCapabilities postgresql() { + return new ExecutionPlanCapabilities( + true, true, true, true, true, true, false, false, false, + PlanTransactionPolicy.SAVEPOINT_IF_ACTIVE + ); + } + + public static ExecutionPlanCapabilities oracle() { + return new ExecutionPlanCapabilities( + true, true, true, true, true, true, true, false, true, + PlanTransactionPolicy.AUXILIARY_DML + ); + } + + public static ExecutionPlanCapabilities mysql() { + return new ExecutionPlanCapabilities( + true, true, true, true, true, true, false, false, false, + PlanTransactionPolicy.STATEMENT_ONLY + ); + } + + public static ExecutionPlanCapabilities mariadb() { + return new ExecutionPlanCapabilities( + true, true, true, false, true, true, false, false, false, + PlanTransactionPolicy.STATEMENT_ONLY + ); + } + + public static ExecutionPlanCapabilities sqlserver() { + return new ExecutionPlanCapabilities( + true, true, true, true, true, true, true, true, false, + PlanTransactionPolicy.RESTORE_SESSION + ); + } + + public static ExecutionPlanCapabilities db2() { + return new ExecutionPlanCapabilities( + true, true, true, true, true, true, true, false, true, + PlanTransactionPolicy.AUXILIARY_DML + ); + } + + public static ExecutionPlanCapabilities dm() { + return new ExecutionPlanCapabilities( + true, true, true, true, true, false, false, false, false, + PlanTransactionPolicy.STATEMENT_ONLY + ); + } + + public static ExecutionPlanCapabilities clickhouse() { + return new ExecutionPlanCapabilities( + true, true, true, false, false, false, false, false, false, + PlanTransactionPolicy.STATEMENT_ONLY + ); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/ExecutionPlanDialect.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/ExecutionPlanDialect.java new file mode 100644 index 00000000..8b3bb8c9 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/ExecutionPlanDialect.java @@ -0,0 +1,13 @@ +package org.jumpserver.chen.framework.datasource.plan; + +import org.jumpserver.chen.framework.datasource.analysis.SqlStatementAnalysis; + +import java.sql.SQLException; + +public interface ExecutionPlanDialect { + PlanDatabase database(); + + ExecutionPlanCapabilities capabilities(); + + DialectPlanResult explainEstimated(PlanExecutionContext context, SqlStatementAnalysis statement) throws SQLException; +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/ExecutionPlanJson.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/ExecutionPlanJson.java new file mode 100644 index 00000000..a18bc54a --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/ExecutionPlanJson.java @@ -0,0 +1,26 @@ +package org.jumpserver.chen.framework.datasource.plan; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; + +public final class ExecutionPlanJson { + public static final Gson GSON = new GsonBuilder() + .serializeNulls() + .create(); + + private ExecutionPlanJson() { + } + + public static JsonElement toJsonTree(ExecutionPlan plan) { + return GSON.toJsonTree(plan); + } + + public static String toJson(ExecutionPlan plan) { + return GSON.toJson(plan); + } + + public static ExecutionPlan fromJson(String json) { + return GSON.fromJson(json, ExecutionPlan.class); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/NormalizedNodeType.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/NormalizedNodeType.java new file mode 100644 index 00000000..ab709757 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/NormalizedNodeType.java @@ -0,0 +1,23 @@ +package org.jumpserver.chen.framework.datasource.plan; + +public enum NormalizedNodeType { + SCAN, + INDEX_SCAN, + INDEX_ONLY_SCAN, + JOIN, + HASH_JOIN, + MERGE_JOIN, + NESTED_LOOP, + SORT, + AGGREGATE, + FILTER, + LIMIT, + SUBQUERY, + HASH, + PARALLEL, + UNION, + VALUES, + MATERIALIZE, + PROJECTION, + OTHER +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanCodes.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanCodes.java new file mode 100644 index 00000000..df733586 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanCodes.java @@ -0,0 +1,26 @@ +package org.jumpserver.chen.framework.datasource.plan; + +public final class PlanCodes { + public static final String PLAN_PARSE_FAILED = "PLAN_PARSE_FAILED"; + public static final String PLAN_LIMIT_REACHED = "PLAN_LIMIT_REACHED"; + public static final String PLAN_TABLE_MISSING = "PLAN_TABLE_MISSING"; + public static final String PLAN_TABLE_INCOMPATIBLE = "PLAN_TABLE_INCOMPATIBLE"; + public static final String PLAN_PERMISSION_DENIED = "PLAN_PERMISSION_DENIED"; + public static final String TRANSACTION_CONTEXT_UNSAFE = "TRANSACTION_CONTEXT_UNSAFE"; + public static final String UNSAFE_TO_ESTIMATE = "UNSAFE_TO_ESTIMATE"; + public static final String VERSION_NOT_VALIDATED = "VERSION_NOT_VALIDATED"; + public static final String AUXILIARY_CLEANUP_FAILED = "AUXILIARY_CLEANUP_FAILED"; + public static final String SESSION_STATE_UNSAFE = "SESSION_STATE_UNSAFE"; + public static final String SESSION_RESTORE_FAILED = "SESSION_RESTORE_FAILED"; + public static final String MISSING_REQUEST_ID = "MISSING_REQUEST_ID"; + public static final String PLAN_REQUEST_NOT_FOUND = "PLAN_REQUEST_NOT_FOUND"; + public static final String PLAN_REQUEST_AMBIGUOUS = "PLAN_REQUEST_AMBIGUOUS"; + public static final String CANCELLED = "CANCELLED"; + public static final String CONNECTION_INVALIDATED = "CONNECTION_INVALIDATED"; + public static final String SQL_ERROR = "SQL_ERROR"; + public static final String UNSUPPORTED_STATEMENT = "UNSUPPORTED_STATEMENT"; + public static final String UNSUPPORTED_DATABASE = "UNSUPPORTED_DATABASE"; + + private PlanCodes() { + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanDatabase.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanDatabase.java new file mode 100644 index 00000000..8fd9410d --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanDatabase.java @@ -0,0 +1,12 @@ +package org.jumpserver.chen.framework.datasource.plan; + +public enum PlanDatabase { + postgresql, + mysql, + mariadb, + oracle, + sqlserver, + db2, + dm, + clickhouse +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanDiagnostic.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanDiagnostic.java new file mode 100644 index 00000000..2dc9f6a8 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanDiagnostic.java @@ -0,0 +1,19 @@ +package org.jumpserver.chen.framework.datasource.plan; + +import java.util.Objects; + +public record PlanDiagnostic( + String code, + String message, + String sqlState, + String vendorCode +) { + public PlanDiagnostic { + Objects.requireNonNull(code, "code"); + Objects.requireNonNull(message, "message"); + } + + public static PlanDiagnostic of(String code, String message) { + return new PlanDiagnostic(code, message, null, null); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanEffects.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanEffects.java new file mode 100644 index 00000000..d10a0ebd --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanEffects.java @@ -0,0 +1,69 @@ +package org.jumpserver.chen.framework.datasource.plan; + +import java.util.Objects; + +public record PlanEffects( + SessionState sessionState, + TransactionState transactionState, + AuxiliaryStorage auxiliaryStorage, + ConnectionDisposition connectionDisposition +) { + public PlanEffects { + Objects.requireNonNull(sessionState, "sessionState"); + Objects.requireNonNull(transactionState, "transactionState"); + Objects.requireNonNull(auxiliaryStorage, "auxiliaryStorage"); + Objects.requireNonNull(connectionDisposition, "connectionDisposition"); + } + + public enum SessionState { + UNCHANGED, + RESTORED, + UNKNOWN + } + + public enum TransactionState { + UNCHANGED, + PARTICIPATED, + FAILED, + UNKNOWN + } + + public enum AuxiliaryStorage { + NONE, + CLEANED, + RESIDUAL, + UNKNOWN + } + + public enum ConnectionDisposition { + REUSE, + DISCARD + } + + public static PlanEffects unchangedReuse() { + return new PlanEffects( + SessionState.UNCHANGED, + TransactionState.UNCHANGED, + AuxiliaryStorage.NONE, + ConnectionDisposition.REUSE + ); + } + + public static PlanEffects savepointParticipated() { + return new PlanEffects( + SessionState.UNCHANGED, + TransactionState.PARTICIPATED, + AuxiliaryStorage.NONE, + ConnectionDisposition.REUSE + ); + } + + public static PlanEffects discard(SessionState sessionState, TransactionState transactionState) { + return new PlanEffects( + sessionState, + transactionState, + AuxiliaryStorage.NONE, + ConnectionDisposition.DISCARD + ); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanExecutionContext.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanExecutionContext.java new file mode 100644 index 00000000..44e74d91 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanExecutionContext.java @@ -0,0 +1,35 @@ +package org.jumpserver.chen.framework.datasource.plan; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +public interface PlanExecutionContext { + Connection connection(); + + PlanTransactionState transactionState(); + + boolean transactionProbeFailed(); + + default String transactionProbeDetail() { + return null; + } + + void registerStatement(Statement statement); + + void unregisterStatement(Statement statement); + + boolean isCancelled(); + + long deadlineMillis(); + + int maxRawBytes(); + + int maxNodes(); + + int maxDepth(); + + String serverVersion(); + + void throwIfCancelled() throws SQLException; +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanI18n.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanI18n.java new file mode 100644 index 00000000..9124a4c2 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanI18n.java @@ -0,0 +1,12 @@ +package org.jumpserver.chen.framework.datasource.plan; + +import org.jumpserver.chen.framework.i18n.MessageUtils; + +public final class PlanI18n { + private PlanI18n() { + } + + public static String msg(String key, String english, Object... args) { + return MessageUtils.getOrDefault(key, english, args); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanLimits.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanLimits.java new file mode 100644 index 00000000..a2ba91d1 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanLimits.java @@ -0,0 +1,13 @@ +package org.jumpserver.chen.framework.datasource.plan; + +public final class PlanLimits { + public static final int MAX_SQL_CHARS = 1_000_000; + public static final int MAX_RAW_BYTES = 2_000_000; + public static final int MAX_NODES = 5_000; + public static final int MAX_DEPTH = 64; + public static final long DEFAULT_TIMEOUT_MS = 30_000L; + public static final long CLEANUP_TIMEOUT_MS = 5_000L; + + private PlanLimits() { + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanMode.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanMode.java new file mode 100644 index 00000000..d7bdc736 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanMode.java @@ -0,0 +1,5 @@ +package org.jumpserver.chen.framework.datasource.plan; + +public enum PlanMode { + ESTIMATED +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanNode.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanNode.java new file mode 100644 index 00000000..c62499ef --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanNode.java @@ -0,0 +1,35 @@ +package org.jumpserver.chen.framework.datasource.plan; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public record PlanNode( + String id, + String nativeId, + NormalizedNodeType nodeType, + String nativeOperator, + String logicalOperator, + String physicalOperator, + String detail, + String table, + String relation, + BigDecimal rows, + BigDecimal cost, + BigDecimal startupCost, + String rowsMeaning, + String costMeaning, + Map predicates, + Map attributes, + List children +) { + public PlanNode { + Objects.requireNonNull(id, "id"); + Objects.requireNonNull(nodeType, "nodeType"); + Objects.requireNonNull(nativeOperator, "nativeOperator"); + predicates = predicates == null ? Map.of() : Map.copyOf(predicates); + attributes = attributes == null ? Map.of() : Map.copyOf(attributes); + children = children == null ? List.of() : List.copyOf(children); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanPrerequisite.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanPrerequisite.java new file mode 100644 index 00000000..346142f2 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanPrerequisite.java @@ -0,0 +1,34 @@ +package org.jumpserver.chen.framework.datasource.plan; + +import java.util.Objects; + +public record PlanPrerequisite( + String code, + Status status, + String message, + String remediation +) { + public PlanPrerequisite { + Objects.requireNonNull(code, "code"); + Objects.requireNonNull(status, "status"); + Objects.requireNonNull(message, "message"); + } + + public enum Status { + MET, + UNMET, + UNKNOWN + } + + public static PlanPrerequisite met(String code, String message) { + return new PlanPrerequisite(code, Status.MET, message, null); + } + + public static PlanPrerequisite unmet(String code, String message, String remediation) { + return new PlanPrerequisite(code, Status.UNMET, message, remediation); + } + + public static PlanPrerequisite unknown(String code, String message, String remediation) { + return new PlanPrerequisite(code, Status.UNKNOWN, message, remediation); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanRawFormat.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanRawFormat.java new file mode 100644 index 00000000..867162b6 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanRawFormat.java @@ -0,0 +1,8 @@ +package org.jumpserver.chen.framework.datasource.plan; + +public enum PlanRawFormat { + JSON, + XML, + TEXT, + TABLE +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanStatus.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanStatus.java new file mode 100644 index 00000000..90ee2834 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanStatus.java @@ -0,0 +1,12 @@ +package org.jumpserver.chen.framework.datasource.plan; + +public enum PlanStatus { + SUCCESS, + RAW_ONLY, + UNSUPPORTED_STATEMENT, + UNSUPPORTED_DATABASE, + PREREQUISITES_UNMET, + ERROR, + CANCELLED, + CONNECTION_INVALIDATED +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanTransactionPolicy.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanTransactionPolicy.java new file mode 100644 index 00000000..c647a7d2 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanTransactionPolicy.java @@ -0,0 +1,8 @@ +package org.jumpserver.chen.framework.datasource.plan; + +public enum PlanTransactionPolicy { + STATEMENT_ONLY, + SAVEPOINT_IF_ACTIVE, + AUXILIARY_DML, + RESTORE_SESSION +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanTransactionState.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanTransactionState.java new file mode 100644 index 00000000..d5da19d9 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/PlanTransactionState.java @@ -0,0 +1,9 @@ +package org.jumpserver.chen.framework.datasource.plan; + +public enum PlanTransactionState { + AUTO_COMMIT, + MANUAL_COMMIT_IDLE, + TRANSACTION_ACTIVE, + TRANSACTION_FAILED, + UNKNOWN +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/UnsupportedExecutionPlanDialect.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/UnsupportedExecutionPlanDialect.java new file mode 100644 index 00000000..46e0d172 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/plan/UnsupportedExecutionPlanDialect.java @@ -0,0 +1,42 @@ +package org.jumpserver.chen.framework.datasource.plan; + +import org.jumpserver.chen.framework.datasource.analysis.SqlStatementAnalysis; + +public final class UnsupportedExecutionPlanDialect implements ExecutionPlanDialect { + public static final UnsupportedExecutionPlanDialect INSTANCE = new UnsupportedExecutionPlanDialect(); + + private UnsupportedExecutionPlanDialect() { + } + + public static UnsupportedExecutionPlanDialect getInstance() { + return INSTANCE; + } + + @Override + public PlanDatabase database() { + return null; + } + + @Override + public ExecutionPlanCapabilities capabilities() { + return ExecutionPlanCapabilities.unsupported(); + } + + @Override + public DialectPlanResult explainEstimated(PlanExecutionContext context, SqlStatementAnalysis statement) { + return new DialectPlanResult( + context == null ? null : context.serverVersion(), + PlanStatus.UNSUPPORTED_DATABASE, + java.util.List.of(), + null, + null, + null, + false, + capabilities(), + java.util.List.of(), + PlanEffects.unchangedReuse(), + PlanDiagnostic.of(PlanCodes.UNSUPPORTED_DATABASE, PlanI18n.msg("Plan.UnsupportedDatabase", "Execution plan is not implemented for this datasource")), + java.util.List.of() + ); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/JdbcDisplayValue.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/JdbcDisplayValue.java new file mode 100644 index 00000000..781679c2 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/JdbcDisplayValue.java @@ -0,0 +1,400 @@ +package org.jumpserver.chen.framework.datasource.sql; + +import com.alibaba.druid.DbType; +import org.apache.commons.lang3.StringUtils; + +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.sql.Types; +import java.util.Locale; +import java.util.regex.Pattern; + +/** + * Type-aware display conversion for JDBC result cells. + * VARBINARY/BINARY/BYTEA stay on the existing 0x hex path; BLOB and spatial + * types are classified from JDBC type, column type name, and dialect. + */ +public final class JdbcDisplayValue { + private static final int SQLSERVER_GEOMETRY = -157; + private static final int SQLSERVER_GEOGRAPHY = -158; + private static final Pattern WKT_PATTERN = Pattern.compile( + "^\\s*(SRID\\s*=\\s*\\d+\\s*;\\s*)?(POINT|LINESTRING|POLYGON|MULTIPOINT|" + + "MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)\\b", + Pattern.CASE_INSENSITIVE + ); + + private JdbcDisplayValue() { + } + + public static boolean isDecimalType(int jdbcType, String typeName) { + if (jdbcType == Types.DECIMAL || jdbcType == Types.NUMERIC) { + return true; + } + return switch (normalizeTypeName(typeName)) { + case "DECIMAL", "NUMERIC", "NUMBER", "DEC", "DECFLOAT" -> true; + default -> false; + }; + } + + public static boolean isBlobType(int jdbcType, String typeName) { + String type = normalizeTypeName(typeName); + if (type.equals("VARBINARY") || type.equals("BINARY") || type.equals("BYTEA") || type.equals("RAW")) { + return false; + } + if (jdbcType == Types.BLOB) { + return true; + } + return switch (type) { + case "BLOB", "TINYBLOB", "MEDIUMBLOB", "LONGBLOB", "IMAGE", "BFILE", "LONG RAW" -> true; + default -> jdbcType == Types.LONGVARBINARY; + }; + } + + public static boolean isGeometryType(int jdbcType, String typeName) { + if (jdbcType == SQLSERVER_GEOMETRY || jdbcType == SQLSERVER_GEOGRAPHY) { + return true; + } + String type = normalizeTypeName(typeName); + if (type.isEmpty()) { + return false; + } + return switch (type) { + case "GEOMETRY", "GEOGRAPHY", "SDO_GEOMETRY", "ST_GEOMETRY", "ST_GEOGRAPHY", + "POINT", "LINESTRING", "POLYGON", "MULTIPOINT", "MULTILINESTRING", + "MULTIPOLYGON", "GEOMETRYCOLLECTION", "GEOMCOLLECTION", + "ST_POINT", "ST_LINESTRING", "ST_POLYGON", "ST_MULTIPOINT", + "ST_MULTILINESTRING", "ST_MULTIPOLYGON", "ST_GEOMCOLLECTION", + "ST_GEOMETRYCOLLECTION", "GEO_POINT", "GEO_LINESTRING", "GEO_POLYGON" -> true; + default -> type.endsWith("GEOMETRY") || type.endsWith("GEOGRAPHY"); + }; + } + + public static String blobSummary() { + return ""; + } + + public static String blobSummary(long byteLength) { + return ""; + } + + public static String geometrySummary(Integer byteLength) { + if (byteLength == null) { + return "[Geometry]"; + } + return "[Geometry, " + byteLength + " B]"; + } + + public static String toGeometryDisplay(Object value, DbType dbType) { + if (value == null) { + return null; + } + if (value instanceof byte[] bytes) { + String wkt = wkbToWktForDialect(bytes, dbType); + if (wkt != null) { + return wkt; + } + return geometrySummary(bytes.length); + } + + String fromMethod = firstWktMethod(value); + if (fromMethod != null) { + return fromMethod; + } + + String text = extractText(value); + if (looksLikeWkt(text)) { + return text.trim(); + } + if (looksLikeHexEwkb(text)) { + return geometrySummary(hexByteLength(text)); + } + if (StringUtils.isNotBlank(text) && !isDefaultObjectToString(value, text) && !looksLikeHexEwkb(text)) { + return text; + } + return geometrySummary(null); + } + + public static String mysqlWkbToWkt(byte[] data) { + if (data == null || data.length < 5) { + return null; + } + // MySQL/MariaDB store a 4-byte SRID prefix before OGC WKB. + String withSrid = readWkb(data, 4); + if (withSrid != null) { + return withSrid; + } + return readWkb(data, 0); + } + + public static boolean looksLikeWkt(String text) { + return text != null && WKT_PATTERN.matcher(text).find(); + } + + static String normalizeTypeName(String typeName) { + if (typeName == null || typeName.isBlank()) { + return ""; + } + String type = typeName.trim(); + int dot = type.lastIndexOf('.'); + if (dot >= 0 && dot < type.length() - 1) { + type = type.substring(dot + 1); + } + int paren = type.indexOf('('); + if (paren >= 0) { + type = type.substring(0, paren); + } + return type.trim().toUpperCase(Locale.ROOT); + } + + private static String wkbToWktForDialect(byte[] bytes, DbType dbType) { + if (dbType == DbType.mysql || dbType == DbType.mariadb) { + return mysqlWkbToWkt(bytes); + } + return null; + } + + private static String firstWktMethod(Object value) { + for (String name : new String[]{"STAsText", "asText", "toText", "getWKT", "toWKT", "asWKT"}) { + String text = invokeStringMethod(value, name); + if (looksLikeWkt(text)) { + return text.trim(); + } + } + return null; + } + + private static String extractText(Object value) { + String fromValue = invokeStringMethod(value, "getValue"); + if (StringUtils.isNotBlank(fromValue)) { + return fromValue; + } + return value.toString(); + } + + private static String invokeStringMethod(Object value, String name) { + try { + Method method = value.getClass().getMethod(name); + Object result = method.invoke(value); + return result == null ? null : result.toString(); + } catch (ReflectiveOperationException e) { + return null; + } + } + + private static boolean looksLikeHexEwkb(String text) { + if (text == null) { + return false; + } + String hex = stripHexPrefix(text.trim()); + if (hex.length() < 16 || (hex.length() & 1) != 0) { + return false; + } + for (int i = 0; i < hex.length(); i++) { + char c = hex.charAt(i); + boolean hexChar = (c >= '0' && c <= '9') + || (c >= 'a' && c <= 'f') + || (c >= 'A' && c <= 'F'); + if (!hexChar) { + return false; + } + } + return true; + } + + private static int hexByteLength(String text) { + return stripHexPrefix(text.trim()).length() / 2; + } + + private static String stripHexPrefix(String text) { + if (text.startsWith("\\x") || text.startsWith("\\X")) { + return text.substring(2); + } + if (text.startsWith("0x") || text.startsWith("0X")) { + return text.substring(2); + } + return text; + } + + private static boolean isDefaultObjectToString(Object value, String text) { + return text.equals(value.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(value))); + } + + private static String readWkb(byte[] data, int offset) { + try { + WkbReader reader = new WkbReader(data, offset); + String wkt = reader.readGeometry(); + return StringUtils.isBlank(wkt) ? null : wkt; + } catch (IllegalArgumentException e) { + return null; + } + } + + private static final class WkbReader { + private static final int POINT = 1; + private static final int LINESTRING = 2; + private static final int POLYGON = 3; + private static final int MULTIPOINT = 4; + private static final int MULTILINESTRING = 5; + private static final int MULTIPOLYGON = 6; + private static final int GEOMETRYCOLLECTION = 7; + + private final byte[] data; + private int pos; + private ByteOrder order = ByteOrder.LITTLE_ENDIAN; + + private WkbReader(byte[] data, int offset) { + this.data = data; + this.pos = offset; + } + + private String readGeometry() { + if (pos >= data.length) { + throw new IllegalArgumentException("truncated wkb"); + } + int endian = data[pos++] & 0xff; + if (endian == 1) { + order = ByteOrder.LITTLE_ENDIAN; + } else if (endian == 0) { + order = ByteOrder.BIG_ENDIAN; + } else { + throw new IllegalArgumentException("invalid byte order"); + } + int type = readInt(); + if ((type & 0xE0000000) != 0) { + // PostGIS EWKB flags — not MySQL WKB. Do not guess the layout. + throw new IllegalArgumentException("ewkb"); + } + return switch (type) { + case POINT -> "POINT" + readPointBody(); + case LINESTRING -> "LINESTRING" + readLineStringBody(); + case POLYGON -> "POLYGON" + readPolygonBody(); + case MULTIPOINT -> "MULTIPOINT" + readMulti(POINT); + case MULTILINESTRING -> "MULTILINESTRING" + readMulti(LINESTRING); + case MULTIPOLYGON -> "MULTIPOLYGON" + readMulti(POLYGON); + case GEOMETRYCOLLECTION -> "GEOMETRYCOLLECTION" + readCollection(); + default -> throw new IllegalArgumentException("unsupported wkb type " + type); + }; + } + + private String readPointBody() { + return "(" + formatCoord(readDouble()) + " " + formatCoord(readDouble()) + ")"; + } + + private String readLineStringBody() { + int count = readInt(); + if (count < 0) { + throw new IllegalArgumentException("negative point count"); + } + if (count == 0) { + return " EMPTY"; + } + StringBuilder builder = new StringBuilder("("); + for (int i = 0; i < count; i++) { + if (i > 0) { + builder.append(", "); + } + builder.append(formatCoord(readDouble())).append(' ').append(formatCoord(readDouble())); + } + return builder.append(')').toString(); + } + + private String readPolygonBody() { + int rings = readInt(); + if (rings < 0) { + throw new IllegalArgumentException("negative ring count"); + } + if (rings == 0) { + return " EMPTY"; + } + StringBuilder builder = new StringBuilder("("); + for (int i = 0; i < rings; i++) { + if (i > 0) { + builder.append(", "); + } + String ring = readLineStringBody(); + if (" EMPTY".equals(ring)) { + throw new IllegalArgumentException("empty ring"); + } + builder.append(ring); + } + return builder.append(')').toString(); + } + + private String readMulti(int expectedType) { + int count = readInt(); + if (count < 0) { + throw new IllegalArgumentException("negative collection count"); + } + if (count == 0) { + return " EMPTY"; + } + StringBuilder builder = new StringBuilder("("); + for (int i = 0; i < count; i++) { + if (i > 0) { + builder.append(", "); + } + String member = readGeometry(); + builder.append(memberBody(member, expectedType)); + } + return builder.append(')').toString(); + } + + private String readCollection() { + int count = readInt(); + if (count < 0) { + throw new IllegalArgumentException("negative collection count"); + } + if (count == 0) { + return " EMPTY"; + } + StringBuilder builder = new StringBuilder("("); + for (int i = 0; i < count; i++) { + if (i > 0) { + builder.append(", "); + } + builder.append(readGeometry()); + } + return builder.append(')').toString(); + } + + private String memberBody(String wkt, int expectedType) { + String prefix = switch (expectedType) { + case POINT -> "POINT"; + case LINESTRING -> "LINESTRING"; + case POLYGON -> "POLYGON"; + default -> throw new IllegalArgumentException("unexpected member type"); + }; + if (!wkt.regionMatches(true, 0, prefix, 0, prefix.length())) { + throw new IllegalArgumentException("mixed collection member"); + } + return wkt.substring(prefix.length()); + } + + private int readInt() { + if (pos + 4 > data.length) { + throw new IllegalArgumentException("truncated int"); + } + int value = ByteBuffer.wrap(data, pos, 4).order(order).getInt(); + pos += 4; + return value; + } + + private double readDouble() { + if (pos + 8 > data.length) { + throw new IllegalArgumentException("truncated double"); + } + double value = ByteBuffer.wrap(data, pos, 8).order(order).getDouble(); + pos += 8; + if (!Double.isFinite(value)) { + throw new IllegalArgumentException("non-finite coordinate"); + } + return value; + } + + private static String formatCoord(double value) { + return BigDecimal.valueOf(value).stripTrailingZeros().toPlainString(); + } + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLActuator.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLActuator.java index d0190053..74f85d6d 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLActuator.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLActuator.java @@ -17,16 +17,22 @@ public interface SQLActuator { List getObjects(String sql, Class clazz, Map fieldMapping) throws SQLException; + List> queryRows(String sql, List parameters) throws SQLException; + int count(SQL sql) throws SQLException; int count(SQLExecutePlan plan) throws SQLException; SQLQueryResult execute(SQLExecutePlan plan) throws SQLException; + SQLQueryResult executeRaw(SQLExecutePlan plan) throws SQLException; + SQLQueryResult execute(SQL sql) throws SQLException; SQLQueryResult executeWithAudit(SQLExecutePlan plan) throws SQLException; + SQLQueryResult executeRawWithAudit(SQLExecutePlan plan) throws SQLException; + SQLQueryResult executeWithAudit(SQL sql) throws SQLException; SQLActuator withConnection(Connection connection); diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLExecutePlan.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLExecutePlan.java index 89aafeb6..23c292bc 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLExecutePlan.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLExecutePlan.java @@ -26,7 +26,8 @@ public class SQLExecutePlan { private SQLActuator sqlActuator; private String targetSQL; private final DbType druidDbType; - private Statement statement; + private volatile Statement statement; + private volatile boolean cancelled; private Connection connection; private ACLResult aclResult; @@ -85,7 +86,10 @@ public SQLQueryResult executeWithAudit() throws SQLException { } - public Statement createStatement() throws SQLException { + public synchronized Statement createStatement() throws SQLException { + if (this.cancelled) { + throw new SQLException(MessageUtils.get("ExecutionCanceled")); + } if (this.statement == null || this.statement.isClosed()) { this.statement = this.connection.createStatement(); } @@ -97,7 +101,14 @@ public SQLStatement getTargetSQLStatement() { } public void cancel() throws SQLException { - this.statement.cancel(); + Statement currentStatement; + synchronized (this) { + this.cancelled = true; + currentStatement = this.statement; + } + if (currentStatement != null && !currentStatement.isClosed()) { + currentStatement.cancel(); + } } public void close() { diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLQueryParams.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLQueryParams.java index 2b7a199c..b065b1e2 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLQueryParams.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLQueryParams.java @@ -14,4 +14,5 @@ public class SQLQueryParams { private int limit; private int timeout = -1; + private String filter; } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLQueryResult.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLQueryResult.java index 01770713..3f7afed9 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLQueryResult.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLQueryResult.java @@ -8,6 +8,7 @@ import java.sql.Time; import java.util.ArrayList; import java.util.List; +import java.util.StringJoiner; @Data @@ -15,10 +16,14 @@ public class SQLQueryResult { private String sql; private int total = -1; private boolean paged; + private boolean truncated; + private int rowLimit; private int updateCount; private boolean hasResultSet = true; private List fields = new ArrayList<>(); private List> data = new ArrayList<>(); + private List results = new ArrayList<>(); + private transient List unmaskedPrimaryKeyValues; private Time startTime; private Time endTime; @@ -48,6 +53,14 @@ public SQLQueryResult(String sql) { } public String getOutput() { + if (!this.results.isEmpty()) { + StringJoiner output = new StringJoiner(System.lineSeparator()); + for (int index = 0; index < this.results.size(); index++) { + output.add("Result " + (index + 1) + ":"); + output.add(this.results.get(index).getOutput()); + } + return output.toString(); + } if (!this.hasResultSet) { return String.format("Query OK, %d rows affected", this.updateCount); } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SqlValidationResult.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SqlValidationResult.java new file mode 100644 index 00000000..6e3da174 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SqlValidationResult.java @@ -0,0 +1,33 @@ +package org.jumpserver.chen.framework.datasource.sql; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public record SqlValidationResult( + boolean parseable, + int statementCount, + String statementType, + List tables, + List columns, + List errors +) { + public SqlValidationResult { + tables = tables == null ? List.of() : List.copyOf(tables); + columns = columns == null ? List.of() : List.copyOf(columns); + errors = errors == null ? List.of() : List.copyOf(errors); + statementType = statementType == null ? "UNKNOWN" : statementType; + } + + public Map toAnalysisMap() { + Map result = new LinkedHashMap<>(); + result.put("valid", parseable); + result.put("parseable", parseable); + result.put("statementCount", statementCount); + result.put("statementType", statementType); + result.put("tables", tables); + result.put("columns", columns); + result.put("errors", errors); + return result; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SqlValidator.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SqlValidator.java new file mode 100644 index 00000000..fe47c163 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SqlValidator.java @@ -0,0 +1,111 @@ +package org.jumpserver.chen.framework.datasource.sql; + +import com.alibaba.druid.DbType; +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.visitor.SchemaStatVisitor; +import com.alibaba.druid.stat.TableStat; +import org.apache.commons.lang3.StringUtils; +import org.jumpserver.chen.framework.i18n.MessageUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +public final class SqlValidator { + public static final String QUERY_UNSUPPORTED_MESSAGE = + "This SQL is not supported by Query because Chen cannot parse it with Druid. Use Console to draft and run it."; + + public static String queryUnsupportedMessage() { + return MessageUtils.getOrDefault("QueryUnsupported", QUERY_UNSUPPORTED_MESSAGE); + } + + private static final int MAX_OBJECTS = 50; + private static final int MAX_ANALYSIS_COLUMNS = 512; + + private SqlValidator() { + } + + public static List parse(DbType dbType, String sql) { + return SQLUtils.parseStatements(sql, dbType); + } + + public static SqlValidationResult validate(DbType dbType, String sql) { + if (StringUtils.isBlank(sql)) { + return unparseable("SQL statement is empty"); + } + + List statements; + try { + statements = parse(dbType, sql); + if (statements == null || statements.isEmpty()) { + return unparseable("SQL statement is empty"); + } + } catch (RuntimeException e) { + return unparseable(safeError(e)); + } + + LinkedHashSet tables = new LinkedHashSet<>(); + LinkedHashSet columns = new LinkedHashSet<>(); + String statementType = statements.size() == 1 ? statementType(statements.get(0)) : "MULTI"; + for (SQLStatement statement : statements) { + try { + SchemaStatVisitor visitor = SQLUtils.createSchemaStatVisitor(dbType); + statement.accept(visitor); + for (TableStat.Name table : visitor.getTables().keySet()) { + addBounded(tables, table.toString(), MAX_OBJECTS); + } + Collection statementColumns = visitor.getColumns(); + for (TableStat.Column column : statementColumns) { + addBounded(columns, column.toString(), MAX_ANALYSIS_COLUMNS); + } + } catch (RuntimeException ignored) { + // Some vendor-specific statements are syntactically valid but do not support schema statistics. + } + } + return new SqlValidationResult( + true, + statements.size(), + statementType, + List.copyOf(tables), + List.copyOf(columns), + List.of() + ); + } + + private static SqlValidationResult unparseable(String error) { + return new SqlValidationResult( + false, + 0, + "UNKNOWN", + List.of(), + List.of(), + List.of(error) + ); + } + + private static String statementType(SQLStatement statement) { + String name = statement.getClass().getSimpleName().toUpperCase(Locale.ROOT); + if (name.startsWith("SQL")) { + name = name.substring(3); + } + if (name.endsWith("STATEMENT")) { + name = name.substring(0, name.length() - "STATEMENT".length()); + } + return name; + } + + private static String safeError(RuntimeException error) { + String message = StringUtils.defaultIfBlank(error.getMessage(), error.getClass().getSimpleName()); + return message.length() > 4096 ? message.substring(0, 4096) : message; + } + + private static void addBounded(Set values, String value, int max) { + if (values.size() < max && StringUtils.isNotBlank(value)) { + values.add(value); + } + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/i18n/MessageUtils.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/i18n/MessageUtils.java index 212c240f..16026d6a 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/i18n/MessageUtils.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/i18n/MessageUtils.java @@ -25,4 +25,18 @@ public static String get(String msgKey, Object... args) { return msgKey; } } + + public static String getOrDefault(String msgKey, String defaultMessage, Object... args) { + try { + var locale = SessionManager.getCurrentSession().getLocale(); + var text = messageSource.getMessage(msgKey, null, locale); + return String.format(text, args); + } catch (Exception e) { + try { + return String.format(defaultMessage, args); + } catch (Exception ignored) { + return defaultMessage; + } + } + } } \ No newline at end of file diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/ACLFilter.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/ACLFilter.java index baa5ae23..10919cc6 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/ACLFilter.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/ACLFilter.java @@ -1,9 +1,17 @@ package org.jumpserver.chen.framework.jms; +import org.jumpserver.chen.framework.jms.acl.ACLCommandContext; import org.jumpserver.chen.framework.jms.acl.ACLResult; import java.sql.Connection; public interface ACLFilter { - ACLResult commandACLFilter(String command, Connection connection); + default ACLResult commandACLFilter(String command, Connection connection) { + ACLCommandContext context = connection == null + ? ACLCommandContext.executionOwned(null) + : ACLCommandContext.queryConsoleOwned(connection); + return this.commandACLFilterWithContext(command, context); + } + + ACLResult commandACLFilterWithContext(String command, ACLCommandContext context); } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/acl/ACLCommandContext.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/acl/ACLCommandContext.java new file mode 100644 index 00000000..ab2b6ac0 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/acl/ACLCommandContext.java @@ -0,0 +1,70 @@ +package org.jumpserver.chen.framework.jms.acl; + +import org.jumpserver.chen.framework.datasource.edit.ConnectionOwnership; + +import java.sql.Connection; +import java.util.Objects; +import java.util.OptionalInt; + +public record ACLCommandContext( + Connection connection, + ConnectionOwnership connectionOwnership, + OptionalInt affectedRows, + String reviewBatchSql +) { + public ACLCommandContext { + Objects.requireNonNull(connectionOwnership, "connectionOwnership"); + Objects.requireNonNull(affectedRows, "affectedRows"); + if (affectedRows.isPresent() && affectedRows.getAsInt() < 0) { + throw new IllegalArgumentException("affectedRows must not be negative"); + } + } + + public ACLCommandContext( + Connection connection, + ConnectionOwnership connectionOwnership, + OptionalInt affectedRows + ) { + this(connection, connectionOwnership, affectedRows, null); + } + + public static ACLCommandContext executionOwned(Connection connection) { + return new ACLCommandContext( + connection, + ConnectionOwnership.EXECUTION_CONTEXT, + OptionalInt.empty(), + null + ); + } + + public static ACLCommandContext queryConsoleOwned(Connection connection) { + return new ACLCommandContext( + Objects.requireNonNull(connection, "connection"), + ConnectionOwnership.QUERY_CONSOLE, + OptionalInt.empty(), + null + ); + } + + public static ACLCommandContext planned( + Connection connection, + ConnectionOwnership connectionOwnership, + int affectedRows + ) { + return planned(connection, connectionOwnership, affectedRows, null); + } + + public static ACLCommandContext planned( + Connection connection, + ConnectionOwnership connectionOwnership, + int affectedRows, + String reviewBatchSql + ) { + return new ACLCommandContext( + connection, + connectionOwnership, + OptionalInt.of(affectedRows), + reviewBatchSql + ); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/acl/ACLResult.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/acl/ACLResult.java index f765be19..7c0b18df 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/acl/ACLResult.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/acl/ACLResult.java @@ -13,4 +13,5 @@ public class ACLResult { private boolean Notify = false; + private String message; } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/entity/CommandRecord.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/entity/CommandRecord.java index 2e82a673..a10187c5 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/entity/CommandRecord.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/entity/CommandRecord.java @@ -2,6 +2,8 @@ import lombok.Data; import org.jumpserver.chen.framework.datasource.sql.SQLQueryResult; +import org.jumpserver.chen.framework.i18n.MessageUtils; +import org.jumpserver.chen.framework.jms.acl.ACLResult; import org.jumpserver.wisp.Common; @Data @@ -18,20 +20,37 @@ public CommandRecord(String input) { this.input = input; } + public void applyAcl(ACLResult aclResult) { + if (aclResult == null) { + return; + } + if (aclResult.getCmdAclId() != null) { + this.setCmdAclId(aclResult.getCmdAclId()); + } + if (aclResult.getCmdGroupId() != null) { + this.setCmdGroupId(aclResult.getCmdGroupId()); + } + if (aclResult.getRiskLevel() != null) { + this.setRiskLevel(aclResult.getRiskLevel()); + } + } + public void setError(String errorMessage) { this.error = true; - this.output = String.format("Error: %s", errorMessage); + this.output = String.format(MessageUtils.getOrDefault("ErrorPrefix", "Error: %s"), errorMessage); } public void setOutput(SQLQueryResult result) { if (this.error) { - this.output = String.format("Error: %s", result.getOutput()); + this.output = String.format(MessageUtils.getOrDefault("ErrorPrefix", "Error: %s"), result.getOutput()); return; } if (!result.isHasResultSet()) { this.output = result.getOutput(); } else { - this.output = String.format("Query OK, %d rows discovered ", result.getData().size()); + this.output = String.format( + MessageUtils.getOrDefault("QueryOkRows", "Query OK, %d rows discovered"), + result.getData().size()); } } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/impl/ACLFilterImpl.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/impl/ACLFilterImpl.java index 2403effe..ac47af58 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/impl/ACLFilterImpl.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/impl/ACLFilterImpl.java @@ -1,22 +1,26 @@ package org.jumpserver.chen.framework.jms.impl; import lombok.extern.slf4j.Slf4j; +import org.jumpserver.chen.framework.datasource.edit.ConnectionOwnership; import org.jumpserver.chen.framework.datasource.sql.SQL; import org.jumpserver.chen.framework.i18n.MessageUtils; import org.jumpserver.chen.framework.jms.ACLFilter; +import org.jumpserver.chen.framework.jms.acl.ACLCommandContext; import org.jumpserver.chen.framework.jms.acl.ACLResult; import org.jumpserver.chen.framework.session.SessionManager; +import org.jumpserver.chen.framework.session.controller.DialogHandle; import org.jumpserver.chen.framework.session.controller.dialog.Button; import org.jumpserver.chen.framework.session.controller.dialog.Dialog; import org.jumpserver.wisp.Common; import org.jumpserver.wisp.ServiceGrpc; import org.jumpserver.wisp.ServiceOuterClass; -import java.sql.Connection; import java.util.List; +import java.util.OptionalInt; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Pattern; @@ -24,6 +28,8 @@ @Slf4j public class ACLFilterImpl implements ACLFilter { + private static final long REVIEW_DECISION_TIMEOUT_SECONDS = 300; + private static final long REVIEW_RPC_TIMEOUT_SECONDS = 30; private final ServiceGrpc.ServiceBlockingStub serviceBlockingStub; private final List commandACLs; @@ -39,7 +45,7 @@ public ACLFilterImpl(Common.Session session, ServiceGrpc.ServiceBlockingStub ser private static final String REJECT_MESSAGE = "reject by acl rule"; @Override - public ACLResult commandACLFilter(String command, Connection connection) { + public ACLResult commandACLFilterWithContext(String command, ACLCommandContext context) { var result = new ACLResult(); var acl = this.matchRule(command, result); @@ -61,46 +67,90 @@ public ACLResult commandACLFilter(String command, Connection connection) { } case Reject -> { result.setRiskLevel(Common.RiskLevel.Reject); + result.setMessage(MessageUtils.get("ACLRejectError")); } case Review -> { - var countDownLatch = new CountDownLatch(1); + var decisionLatch = new CountDownLatch(1); + var resultLatch = new CountDownLatch(1); AtomicReference exception = new AtomicReference<>(null); + AtomicBoolean submitted = new AtomicBoolean(false); + AtomicReference dialogHandle = new AtomicReference<>(DialogHandle.NOOP); + var controller = SessionManager.getCurrentSession().getController(); + String dialogOwner = controller.getDialogOwner(); var dialog = new Dialog(MessageUtils.get("CommandReview")); dialog.setBody(MessageUtils.get("CommandReviewMessage")); dialog.addButton(new Button(MessageUtils.get("Submit"), "submit", () -> { - + if (!submitted.compareAndSet(false, true)) { + return; + } + dialogHandle.get().close(); var token = SessionManager.getContextToken(); new Thread(() -> { SessionManager.setContext(token); + controller.bindDialogOwner(dialogOwner); try { - this.createAndWaitTicket(command, acl, connection); + if (controller.isDialogOwnerCancelled(dialogOwner)) { + throw new RuntimeException(MessageUtils.get("UserCancelCommandReviewError")); + } + this.createAndWaitTicket(command, acl, context); } catch (Exception e) { exception.set(e); } finally { - countDownLatch.countDown(); + resultLatch.countDown(); + controller.clearDialogOwner(); } }).start(); + decisionLatch.countDown(); })); dialog.addButton(new Button(MessageUtils.get("Cancel"), "cancel", () -> { - exception.set(new RuntimeException(MessageUtils.get("UserCancelCommandReviewError"))); - countDownLatch.countDown(); + exception.compareAndSet( + null, + new RuntimeException(MessageUtils.get("UserCancelCommandReviewError")) + ); + decisionLatch.countDown(); + resultLatch.countDown(); })); - SessionManager.getCurrentSession().getController().showDialog(dialog); + dialogHandle.set(controller.showDialog(dialog, () -> { + exception.compareAndSet( + null, + new RuntimeException(MessageUtils.get("UserCancelCommandReviewError")) + ); + decisionLatch.countDown(); + resultLatch.countDown(); + })); try { - countDownLatch.await(); - if (exception.get() != null) { + if (!decisionLatch.await(REVIEW_DECISION_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + exception.compareAndSet( + null, + new RuntimeException(MessageUtils.get("CommandReviewTimeoutError")) + ); + dialogHandle.get().cancel(); + } + if (submitted.get() && exception.get() == null && + !resultLatch.await(WAIT_TICKET_TIMEOUT, TimeUnit.MILLISECONDS)) { + exception.compareAndSet( + null, + new RuntimeException(MessageUtils.get("CommandReviewTimeoutError")) + ); + controller.cancelCurrentDialog(dialogOwner); + } + if (!submitted.get() || exception.get() != null) { result.setRiskLevel(Common.RiskLevel.ReviewReject); + result.setMessage(reviewRejectMessage(exception.get())); } else { result.setRiskLevel(Common.RiskLevel.ReviewAccept); } } catch (InterruptedException e) { - throw new RuntimeException(e); + Thread.currentThread().interrupt(); + controller.cancelCurrentDialog(dialogOwner); + result.setRiskLevel(Common.RiskLevel.ReviewReject); + result.setMessage(MessageUtils.get("UserCancelCommandReviewError")); } finally { - SessionManager.getCurrentSession().getController().closeDialog(); + dialogHandle.get().close(); } } } @@ -109,27 +159,24 @@ public ACLResult commandACLFilter(String command, Connection connection) { return result; } - - private void createAndWaitTicket(String command, Common.CommandACL commandACL, Connection connection) { - var affectRows = 0; - try { - var sqlActuator = SessionManager.getCurrentSession() - .getDatasource() - .getConnectionManager() - .getSqlActuator(); - if (connection != null) { - sqlActuator = sqlActuator.withConnection(connection); - } - affectRows = sqlActuator.getAffectedRows(SQL.of(command)); - } catch (Exception e) { - log.error("get affected rows failed", e); + private String reviewRejectMessage(Exception exception) { + if (exception != null && exception.getMessage() != null && !exception.getMessage().isBlank()) { + return exception.getMessage(); } + return MessageUtils.get("UserCancelCommandReviewError"); + } - var input = command; - if (affectRows != -1) { - input = String.format("Affected rows: %d\n%s", affectRows, command); - } + private void createAndWaitTicket(String command, Common.CommandACL commandACL, ACLCommandContext context) { + OptionalInt affectedRows = this.estimateAffectedRows(command, context); + var input = reviewTicketCommand(command, context); + String affectedRowsValue = affectedRows.isPresent() + ? Integer.toString(affectedRows.getAsInt()) + : MessageUtils.getOrDefault("Unknown", "unknown"); + input = String.format("%s: %s\n%s", + MessageUtils.getOrDefault("AffectedRows", "Affected rows"), + affectedRowsValue, + input); var req = ServiceOuterClass.CommandConfirmRequest .newBuilder() @@ -137,35 +184,71 @@ private void createAndWaitTicket(String command, Common.CommandACL commandACL, C .setSessionId(this.session.getId()) .setCmdAclId(commandACL.getId()) .build(); - var resp = this.serviceBlockingStub.createCommandTicket(req); + var resp = this.serviceBlockingStub + .withDeadlineAfter(REVIEW_RPC_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .createCommandTicket(req); if (!resp.getStatus().getOk()) { - throw new RuntimeException("create command ticket failed: " + resp.getStatus().getErr()); + throw new RuntimeException(MessageUtils.getOrDefault( + "CreateCommandTicketFailed", + "Failed to create command ticket: %s", + resp.getStatus().getErr())); + } + this.waitForTicketStatusChange(resp.getInfo()); + } + + OptionalInt estimateAffectedRows(String command, ACLCommandContext context) { + if (context.affectedRows().isPresent()) { + return context.affectedRows(); + } + if (context.connectionOwnership() == ConnectionOwnership.QUERY_CONSOLE) { + return OptionalInt.empty(); + } + + try { + var sqlActuator = SessionManager.getCurrentSession() + .getDatasource() + .getConnectionManager() + .getSqlActuator(); + if (context.connection() != null) { + sqlActuator = sqlActuator.withConnection(context.connection()); + } + int affectedRows = sqlActuator.getAffectedRows(SQL.of(command)); + return affectedRows < 0 ? OptionalInt.empty() : OptionalInt.of(affectedRows); + } catch (Exception e) { + log.warn("get affected rows failed: {}", e.getClass().getSimpleName()); + return OptionalInt.empty(); + } + } + + private String reviewTicketCommand(String command, ACLCommandContext context) { + String batchSql = context.reviewBatchSql(); + if (batchSql == null || batchSql.isBlank() || batchSql.equals(command)) { + return command; } - this.waitForTicketStatusChange(command, resp.getInfo()); + return String.format( + MessageUtils.getOrDefault( + "BatchSqlReview", + "Batch SQL:\n%s\n\nReview-triggering SQL:\n%s" + ), + batchSql, + command + ); } - private void openCommandReviewEvent(Runnable cancel, String command, long startTIme, long endTime) { + private DialogHandle openCommandReviewEvent(Runnable cancel) { var dialog = new Dialog(MessageUtils.get("CommandReview")); dialog.setBody(MessageUtils.get("WaitCommandReviewMessage")); dialog.addButton(new Button(MessageUtils.get("Cancel"), "cancel", () -> { cancel.run(); - SessionManager.getCurrentSession().getController().closeDialog(); })); - SessionManager.getCurrentSession().getController().showDialog(dialog); - } - - private void closeCommandReviewEvent() { - SessionManager.getCurrentSession().getController().closeDialog(); + return SessionManager.getCurrentSession().getController().showDialog(dialog, cancel); } private static final long WAIT_TICKET_TIMEOUT = 30 * 60 * 1000; private static final long WAIT_TICKET_INTERVAL = 5 * 1000; - private void waitForTicketStatusChange(String cmd, ServiceOuterClass.TicketInfo ticketInfo) { - - long startTime = System.currentTimeMillis(); - long endTime = startTime + WAIT_TICKET_TIMEOUT; + private void waitForTicketStatusChange(ServiceOuterClass.TicketInfo ticketInfo) { CountDownLatch cdl = new CountDownLatch(1); Timer timer = new Timer(); @@ -173,12 +256,11 @@ private void waitForTicketStatusChange(String cmd, ServiceOuterClass.TicketInfo final AtomicBoolean ticketClosed = new AtomicBoolean(false); AtomicReference exception = new AtomicReference<>(null); - this.openCommandReviewEvent(() -> { + DialogHandle dialogHandle = this.openCommandReviewEvent(() -> { exception.set(new RuntimeException(MessageUtils.get("UserCancelCommandReviewError"))); cdl.countDown(); timer.cancel(); - - }, cmd, startTime, endTime); + }); try { var stub = this.serviceBlockingStub; @@ -189,49 +271,63 @@ private void waitForTicketStatusChange(String cmd, ServiceOuterClass.TicketInfo public void run() { SessionManager.setContext(token); - if (System.currentTimeMillis() > endTime) { - exception.set(new RuntimeException(MessageUtils.get("CommandReviewTimeoutError"))); - timer.cancel(); - cdl.countDown(); - } - var checkRequest = ServiceOuterClass .TicketRequest.newBuilder() .setReq(ticketInfo.getCheckReq()) .build(); - var checkResponse = stub.checkTicketState(checkRequest); - - if (!checkResponse.getStatus().getOk()) { - throw new RuntimeException("Failed to check ticket status: " + checkResponse.getStatus().getErr()); - } + try { + var checkResponse = stub + .withDeadlineAfter(REVIEW_RPC_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .checkTicketState(checkRequest); - switch (checkResponse.getData().getState()) { - case Approved -> { - ticketClosed.set(true); - timer.cancel(); - cdl.countDown(); + if (!checkResponse.getStatus().getOk()) { + throw new RuntimeException( + "Failed to check ticket status: " + checkResponse.getStatus().getErr() + ); } - case Rejected, Closed -> { - ticketClosed.set(true); - exception.set(new RuntimeException(MessageUtils.get("CommandReviewRejectBy", checkResponse.getData().getProcessor()))); - timer.cancel(); - cdl.countDown(); + + switch (checkResponse.getData().getState()) { + case Approved -> { + ticketClosed.set(true); + timer.cancel(); + cdl.countDown(); + } + case Rejected, Closed -> { + ticketClosed.set(true); + exception.set(new RuntimeException(MessageUtils.get( + "CommandReviewRejectBy", + checkResponse.getData().getProcessor() + ))); + timer.cancel(); + cdl.countDown(); + } } + } catch (RuntimeException e) { + exception.compareAndSet(null, e); + timer.cancel(); + cdl.countDown(); } } }, 0, WAIT_TICKET_INTERVAL); try { - cdl.await(); + if (!cdl.await(WAIT_TICKET_TIMEOUT, TimeUnit.MILLISECONDS)) { + exception.compareAndSet( + null, + new RuntimeException(MessageUtils.get("CommandReviewTimeoutError")) + ); + } if (exception.get() != null) { throw exception.get(); } } catch (InterruptedException e) { - log.error("wait for ticket status change failed: {}", e.getMessage()); + Thread.currentThread().interrupt(); + throw new RuntimeException(e); } } finally { - this.closeCommandReviewEvent(); + timer.cancel(); + dialogHandle.close(); if (!ticketClosed.get()) { this.closeTicket(ticketInfo); } @@ -242,7 +338,9 @@ private void closeTicket(ServiceOuterClass.TicketInfo ticketInfo) { var cancelRequest = ServiceOuterClass.TicketRequest.newBuilder() .setReq(ticketInfo.getCancelReq()) .build(); - var cancelResponse = this.serviceBlockingStub.cancelTicket(cancelRequest); + var cancelResponse = this.serviceBlockingStub + .withDeadlineAfter(REVIEW_RPC_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .cancelTicket(cancelRequest); if (!cancelResponse.getStatus().getOk()) { log.error("close ticket failed: {}", cancelResponse.getStatus().getErr()); } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/impl/CommandHandlerImpl.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/impl/CommandHandlerImpl.java index 92e84e9d..6f21d672 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/impl/CommandHandlerImpl.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/impl/CommandHandlerImpl.java @@ -6,10 +6,12 @@ import org.jumpserver.wisp.Common; import org.jumpserver.wisp.ServiceGrpc; import org.jumpserver.wisp.ServiceOuterClass; -import org.springframework.scheduling.annotation.Async; + +import java.util.concurrent.TimeUnit; @Slf4j public class CommandHandlerImpl implements CommandHandler { + private static final long COMMAND_UPLOAD_TIMEOUT_SECONDS = 30; private final Common.Session session; private final ServiceGrpc.ServiceBlockingStub serviceBlockingStub; @@ -20,7 +22,6 @@ public CommandHandlerImpl(Common.Session session, ServiceGrpc.ServiceBlockingStu } @Override - @Async public void recordCommand(CommandRecord commandRecord) { var reqBuilder = ServiceOuterClass.CommandRequest @@ -33,14 +34,29 @@ public void recordCommand(CommandRecord commandRecord) { .setTimestamp(System.currentTimeMillis() / 1000) .setInput(commandRecord.getInput()) .setOutput(commandRecord.getOutput()) - .setRiskLevel(commandRecord.getRiskLevel()); + .setRiskLevel(commandRecord.getRiskLevel() != null + ? commandRecord.getRiskLevel() + : Common.RiskLevel.Normal); - if (commandRecord.getCmdAclId() != null && commandRecord.getCmdGroupId() != null) { + if (commandRecord.getCmdAclId() != null && !commandRecord.getCmdAclId().isBlank()) { reqBuilder.setCmdAclId(commandRecord.getCmdAclId()); + } + if (commandRecord.getCmdGroupId() != null && !commandRecord.getCmdGroupId().isBlank()) { reqBuilder.setCmdGroupId(commandRecord.getCmdGroupId()); } - var resp = this.serviceBlockingStub.uploadCommand(reqBuilder.build()); + log.info( + "upload command sessionId={} command={} riskLevel={} cmdAclId={} cmdGroupId={}", + this.session.getId(), + commandRecord.getInput(), + commandRecord.getRiskLevel(), + commandRecord.getCmdAclId(), + commandRecord.getCmdGroupId() + ); + + var resp = this.serviceBlockingStub + .withDeadlineAfter(COMMAND_UPLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .uploadCommand(reqBuilder.build()); if (!resp.getStatus().getOk()) { throw new RuntimeException("upload command failed: " + resp.getStatus().getErr()); } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/MetadataQueryAuditFunction.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/MetadataQueryAuditFunction.java new file mode 100644 index 00000000..ca5fadcf --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/MetadataQueryAuditFunction.java @@ -0,0 +1,10 @@ +package org.jumpserver.chen.framework.session; + +import java.sql.SQLException; +import java.util.List; +import java.util.Map; + +@FunctionalInterface +public interface MetadataQueryAuditFunction { + List> run() throws SQLException; +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/Session.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/Session.java index b0ba5e86..9f4036ac 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/Session.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/Session.java @@ -3,6 +3,7 @@ import org.jumpserver.chen.framework.console.Console; import org.jumpserver.chen.framework.datasource.Datasource; import org.jumpserver.chen.framework.datasource.sql.SQLQueryResult; +import org.jumpserver.chen.framework.jms.acl.ACLCommandContext; import org.jumpserver.chen.framework.jms.acl.ACLResult; import org.jumpserver.chen.framework.jms.entity.CommandRecord; import org.jumpserver.chen.framework.jms.exception.CommandRejectException; @@ -79,6 +80,8 @@ public interface Session { boolean isActive(); + boolean isClosing(); + void close(); void close(String message, Object... args); @@ -86,6 +89,16 @@ public interface Session { // 在有审计的情况下执行命令 SQLQueryResult withAudit(String command, QueryAuditFunction queryAuditFunction) throws SQLException, CommandRejectException; + default SQLQueryResult withAudit(String command, ACLResult aclResult, QueryAuditFunction queryAuditFunction) + throws SQLException, CommandRejectException { + return withAudit(command, queryAuditFunction); + } + + List> withMetadataQueryAudit( + String command, + MetadataQueryAuditFunction queryAuditFunction + ) throws SQLException; + void recordCommand(String command); void recordCommand(CommandRecord commandRecord); @@ -94,6 +107,10 @@ public interface Session { ACLResult checkACL(String command, Connection connection); + default ACLResult checkACLWithContext(String command, ACLCommandContext context) { + return this.checkACL(command, context.connection()); + } + boolean enableAutoComplete(); void setEnableAutoComplete(boolean enableAutoComplete); diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/SessionManager.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/SessionManager.java index 659ad02b..ad2bea06 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/SessionManager.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/SessionManager.java @@ -1,10 +1,12 @@ package org.jumpserver.chen.framework.session; import lombok.extern.slf4j.Slf4j; +import org.jumpserver.chen.framework.console.Console; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; @Slf4j @@ -31,6 +33,20 @@ public static void unregisterSession(String token) { log.info("session {} unregistered, current session count {}", token, instance.getCurrentSessionCount()); } + public static boolean registerConsole(String token, String consoleId, Console console) { + var registered = new AtomicBoolean(false); + instance.store.computeIfPresent(token, (ignored, session) -> { + synchronized (session) { + if (!session.isClosing()) { + session.getConsoles().put(consoleId, console); + registered.set(true); + } + } + return session; + }); + return registered.get(); + } + public int getCurrentSessionCount() { return instance.store.size(); } @@ -48,7 +64,8 @@ public static SessionManager getInstance() { } public static Session getCurrentSession() { - return instance.store.get(token.get()); + String currentToken = token.get(); + return currentToken == null ? null : instance.store.get(currentToken); } public static Map getStore() { diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/Controller.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/Controller.java index 5c7ce464..5e97c0ca 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/Controller.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/Controller.java @@ -7,7 +7,37 @@ public interface Controller { void showDialog(Dialog dialog); + default DialogHandle showDialog(Dialog dialog, Runnable onCancel) { + this.showDialog(dialog); + return DialogHandle.NOOP; + } + void closeDialog(); + + default void bindDialogOwner(String owner) { + } + + default String getDialogOwner() { + return null; + } + + default void clearDialogOwner() { + } + + default void cancelDialogs(String owner) { + } + + default void cancelCurrentDialog(String owner) { + this.cancelDialogs(owner); + } + + default boolean isDialogOwnerCancelled(String owner) { + return false; + } + + default void cancelAllDialogs() { + } + void showMessage(MessageLevel level, String message); void handlePacket(Packet packet); diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/DialogHandle.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/DialogHandle.java new file mode 100644 index 00000000..f40c1486 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/DialogHandle.java @@ -0,0 +1,18 @@ +package org.jumpserver.chen.framework.session.controller; + +public interface DialogHandle extends AutoCloseable { + DialogHandle NOOP = new DialogHandle() { + @Override + public void cancel() { + } + + @Override + public void close() { + } + }; + + void cancel(); + + @Override + void close(); +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/dialog/Dialog.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/dialog/Dialog.java index 44e5d884..c0642c59 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/dialog/Dialog.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/dialog/Dialog.java @@ -10,6 +10,7 @@ @Data public class Dialog { + private String id; private String title = "提示"; private String width = "30%"; private String body; diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/impl/BaseController.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/impl/BaseController.java index 629f0f8d..8317de6e 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/impl/BaseController.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/impl/BaseController.java @@ -1,18 +1,31 @@ package org.jumpserver.chen.framework.session.controller.impl; +import com.google.gson.Gson; import lombok.extern.slf4j.Slf4j; import org.jumpserver.chen.framework.session.SessionManager; import org.jumpserver.chen.framework.session.controller.Controller; +import org.jumpserver.chen.framework.session.controller.DialogHandle; import org.jumpserver.chen.framework.session.controller.dialog.Dialog; import org.jumpserver.chen.framework.session.controller.message.Message; import org.jumpserver.chen.framework.session.controller.message.MessageLevel; import org.jumpserver.chen.framework.ws.io.Packet; import org.jumpserver.chen.framework.ws.io.PacketIO; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + @Slf4j public class BaseController implements Controller { + private static final Gson GSON = new Gson(); + private static final String SESSION_DIALOG_OWNER = "session"; + private final PacketIO packetIO; - private Dialog currentDialog; + private final Object dialogLock = new Object(); + private final ThreadLocal dialogOwner = new ThreadLocal<>(); + private final Set cancelledOwners = new HashSet<>(); + private ActiveDialog currentDialog; + private boolean allDialogsCancelled; public BaseController(PacketIO packetIO) { this.packetIO = packetIO; @@ -20,14 +33,123 @@ public BaseController(PacketIO packetIO) { @Override public void showDialog(Dialog dialog) { - this.currentDialog = dialog; - this.packetIO.sendPacket("show_dialog", dialog); + this.showDialog(dialog, () -> { + }); + } + + @Override + public DialogHandle showDialog(Dialog dialog, Runnable onCancel) { + String id = UUID.randomUUID().toString(); + dialog.setId(id); + ActiveDialog activeDialog = new ActiveDialog( + id, + this.dialogOwner.get() == null ? SESSION_DIALOG_OWNER : this.dialogOwner.get(), + dialog, + onCancel + ); + ActiveDialog replaced; + boolean ownerCancelled; + synchronized (this.dialogLock) { + ownerCancelled = this.allDialogsCancelled || this.cancelledOwners.contains(activeDialog.owner()); + if (ownerCancelled) { + replaced = null; + } else { + replaced = this.currentDialog; + this.currentDialog = activeDialog; + this.packetIO.sendPacket("show_dialog", dialog); + } + } + if (ownerCancelled) { + runCancel(activeDialog); + return DialogHandle.NOOP; + } + if (replaced != null) { + log.warn("replace unfinished dialog, oldDialogId={}, newDialogId={}", replaced.id(), id); + runCancel(replaced); + } + return new DialogHandle() { + @Override + public void cancel() { + BaseController.this.finishDialog(id, true); + } + + @Override + public void close() { + BaseController.this.finishDialog(id, false); + } + }; } @Override public void closeDialog() { - this.currentDialog = null; - this.packetIO.sendPacket("close_dialog", null); + ActiveDialog activeDialog; + synchronized (this.dialogLock) { + activeDialog = this.currentDialog; + if (activeDialog == null) { + this.packetIO.sendPacket("close_dialog", null); + return; + } + } + this.finishDialog(activeDialog.id(), true); + } + + @Override + public void bindDialogOwner(String owner) { + this.dialogOwner.set(owner); + } + + @Override + public String getDialogOwner() { + return this.dialogOwner.get() == null ? SESSION_DIALOG_OWNER : this.dialogOwner.get(); + } + + @Override + public void clearDialogOwner() { + this.dialogOwner.remove(); + } + + @Override + public void cancelDialogs(String owner) { + synchronized (this.dialogLock) { + this.cancelledOwners.add(owner); + } + this.cancelCurrentDialog(owner); + } + + @Override + public void cancelCurrentDialog(String owner) { + ActiveDialog activeDialog; + synchronized (this.dialogLock) { + activeDialog = this.currentDialog; + if (activeDialog == null || !activeDialog.owner().equals(owner)) { + return; + } + this.currentDialog = null; + this.packetIO.sendPacket("close_dialog", null); + } + runCancel(activeDialog); + } + + @Override + public boolean isDialogOwnerCancelled(String owner) { + synchronized (this.dialogLock) { + return this.allDialogsCancelled || this.cancelledOwners.contains(owner); + } + } + + @Override + public void cancelAllDialogs() { + ActiveDialog activeDialog; + synchronized (this.dialogLock) { + this.allDialogsCancelled = true; + activeDialog = this.currentDialog; + if (activeDialog == null) { + return; + } + this.currentDialog = null; + this.packetIO.sendPacket("close_dialog", null); + } + runCancel(activeDialog); } @Override @@ -38,9 +160,17 @@ public void showMessage(MessageLevel level, String message) { @Override public void handlePacket(Packet packet) { switch (packet.getType()) { - case "dialog_event": - this.onDialogEvent((String) packet.getData()); + case "dialog_event": { + try { + var event = GSON.fromJson(GSON.toJson(packet.getData()), DialogEvent.class); + if (event != null) { + this.onDialogEvent(event.dialogId(), event.event()); + } + } catch (RuntimeException e) { + log.warn("ignore invalid dialog event"); + } break; + } case "input_active": this.refreshLastActiveTime(); break; @@ -60,19 +190,51 @@ public void sendFile(String fileKey) { this.packetIO.sendPacket("download", fileKey); } - private void onDialogEvent(String event) { - if (this.currentDialog == null) { - this.closeDialog(); - return; + private void onDialogEvent(String dialogId, String event) { + synchronized (this.dialogLock) { + ActiveDialog activeDialog = this.currentDialog; + if (activeDialog == null || !activeDialog.id().equals(dialogId)) { + log.warn("ignore stale dialog event, dialogId={}, event={}", dialogId, event); + return; + } + try { + var method = activeDialog.dialog().getEvent(event); + if (method != null) { + method.run(); + } + } catch (Exception e) { + this.showMessage(MessageLevel.ERROR, "handle event %s error:".formatted(event) + e.getMessage()); + } } - try { - var method = this.currentDialog.getEvent(event); - if (method != null) { - method.run(); + } + + private void finishDialog(String id, boolean cancel) { + ActiveDialog activeDialog; + synchronized (this.dialogLock) { + if (this.currentDialog == null || !this.currentDialog.id().equals(id)) { + return; } - } catch (Exception e) { - this.showMessage(MessageLevel.ERROR, "handle event %s error:".formatted(event) + e.getMessage()); + activeDialog = this.currentDialog; + this.currentDialog = null; + this.packetIO.sendPacket("close_dialog", null); + } + if (cancel) { + runCancel(activeDialog); + } + } + + private static void runCancel(ActiveDialog activeDialog) { + try { + activeDialog.onCancel().run(); + } catch (RuntimeException e) { + log.warn("cancel dialog failed, dialogId={}", activeDialog.id(), e); } } + private record ActiveDialog(String id, String owner, Dialog dialog, Runnable onCancel) { + } + + private record DialogEvent(String dialogId, String event) { + } + } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/impl/BaseSession.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/impl/BaseSession.java index 1dd8b52d..0c9adc17 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/impl/BaseSession.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/impl/BaseSession.java @@ -9,6 +9,7 @@ import org.jumpserver.chen.framework.jms.entity.CommandRecord; import org.jumpserver.chen.framework.jms.exception.CommandRejectException; import org.jumpserver.chen.framework.session.QueryAuditFunction; +import org.jumpserver.chen.framework.session.MetadataQueryAuditFunction; import org.jumpserver.chen.framework.session.Session; import org.jumpserver.chen.framework.session.SessionManager; import org.jumpserver.chen.framework.jms.acl.ACLResult; @@ -23,10 +24,12 @@ import java.sql.Connection; import java.sql.SQLException; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; @Slf4j public class BaseSession implements Session { @@ -44,6 +47,7 @@ public class BaseSession implements Session { @Getter Map consoles = new ConcurrentHashMap<>(); + private final AtomicBoolean closeStarted = new AtomicBoolean(false); @Getter @Setter @@ -140,6 +144,14 @@ public SQLQueryResult withAudit(String command, QueryAuditFunction queryAuditFun return queryAuditFunction.run(); } + @Override + public List> withMetadataQueryAudit( + String command, + MetadataQueryAuditFunction queryAuditFunction + ) throws SQLException { + return queryAuditFunction.run(); + } + @Override public void recordCommand(String command) { CommandRecord commandRecord = new CommandRecord(command); @@ -199,8 +211,30 @@ public boolean isActive() { return this.getPacketIO() != null && this.getPacketIO().getWsSession().isOpen(); } + @Override + public boolean isClosing() { + return this.closeStarted.get(); + } + @Override public void close() { + if (!this.beginClose()) { + return; + } + this.closeSessionResources(); + } + + protected final boolean beginClose() { + synchronized (this) { + return this.closeStarted.compareAndSet(false, true); + } + } + + protected final void closeSessionResources() { + if (this.getController() != null) { + this.getController().cancelAllDialogs(); + } + this.closeConsoles(); SessionManager.unregisterSession(this.getWebToken()); this.getDatasource().close(); this.getPacketIO().close(); @@ -210,6 +244,24 @@ public void close() { } } + protected final void closeConsoles() { + var detached = new ArrayList>(); + while (!this.consoles.isEmpty()) { + for (var entry : this.consoles.entrySet()) { + if (this.consoles.remove(entry.getKey(), entry.getValue())) { + detached.add(Map.entry(entry.getKey(), entry.getValue())); + } + } + } + for (var entry : detached) { + try { + entry.getValue().close(); + } catch (RuntimeException e) { + log.warn("close console failed, consoleId={}", entry.getKey(), e); + } + } + } + @Override public void close(String message, Object... args) { this.close(); diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/impl/JMSSession.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/impl/JMSSession.java index 5f377b18..d106c0af 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/impl/JMSSession.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/impl/JMSSession.java @@ -9,6 +9,7 @@ import org.jumpserver.chen.framework.jms.ACLFilter; import org.jumpserver.chen.framework.jms.CommandHandler; import org.jumpserver.chen.framework.jms.ReplayHandler; +import org.jumpserver.chen.framework.jms.acl.ACLCommandContext; import org.jumpserver.chen.framework.jms.acl.ACLResult; import org.jumpserver.chen.framework.jms.entity.CommandRecord; import org.jumpserver.chen.framework.jms.exception.CommandRejectException; @@ -16,6 +17,7 @@ import org.jumpserver.chen.framework.jms.impl.CommandHandlerImpl; import org.jumpserver.chen.framework.jms.impl.ReplayHandlerImpl; import org.jumpserver.chen.framework.session.QueryAuditFunction; +import org.jumpserver.chen.framework.session.MetadataQueryAuditFunction; import org.jumpserver.chen.framework.session.SessionManager; import org.jumpserver.chen.framework.session.controller.dialog.Button; import org.jumpserver.chen.framework.session.controller.dialog.Dialog; @@ -33,11 +35,15 @@ import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; @Slf4j public class JMSSession extends BaseSession { + private static final int METADATA_AUDIT_ERROR_MAX_LENGTH = 1024; + @Getter private final Common.Session jmsSession; @@ -52,7 +58,6 @@ public class JMSSession extends BaseSession { private final long maxIdleTimeDelta; private final long expireTime; - private LocalDateTime maxSessionEndTime; private int maxSessionEndHours; private LocalDateTime dynamicEndTime; @@ -106,7 +111,6 @@ public JMSSession(Common.Session session, this.commandACLs = tokenResp.getData().getFilterRulesList(); this.expireTime = tokenResp.getData().getExpireInfo().getExpireAt(); this.maxIdleTimeDelta = tokenResp.getData().getSetting().getMaxIdleTime(); - this.maxSessionEndHours = tokenResp.getData().getSetting().getMaxSessionTime(); this.maxSessionEndTime = LocalDateTime.now().plusHours(tokenResp.getData().getSetting().getMaxSessionTime()); this.dynamicEndTime = this.maxSessionEndTime; @@ -150,11 +154,16 @@ public void recordCommand(String command) { @Override public ACLResult checkACL(String command) { - return this.aclFilter.commandACLFilter(command, null); + return this.aclFilter.commandACLFilterWithContext(command, ACLCommandContext.executionOwned(null)); } public ACLResult checkACL(String command, Connection connection) { - return this.aclFilter.commandACLFilter(command, connection); + return this.aclFilter.commandACLFilterWithContext(command, ACLCommandContext.queryConsoleOwned(connection)); + } + + @Override + public ACLResult checkACLWithContext(String command, ACLCommandContext context) { + return this.aclFilter.commandACLFilterWithContext(command, context); } @Override @@ -261,6 +270,17 @@ private void startWaitIdleTime() { @Override public void close() { + if (!this.beginClose()) { + return; + } + this.closeJmsSessionResources(); + } + + private void closeJmsSessionResources() { + if (this.getController() != null) { + this.getController().cancelAllDialogs(); + } + this.closeConsoles(); try { this.replayHandler.release(); this.finishedJmsSession(); @@ -270,23 +290,27 @@ public void close() { } } finally { - super.close(); + super.closeSessionResources(); } } public void close(String message, String reason, Object... args) { + if (!this.beginClose()) { + return; + } SessionManager.setContext(this.getWebToken()); + try { + this.getPacketIO().sendPacket("session_close", sessionClosePacketData(reason, args)); - this.getPacketIO().sendPacket("session_close", null); - - var dialog = new Dialog(MessageUtils.get("SessionFinished")); - dialog.setBody(MessageUtils.get(message, args)); - this.getController().showDialog(dialog); - - this.recordLifecycle(ServiceOuterClass.SessionLifecycleLogRequest.EventType.AssetConnectFinished, reason); - this.closed = true; + var dialog = new Dialog(MessageUtils.get("SessionFinished")); + dialog.setBody(MessageUtils.get(message, args)); + this.getController().showDialog(dialog); - this.close(); + this.recordLifecycle(ServiceOuterClass.SessionLifecycleLogRequest.EventType.AssetConnectFinished, reason); + this.closed = true; + } finally { + this.closeJmsSessionResources(); + } } private void finishedJmsSession() { @@ -320,6 +344,12 @@ private void closeGateway() { @Override public SQLQueryResult withAudit(String command, QueryAuditFunction queryAuditFunction) throws SQLException, CommandRejectException { + return withAudit(command, null, queryAuditFunction); + } + + @Override + public SQLQueryResult withAudit(String command, ACLResult aclResult, QueryAuditFunction queryAuditFunction) + throws SQLException, CommandRejectException { synchronized (this) { this.refreshLastActiveTime(); } @@ -328,26 +358,120 @@ public SQLQueryResult withAudit(String command, QueryAuditFunction queryAuditFun } CommandRecord commandRecord = new CommandRecord(command); + commandRecord.applyAcl(aclResult); + Throwable primaryFailure = null; try { this.replayHandler.writeInput(commandRecord.getInput()); var result = queryAuditFunction.run(); commandRecord.setOutput(result); - - commandRecord.setCmdAclId(result.getAclResult().getCmdAclId()); - commandRecord.setCmdGroupId(result.getAclResult().getCmdGroupId()); - commandRecord.setRiskLevel(result.getAclResult().getRiskLevel()); + commandRecord.applyAcl(result.getAclResult()); this.replayHandler.writeOutput(result.getOutput()); return result; - } catch (SQLException e) { + } catch (SQLException | RuntimeException e) { + primaryFailure = e; commandRecord.setError(e.getMessage()); - this.replayHandler.writeOutput(e.getMessage()); + this.writeReplayFailure(e.getMessage(), e); throw e; } finally { - this.commandHandler.recordCommand(commandRecord); + try { + this.commandHandler.recordCommand(commandRecord); + } catch (RuntimeException auditFailure) { + if (primaryFailure != null) { + primaryFailure.addSuppressed(auditFailure); + } else { + throw auditFailure; + } + } + } + } + + @Override + public List> withMetadataQueryAudit( + String command, + MetadataQueryAuditFunction queryAuditFunction + ) throws SQLException { + CommandRecord commandRecord = new CommandRecord(command); + Throwable primaryFailure = null; + + try { + this.replayHandler.writeInput(commandRecord.getInput()); + + var rows = queryAuditFunction.run(); + var output = String.format("Metadata query OK, %d rows discovered", rows.size()); + commandRecord.setOutput(output); + this.replayHandler.writeOutput(output); + return rows; + } catch (SQLException | RuntimeException e) { + primaryFailure = e; + var output = metadataAuditErrorSummary(e); + commandRecord.setError(output); + this.writeReplayFailure(output, e); + throw e; + } finally { + try { + this.commandHandler.recordCommand(commandRecord); + } catch (RuntimeException auditFailure) { + if (primaryFailure != null) { + primaryFailure.addSuppressed(auditFailure); + } else { + throw auditFailure; + } + } + } + } + + private static String metadataAuditErrorSummary(Throwable failure) { + SQLException sqlFailure = null; + for (var cause = failure; cause != null; cause = cause.getCause()) { + if (cause instanceof SQLException sqlException) { + sqlFailure = sqlException; + } + } + var message = failure.getMessage(); + if (sqlFailure != null && sqlFailure.getMessage() != null) { + message = sqlFailure.getMessage(); + } + message = message == null || message.isBlank() ? "Metadata query failed" : message; + message = message.replaceAll("[\\r\\n\\t]+", " ").trim(); + var summary = sqlFailure == null + ? message + : String.format( + "SQLState=%s, vendorCode=%d, message=%s", + sqlFailure.getSQLState() == null ? "" : sqlFailure.getSQLState(), + sqlFailure.getErrorCode(), + message + ); + return summary.length() <= METADATA_AUDIT_ERROR_MAX_LENGTH + ? summary + : summary.substring(0, METADATA_AUDIT_ERROR_MAX_LENGTH); + } + + private void writeReplayFailure(String output, Throwable primaryFailure) { + try { + this.replayHandler.writeOutput(output); + } catch (RuntimeException replayFailure) { + primaryFailure.addSuppressed(replayFailure); + } + } + + static final String ADMIN_TERMINATE_REASON = "admin_terminate"; + + static Map sessionClosePacketData(String reason, Object... args) { + if (!ADMIN_TERMINATE_REASON.equals(reason)) { + return null; + } + Map data = new LinkedHashMap<>(); + data.put("reason", ADMIN_TERMINATE_REASON); + if (args != null && args.length > 0 && args[0] != null) { + String terminatedBy = String.valueOf(args[0]).trim(); + if (!terminatedBy.isEmpty()) { + data.put("terminatedBy", terminatedBy); + } } + return data; } } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/utils/CodeUtils.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/utils/CodeUtils.java index f462447d..945efe32 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/utils/CodeUtils.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/utils/CodeUtils.java @@ -9,10 +9,8 @@ public class CodeUtils { * @return */ public static String escapeCsvValue(String value) { - if (value.contains("\"") || value.contains(",") || value.contains("\n")) { - // 对引号进行转义 + if (value.contains("\"") || value.contains(",") || value.contains("\n") || value.contains("\r")) { value = value.replace("\"", "\"\""); - // 用引号包围值 value = "\"" + value + "\""; } return value; diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/utils/PageUtils.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/utils/PageUtils.java index b6cb9631..cdb415d1 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/utils/PageUtils.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/utils/PageUtils.java @@ -15,6 +15,7 @@ import com.alibaba.druid.sql.dialect.oracle.visitor.OracleASTVisitorAdapter; import com.alibaba.druid.sql.dialect.postgresql.ast.stmt.PGSelectQueryBlock; import com.alibaba.druid.sql.dialect.sqlserver.ast.SQLServerSelectQueryBlock; +import com.alibaba.druid.sql.visitor.SQLASTVisitor; import com.alibaba.druid.util.JdbcUtils; import java.util.Iterator; @@ -39,6 +40,36 @@ public static String count(String sql, DbType dbType) { } } + public static String filter(String sql, DbType dbType, String conditionSql) { + if (conditionSql == null || conditionSql.isBlank()) { + return sql; + } + + SQLStatement sourceStatement = SQLUtils.parseSingleStatement(sql, dbType); + if (!(sourceStatement instanceof SQLSelectStatement sourceSelect) || + !(sourceSelect.getSelect().getQuery() instanceof SQLSelectQueryBlock sourceQuery)) { + throw new IllegalArgumentException("Data view filter requires a simple SELECT query"); + } + + List filterStatements = SQLUtils.parseStatements( + "SELECT * FROM CHEN_FILTER_SOURCE WHERE " + conditionSql, + dbType + ); + if (filterStatements.size() != 1 || !(filterStatements.get(0) instanceof SQLSelectStatement filterSelect) || + !(filterSelect.getSelect().getQuery() instanceof SQLSelectQueryBlock filterQuery) || + filterQuery.getWhere() == null || filterQuery.getGroupBy() != null || + filterQuery.getOrderBy() != null || filterQuery.getLimit() != null) { + throw new IllegalArgumentException("Invalid data view WHERE condition"); + } + + SQLExpr condition = filterQuery.getWhere().clone(); + SQLExpr current = sourceQuery.getWhere(); + sourceQuery.setWhere(current == null + ? condition + : new SQLBinaryOpExpr(current, SQLBinaryOperator.BooleanAnd, condition, dbType)); + return SQLUtils.toSQLString(sourceStatement, dbType); + } + public static String limit(String sql, DbType dbType, int offset, int count) { List stmtList = SQLUtils.parseStatements(sql, dbType); if (stmtList.size() != 1) { @@ -258,13 +289,12 @@ private static boolean limitSQLServer(SQLSelect select, DbType dbType, int offse SQLBinaryOpExpr pageCondition = new SQLBinaryOpExpr(gt, SQLBinaryOperator.BooleanAnd, lteq, DbType.sqlserver); SQLServerSelectQueryBlock queryBlock; SQLAggregateExpr aggregateExpr; - SQLOrderBy orderBy; SQLServerSelectQueryBlock countQueryBlock; if (query instanceof SQLSelectQueryBlock) { queryBlock = (SQLServerSelectQueryBlock) query; if (offset <= 0) { SQLTop top = queryBlock.getTop(); - if (check && top != null && !top.isPercent() && top.getExpr() instanceof SQLNumericLiteralExpr) { + if (top != null && !top.isPercent() && top.getExpr() instanceof SQLNumericLiteralExpr) { int rowCount = ((SQLNumericLiteralExpr) top.getExpr()).getNumber().intValue(); if (rowCount <= count) { return false; @@ -273,23 +303,8 @@ private static boolean limitSQLServer(SQLSelect select, DbType dbType, int offse queryBlock.setTop(new SQLTop(new SQLNumberExpr(count))); return true; } else { - // 创建 SELECT NULL 的子查询 - SQLSelectQueryBlock selectQueryBlock = new SQLSelectQueryBlock(); - selectQueryBlock.addSelectItem(new SQLSelectItem(new SQLNullExpr())); - - SQLSelect selectNull = new SQLSelect(); - selectNull.setQuery(selectQueryBlock); - - SQLQueryExpr selectNullExpr = new SQLQueryExpr(selectNull); - - // 使用 SELECT NULL 的子查询作为 ORDER BY 的一部分 - SQLSelectOrderByItem orderByItem = new SQLSelectOrderByItem(selectNullExpr); - SQLOrderBy orderByNull = new SQLOrderBy(); - orderByNull.addItem(orderByItem); - aggregateExpr = new SQLAggregateExpr("ROW_NUMBER"); - aggregateExpr.setOver(new SQLOver(orderByNull)); - + aggregateExpr.setOver(new SQLOver(sqlServerRowNumberOrderBy(select, queryBlock))); queryBlock.getSelectList().add(new SQLSelectItem(aggregateExpr, "ROWNUM")); countQueryBlock = new SQLServerSelectQueryBlock(); @@ -306,22 +321,8 @@ private static boolean limitSQLServer(SQLSelect select, DbType dbType, int offse select.setQuery(queryBlock); return true; } else { - // 重复上述逻辑,因为需要处理非 SQLSelectQueryBlock 的情况 - SQLSelectQueryBlock selectQueryBlockForNonBlock = new SQLSelectQueryBlock(); - selectQueryBlockForNonBlock.addSelectItem(new SQLSelectItem(new SQLNullExpr())); - - SQLSelect selectNullForNonBlock = new SQLSelect(); - selectNullForNonBlock.setQuery(selectQueryBlockForNonBlock); - - SQLQueryExpr selectNullExprForNonBlock = new SQLQueryExpr(selectNullForNonBlock); - - SQLSelectOrderByItem orderByItemForNonBlock = new SQLSelectOrderByItem(selectNullExprForNonBlock); - SQLOrderBy orderByNullForNonBlock = new SQLOrderBy(); - orderByNullForNonBlock.addItem(orderByItemForNonBlock); - aggregateExpr = new SQLAggregateExpr("ROW_NUMBER"); - aggregateExpr.setOver(new SQLOver(orderByNullForNonBlock)); - + aggregateExpr.setOver(new SQLOver(sqlServerRowNumberOrderBy(select, null))); queryBlock.getSelectList().add(new SQLSelectItem(aggregateExpr, "ROWNUM")); countQueryBlock = new SQLServerSelectQueryBlock(); @@ -334,6 +335,35 @@ private static boolean limitSQLServer(SQLSelect select, DbType dbType, int offse } } + private static SQLOrderBy sqlServerRowNumberOrderBy(SQLSelect select, SQLSelectQueryBlock queryBlock) { + SQLOrderBy orderBy = select.getOrderBy(); + if (orderBy != null) { + select.setOrderBy(null); + } else if (queryBlock != null && queryBlock.getOrderBy() != null) { + orderBy = queryBlock.getOrderBy(); + queryBlock.setOrderBy(null); + } + if (orderBy == null || orderBy.getItems().isEmpty()) { + return sqlServerDummyOrderBy(); + } + return orderBy; + } + + private static SQLOrderBy sqlServerDummyOrderBy() { + SQLSelectQueryBlock selectQueryBlock = new SQLSelectQueryBlock(); + selectQueryBlock.addSelectItem(new SQLSelectItem(new SQLNullExpr())); + + SQLSelect selectNull = new SQLSelect(); + selectNull.setQuery(selectQueryBlock); + + SQLQueryExpr selectNullExpr = new SQLQueryExpr(selectNull); + selectNullExpr.setParenthesized(true); + + SQLOrderBy orderByNull = new SQLOrderBy(); + orderByNull.addItem(new SQLSelectOrderByItem(selectNullExpr)); + return orderByNull; + } + private static boolean limitOracle(SQLSelect select, DbType dbType, int offset, int count, boolean check) { SQLSelectQuery query = select.getQuery(); @@ -450,6 +480,12 @@ private static String count(SQLSelect select, DbType dbType) { return createCountUseSubQuery(select, dbType); } + // PostgreSQL functions in the SELECT list may be set-returning functions. + // Keep the projection so COUNT observes the rows expanded by the function. + if (dbType == DbType.postgresql && containsMethodInvoke(selectList)) { + return createCountUseSubQuery(select, dbType); + } + // 情况 2: DISTINCT 情况下,Oracle 特别处理 if (distinctOption == SQLSetQuantifier.DISTINCT) { if (dbType == DbType.oracle && ( @@ -484,6 +520,18 @@ private static String count(SQLSelect select, DbType dbType) { } } + private static boolean containsMethodInvoke(List selectList) { + boolean[] found = {false}; + SQLASTVisitor visitor = SQLASTVisitor.ofMethodInvoke(expr -> found[0] = true); + for (SQLSelectItem item : selectList) { + item.getExpr().accept(visitor); + if (found[0]) { + return true; + } + } + return false; + } + private static String createCountUseSubQuery(SQLSelect select, DbType dbType) { SQLSelectQueryBlock countSelectQuery = createQueryBlock(dbType); SQLSelectItem countItem = createCountItem(dbType); @@ -606,6 +654,21 @@ public static int getLimit(String sql, DbType dbType) { return Integer.MAX_VALUE; } + if (query instanceof SQLServerSelectQueryBlock) { + SQLTop top = ((SQLServerSelectQueryBlock) query).getTop(); + if (top == null || top.isPercent()) { + return -1; + } + + rowCountExpr = top.getExpr(); + if (rowCountExpr instanceof SQLNumericLiteralExpr) { + rowCount = ((SQLNumericLiteralExpr) rowCountExpr).getNumber().intValue(); + return rowCount; + } + + return Integer.MAX_VALUE; + } + return -1; } } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/ws/ConsoleWebSocketHandler.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/ws/ConsoleWebSocketHandler.java index e03911f1..609b7df3 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/ws/ConsoleWebSocketHandler.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/ws/ConsoleWebSocketHandler.java @@ -6,23 +6,26 @@ import org.jumpserver.chen.framework.console.Console; import org.jumpserver.chen.framework.console.DataViewConsole; import org.jumpserver.chen.framework.console.QueryConsole; +import org.jumpserver.chen.framework.console.action.QueryConsoleAction; +import org.jumpserver.chen.framework.console.context.ConsoleContext; +import org.jumpserver.chen.framework.console.context.ConsoleContextResolutionException; +import org.jumpserver.chen.framework.console.context.ConsoleContextResolver; import org.jumpserver.chen.framework.console.entity.request.Connect; -import org.jumpserver.chen.framework.datasource.ResourceBrowser; -import org.jumpserver.chen.framework.datasource.entity.resource.TreeNode; import org.jumpserver.chen.framework.session.SessionManager; import org.jumpserver.chen.framework.session.controller.message.Message; import org.jumpserver.chen.framework.session.controller.message.MessageLevel; import org.jumpserver.chen.framework.ws.io.Packet; import org.jumpserver.chen.framework.ws.io.PacketIO; -import org.jumpserver.chen.framework.utils.TreeUtils; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.WebSocketMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.adapter.NativeWebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; -import java.sql.SQLException; -import java.util.Set; +import java.util.ArrayDeque; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -30,11 +33,6 @@ public class ConsoleWebSocketHandler extends TextWebSocketHandler { private static final Gson GSON = new Gson(); - private static final Set QUERY_NODE_TYPES = - Set.of("datasource", "database", "schema", "table"); - private static final Set DATA_VIEW_NODE_TYPES = Set.of("table", "view"); - - @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { if (session instanceof NativeWebSocketSession ns) { @@ -47,115 +45,219 @@ public void afterConnectionEstablished(WebSocketSession session) throws Exceptio private final ExecutorService executorService = Executors.newFixedThreadPool(10); + private final Map sessionExecutors = new ConcurrentHashMap<>(); @Override public void handleMessage(WebSocketSession session, WebSocketMessage message) throws Exception { + var token = (String) session.getAttributes().get("token"); + var packet = GSON.fromJson(message.getPayload().toString(), Packet.class); - executorService.submit(() -> { - try { - var token = (String) session.getAttributes().get("token"); - SessionManager.setContext(token); - - var packet = GSON.fromJson(message.getPayload().toString(), Packet.class); - if (StringUtils.equals(packet.getType(), Packet.TYPE_CONNECT)) { - onConnectPacket(session, packet); - } else { - var console = SessionManager - .getCurrentSession() - .getConsoles() - .get(session.getId()); - - if (console != null) { - var db = TreeUtils.getValue(console.getNodeKey(), "database"); - SessionManager.getCurrentSession().getDatasource().getConnectionManager().setDatabaseContext(db); - - var handler = SessionManager.getCurrentSession().getConsoles().get(session.getId()); - handler.handle(packet); - } - } - } catch (Exception e) { - log.error("handle message error", e); + if (StringUtils.equals(packet.getType(), "ping")) { + new PacketIO(session).sendPacket("pong", null); + return; + } + + if (this.isQueryConsoleCancel(token, session.getId(), packet)) { + this.cancelQueryConsole(token, session.getId()); + return; + } + + this.sessionExecutors + .computeIfAbsent(session.getId(), ignored -> new SerialExecutor(this.executorService)) + .execute(() -> this.processMessage(session, token, packet)); + } + + private void processMessage(WebSocketSession session, String token, Packet packet) { + try { + SessionManager.setContext(token); + var currentSession = SessionManager.getCurrentSession(); + if (currentSession == null) { + return; + } + if (currentSession.getController() != null) { + currentSession.getController().bindDialogOwner(session.getId()); + } + if (StringUtils.equals(packet.getType(), Packet.TYPE_CONNECT)) { + onConnectPacket(session, packet); + return; + } + + var console = currentSession.getConsoles().get(session.getId()); + if (console != null) { + this.setDatabaseContext(console); + console.handle(packet); } - }); + } catch (Exception e) { + log.error("handle message error", e); + var serialExecutor = this.sessionExecutors.remove(session.getId()); + if (serialExecutor != null) { + serialExecutor.shutdown(); + } + this.closeConsole(token, session.getId()); + this.closeWebSocket(session); + } finally { + var currentSession = SessionManager.getSession(token); + if (currentSession != null && currentSession.getController() != null) { + currentSession.getController().clearDialogOwner(); + } + } + } + + private boolean isQueryConsoleCancel(String token, String sessionId, Packet packet) { + if (!StringUtils.equals(packet.getType(), Packet.TYPE_QUERY_CONSOLE_ACTION)) { + return false; + } + var action = GSON.fromJson(GSON.toJson(packet.getData()), QueryConsoleAction.class); + if (!StringUtils.equals(action.getAction(), QueryConsoleAction.ACTION_CANCEL)) { + return false; + } + var currentSession = SessionManager.getSession(token); + return currentSession != null && currentSession.getConsoles().get(sessionId) instanceof QueryConsole; + } + + private void cancelQueryConsole(String token, String sessionId) { + var currentSession = SessionManager.getSession(token); + if (currentSession == null) { + return; + } + var console = currentSession.getConsoles().get(sessionId); + if (console instanceof QueryConsole queryConsole) { + queryConsole.handleCancel(); + } } private void onConnectPacket(WebSocketSession session, Packet packet) { Connect connect = GSON.fromJson(GSON.toJson(packet.getData()), Connect.class); var webSess = SessionManager.getCurrentSession(); - var node = this.resolveNode( - webSess.getDatasource().getResourceBrowser(), - connect.getNodeKey(), - connect.getType() - ); - if (node == null) { + ConsoleContext context; + try { + context = new ConsoleContextResolver(webSess.getDatasource().getResourceBrowser()) + .resolve(connect.getNodeKey(), connect.getType()); + } catch (ConsoleContextResolutionException e) { new PacketIO(session).sendPacket( "show_message", new Message(MessageLevel.ERROR, "Invalid console context") ); + this.closeSessionConsole(session); + this.closeWebSocket(session); return; } - connect.setNodeKey(node.getKey()); - Console console = switch (connect.getType()) { - case Connect.CONSOLE_TYPE_QUERY -> - new QueryConsole(webSess.getDatasource(), session, node.getKey()); - case Connect.CONSOLE_TYPE_DATA_VIEW -> - new DataViewConsole(webSess.getDatasource(), session, node.getKey()); - default -> null; - }; + connect.setNodeKey(context.nodeKey()); + Console console = this.createConsole(connect.getType(), webSess.getDatasource(), session, context); if (console != null) { - webSess.getConsoles().put(session.getId(), console); - - var db = TreeUtils.getValue(console.getNodeKey(), "database"); - SessionManager.getCurrentSession().getDatasource().getConnectionManager().setDatabaseContext(db); - + this.setDatabaseContext(console); + var token = (String) session.getAttributes().get("token"); + if (!SessionManager.registerConsole(token, session.getId(), console)) { + console.close(); + this.closeWebSocket(session); + return; + } console.onInit(connect); log.info("User {} open a console ", webSess.getUsername()); } } - TreeNode resolveNode(ResourceBrowser resourceBrowser, String nodeKey, String consoleType) { - if (resourceBrowser == null || StringUtils.isBlank(nodeKey) || StringUtils.isBlank(consoleType)) { - return null; - } - TreeNode node; - try { - var root = resourceBrowser.getTree(); - node = root == null ? null : TreeUtils.getNode(root, nodeKey); - } catch (SQLException e) { - return null; - } - if (node == null) { - return null; - } - var allowedTypes = switch (consoleType) { - case Connect.CONSOLE_TYPE_QUERY -> QUERY_NODE_TYPES; - case Connect.CONSOLE_TYPE_DATA_VIEW -> DATA_VIEW_NODE_TYPES; - default -> Set.of(); + protected Console createConsole(String type, org.jumpserver.chen.framework.datasource.Datasource datasource, + WebSocketSession session, ConsoleContext context) { + return switch (type) { + case Connect.CONSOLE_TYPE_QUERY -> new QueryConsole(datasource, session, context); + case Connect.CONSOLE_TYPE_CONSOLE -> new QueryConsole(datasource, session, context, true); + case Connect.CONSOLE_TYPE_DATA_VIEW -> new DataViewConsole(datasource, session, context); + default -> null; }; - return allowedTypes.contains(node.getType()) ? node : null; } + private void setDatabaseContext(Console console) { + var connectionManager = SessionManager.getCurrentSession().getDatasource().getConnectionManager(); + var db = console.getContext().database(); + if (StringUtils.isNotBlank(db)) { + connectionManager.setDatabaseContext(db); + } + } + + @Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { log.error("websocket error", exception); + this.closeSessionConsole(session); } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { + this.closeSessionConsole(session); + } + + private void closeSessionConsole(WebSocketSession session) { + var serialExecutor = this.sessionExecutors.remove(session.getId()); + if (serialExecutor != null) { + serialExecutor.shutdown(); + } var token = (String) session.getAttributes().get("token"); + this.closeConsole(token, session.getId()); + } + + private void closeConsole(String token, String sessionId) { SessionManager.setContext(token); - var sess = SessionManager.getCurrentSession(); - if (sess == null) { + var currentSession = SessionManager.getCurrentSession(); + if (currentSession == null) { return; } - Console console = SessionManager - .getCurrentSession() - .getConsoles() - .get(session.getId()); + if (currentSession.getController() != null) { + currentSession.getController().cancelDialogs(sessionId); + } + Console console = currentSession.getConsoles().remove(sessionId); if (console != null) { console.close(); } - SessionManager.getCurrentSession().getConsoles().remove(session.getId()); + } + + private void closeWebSocket(WebSocketSession session) { + try { + if (session.isOpen()) { + session.close(CloseStatus.SERVER_ERROR); + } + } catch (Exception e) { + log.warn("close websocket session failed, sessionId={}", session.getId(), e); + } + } + + private static final class SerialExecutor { + private final ExecutorService executor; + private final Queue tasks = new ArrayDeque<>(); + private Runnable active; + private boolean accepting = true; + + private SerialExecutor(ExecutorService executor) { + this.executor = executor; + } + + private synchronized void execute(Runnable task) { + if (!this.accepting) { + return; + } + this.tasks.offer(() -> { + try { + task.run(); + } finally { + this.scheduleNext(); + } + }); + if (this.active == null) { + this.scheduleNext(); + } + } + + private synchronized void scheduleNext() { + this.active = this.tasks.poll(); + if (this.active != null) { + this.executor.submit(this.active); + } + } + + private synchronized void shutdown() { + this.accepting = false; + this.tasks.clear(); + } } } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/ws/SessionWebSocketHandler.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/ws/SessionWebSocketHandler.java index 3b56263b..7bd21dc6 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/ws/SessionWebSocketHandler.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/ws/SessionWebSocketHandler.java @@ -9,6 +9,7 @@ import org.jumpserver.chen.framework.ws.io.Packet; import org.jumpserver.chen.framework.ws.io.PacketIO; import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.adapter.NativeWebSocketSession; @@ -18,6 +19,11 @@ public class SessionWebSocketHandler extends TextWebSocketHandler { private static final Gson GSON = new Gson(); + private final WebSocketHandler agentHandler; + + public SessionWebSocketHandler(WebSocketHandler agentHandler) { + this.agentHandler = agentHandler; + } @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { @@ -78,6 +84,11 @@ public void afterConnectionEstablished(WebSocketSession session) throws Exceptio sess.getPacketIO().sendPacket("set_ready", null); + try { + agentHandler.afterConnectionEstablished(session); + } catch (Exception e) { + log.warn("Initialize database session tools failed", e); + } } @Override @@ -96,10 +107,16 @@ public void handleMessage(WebSocketSession session, WebSocketMessage message) log.debug("receive ping packet from session {}", webSession.getUsername()); webSession.getPacketIO().sendPacket("pong", null); } + case "mcp.request", "mcp.cancel" -> agentHandler.handleMessage(session, message); default -> webSession.getController().handlePacket(packet); } } + @Override + public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { + agentHandler.handleTransportError(session, exception); + } + @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { var token = (String) session.getAttributes().get("token"); @@ -107,6 +124,11 @@ public void afterConnectionClosed(WebSocketSession session, CloseStatus closeSta if (!SessionManager.releasePrimaryWebSocket(token, session.getId())) { return; } + try { + agentHandler.afterConnectionClosed(session, closeStatus); + } catch (Exception e) { + log.debug("Close database session tools failed", e); + } log.info("Primary WebSocket connection closed: code={}", closeStatus.getCode()); SessionManager.setContext(token); var sess = SessionManager.getCurrentSession(); diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/ws/io/Packet.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/ws/io/Packet.java index d6ec9419..a5b77d0d 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/ws/io/Packet.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/ws/io/Packet.java @@ -15,11 +15,10 @@ public class Packet { public static final String TYPE_DATA_VIEW_ACTION = "data_view_action"; public static final String TYPE_UPDATE_DATA_VIEW = "update_data_view"; public static final String TYPE_NEW_DATA_VIEW = "new_data_view"; + public static final String TYPE_EXECUTION_PLAN = "execution_plan"; private String type; private Object data; } - - diff --git a/backend/framework/src/test/java/org/jumpserver/chen/framework/console/dataview/export/DataExportTest.java b/backend/framework/src/test/java/org/jumpserver/chen/framework/console/dataview/export/DataExportTest.java new file mode 100644 index 00000000..d8a3a9a0 --- /dev/null +++ b/backend/framework/src/test/java/org/jumpserver/chen/framework/console/dataview/export/DataExportTest.java @@ -0,0 +1,156 @@ +package org.jumpserver.chen.framework.console.dataview.export; + +import org.jumpserver.chen.framework.console.dataview.DataView; +import org.jumpserver.chen.framework.console.dataview.DataViewData; +import org.jumpserver.chen.framework.datasource.entity.resource.Field; +import org.jumpserver.chen.framework.datasource.sql.SQLQueryResult; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.ZipFile; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class DataExportTest { + @TempDir + Path tempDir; + + @Test + void excelCurrentPageExportKeepsRefreshAndPagingUsable() throws Exception { + verifyCurrentPageExport(new DataExportExcel(), "xlsx"); + } + + @Test + void csvCurrentPageExportKeepsRefreshAndPagingUsable() throws Exception { + verifyCurrentPageExport(new DataExportCSV(), "csv"); + } + + @Test + void allDataSnapshotExportsRemainUnchanged() throws Exception { + DataViewData data = dataViewData(); + List fields = data.getFields(); + List> rows = data.getData(); + + Path excel = tempDir.resolve("all.xlsx"); + Path csv = tempDir.resolve("all.csv"); + new DataExportExcel().exportData(excel.toString(), data); + new DataExportCSV().exportData(csv.toString(), data); + + assertSame(fields, data.getFields()); + assertSame(rows, data.getData()); + assertEquals(List.of("ROWNUM", "ID", "NAME"), fieldNames(data)); + assertEquals(3, data.getData().get(0).size()); + assertExportContent(excel, csv); + } + + private void verifyCurrentPageExport(DataExportInterface exporter, String extension) throws Exception { + List requests = new ArrayList<>(); + DataView view = new DataView("test", null, null); + view.setLoadDataInterface(params -> { + requests.add(new Request(params.getOffset(), params.getLimit())); + return queryResult(); + }); + view.loadData(); + + exportAndAssertSourceUnchanged(exporter, view.getData(), "refresh." + extension); + view.refresh(); + + exportAndAssertSourceUnchanged(exporter, view.getData(), "next-page." + extension); + view.nextPage(); + + exportAndAssertSourceUnchanged(exporter, view.getData(), "change-limit." + extension); + view.changeLimit(100); + + assertEquals(List.of( + new Request(0, 50), + new Request(0, 50), + new Request(50, 50), + new Request(0, 100) + ), requests); + assertEquals(List.of("ROWNUM", "ID", "NAME"), fieldNames(view.getData())); + } + + private void exportAndAssertSourceUnchanged(DataExportInterface exporter, DataViewData data, String filename) + throws Exception { + List fields = data.getFields(); + List> rows = data.getData(); + + exporter.exportData(tempDir.resolve(filename).toString(), data); + + assertSame(fields, data.getFields()); + assertSame(rows, data.getData()); + assertEquals(List.of("ROWNUM", "ID", "NAME"), fieldNames(data)); + assertEquals(3, data.getData().get(0).size()); + } + + private SQLQueryResult queryResult() { + DataViewData data = dataViewData(); + SQLQueryResult result = new SQLQueryResult("SELECT ROWNUM, ID, NAME FROM users"); + result.setTotal(120); + result.setFields(data.getFields()); + result.setData(List.of(List.of(1, 7, "Alice"))); + return result; + } + + private DataViewData dataViewData() { + DataViewData data = new DataViewData(); + data.setFields(new ArrayList<>(List.of(field("ROWNUM"), field("ID"), field("NAME")))); + Map row = new LinkedHashMap<>(); + row.put("ROWNUM", 1); + row.put("ID", 7); + row.put("NAME", "Alice"); + data.setData(new ArrayList<>(List.of(row))); + return data; + } + + private Field field(String name) { + Field field = new Field(); + field.setName(name); + return field; + } + + private List fieldNames(DataViewData data) { + return data.getFields().stream().map(Field::getName).toList(); + } + + private void assertExportContent(Path excel, Path csv) throws IOException { + String workbookXml = readWorkbookXml(excel); + assertTrue(workbookXml.contains("ID")); + assertTrue(workbookXml.contains("NAME")); + assertTrue(workbookXml.contains("Alice")); + assertFalse(workbookXml.contains("ROWNUM")); + + String csvContent = Files.readString(csv); + assertEquals("ID,NAME," + System.lineSeparator() + + "7,Alice," + System.lineSeparator() + + System.lineSeparator(), csvContent); + } + + private String readWorkbookXml(Path workbook) throws IOException { + StringBuilder xml = new StringBuilder(); + try (ZipFile zip = new ZipFile(workbook.toFile())) { + var entries = zip.entries(); + while (entries.hasMoreElements()) { + var entry = entries.nextElement(); + try (var input = zip.getInputStream(entry)) { + xml.append(new String(input.readAllBytes(), StandardCharsets.UTF_8)); + } + } + } + return xml.toString(); + } + + private record Request(int offset, int limit) { + } +} diff --git a/backend/framework/src/test/java/org/jumpserver/chen/framework/datasource/sql/SQLExecutePlanSqlServerLimitTest.java b/backend/framework/src/test/java/org/jumpserver/chen/framework/datasource/sql/SQLExecutePlanSqlServerLimitTest.java new file mode 100644 index 00000000..b5b92214 --- /dev/null +++ b/backend/framework/src/test/java/org/jumpserver/chen/framework/datasource/sql/SQLExecutePlanSqlServerLimitTest.java @@ -0,0 +1,76 @@ +package org.jumpserver.chen.framework.datasource.sql; + +import com.alibaba.druid.DbType; +import org.jumpserver.chen.framework.utils.PageUtils; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class SQLExecutePlanSqlServerLimitTest { + private static final String TOP5_SQL = + "SELECT TOP (5) id, name, CreditLimit FROM [dbo].[Customers] ORDER BY [CreditLimit] DESC"; + private static final String ALL_ROWS_SQL = + "SELECT id, name, CreditLimit FROM [dbo].[Customers] ORDER BY [CreditLimit] DESC"; + + @Test + void generateTargetSQLKeepsUserTopInsteadOfDefaultPageSize() throws Exception { + SQLExecutePlan plan = plan(TOP5_SQL, 0, 50); + plan.generateTargetSQL(); + + String target = compact(plan.getTargetSQL()); + assertEquals(5, PageUtils.getLimit(plan.getTargetSQL(), DbType.sqlserver)); + assertTrue(target.contains("TOP (5)") || target.contains("TOP 5")); + assertFalse(target.contains("TOP 50")); + } + + @Test + void generateTargetSQLStillWrapsDefaultPageSizeWhenNoTop() throws Exception { + SQLExecutePlan plan = plan(ALL_ROWS_SQL, 0, 50); + plan.setSqlActuator(countStub(7)); + plan.generateTargetSQL(); + + String target = compact(plan.getTargetSQL()); + assertTrue(target.startsWith("SELECT TOP 50 ")); + assertFalse(target.contains("ROW_NUMBER()")); + } + + private static SQLExecutePlan plan(String sql, int offset, int limit) { + SQLExecutePlan plan = new SQLExecutePlan(sql, DbType.sqlserver); + SQLQueryParams params = new SQLQueryParams(); + params.setOffset(offset); + params.setLimit(limit); + plan.setSqlQueryParams(params); + return plan; + } + + private static SQLActuator countStub(int count) { + return (SQLActuator) java.lang.reflect.Proxy.newProxyInstance( + SQLActuator.class.getClassLoader(), + new Class[]{SQLActuator.class}, + (proxy, method, args) -> { + if ("count".equals(method.getName())) { + return count; + } + return defaultValue(method.getReturnType()); + }); + } + + private static Object defaultValue(Class type) { + if (type == Boolean.TYPE) { + return false; + } + if (type == Integer.TYPE) { + return 0; + } + if (type == Long.TYPE) { + return 0L; + } + return null; + } + + private static String compact(String sql) { + return sql.replaceAll("\\s+", " ").trim(); + } +} diff --git a/backend/framework/src/test/java/org/jumpserver/chen/framework/i18n/UserVisibleMessageI18nTest.java b/backend/framework/src/test/java/org/jumpserver/chen/framework/i18n/UserVisibleMessageI18nTest.java new file mode 100644 index 00000000..77a1080b --- /dev/null +++ b/backend/framework/src/test/java/org/jumpserver/chen/framework/i18n/UserVisibleMessageI18nTest.java @@ -0,0 +1,149 @@ +package org.jumpserver.chen.framework.i18n; + +import org.jumpserver.chen.framework.datasource.sql.SqlValidator; +import org.jumpserver.chen.framework.i18n.MessageUtils; +import org.jumpserver.chen.framework.session.Session; +import org.jumpserver.chen.framework.session.SessionManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.context.support.ReloadableResourceBundleMessageSource; + +import java.lang.reflect.Proxy; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class UserVisibleMessageI18nTest { + private String sessionToken; + private final AtomicReference locale = new AtomicReference<>(Locale.US); + + @BeforeEach + void setUp() { + ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); + Path bundle = propertiesDir().resolve("chen"); + messageSource.setBasename(bundle.toUri().toString()); + messageSource.setDefaultEncoding("UTF-8"); + messageSource.setFallbackToSystemLocale(false); + new MessageUtils(messageSource); + Session session = (Session) Proxy.newProxyInstance( + Session.class.getClassLoader(), + new Class[]{Session.class}, + (proxy, method, args) -> "getLocale".equals(method.getName()) ? this.locale.get() : null); + this.sessionToken = SessionManager.registerSession(session); + SessionManager.setContext(this.sessionToken); + } + + @AfterEach + void tearDown() { + if (this.sessionToken != null) { + SessionManager.unregisterSession(this.sessionToken); + } + SessionManager.setContext(null); + } + + @Test + void chineseAndEnglishUserVisibleMessagesDiffer() { + this.locale.set(Locale.US); + String enAffected = MessageUtils.get("AffectedRows"); + String enUnknown = MessageUtils.get("Unknown"); + String enUnsupported = SqlValidator.queryUnsupportedMessage(); + String enExported = MessageUtils.get("RowsExported", 3); + String enError = MessageUtils.get("ErrorPrefix", "boom"); + + this.locale.set(Locale.CHINA); + String zhAffected = MessageUtils.get("AffectedRows"); + String zhUnknown = MessageUtils.get("Unknown"); + String zhUnsupported = SqlValidator.queryUnsupportedMessage(); + String zhExported = MessageUtils.get("RowsExported", 3); + String zhError = MessageUtils.get("ErrorPrefix", "boom"); + + assertEquals("Affected rows", enAffected); + assertEquals("unknown", enUnknown); + assertEquals("受影响行数", zhAffected); + assertEquals("未知", zhUnknown); + assertEquals("Error: boom", enError); + assertEquals("错误: boom", zhError); + assertNotEquals(enAffected, zhAffected); + assertNotEquals(enUnknown, zhUnknown); + assertNotEquals(enUnsupported, zhUnsupported); + assertNotEquals(enExported, zhExported); + assertTrue(enExported.contains("3")); + assertTrue(zhExported.contains("3")); + } + + @Test + void affectedRowsTicketPrefixDoesNotRewriteSql() { + String sql = "UPDATE accounts SET balance = balance - 10"; + this.locale.set(Locale.CHINA); + String zh = formatTicket(sql, null); + this.locale.set(Locale.US); + String en = formatTicket(sql, 3); + + assertTrue(zh.startsWith("受影响行数: 未知\n")); + assertTrue(zh.endsWith("\n" + sql)); + assertTrue(en.startsWith("Affected rows: 3\n")); + assertTrue(en.endsWith("\n" + sql)); + assertEquals(sql, zh.substring(zh.indexOf('\n') + 1)); + assertEquals(sql, en.substring(en.indexOf('\n') + 1)); + } + + @Test + void propertiesKeySetsStayAligned() throws Exception { + Path dir = propertiesDir(); + List files = Files.list(dir) + .filter(path -> path.getFileName().toString().startsWith("chen") + && path.getFileName().toString().endsWith(".properties")) + .sorted() + .toList(); + assertFalse(files.isEmpty()); + Set expected = null; + for (Path file : files) { + Set keys = Files.readAllLines(file).stream() + .filter(line -> !line.isBlank() && !line.startsWith("#") && line.contains("=")) + .map(line -> line.substring(0, line.indexOf('='))) + .collect(Collectors.toCollection(HashSet::new)); + if (expected == null) { + expected = keys; + } else { + assertEquals(expected, keys, file.getFileName().toString()); + } + } + assertTrue(expected.contains("AffectedRows")); + assertTrue(expected.contains("Unknown")); + assertTrue(expected.contains("QueryUnsupported")); + assertTrue(expected.contains("RowsExported")); + assertTrue(expected.contains("ErrorPrefix")); + } + + private static Path propertiesDir() { + Path dir = Path.of("../web/src/main/resources/i18n"); + if (!Files.isDirectory(dir)) { + dir = Path.of("backend/web/src/main/resources/i18n"); + } + if (!Files.isDirectory(dir)) { + dir = Path.of("src/main/resources/i18n"); + } + return dir.toAbsolutePath().normalize(); + } + + private static String formatTicket(String sql, Integer affectedRows) { + String value = affectedRows == null + ? MessageUtils.getOrDefault("Unknown", "unknown") + : Integer.toString(affectedRows); + return String.format("%s: %s\n%s", + MessageUtils.getOrDefault("AffectedRows", "Affected rows"), + value, + sql); + } +} diff --git a/backend/framework/src/test/java/org/jumpserver/chen/framework/utils/PageUtilsFilterTest.java b/backend/framework/src/test/java/org/jumpserver/chen/framework/utils/PageUtilsFilterTest.java new file mode 100644 index 00000000..701bdad9 --- /dev/null +++ b/backend/framework/src/test/java/org/jumpserver/chen/framework/utils/PageUtilsFilterTest.java @@ -0,0 +1,64 @@ +package org.jumpserver.chen.framework.utils; + +import com.alibaba.druid.DbType; +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class PageUtilsFilterTest { + @Test + void addsFilterToTableQuery() { + String sql = PageUtils.filter( + "select * from `demo`.`users`", + DbType.mysql, + "status = 'active' AND id > 10" + ); + + String where = where(sql, DbType.mysql); + assertTrue(where.contains("status = 'active'")); + assertTrue(where.contains("id > 10")); + } + + @Test + void combinesWithExistingWhereClause() { + String sql = PageUtils.filter("select * from users where tenant_id = 7", DbType.mysql, "enabled = 1"); + + String where = where(sql, DbType.mysql); + assertTrue(where.contains("tenant_id = 7")); + assertTrue(where.contains("enabled = 1")); + } + + @Test + void acceptsDialectSpecificQuotedIdentifiers() { + String sql = PageUtils.filter("select * from \"public\".\"users\"", DbType.postgresql, "\"displayName\" IS NOT NULL"); + + assertTrue(where(sql, DbType.postgresql).contains("\"displayName\" IS NOT NULL")); + } + + @Test + void emptyFilterLeavesSqlUnchanged() { + String sql = "select * from users"; + assertEquals(sql, PageUtils.filter(sql, DbType.mysql, " ")); + } + + @Test + void rejectsAdditionalStatementsAndClauses() { + assertThrows(IllegalArgumentException.class, + () -> PageUtils.filter("select * from users", DbType.mysql, "1 = 1; delete from users")); + assertThrows(IllegalArgumentException.class, + () -> PageUtils.filter("select * from users", DbType.mysql, "1 = 1 order by id")); + assertThrows(IllegalArgumentException.class, + () -> PageUtils.filter("select * from users", DbType.mysql, "1 = 1 limit 1")); + } + + private String where(String sql, DbType dbType) { + SQLSelectStatement statement = (SQLSelectStatement) SQLUtils.parseSingleStatement(sql, dbType); + SQLSelectQueryBlock query = (SQLSelectQueryBlock) statement.getSelect().getQuery(); + return query.getWhere().toString(); + } +} diff --git a/backend/framework/src/test/java/org/jumpserver/chen/framework/utils/PageUtilsSqlServerLimitTest.java b/backend/framework/src/test/java/org/jumpserver/chen/framework/utils/PageUtilsSqlServerLimitTest.java new file mode 100644 index 00000000..acb08b41 --- /dev/null +++ b/backend/framework/src/test/java/org/jumpserver/chen/framework/utils/PageUtilsSqlServerLimitTest.java @@ -0,0 +1,162 @@ +package org.jumpserver.chen.framework.utils; + +import com.alibaba.druid.DbType; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class PageUtilsSqlServerLimitTest { + private static final String CROSS_JOIN_SQL = """ + SELECT + a.object_id AS a_id, + b.object_id AS b_id + FROM sys.all_objects a + CROSS JOIN sys.all_objects b + """; + + @Test + void firstPageUsesTop() { + String sql = normalize(PageUtils.limit(CROSS_JOIN_SQL, DbType.sqlserver, 0, 100)); + + assertTrue(sql.startsWith("SELECT TOP 100 ")); + assertTrue(sql.contains("FROM sys.all_objects a CROSS JOIN sys.all_objects b")); + assertFalse(sql.contains("ROW_NUMBER()")); + assertFalse(sql.contains("ORDER BY SELECT NULL")); + } + + @Test + void defaultPageSizeWrapsFirstPageWithTop50() { + String sql = normalize(PageUtils.limit( + "SELECT id, name, CreditLimit FROM [dbo].[Customers] ORDER BY [CreditLimit] DESC", + DbType.sqlserver, 0, 50)); + + assertTrue(sql.startsWith("SELECT TOP 50 ")); + assertTrue(sql.contains("ORDER BY CreditLimit DESC") || sql.contains("ORDER BY [CreditLimit] DESC")); + assertFalse(sql.contains("ROW_NUMBER()")); + } + + @Test + void defaultPageSizeSecondPageUsesRowNumber() { + String sql = normalize(PageUtils.limit( + "SELECT id, name, CreditLimit FROM [dbo].[Customers] ORDER BY [CreditLimit] DESC", + DbType.sqlserver, 50, 50)); + + assertTrue(sql.contains("ROW_NUMBER() OVER (ORDER BY [CreditLimit] DESC) AS ROWNUM") + || sql.contains("ROW_NUMBER() OVER (ORDER BY CreditLimit DESC) AS ROWNUM")); + assertTrue(sql.contains("WHERE ROWNUM > 50 AND ROWNUM <= 100")); + assertFalse(sql.contains("ORDER BY SELECT NULL")); + } + + @Test + void secondPageWrapsWithParenthesizedDummyOrderBy() { + String sql = normalize(PageUtils.limit(CROSS_JOIN_SQL, DbType.sqlserver, 100, 100)); + + assertTrue(sql.contains("ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS ROWNUM")); + assertFalse(sql.contains("ORDER BY SELECT NULL")); + assertTrue(sql.contains("WHERE ROWNUM > 100 AND ROWNUM <= 200")); + assertTrue(sql.contains("FROM (SELECT")); + } + + @Test + void secondPageReusesOriginalOrderBy() { + String source = "SELECT a.object_id AS a_id FROM sys.all_objects a ORDER BY a.object_id"; + String sql = normalize(PageUtils.limit(source, DbType.sqlserver, 100, 100)); + + assertTrue(sql.contains("ROW_NUMBER() OVER (ORDER BY a.object_id) AS ROWNUM")); + assertEquals(1, countOf(sql, "ORDER BY a.object_id")); + assertFalse(sql.contains("SELECT NULL")); + assertTrue(sql.contains("WHERE ROWNUM > 100 AND ROWNUM <= 200")); + } + + @Test + void firstPageKeepsOriginalOrderByWithTop() { + String source = "SELECT a.object_id AS a_id FROM sys.all_objects a ORDER BY a.object_id"; + String sql = normalize(PageUtils.limit(source, DbType.sqlserver, 0, 100)); + + assertTrue(sql.startsWith("SELECT TOP 100 ")); + assertTrue(sql.endsWith("ORDER BY a.object_id")); + assertFalse(sql.contains("ROW_NUMBER()")); + } + + @Test + void getLimitRecognizesSqlServerTop() { + assertEquals(-1, PageUtils.getLimit(CROSS_JOIN_SQL, DbType.sqlserver)); + assertEquals(10, PageUtils.getLimit("SELECT TOP 10 * FROM sys.all_objects", DbType.sqlserver)); + assertEquals(20, PageUtils.getLimit("SELECT TOP (20) name FROM sys.all_objects", DbType.sqlserver)); + } + + @Test + void existingTopIsLeftToGetLimitInsteadOfBrokenWrap() { + String source = "SELECT TOP 10 * FROM sys.all_objects"; + assertEquals(10, PageUtils.getLimit(source, DbType.sqlserver)); + + String firstPage = normalize(PageUtils.limit(source, DbType.sqlserver, 0, 100)); + assertTrue(firstPage.contains("TOP 10")); + assertFalse(firstPage.contains("TOP 100")); + assertFalse(firstPage.contains("ROW_NUMBER()")); + + String secondPage = normalize(PageUtils.limit(source, DbType.sqlserver, 100, 100)); + assertTrue(secondPage.contains("ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS ROWNUM")); + assertFalse(secondPage.contains("ORDER BY SELECT NULL")); + } + + @Test + void userTopIsNotOverwrittenByDefaultPageSize() { + String source = "SELECT TOP (5) id, name, CreditLimit FROM [dbo].[Customers] ORDER BY [CreditLimit] DESC"; + + assertEquals(-1, PageUtils.getLimit( + "SELECT id, name, CreditLimit FROM [dbo].[Customers] ORDER BY [CreditLimit] DESC", + DbType.sqlserver)); + assertEquals(5, PageUtils.getLimit(source, DbType.sqlserver)); + + String wrapped = generateTargetSQL(source, 0, 50); + assertEquals(5, PageUtils.getLimit(wrapped, DbType.sqlserver)); + assertFalse(normalize(wrapped).contains("TOP 50")); + assertTrue(normalize(wrapped).contains("TOP (5)") || normalize(wrapped).contains("TOP 5")); + + String withoutTop = generateTargetSQL( + "SELECT id, name, CreditLimit FROM [dbo].[Customers] ORDER BY [CreditLimit] DESC", + 0, 50); + assertTrue(normalize(withoutTop).startsWith("SELECT TOP 50 ")); + } + + @Test + void otherDialectsKeepExistingLimitStyle() { + String mysql = normalize(PageUtils.limit("SELECT id FROM t", DbType.mysql, 100, 50)); + assertTrue(mysql.contains("LIMIT 100, 50") || mysql.contains("LIMIT 50 OFFSET 100")); + + String postgres = normalize(PageUtils.limit("SELECT id FROM t", DbType.postgresql, 100, 50)); + assertTrue(postgres.contains("LIMIT 50 OFFSET 100") || postgres.contains("LIMIT 50")); + + String oracle = normalize(PageUtils.limit("SELECT id FROM t", DbType.oracle, 100, 50)); + assertTrue(oracle.contains("ROWNUM")); + assertTrue(oracle.contains("RN > 100") || oracle.contains("ROWNUM <= 150")); + + String db2 = normalize(PageUtils.limit("SELECT id FROM t", DbType.db2, 0, 50)).toUpperCase(); + assertTrue(db2.contains("FETCH FIRST") || db2.contains("FIRST 50") || db2.contains("FIRST")); + } + + private static String generateTargetSQL(String source, int offset, int limit) { + if (PageUtils.getLimit(source, DbType.sqlserver) > -1) { + return source; + } + return PageUtils.limit(source, DbType.sqlserver, offset, limit); + } + + private static String normalize(String sql) { + return sql.replaceAll("\\s+", " ") + .replaceAll("\\(\\s+", "(") + .replaceAll("\\s+\\)", ")") + .trim(); + } + + private static int countOf(String sql, String token) { + int count = 0; + for (int index = 0; (index = sql.indexOf(token, index)) >= 0; index += token.length()) { + count++; + } + return count; + } +} diff --git a/backend/modules/pom.xml b/backend/modules/pom.xml index 82a3d08b..9bc2f07a 100644 --- a/backend/modules/pom.xml +++ b/backend/modules/pom.xml @@ -18,6 +18,11 @@ framework ${revision} + + junit + junit + test + diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickHouseDatasource.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickHouseDatasource.java index 55344a16..dd4b696c 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickHouseDatasource.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickHouseDatasource.java @@ -4,8 +4,11 @@ import org.jumpserver.chen.framework.datasource.DatasourceFactory; import org.jumpserver.chen.framework.datasource.base.BaseDatasource; import org.jumpserver.chen.framework.datasource.entity.DBConnectInfo; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; +import org.jumpserver.chen.framework.datasource.plan.ExecutionPlanDialect; public class ClickHouseDatasource extends BaseDatasource { + private final ExecutionPlanDialect executionPlanDialect = new ClickHouseExecutionPlanDialect(); static { DatasourceFactory.Register(ClickHouseDatasource.class); @@ -13,6 +16,7 @@ public class ClickHouseDatasource extends BaseDatasource { public ClickHouseDatasource(DBConnectInfo dbConnectInfo) { this.connectionManager = new ClickhouseConnectionManager(dbConnectInfo, this); + this.metadataCatalog = new MetadataCatalog(this.connectionManager, new ClickhouseMetadataProvider(this.connectionManager)); this.resourceBrowser = new ClickhouseResourceBrowser(this.connectionManager); this.actionHandler = new ClickhouseActionHandler(); } @@ -22,6 +26,11 @@ public String getName() { return "clickhouse"; } + @Override + public ExecutionPlanDialect getExecutionPlanDialect() { + return this.executionPlanDialect; + } + @Override public DbType getDruidDbType() { return DbType.clickhouse; diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickHouseExecutionPlanDialect.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickHouseExecutionPlanDialect.java new file mode 100644 index 00000000..c33a0d95 --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickHouseExecutionPlanDialect.java @@ -0,0 +1,213 @@ +package org.jumpserver.chen.modules.clickhouse; + +import org.jumpserver.chen.framework.datasource.analysis.SqlStatementAnalysis; +import org.jumpserver.chen.framework.datasource.plan.BaseExecutionPlanDialect; +import org.jumpserver.chen.framework.datasource.plan.DialectPlanResult; +import org.jumpserver.chen.framework.datasource.plan.ExecutionPlanCapabilities; +import org.jumpserver.chen.framework.datasource.plan.PlanCodes; +import org.jumpserver.chen.framework.datasource.plan.PlanDatabase; +import org.jumpserver.chen.framework.datasource.plan.PlanDiagnostic; +import org.jumpserver.chen.framework.datasource.plan.PlanEffects; +import org.jumpserver.chen.framework.datasource.plan.PlanI18n; +import org.jumpserver.chen.framework.datasource.plan.PlanExecutionContext; +import org.jumpserver.chen.framework.datasource.plan.PlanPrerequisite; +import org.jumpserver.chen.framework.datasource.plan.PlanRawFormat; +import org.jumpserver.chen.framework.datasource.plan.PlanStatus; +import org.jumpserver.chen.framework.datasource.plan.PlanTransactionState; + +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +public class ClickHouseExecutionPlanDialect extends BaseExecutionPlanDialect { + /* Unvalidated pairs still use PLAN JSON as a testable candidate; text EXPLAIN is never generated. */ + private static final Set VALIDATED_JSON_SERVER_VERSIONS = Set.of(); + private static final Set VALIDATED_DRIVER_VERSIONS = Set.of(); + private static final Set SAFE_FUNCTIONS = Set.of( + "abs", "arrayjoin", "avg", "cast", "coalesce", "concat", "count", "dateadd", + "datediff", "dense_rank", "formatdatetime", "greatest", "if", "ifnull", "lag", + "lead", "least", "length", "lower", "max", "min", "multiif", "now", "nullif", + "rank", "row_number", "substring", "sum", "toDate", "todate", "todatetime", + "tofloat64", "toint32", "toint64", "tostring", "touint32", "touint64", "trim", + "upper" + ); + private static final Set EXTERNAL_FUNCTIONS = Set.of( + "azureblobstorage", "cluster", "clusterallreplicas", "deltalake", "dictionary", + "file", "hdfs", "hudi", "iceberg", "input", "jdbc", "mongodb", "mysql", + "odbc", "postgresql", "remote", "remotesecure", "s3", "s3cluster", "url" + ); + + private final ExecutionPlanCapabilities capabilities = ExecutionPlanCapabilities.clickhouse(); + private final Set validatedJsonServerVersions; + private final Set validatedDriverVersions; + + public ClickHouseExecutionPlanDialect() { + this(VALIDATED_JSON_SERVER_VERSIONS, VALIDATED_DRIVER_VERSIONS); + } + + ClickHouseExecutionPlanDialect(Set validatedJsonServerVersions, Set validatedDriverVersions) { + this.validatedJsonServerVersions = Set.copyOf(validatedJsonServerVersions); + this.validatedDriverVersions = Set.copyOf(validatedDriverVersions); + } + + @Override + public PlanDatabase database() { + return PlanDatabase.clickhouse; + } + + @Override + public ExecutionPlanCapabilities capabilities() { + return capabilities; + } + + @Override + public DialectPlanResult explainEstimated(PlanExecutionContext context, SqlStatementAnalysis statement) + throws SQLException { + List prerequisites = new ArrayList<>(); + PlanDiagnostic unsafe = unsafeFunctions(statement); + if (unsafe != null) { + prerequisites.add(PlanPrerequisite.unmet( + PlanCodes.UNSAFE_TO_ESTIMATE, unsafe.message(), + "Use ordinary tables and validated ClickHouse built-in functions" + )); + return unmet(context.serverVersion(), prerequisites, unsafe); + } + if (!autoCommitIdle(context)) { + String message = "ClickHouse experimental or unknown transaction context is not used for EXPLAIN"; + prerequisites.add(PlanPrerequisite.unknown( + PlanCodes.TRANSACTION_CONTEXT_UNSAFE, message, + "Finish the transaction or reconnect with auto-commit enabled" + )); + return unmet(context.serverVersion(), prerequisites, + PlanDiagnostic.of(PlanCodes.TRANSACTION_CONTEXT_UNSAFE, message)); + } + + VersionIdentity version = versionIdentity(context); + boolean jsonValidated = validated(version, validatedJsonServerVersions); + if (!jsonValidated) { + String message = "ClickHouse server/driver pair has not passed the real JDBC PLAN release gate: server=" + + version.serverVersion() + ", driver=" + version.driverVersion(); + prerequisites.add(PlanPrerequisite.unknown( + PlanCodes.VERSION_NOT_VALIDATED, message, + "Treat this PLAN JSON run as validation evidence until the pair passes the release gate" + )); + } else { + prerequisites.add(PlanPrerequisite.met("server-version", version.serverVersion())); + prerequisites.add(PlanPrerequisite.met("jdbc-driver", version.driverName() + " " + version.driverVersion())); + } + + prerequisites.add(PlanPrerequisite.met("statement", "Single SELECT using EXPLAIN PLAN json=1")); + return explainJson(context, statement, version.serverVersion(), prerequisites); + } + + private DialectPlanResult explainJson( + PlanExecutionContext context, SqlStatementAnalysis statement, + String serverVersion, List prerequisites + ) throws SQLException { + BoundedRaw raw = executeExplain(context, "EXPLAIN PLAN json=1 " + statement.sql()); + if (raw.truncated()) { + return rawOnly(serverVersion, prerequisites, raw, PlanRawFormat.JSON, + ClickHousePlanParser.JSON_FORMAT_VERSION, + PlanDiagnostic.of(PlanCodes.PLAN_LIMIT_REACHED, "ClickHouse PLAN JSON exceeded the display limit")); + } + ClickHousePlanParser.ParseResult parsed = ClickHousePlanParser.parseJson( + raw.text(), context.maxNodes(), context.maxDepth() + ); + if (!parsed.structured()) { + return rawOnly(serverVersion, prerequisites, raw, PlanRawFormat.JSON, + ClickHousePlanParser.JSON_FORMAT_VERSION, parsed.error()); + } + return new DialectPlanResult(serverVersion, PlanStatus.SUCCESS, parsed.roots(), raw.text(), PlanRawFormat.JSON, + ClickHousePlanParser.JSON_FORMAT_VERSION, false, capabilities, prerequisites, + PlanEffects.unchangedReuse(), null, parsed.warnings()); + } + + private BoundedRaw executeExplain(PlanExecutionContext context, String sql) throws SQLException { + Statement statement = null; + ResultSet resultSet = null; + try { + context.throwIfCancelled(); + statement = context.connection().createStatement(); + context.registerStatement(statement); + resultSet = statement.executeQuery(sql); + StringBuilder raw = new StringBuilder(); + while (resultSet.next()) { + String value = resultSet.getString(1); + if (value != null) raw.append(value); + raw.append('\n'); + if (utf8ByteLength(raw) > context.maxRawBytes()) break; + } + return boundRaw(raw.toString().stripTrailing(), context.maxRawBytes()); + } catch (SQLException e) { + throwIfConnectionUnusable(context, e, capabilities); + throw e; + } finally { + closeQuietly(resultSet); + context.unregisterStatement(statement); + closeQuietly(statement); + } + } + + private static boolean autoCommitIdle(PlanExecutionContext context) throws SQLException { + PlanTransactionState state = context.transactionState(); + if (context.transactionProbeFailed()) return false; + if (state == PlanTransactionState.AUTO_COMMIT) return true; + return state == PlanTransactionState.UNKNOWN && context.connection().getAutoCommit(); + } + + private VersionIdentity versionIdentity(PlanExecutionContext context) throws SQLException { + DatabaseMetaData metadata = context.connection().getMetaData(); + return new VersionIdentity( + metadata.getDatabaseProductVersion(), metadata.getDriverName(), metadata.getDriverVersion() + ); + } + + private boolean validated(VersionIdentity version, Set serverVersions) { + return serverVersions.stream().anyMatch(version.serverVersion()::startsWith) + && validatedDriverVersions.stream().anyMatch(version.driverVersion()::startsWith); + } + + static PlanDiagnostic unsafeFunctions(SqlStatementAnalysis statement) { + for (String rawName : statement.functionNames()) { + String name = rawName == null ? "" : rawName.replace("`", "").trim().toLowerCase(Locale.ROOT); + int dot = name.lastIndexOf('.'); + String function = dot < 0 ? name : name.substring(dot + 1); + if (dot >= 0 || EXTERNAL_FUNCTIONS.contains(function) || !SAFE_FUNCTIONS.contains(function)) { + return PlanDiagnostic.of(PlanCodes.UNSAFE_TO_ESTIMATE, + "Function " + rawName + " may perform external access or unknown schema inference"); + } + } + return null; + } + + private DialectPlanResult unmet(String serverVersion, List prerequisites, PlanDiagnostic error) { + return new DialectPlanResult(serverVersion, PlanStatus.PREREQUISITES_UNMET, List.of(), null, null, + null, false, capabilities, prerequisites, PlanEffects.unchangedReuse(), error, List.of()); + } + + private DialectPlanResult rawOnly( + String serverVersion, List prerequisites, BoundedRaw raw, + PlanRawFormat format, String formatVersion, PlanDiagnostic warning + ) { + return new DialectPlanResult(serverVersion, PlanStatus.RAW_ONLY, List.of(), raw.text(), format, + formatVersion, raw.truncated(), capabilities, prerequisites, PlanEffects.unchangedReuse(), + null, warning == null ? List.of() : List.of(warning)); + } + + private record VersionIdentity(String serverVersion, String driverName, String driverVersion) { + private VersionIdentity { + serverVersion = valueOrUnknown(serverVersion); + driverName = valueOrUnknown(driverName); + driverVersion = valueOrUnknown(driverVersion); + } + + private static String valueOrUnknown(String value) { + return value == null || value.isBlank() ? "unknown" : value; + } + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickHousePlanParser.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickHousePlanParser.java new file mode 100644 index 00000000..3f2ff108 --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickHousePlanParser.java @@ -0,0 +1,228 @@ +package org.jumpserver.chen.modules.clickhouse; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.jumpserver.chen.framework.datasource.plan.NormalizedNodeType; +import org.jumpserver.chen.framework.datasource.plan.PlanCodes; +import org.jumpserver.chen.framework.datasource.plan.PlanDiagnostic; +import org.jumpserver.chen.framework.datasource.plan.PlanNode; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +/** Pure parser for ClickHouse EXPLAIN PLAN json=1 output. */ +public final class ClickHousePlanParser { + public static final String JSON_FORMAT_VERSION = "clickhouse-plan-json"; + public static final String TEXT_FORMAT_VERSION = "clickhouse-plan-text"; + + private ClickHousePlanParser() { + } + + public static ParseResult parseJson(String rawJson, int maxNodes, int maxDepth) { + if (rawJson == null || rawJson.isBlank()) { + return ParseResult.failed(PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, "Empty ClickHouse PLAN JSON")); + } + JsonElement parsed; + try { + parsed = JsonParser.parseString(rawJson); + } catch (RuntimeException e) { + return ParseResult.failed(PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, "ClickHouse PLAN JSON is not valid")); + } + List planObjects = unwrapPlans(parsed); + if (planObjects.isEmpty()) { + return ParseResult.failed(PlanDiagnostic.of( + PlanCodes.PLAN_PARSE_FAILED, + "Unrecognized ClickHouse PLAN JSON shape" + )); + } + AtomicInteger ids = new AtomicInteger(); + AtomicInteger count = new AtomicInteger(); + List roots = new ArrayList<>(); + List warnings = new ArrayList<>(); + try { + for (JsonObject plan : planObjects) { + roots.add(convert(plan, ids, count, 1, maxNodes, maxDepth)); + } + } catch (LimitReachedException e) { + warnings.add(PlanDiagnostic.of(PlanCodes.PLAN_LIMIT_REACHED, e.getMessage())); + } + if (roots.isEmpty()) { + return new ParseResult(List.of(), warnings, false, + PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, "ClickHouse PLAN JSON produced no nodes")); + } + return new ParseResult(roots, warnings, true, null); + } + + public static ParseResult parseText(String rawText, int maxNodes, int maxDepth) { + if (rawText == null || rawText.isBlank()) { + return ParseResult.failed(PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, "Empty ClickHouse PLAN text")); + } + List roots = new ArrayList<>(); + List stack = new ArrayList<>(); + int id = 0; + int baseIndent = -1; + List warnings = new ArrayList<>(); + for (String line : rawText.split("\\R", -1)) { + if (line.isBlank()) { + continue; + } + int indent = leadingSpaces(line); + if (baseIndent < 0) { + baseIndent = indent; + } + int level = Math.max(0, (indent - baseIndent) / 2); + if (++id > maxNodes || level + 1 > maxDepth) { + warnings.add(PlanDiagnostic.of(PlanCodes.PLAN_LIMIT_REACHED, "ClickHouse execution plan exceeded size limits")); + break; + } + String value = line.strip(); + TextNode node = new TextNode("n" + id, value); + while (stack.size() > level) { + stack.remove(stack.size() - 1); + } + if (level > 0 && !stack.isEmpty()) { + stack.get(stack.size() - 1).children.add(node); + } else { + roots.add(node); + } + stack.add(node); + } + if (roots.isEmpty()) { + return ParseResult.failed(PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, "Unrecognized ClickHouse PLAN text")); + } + return new ParseResult(roots.stream().map(TextNode::freeze).toList(), warnings, true, null); + } + + private static List unwrapPlans(JsonElement value) { + List result = new ArrayList<>(); + if (value == null || value.isJsonNull()) { + return result; + } + if (value.isJsonArray()) { + for (JsonElement item : value.getAsJsonArray()) { + result.addAll(unwrapPlans(item)); + } + return result; + } + if (!value.isJsonObject()) { + return result; + } + JsonObject object = value.getAsJsonObject(); + JsonElement wrapper = first(object, "Plan", "plan"); + if (wrapper != null) { + return unwrapPlans(wrapper); + } + if (first(object, "Node Type", "NodeType", "node_type") != null) { + result.add(object); + } + return result; + } + + private static PlanNode convert( + JsonObject object, + AtomicInteger ids, + AtomicInteger count, + int depth, + int maxNodes, + int maxDepth + ) { + if (count.incrementAndGet() > maxNodes || depth > maxDepth) { + throw new LimitReachedException("ClickHouse execution plan exceeded size limits"); + } + String operator = text(first(object, "Node Type", "NodeType", "node_type")); + if (operator == null || operator.isBlank()) { + operator = "Unknown PLAN node"; + } + List children = new ArrayList<>(); + JsonElement plans = first(object, "Plans", "plans"); + if (plans != null && plans.isJsonArray()) { + for (JsonElement child : plans.getAsJsonArray()) { + if (child.isJsonObject()) { + children.add(convert(child.getAsJsonObject(), ids, count, depth + 1, maxNodes, maxDepth)); + } + } + } + return new PlanNode( + "n" + ids.incrementAndGet(), text(first(object, "Node Id", "NodeId", "node_id")), + mapNodeType(operator), operator, null, operator, + text(first(object, "Description", "description")), null, null, + null, null, null, null, null, Map.of(), Map.of(), children + ); + } + + static NormalizedNodeType mapNodeType(String operator) { + String value = operator == null ? "" : operator.toLowerCase(Locale.ROOT); + if (value.contains("join")) return value.contains("hash") ? NormalizedNodeType.HASH_JOIN : NormalizedNodeType.JOIN; + if (value.contains("read") || value.contains("scan")) return NormalizedNodeType.SCAN; + if (value.startsWith("expression") || value.contains("project")) return NormalizedNodeType.PROJECTION; + if (value.contains("sort") || value.contains("order")) return NormalizedNodeType.SORT; + if (value.contains("aggregat")) return NormalizedNodeType.AGGREGATE; + if (value.contains("filter")) return NormalizedNodeType.FILTER; + if (value.contains("limit")) return NormalizedNodeType.LIMIT; + if (value.contains("union")) return NormalizedNodeType.UNION; + return NormalizedNodeType.OTHER; + } + + private static JsonElement first(JsonObject object, String... names) { + for (String name : names) { + if (object.has(name)) return object.get(name); + } + return null; + } + + private static String text(JsonElement value) { + if (value == null || value.isJsonNull()) return null; + return value.isJsonPrimitive() ? value.getAsString() : value.toString(); + } + + private static int leadingSpaces(String value) { + int result = 0; + while (result < value.length() && Character.isWhitespace(value.charAt(result))) result++; + return result; + } + + public record ParseResult( + List roots, + List warnings, + boolean structured, + PlanDiagnostic error + ) { + public ParseResult { + roots = roots == null ? List.of() : List.copyOf(roots); + warnings = warnings == null ? List.of() : List.copyOf(warnings); + } + + static ParseResult failed(PlanDiagnostic error) { + return new ParseResult(List.of(), List.of(), false, error); + } + } + + private static final class TextNode { + private final String id; + private final String operator; + private final List children = new ArrayList<>(); + + private TextNode(String id, String operator) { + this.id = id; + this.operator = operator; + } + + private PlanNode freeze() { + return new PlanNode( + id, null, mapNodeType(operator), operator, null, operator, null, + null, null, null, null, null, null, null, + Map.of(), Map.of(), children.stream().map(TextNode::freeze).toList() + ); + } + } + + private static final class LimitReachedException extends RuntimeException { + private LimitReachedException(String message) { + super(message); + } + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseActionHandler.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseActionHandler.java index ce835934..96042892 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseActionHandler.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseActionHandler.java @@ -58,19 +58,6 @@ public List getTableActions(TreeNode node) { ); } - private static final String SQL_SELECT_SCHEMA_DETAIL = "select * from information_schema.SCHEMATA where SCHEMA_NAME = '?'"; - - public EventEmitter onSchemaProperties(TreeNode node) throws SQLException { - return this.onShowObjectProperties("schema", SQL_SELECT_SCHEMA_DETAIL, node); - } - - private static final String SQL_SELECT_TABLE_DETAIL = "select TABLE_NAME,TABLE_SCHEMA,TABLE_TYPE,ENGINE,AVG_ROW_LENGTH,DATA_LENGTH,MAX_DATA_LENGTH,CREATE_TIME,TABLE_COLLATION from information_schema.TABLES WHERE TABLE_NAME = '?'"; - - public EventEmitter onTableProperties(TreeNode node) throws SQLException { - return this.onShowObjectProperties("table", SQL_SELECT_TABLE_DETAIL, node); - } - - @Override public EventEmitter handleForm(FormData formData) throws SQLException { return null; diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseMetadataProvider.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseMetadataProvider.java new file mode 100644 index 00000000..034f02f3 --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseMetadataProvider.java @@ -0,0 +1,261 @@ +package org.jumpserver.chen.modules.clickhouse; + +import org.jumpserver.chen.framework.datasource.ConnectionManager; +import org.jumpserver.chen.framework.datasource.metadata.BaseDatabaseMetadataProvider; +import org.jumpserver.chen.framework.datasource.metadata.ColumnMetadata; +import org.jumpserver.chen.framework.datasource.metadata.IndexMetadata; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCapabilities; +import org.jumpserver.chen.framework.datasource.metadata.ObjectProperties; +import org.jumpserver.chen.framework.datasource.metadata.ObjectRef; +import org.jumpserver.chen.framework.datasource.metadata.ObjectStatistics; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; +import org.jumpserver.chen.framework.datasource.metadata.RelationMetadata; +import org.jumpserver.chen.framework.datasource.metadata.RelationScope; +import org.jumpserver.chen.framework.datasource.metadata.SchemaMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ScopeKind; +import org.jumpserver.chen.framework.datasource.metadata.ScopeProperties; +import org.jumpserver.chen.framework.datasource.metadata.ScopeRef; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +public class ClickhouseMetadataProvider extends BaseDatabaseMetadataProvider { + + public ClickhouseMetadataProvider(ConnectionManager connectionManager) { + super(connectionManager); + } + + private static final MetadataCapabilities CAPABILITIES = new MetadataCapabilities( + false, true, true, true, true, false, false, true, true, + true, true, true, false, false, true, true, false, true + ); + + private static final String VIEW_ENGINES = "'View', 'MaterializedView', 'LiveView', 'WindowView'"; + + private static final String SQL_SCHEMAS = "SELECT name AS name FROM system.databases"; + + private static final String SQL_TABLES = """ + SELECT name, + engine, + NULL AS character_set, + NULL AS collation, + comment + FROM system.tables + WHERE database = ? AND is_temporary = 0 AND engine NOT IN (%s) + ORDER BY name + """.formatted(VIEW_ENGINES); + + private static final String SQL_TABLE_STATS = """ + SELECT name, + total_rows AS estimated_rows, + total_bytes AS total_size_bytes + FROM system.tables + WHERE database = ? AND is_temporary = 0 AND engine NOT IN (%s) + ORDER BY name + """.formatted(VIEW_ENGINES); + + private static final String SQL_VIEWS = """ + SELECT name, engine AS type, comment + FROM system.tables + WHERE database = ? AND is_temporary = 0 AND engine IN (%s) + ORDER BY name + """.formatted(VIEW_ENGINES); + + private static final String SQL_INDEXES = """ + SELECT name, + table AS table_name, + NULL AS column_name, + expr AS expression, + NULL AS is_unique, + type AS method + FROM system.data_skipping_indices + WHERE database = ? + ORDER BY table, name + """; + + private static final String SQL_COLUMNS = """ + SELECT name AS name, table AS table_name, position AS ordinal, + type AS native_type, type AS jdbc_type_name, + NULL AS size, NULL AS scale, NULL AS nullable, + default_expression AS default_value, comment AS comment + FROM system.columns + WHERE database = ? AND table IN (__IN__) + ORDER BY table, position + """; + + private static final String SQL_TABLE_INDEXES = """ + SELECT name, table AS table_name, NULL AS column_name, expr AS expression, + NULL AS is_unique, type AS method + FROM system.data_skipping_indices + WHERE database = ? AND table IN (__IN__) + ORDER BY table, name + """; + + @Override + public MetadataCapabilities capabilities() { + return CAPABILITIES; + } + + @Override + public List listSchemas(String catalog) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_SCHEMAS, List.of())) { + result.add(new SchemaMetadata(null, stringValue(row, "name"))); + } + return result; + } + + @Override + public List listRelations(RelationScope scope, Set kinds) throws SQLException { + var result = new ArrayList(); + if (kinds.contains(RelationKind.TABLE)) { + for (var row : query(SQL_TABLES, List.of(scope.schema()))) { + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + stringValue(row, "comment"), + stringValue(row, "engine"), + null, + null + )); + } + } + if (kinds.contains(RelationKind.VIEW) || kinds.contains(RelationKind.MATERIALIZED_VIEW)) { + for (var row : query(SQL_VIEWS, List.of(scope.schema()))) { + var type = stringValue(row, "type"); + var kind = type != null && type.toUpperCase(Locale.ROOT).contains("MATERIALIZED") + ? RelationKind.MATERIALIZED_VIEW + : RelationKind.VIEW; + if (!kinds.contains(kind)) { + continue; + } + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), kind), + stringValue(row, "comment"), + null, + null, + null + )); + } + } + return result; + } + + @Override + public List listColumns(List relations) throws SQLException { + return loadColumns(SQL_COLUMNS, relations).stream() + .map(column -> new ColumnMetadata( + column.owner(), column.name(), column.ordinal(), column.nativeType(), column.jdbcType(), + column.size(), column.scale(), isNullableType(column.nativeType()), + column.defaultValue(), column.comment() + )) + .toList(); + } + + static boolean isNullableType(String nativeType) { + if (nativeType == null) { + return false; + } + for (int index = 0; index < nativeType.length(); index++) { + if (!Character.isJavaIdentifierStart(nativeType.charAt(index))) { + continue; + } + int end = index + 1; + while (end < nativeType.length() && Character.isJavaIdentifierPart(nativeType.charAt(end))) { + end++; + } + if (nativeType.regionMatches(true, index, "Nullable", 0, end - index)) { + int next = end; + while (next < nativeType.length() && Character.isWhitespace(nativeType.charAt(next))) { + next++; + } + if (next < nativeType.length() && nativeType.charAt(next) == '(') { + return true; + } + } + index = end - 1; + } + return false; + } + + @Override + public List listIndexes(RelationScope scope) throws SQLException { + return groupIndexRows(query(SQL_INDEXES, List.of(scope.schema())), scope); + } + + @Override + public List listIndexes(List relations) throws SQLException { + if (relations.isEmpty()) { + return List.of(); + } + var first = relations.get(0); + return groupIndexRows(queryKeys(SQL_TABLE_INDEXES, relations, first.schema()), + new RelationScope(first.catalog(), first.schema())); + } + + @Override + public List listStatistics(RelationScope scope) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_TABLE_STATS, List.of(scope.schema()))) { + result.add(new ObjectStatistics( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + longValue(row, "estimated_rows"), + longValue(row, "total_size_bytes") + )); + } + return result; + } + + @Override + public String getSchemaDefinition(RelationScope scope) throws SQLException { + var rows = query("SHOW CREATE DATABASE " + quoteIdentifier(scope.schema()), List.of()); + if (rows.isEmpty()) { + return null; + } + var values = rows.get(0).values().iterator(); + values.next(); + return values.hasNext() ? String.valueOf(values.next()) : null; + } + + @Override + public String getTableDefinition(ObjectRef relation) throws SQLException { + var sql = "SHOW CREATE TABLE " + quoteIdentifier(relation.schema()) + "." + quoteIdentifier(relation.name()); + var rows = query(sql, List.of()); + if (rows.isEmpty()) { + return null; + } + return String.valueOf(rows.get(0).values().iterator().next()); + } + + private static final String SQL_TABLE_PROPERTIES = """ + SELECT name, engine, total_rows, total_bytes, comment + FROM system.tables + WHERE database = ? AND name = ? + """; + + private static final String SQL_SCHEMA_PROPERTIES = """ + SELECT name, engine, data_path, metadata_path, uuid, comment + FROM system.databases + WHERE name = ? + """; + + @Override + public ObjectProperties objectProperties(ObjectRef ref) throws SQLException { + return loadObjectProperties(ref, SQL_TABLE_PROPERTIES); + } + + @Override + public ScopeProperties scopeProperties(ScopeRef ref) throws SQLException { + if (ref.kind() != ScopeKind.SCHEMA) { + return super.scopeProperties(ref); + } + return loadScopeProperties(ref, SQL_SCHEMA_PROPERTIES, List.of(ref.scope().schema())); + } + + @Override + protected String quoteIdentifier(String value) { + return "`" + value.replace("`", "``") + "`"; + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseResourceBrowser.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseResourceBrowser.java index 92427c47..e53f69a0 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseResourceBrowser.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseResourceBrowser.java @@ -2,49 +2,9 @@ import org.jumpserver.chen.framework.datasource.ConnectionManager; import org.jumpserver.chen.framework.datasource.base.BaseResourceBrowser; -import org.jumpserver.chen.framework.datasource.entity.resource.Field; -import org.jumpserver.chen.framework.datasource.entity.resource.Schema; -import org.jumpserver.chen.framework.datasource.entity.resource.Table; -import org.jumpserver.chen.framework.datasource.entity.resource.View; -import org.jumpserver.chen.framework.datasource.sql.SQL; - -import java.sql.SQLException; -import java.util.List; - public class ClickhouseResourceBrowser extends BaseResourceBrowser { public ClickhouseResourceBrowser(ConnectionManager connectionManager) { - super(connectionManager, new ClickhouseSQLHintsHandler(connectionManager)); - } - - private static final String SQL_GET_SCHEMAS = "select schema_name as name from information_schema.schemata"; - - @Override - public List getSchemas() throws SQLException { - return this.getSchemas(SQL.of(SQL_GET_SCHEMAS)); + super(connectionManager); } - - private static final String SQL_GET_TABLES = "select table_name from information_schema.tables where table_schema = '?'"; - - @Override - public List
getTables(String schema) throws SQLException { - return this.getTables(SQL.of(SQL_GET_TABLES, schema)); - } - - private static final String SQL_GET_VIEWS = "select table_name from information_schema.views where table_schema = '?'"; - - @Override - public List getViews(String schema) throws SQLException { - return this.getViews(SQL.of(SQL_GET_VIEWS, schema)); - } - - private static final String SQL_GET_FIELDS = "SELECT COLUMN_NAME AS NAME, COLUMN_TYPE AS TYPE, COLUMN_KEY AS `KEY`, IS_NULLABLE AS `NULLABLE`, COLUMN_DEFAULT AS `DEFAULT`, EXTRA AS EXTRA, COLUMN_COMMENT AS COMMENT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '?' AND TABLE_NAME = '?'"; - - @Override - public List getFields(String schema, String table) throws SQLException { - return this.getFields(SQL.of(SQL_GET_FIELDS, schema, table)); - } - - - } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseSQLHintsHandler.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseSQLHintsHandler.java deleted file mode 100644 index 007f62e9..00000000 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseSQLHintsHandler.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.jumpserver.chen.modules.clickhouse; - -import org.jumpserver.chen.framework.datasource.ConnectionManager; -import org.jumpserver.chen.framework.datasource.base.BaseSQLHintsHandler; -import org.jumpserver.chen.framework.datasource.entity.resource.Field; -import org.jumpserver.chen.framework.datasource.entity.resource.Table; -import org.jumpserver.chen.framework.datasource.sql.SQL; - -import java.sql.SQLException; -import java.util.List; -import java.util.Map; - -public class ClickhouseSQLHintsHandler extends BaseSQLHintsHandler { - private final ConnectionManager connectionManager; - - public ClickhouseSQLHintsHandler(ConnectionManager connectionManager) { - this.connectionManager = connectionManager; - } - - private static final String SQL_GET_SCHEMA_ALL_FIELDS = "select column_name as name, table_schema,table_name from information_schema.columns where table_schema = '?'"; - - public List getAllFields(String schema) throws SQLException { - return this.connectionManager - .getSqlActuator() - .getObjects(SQL.of(SQL_GET_SCHEMA_ALL_FIELDS, schema).getSql(), Field.class, Map.of("name", 1, "schema", 2, "table", 3)); - - } - - private static final String SQL_GET_ALL_TABLES = "select table_name as name, table_schema from information_schema.tables where table_schema='?' "; - - public List
getALlTables(String context) throws SQLException { - return this.connectionManager.getSqlActuator() - .getObjects(SQL.of(SQL_GET_ALL_TABLES, context) - .getSql(), Table.class, Map.of("name", 1, "schema", 2)); - } -} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMActionHandler.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMActionHandler.java index 34a88493..376e5c19 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMActionHandler.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMActionHandler.java @@ -58,13 +58,6 @@ public List getTableActions(TreeNode node) { ); } - private static final String SQL_SELECT_TABLE_DETAIL = "select TABNAME, TBSPACE, TABSCHEMA, TYPE, STATUS, COLCOUNT, ACTIVE_BLOCKS, AVGROWSIZE, OWNER, CREATE_TIME from syscat.TABLES WHERE TBSPACE is not null AND TABNAME = '?'"; - - public EventEmitter onTableProperties(TreeNode node) throws SQLException { - return this.onShowObjectProperties("table", SQL_SELECT_TABLE_DETAIL, node); - } - - @Override public EventEmitter handleForm(FormData formData) throws SQLException { return null; diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMActuator.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMActuator.java index 2cfcf281..6bdfca34 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMActuator.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMActuator.java @@ -41,7 +41,9 @@ public void changeSchema(String schema) throws SQLException { @Override public SQLExecutePlan createPlan(String schema, String table, SQLQueryParams sqlQueryParams) throws SQLException { - var sql = SQL.of("select * from ?.?", schema, table); + var schemaIdentifier = SQLIdentifier.quote(this.getDbType(), schema); + var tableIdentifier = SQLIdentifier.quote(this.getDbType(), table); + var sql = SQL.of("select * from " + schemaIdentifier + "." + tableIdentifier); return this.createPlan(sql, sqlQueryParams); } } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMDatasource.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMDatasource.java index ee517fe9..e515073b 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMDatasource.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMDatasource.java @@ -4,8 +4,11 @@ import org.jumpserver.chen.framework.datasource.DatasourceFactory; import org.jumpserver.chen.framework.datasource.base.BaseDatasource; import org.jumpserver.chen.framework.datasource.entity.DBConnectInfo; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; +import org.jumpserver.chen.framework.datasource.plan.ExecutionPlanDialect; public class DMDatasource extends BaseDatasource { + private final ExecutionPlanDialect executionPlanDialect = new DmExecutionPlanDialect(); static { DatasourceFactory.Register(DMDatasource.class); @@ -13,6 +16,7 @@ public class DMDatasource extends BaseDatasource { public DMDatasource(DBConnectInfo dbConnectInfo) { this.connectionManager = new DMConnectionManager(dbConnectInfo, this); + this.metadataCatalog = new MetadataCatalog(this.connectionManager, new DmMetadataProvider(this.connectionManager)); this.resourceBrowser = new DMResourceBrowser(this.connectionManager); this.actionHandler = new DMActionHandler(); } @@ -22,6 +26,11 @@ public String getName() { return "dameng"; } + @Override + public ExecutionPlanDialect getExecutionPlanDialect() { + return this.executionPlanDialect; + } + @Override public DbType getDruidDbType() { diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMResourceBrowser.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMResourceBrowser.java index 5fa4d5dd..c8a58c50 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMResourceBrowser.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMResourceBrowser.java @@ -2,49 +2,9 @@ import org.jumpserver.chen.framework.datasource.ConnectionManager; import org.jumpserver.chen.framework.datasource.base.BaseResourceBrowser; -import org.jumpserver.chen.framework.datasource.entity.resource.Field; -import org.jumpserver.chen.framework.datasource.entity.resource.Schema; -import org.jumpserver.chen.framework.datasource.entity.resource.Table; -import org.jumpserver.chen.framework.datasource.entity.resource.View; -import org.jumpserver.chen.framework.datasource.sql.SQL; - -import java.sql.SQLException; -import java.util.List; - public class DMResourceBrowser extends BaseResourceBrowser { public DMResourceBrowser(ConnectionManager connectionManager) { - super(connectionManager, new DMSQLHintsHandler(connectionManager)); - } - - private static final String SQL_GET_SCHEMAS = "SELECT NAME FROM SYSOBJECTS t WHERE t.TYPE$ ='SCH'"; - - @Override - public List getSchemas() throws SQLException { - return this.getSchemas(SQL.of(SQL_GET_SCHEMAS)); + super(connectionManager); } - - private static final String SQL_GET_TABLES = "SELECT TABLE_NAME AS NAME FROM ALL_TABLES WHERE OWNER='?'"; - - @Override - public List
getTables(String schema) throws SQLException { - return this.getTables(SQL.of(SQL_GET_TABLES, schema)); - } - - private static final String SQL_GET_VIEWS = "SELECT VIEW_NAME AS NAME FROM ALL_VIEWS WHERE OWNER='?'"; - - @Override - public List getViews(String schema) throws SQLException { - return this.getViews(SQL.of(SQL_GET_VIEWS, schema)); - } - - private static final String SQL_GET_FIELDS = "SELECT COLUMN_NAME AS `NAME`,DATA_TYPE AS `TYPE`,DATA_LENGTH AS `LENGTH`,DATA_DEFAULT AS `DEFAULT` FROM ALL_TAB_COLUMNS WHERE OWNER='?' AND TABLE_NAME='?'"; - - @Override - public List getFields(String schema, String table) throws SQLException { - return this.getFields(SQL.of(SQL_GET_FIELDS, schema, table)); - } - - - } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMSQLHintsHandler.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMSQLHintsHandler.java deleted file mode 100644 index ee036e35..00000000 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMSQLHintsHandler.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.jumpserver.chen.modules.dameng; - -import org.jumpserver.chen.framework.datasource.ConnectionManager; -import org.jumpserver.chen.framework.datasource.base.BaseSQLHintsHandler; -import org.jumpserver.chen.framework.datasource.entity.resource.Field; -import org.jumpserver.chen.framework.datasource.entity.resource.Table; -import org.jumpserver.chen.framework.datasource.sql.SQL; - -import java.sql.SQLException; -import java.util.List; -import java.util.Map; - -public class DMSQLHintsHandler extends BaseSQLHintsHandler { - private final ConnectionManager connectionManager; - - public DMSQLHintsHandler(ConnectionManager connectionManager) { - this.connectionManager = connectionManager; - } - - private static final String SQL_GET_ALL_TABLES = "SELECT TABLE_NAME,OWNER FROM ALL_TABLES WHERE OWNER= '?'"; - - public List
getALlTables(String context) throws SQLException { - return this.connectionManager.getSqlActuator() - .getObjects(SQL.of(SQL_GET_ALL_TABLES, context) - .getSql(), Table.class, Map.of("name", 1, "schema", 2)); - } - - private static final String SQL_GET_SCHEMA_ALL_FIELDS = "SELECT COLUMN_NAME,OWNER,TABLE_NAME FROM ALL_TAB_COLUMNS WHERE OWNER = '?'"; - - public List getAllFields(String schema) throws SQLException { - return this.connectionManager - .getSqlActuator() - .getObjects(SQL.of(SQL_GET_SCHEMA_ALL_FIELDS, schema).getSql(), Field.class, Map.of("name", 1, "schema", 2, "table", 3)); - - } -} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DmExecutionPlanDialect.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DmExecutionPlanDialect.java new file mode 100644 index 00000000..1a4b4d7a --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DmExecutionPlanDialect.java @@ -0,0 +1,198 @@ +package org.jumpserver.chen.modules.dameng; + +import org.jumpserver.chen.framework.datasource.analysis.SqlStatementAnalysis; +import org.jumpserver.chen.framework.datasource.plan.BaseExecutionPlanDialect; +import org.jumpserver.chen.framework.datasource.plan.DialectPlanResult; +import org.jumpserver.chen.framework.datasource.plan.ExecutionPlanCapabilities; +import org.jumpserver.chen.framework.datasource.plan.PlanCodes; +import org.jumpserver.chen.framework.datasource.plan.PlanDatabase; +import org.jumpserver.chen.framework.datasource.plan.PlanDiagnostic; +import org.jumpserver.chen.framework.datasource.plan.PlanEffects; +import org.jumpserver.chen.framework.datasource.plan.PlanI18n; +import org.jumpserver.chen.framework.datasource.plan.PlanExecutionContext; +import org.jumpserver.chen.framework.datasource.plan.PlanPrerequisite; +import org.jumpserver.chen.framework.datasource.plan.PlanRawFormat; +import org.jumpserver.chen.framework.datasource.plan.PlanStatus; +import org.jumpserver.chen.framework.datasource.plan.PlanTransactionState; + +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +public class DmExecutionPlanDialect extends BaseExecutionPlanDialect { + /* Real-database validated pairs are reported as such; unvalidated pairs remain testable candidates. */ + private static final Set VALIDATED_SERVER_VERSIONS = Set.of(); + private static final Set VALIDATED_DRIVER_VERSIONS = Set.of(); + private static final Set SAFE_FUNCTIONS = Set.of( + "abs", "avg", "cast", "ceil", "ceiling", "coalesce", "concat", "count", + "current_date", "current_time", "current_timestamp", "dateadd", "datediff", + "dense_rank", "extract", "floor", "greatest", "lag", "lead", "least", + "length", "lower", "ltrim", "max", "min", "mod", "nullif", "rank", + "replace", "round", "row_number", "rtrim", "substr", "substring", "sum", + "trim", "trunc", "upper" + ); + + private final ExecutionPlanCapabilities capabilities = ExecutionPlanCapabilities.dm(); + private final Set validatedServerVersions; + private final Set validatedDriverVersions; + + public DmExecutionPlanDialect() { + this(VALIDATED_SERVER_VERSIONS, VALIDATED_DRIVER_VERSIONS); + } + + DmExecutionPlanDialect(Set validatedServerVersions, Set validatedDriverVersions) { + this.validatedServerVersions = Set.copyOf(validatedServerVersions); + this.validatedDriverVersions = Set.copyOf(validatedDriverVersions); + } + + @Override + public PlanDatabase database() { + return PlanDatabase.dm; + } + + @Override + public ExecutionPlanCapabilities capabilities() { + return capabilities; + } + + @Override + public DialectPlanResult explainEstimated(PlanExecutionContext context, SqlStatementAnalysis statement) + throws SQLException { + List prerequisites = new ArrayList<>(); + PlanDiagnostic unsafe = unsafeFunctions(statement); + if (unsafe != null) { + prerequisites.add(PlanPrerequisite.unmet( + PlanCodes.UNSAFE_TO_ESTIMATE, unsafe.message(), + "Use only validated DM built-in functions and ordinary tables" + )); + return unmet(context.serverVersion(), prerequisites, unsafe); + } + + PlanTransactionState transactionState = context.transactionState(); + if (context.transactionProbeFailed() || transactionState != PlanTransactionState.AUTO_COMMIT) { + String detail = context.transactionProbeDetail(); + String message = "DM EXPLAIN is enabled only for a verified auto-commit idle connection" + + (detail == null || detail.isBlank() ? "" : ": " + detail); + prerequisites.add(PlanPrerequisite.unknown( + PlanCodes.TRANSACTION_CONTEXT_UNSAFE, message, + "Finish the transaction or reconnect in auto-commit mode" + )); + return unmet(context.serverVersion(), prerequisites, + PlanDiagnostic.of(PlanCodes.TRANSACTION_CONTEXT_UNSAFE, message)); + } + + VersionIdentity version = versionIdentity(context); + boolean versionValidated = validated(version); + if (!versionValidated) { + String message = "DM server/driver pair has not passed the real JDBC release gate: server=" + + version.serverVersion() + ", driver=" + version.driverVersion(); + prerequisites.add(PlanPrerequisite.unknown( + PlanCodes.VERSION_NOT_VALIDATED, message, + "Treat this run as validation evidence until the pair passes the release gate" + )); + } else { + prerequisites.add(PlanPrerequisite.met("server-version", version.serverVersion())); + prerequisites.add(PlanPrerequisite.met("jdbc-driver", version.driverName() + " " + version.driverVersion())); + } + + prerequisites.add(PlanPrerequisite.met("statement", "Single SELECT using ordinary EXPLAIN")); + BoundedRaw raw = executeExplain(context, statement.sql()); + if (raw.truncated()) { + return rawOnly(version.serverVersion(), prerequisites, raw, + PlanDiagnostic.of(PlanCodes.PLAN_LIMIT_REACHED, "DM EXPLAIN text exceeded the display limit")); + } + DmPlanParser.ParseResult parsed = DmPlanParser.parse(raw.text(), context.maxNodes(), context.maxDepth()); + if (!parsed.structured()) { + return rawOnly(version.serverVersion(), prerequisites, raw, parsed.error()); + } + return new DialectPlanResult( + version.serverVersion(), PlanStatus.SUCCESS, parsed.roots(), raw.text(), PlanRawFormat.TEXT, + DmPlanParser.RAW_FORMAT_VERSION, false, capabilities, prerequisites, + PlanEffects.unchangedReuse(), null, parsed.warnings() + ); + } + + private BoundedRaw executeExplain(PlanExecutionContext context, String sql) throws SQLException { + Statement statement = null; + ResultSet resultSet = null; + try { + context.throwIfCancelled(); + statement = context.connection().createStatement(); + context.registerStatement(statement); + resultSet = statement.executeQuery("EXPLAIN " + sql); + ResultSetMetaData metadata = resultSet.getMetaData(); + StringBuilder raw = new StringBuilder(); + while (resultSet.next()) { + for (int column = 1; column <= metadata.getColumnCount(); column++) { + if (column > 1) raw.append('\t'); + String value = resultSet.getString(column); + if (value != null) raw.append(value); + } + raw.append('\n'); + if (utf8ByteLength(raw) > context.maxRawBytes()) break; + } + return boundRaw(raw.toString(), context.maxRawBytes()); + } catch (SQLException e) { + throwIfConnectionUnusable(context, e, capabilities); + throw e; + } finally { + closeQuietly(resultSet); + context.unregisterStatement(statement); + closeQuietly(statement); + } + } + + private VersionIdentity versionIdentity(PlanExecutionContext context) throws SQLException { + DatabaseMetaData metadata = context.connection().getMetaData(); + String server = metadata.getDatabaseProductVersion(); + return new VersionIdentity(server, metadata.getDriverName(), metadata.getDriverVersion()); + } + + private boolean validated(VersionIdentity version) { + return validatedServerVersions.stream().anyMatch(version.serverVersion()::startsWith) + && validatedDriverVersions.stream().anyMatch(version.driverVersion()::startsWith); + } + + static PlanDiagnostic unsafeFunctions(SqlStatementAnalysis statement) { + for (String rawName : statement.functionNames()) { + String name = rawName == null ? "" : rawName.replace("\"", "").trim().toLowerCase(Locale.ROOT); + int dot = name.lastIndexOf('.'); + if (dot >= 0 || !SAFE_FUNCTIONS.contains(name)) { + return PlanDiagnostic.of(PlanCodes.UNSAFE_TO_ESTIMATE, + "Function " + rawName + " is not a validated DM built-in"); + } + } + return null; + } + + private DialectPlanResult unmet(String serverVersion, List prerequisites, PlanDiagnostic error) { + return new DialectPlanResult(serverVersion, PlanStatus.PREREQUISITES_UNMET, List.of(), null, null, + null, false, capabilities, prerequisites, PlanEffects.unchangedReuse(), error, List.of()); + } + + private DialectPlanResult rawOnly( + String serverVersion, List prerequisites, BoundedRaw raw, PlanDiagnostic warning + ) { + return new DialectPlanResult(serverVersion, PlanStatus.RAW_ONLY, List.of(), raw.text(), PlanRawFormat.TEXT, + DmPlanParser.RAW_FORMAT_VERSION, raw.truncated(), capabilities, prerequisites, + PlanEffects.unchangedReuse(), null, warning == null ? List.of() : List.of(warning)); + } + + private record VersionIdentity(String serverVersion, String driverName, String driverVersion) { + private VersionIdentity { + serverVersion = valueOrUnknown(serverVersion); + driverName = valueOrUnknown(driverName); + driverVersion = valueOrUnknown(driverVersion); + } + + private static String valueOrUnknown(String value) { + return value == null || value.isBlank() ? "unknown" : value; + } + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DmMetadataProvider.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DmMetadataProvider.java new file mode 100644 index 00000000..7396f116 --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DmMetadataProvider.java @@ -0,0 +1,267 @@ +package org.jumpserver.chen.modules.dameng; + +import org.jumpserver.chen.framework.datasource.ConnectionManager; +import org.jumpserver.chen.framework.datasource.metadata.BaseDatabaseMetadataProvider; +import org.jumpserver.chen.framework.datasource.metadata.ColumnMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ConstraintMetadata; +import org.jumpserver.chen.framework.datasource.metadata.IndexMetadata; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCapabilities; +import org.jumpserver.chen.framework.datasource.metadata.ObjectProperties; +import org.jumpserver.chen.framework.datasource.metadata.ForeignKeyMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ObjectRef; +import org.jumpserver.chen.framework.datasource.metadata.PrimaryKeyMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ObjectStatistics; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; +import org.jumpserver.chen.framework.datasource.metadata.RelationMetadata; +import org.jumpserver.chen.framework.datasource.metadata.RelationScope; +import org.jumpserver.chen.framework.datasource.metadata.SchemaMetadata; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class DmMetadataProvider extends BaseDatabaseMetadataProvider { + + public DmMetadataProvider(ConnectionManager connectionManager) { + super(connectionManager); + } + + private static final MetadataCapabilities CAPABILITIES = new MetadataCapabilities( + false, true, true, true, true, true, true, true, false, + true, false, false, false, false, true, true, true, true + ); + + private static final String SQL_SCHEMAS = "SELECT NAME AS name FROM SYSOBJECTS t WHERE t.TYPE$ ='SCH'"; + + private static final String SQL_TABLES = """ + SELECT t.table_name AS name, + c.comments AS object_comment + FROM all_tables t + LEFT JOIN all_tab_comments c + ON c.owner = t.owner AND c.table_name = t.table_name AND c.table_type = 'TABLE' + WHERE t.owner = ? + ORDER BY t.table_name + """; + + private static final String SQL_TABLE_STATS = """ + SELECT t.table_name AS name, + t.num_rows AS estimated_rows, + NULL AS total_size_bytes + FROM all_tables t + WHERE t.owner = ? + ORDER BY t.table_name + """; + + private static final String SQL_VIEWS = """ + SELECT v.view_name AS name, c.comments AS object_comment + FROM all_views v + LEFT JOIN all_tab_comments c + ON c.owner = v.owner AND c.table_name = v.view_name AND c.table_type = 'VIEW' + WHERE v.owner = ? + ORDER BY v.view_name + """; + + private static final String SQL_INDEXES = """ + SELECT i.index_name AS name, + i.table_name AS table_name, + CASE WHEN e.column_expression IS NULL THEN c.column_name END AS column_name, + e.column_expression AS expression, + CASE WHEN i.uniqueness = 'UNIQUE' THEN 1 ELSE 0 END AS is_unique, + i.index_type AS method, + c.descend AS sort_order + FROM all_indexes i + JOIN all_ind_columns c + ON c.index_owner = i.owner AND c.index_name = i.index_name AND c.table_owner = i.table_owner + LEFT JOIN all_ind_expressions e + ON e.index_owner = c.index_owner AND e.index_name = c.index_name + AND e.table_owner = c.table_owner AND e.column_position = c.column_position + WHERE i.owner = ? + ORDER BY i.table_name, i.index_name, c.column_position + """; + + private static final String SQL_COLUMNS = """ + SELECT c.COLUMN_NAME AS name, c.TABLE_NAME AS table_name, c.COLUMN_ID AS ordinal, + c.DATA_TYPE AS native_type, c.DATA_TYPE AS jdbc_type_name, + COALESCE(c.CHAR_LENGTH, c.DATA_PRECISION, c.DATA_LENGTH) AS size, + c.DATA_SCALE AS scale, c.NULLABLE AS nullable, + c.DATA_DEFAULT AS default_value, cc.COMMENTS AS object_comment + FROM ALL_TAB_COLUMNS c + LEFT JOIN ALL_COL_COMMENTS cc + ON cc.OWNER = c.OWNER AND cc.TABLE_NAME = c.TABLE_NAME AND cc.COLUMN_NAME = c.COLUMN_NAME + WHERE c.OWNER = ? AND c.TABLE_NAME IN (__IN__) + ORDER BY c.TABLE_NAME, c.COLUMN_ID + """; + + private static final String SQL_TABLE_INDEXES = """ + SELECT i.index_name AS name, i.table_name AS table_name, + CASE WHEN e.column_expression IS NULL THEN c.column_name END AS column_name, + e.column_expression AS expression, + CASE WHEN i.uniqueness = 'UNIQUE' THEN 1 ELSE 0 END AS is_unique, + i.index_type AS method, + c.descend AS sort_order + FROM all_indexes i + JOIN all_ind_columns c + ON c.index_owner = i.owner AND c.index_name = i.index_name AND c.table_owner = i.table_owner + LEFT JOIN all_ind_expressions e + ON e.index_owner = c.index_owner AND e.index_name = c.index_name + AND e.table_owner = c.table_owner AND e.column_position = c.column_position + WHERE i.owner = ? AND i.table_name IN (__IN__) + ORDER BY i.table_name, i.index_name, c.column_position + """; + + @Override + public MetadataCapabilities capabilities() { + return CAPABILITIES; + } + + @Override + public List listSchemas(String catalog) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_SCHEMAS, List.of())) { + result.add(new SchemaMetadata(null, stringValue(row, "name"))); + } + return result; + } + + @Override + public List listRelations(RelationScope scope, Set kinds) throws SQLException { + var result = new ArrayList(); + if (kinds.contains(RelationKind.TABLE)) { + for (var row : query(SQL_TABLES, List.of(scope.schema()))) { + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + stringValue(row, "object_comment"), + null, + null, + null + )); + } + } + if (kinds.contains(RelationKind.VIEW)) { + for (var row : query(SQL_VIEWS, List.of(scope.schema()))) { + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.VIEW), + stringValue(row, "object_comment"), + null, + null, + null + )); + } + } + return result; + } + + @Override + public List listColumns(List relations) throws SQLException { + return loadColumns(SQL_COLUMNS, relations); + } + + @Override + public List listIndexes(RelationScope scope) throws SQLException { + return groupIndexRows(query(SQL_INDEXES, List.of(scope.schema())), scope); + } + + @Override + public List listIndexes(List relations) throws SQLException { + if (relations.isEmpty()) { + return List.of(); + } + var first = relations.get(0); + return groupIndexRows(queryKeys(SQL_TABLE_INDEXES, relations, first.schema()), + new RelationScope(first.catalog(), first.schema())); + } + + @Override + public String getTableDefinition(ObjectRef relation) throws SQLException { + var objectType = relation.kind() == RelationKind.TABLE ? "TABLE" : "VIEW"; + var rows = query("SELECT DBMS_METADATA.GET_DDL(?, ?, ?) AS definition FROM dual", + List.of(objectType, relation.name(), relation.schema())); + return rows.isEmpty() ? null : stringValue(rows.get(0), "definition"); + } + + @Override + public List listStatistics(RelationScope scope) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_TABLE_STATS, List.of(scope.schema()))) { + result.add(new ObjectStatistics( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + longValue(row, "estimated_rows"), + longValue(row, "total_size_bytes") + )); + } + return result; + } + + private static final String SQL_TABLE_PROPERTIES = """ + SELECT table_name, tablespace_name, status, num_rows, blocks, avg_row_len, sample_size, owner + FROM all_tables + WHERE owner = ? AND table_name = ? + """; + + @Override + public ObjectProperties objectProperties(ObjectRef ref) throws SQLException { + return loadObjectProperties(ref, SQL_TABLE_PROPERTIES); + } + + private static final String SQL_PRIMARY_KEYS = """ + SELECT c.table_name AS table_name, cc.column_name AS column_name, c.constraint_name AS name + FROM all_constraints c + JOIN all_cons_columns cc ON cc.owner = c.owner AND cc.constraint_name = c.constraint_name AND cc.table_name = c.table_name + WHERE c.constraint_type = 'P' AND c.owner = ? AND c.table_name IN (__IN__) + ORDER BY c.table_name, c.constraint_name, cc.position + """; + + private static final String SQL_FOREIGN_KEYS = """ + SELECT c.table_name AS table_name, cc.column_name AS column_name, c.constraint_name AS name, + rc.owner AS referenced_schema, rc.table_name AS referenced_table, rcc.column_name AS referenced_column + FROM all_constraints c + JOIN all_cons_columns cc ON cc.owner = c.owner AND cc.constraint_name = c.constraint_name AND cc.table_name = c.table_name + JOIN all_constraints rc ON rc.owner = c.r_owner AND rc.constraint_name = c.r_constraint_name + JOIN all_cons_columns rcc ON rcc.owner = rc.owner AND rcc.constraint_name = rc.constraint_name AND rcc.position = cc.position + WHERE c.constraint_type = 'R' AND c.owner = ? AND c.table_name IN (__IN__) + ORDER BY c.table_name, c.constraint_name, cc.position + """; + + private static final String SQL_CONSTRAINTS = """ + SELECT c.table_name AS table_name, c.constraint_name AS name, + CASE c.constraint_type WHEN 'P' THEN 'PRIMARY KEY' WHEN 'R' THEN 'FOREIGN KEY' + WHEN 'U' THEN 'UNIQUE' WHEN 'C' THEN 'CHECK' END AS constraint_type, + cc.column_name AS column_name, rc.owner AS referenced_schema, + rc.table_name AS referenced_table, rcc.column_name AS referenced_column, + c.search_condition AS definition + FROM all_constraints c + LEFT JOIN all_cons_columns cc + ON cc.owner = c.owner AND cc.constraint_name = c.constraint_name AND cc.table_name = c.table_name + LEFT JOIN all_constraints rc ON rc.owner = c.r_owner AND rc.constraint_name = c.r_constraint_name + LEFT JOIN all_cons_columns rcc + ON rcc.owner = rc.owner AND rcc.constraint_name = rc.constraint_name AND rcc.position = cc.position + WHERE c.constraint_type IN ('P', 'R', 'U', 'C') + AND c.owner = ? AND c.table_name IN (__IN__) + ORDER BY c.table_name, c.constraint_name, cc.position + """; + + @Override + public List listPrimaryKeys(List relations) throws SQLException { + var first = relations.get(0); + return groupPrimaryKeys(queryKeys(SQL_PRIMARY_KEYS, relations, first.schema()), + new RelationScope(first.catalog(), first.schema())); + } + + @Override + public List listForeignKeys(List relations) throws SQLException { + var first = relations.get(0); + return groupForeignKeys(queryKeys(SQL_FOREIGN_KEYS, relations, first.schema()), + new RelationScope(first.catalog(), first.schema())); + } + + @Override + public List listConstraints(List relations) throws SQLException { + if (relations.isEmpty()) { + return List.of(); + } + var first = relations.get(0); + return groupConstraints(queryKeys(SQL_CONSTRAINTS, relations, first.schema()), + new RelationScope(first.catalog(), first.schema())); + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DmPlanParser.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DmPlanParser.java new file mode 100644 index 00000000..6acca228 --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DmPlanParser.java @@ -0,0 +1,200 @@ +package org.jumpserver.chen.modules.dameng; + +import org.jumpserver.chen.framework.datasource.plan.NormalizedNodeType; +import org.jumpserver.chen.framework.datasource.plan.PlanCodes; +import org.jumpserver.chen.framework.datasource.plan.PlanDiagnostic; +import org.jumpserver.chen.framework.datasource.plan.PlanNode; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** Pure parser for the numbered, indented text emitted by DM's ordinary EXPLAIN. */ +public final class DmPlanParser { + public static final String RAW_FORMAT_VERSION = "dm-explain-text"; + public static final String ROWS_MEANING = "DM estimated rows"; + public static final String COST_MEANING = "DM optimizer cost"; + + private static final Pattern NODE = Pattern.compile( + "^(\\s*)(\\d+)[ \\t](\\s*)#?([^:\\[]+?)\\s*:?\\s*\\[\\s*([^,\\]]+)\\s*,\\s*([^,\\]]+)\\s*,\\s*([^\\]]+)\\s*\\](.*)$" + ); + + private DmPlanParser() { + } + + public static ParseResult parse(String rawText, int maxNodes, int maxDepth) { + List warnings = new ArrayList<>(); + if (rawText == null || rawText.isBlank()) { + return ParseResult.failed(PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, "Empty DM EXPLAIN text")); + } + + List roots = new ArrayList<>(); + List stack = new ArrayList<>(); + MutableNode current = null; + int count = 0; + int baseIndent = -1; + for (String line : rawText.split("\\R", -1)) { + if (line.isBlank()) { + continue; + } + Matcher matcher = NODE.matcher(line); + if (matcher.matches()) { + int indent = visualIndent(matcher.group(1)) + visualIndent(matcher.group(3)); + if (baseIndent < 0) { + baseIndent = indent; + } + int level = Math.max(0, (indent - baseIndent) / 2); + if (++count > maxNodes || level + 1 > maxDepth) { + warnings.add(PlanDiagnostic.of(PlanCodes.PLAN_LIMIT_REACHED, "DM execution plan exceeded size limits")); + break; + } + String ordinal = matcher.group(2); + String operator = matcher.group(4).trim(); + BigDecimal cost = decimal(matcher.group(5)); + BigDecimal rows = decimal(matcher.group(6)); + String bytes = matcher.group(7).trim(); + String tail = matcher.group(8).trim(); + MutableNode node = new MutableNode( + "n" + count, ordinal, operator, rows, cost, bytes, + tail == null || tail.isBlank() ? null : tail + ); + while (stack.size() > level) { + stack.remove(stack.size() - 1); + } + if (level > 0 && !stack.isEmpty()) { + stack.get(stack.size() - 1).children.add(node); + } else { + roots.add(node); + } + stack.add(node); + current = node; + continue; + } + if (current != null) { + current.appendDetail(line.strip()); + } else { + warnings.add(PlanDiagnostic.of( + PlanCodes.PLAN_PARSE_FAILED, + "DM EXPLAIN returned an unrecognized leading line; it remains available in raw text" + )); + } + } + + if (roots.isEmpty()) { + return new ParseResult(List.of(), warnings, false, + PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, "Unrecognized DM EXPLAIN text shape")); + } + return new ParseResult(roots.stream().map(MutableNode::freeze).toList(), warnings, true, null); + } + + private static int visualIndent(String value) { + int result = 0; + for (int i = 0; value != null && i < value.length(); i++) { + result += value.charAt(i) == '\t' ? 4 : 1; + } + return result; + } + + private static BigDecimal decimal(String value) { + try { + return new BigDecimal(value.trim()); + } catch (RuntimeException ignored) { + return null; + } + } + + static NormalizedNodeType mapNodeType(String operator) { + String value = operator == null ? "" : operator.toUpperCase(Locale.ROOT); + if (value.contains("JOIN") || value.contains("NL") || value.contains("HASH2")) { + return value.contains("HASH") ? NormalizedNodeType.HASH_JOIN : NormalizedNodeType.JOIN; + } + if (value.contains("INDEX") || value.contains("SSEK") || value.contains("BLKUP")) { + return NormalizedNodeType.INDEX_SCAN; + } + if (value.contains("SCAN") || value.contains("CSCN") || value.contains("SSCN")) { + return NormalizedNodeType.SCAN; + } + if (value.contains("SORT")) { + return NormalizedNodeType.SORT; + } + if (value.contains("AGG") || value.contains("GROUP")) { + return NormalizedNodeType.AGGREGATE; + } + if (value.contains("FILTER") || value.contains("SLCT")) { + return NormalizedNodeType.FILTER; + } + if (value.contains("LIMIT") || value.contains("TOP")) { + return NormalizedNodeType.LIMIT; + } + if (value.contains("UNION")) { + return NormalizedNodeType.UNION; + } + if (value.contains("PRJT") || value.contains("PROJECT")) { + return NormalizedNodeType.PROJECTION; + } + return NormalizedNodeType.OTHER; + } + + public record ParseResult( + List roots, + List warnings, + boolean structured, + PlanDiagnostic error + ) { + public ParseResult { + roots = roots == null ? List.of() : List.copyOf(roots); + warnings = warnings == null ? List.of() : List.copyOf(warnings); + } + + static ParseResult failed(PlanDiagnostic error) { + return new ParseResult(List.of(), List.of(), false, error); + } + } + + private static final class MutableNode { + private final String id; + private final String nativeId; + private final String operator; + private final BigDecimal rows; + private final BigDecimal cost; + private final String bytes; + private final List children = new ArrayList<>(); + private String detail; + + private MutableNode( + String id, String nativeId, String operator, BigDecimal rows, + BigDecimal cost, String bytes, String detail + ) { + this.id = id; + this.nativeId = nativeId; + this.operator = operator; + this.rows = rows; + this.cost = cost; + this.bytes = bytes; + this.detail = detail; + } + + private void appendDetail(String continuation) { + detail = detail == null ? continuation : detail + "\n" + continuation; + } + + private PlanNode freeze() { + Map attributes = new LinkedHashMap<>(); + if (bytes != null && !bytes.isBlank()) { + attributes.put("bytes", bytes); + } + return new PlanNode( + id, nativeId, mapNodeType(operator), operator, null, operator, + detail, null, null, rows, cost, null, + rows == null ? null : ROWS_MEANING, + cost == null ? null : COST_MEANING, + Map.of(), attributes, children.stream().map(MutableNode::freeze).toList() + ); + } + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2ActionHandler.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2ActionHandler.java index 84f80225..75f05314 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2ActionHandler.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2ActionHandler.java @@ -58,19 +58,6 @@ public List getTableActions(TreeNode node) { ); } - private static final String SQL_SELECT_SCHEMA_DETAIL = "select * from syscat.schemata where schemaname = '?'"; - - public EventEmitter onSchemaProperties(TreeNode node) throws SQLException { - return this.onShowObjectProperties("schema", SQL_SELECT_SCHEMA_DETAIL, node); - } - - private static final String SQL_SELECT_TABLE_DETAIL = "select TABNAME, TBSPACE, TABSCHEMA, TYPE, STATUS, COLCOUNT, ACTIVE_BLOCKS, AVGROWSIZE, OWNER, CREATE_TIME from syscat.TABLES WHERE TBSPACE is not null AND TABNAME = '?'"; - - public EventEmitter onTableProperties(TreeNode node) throws SQLException { - return this.onShowObjectProperties("table", SQL_SELECT_TABLE_DETAIL, node); - } - - @Override public EventEmitter handleForm(FormData formData) throws SQLException { return null; diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2Actuator.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2Actuator.java index e99e4faf..00b931f5 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2Actuator.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2Actuator.java @@ -41,7 +41,9 @@ public void changeSchema(String schema) throws SQLException { @Override public SQLExecutePlan createPlan(String schema, String table, SQLQueryParams sqlQueryParams) throws SQLException { - var sql = SQL.of("select * from ?.?", schema, table); + var schemaIdentifier = SQLIdentifier.quote(this.getDbType(), schema); + var tableIdentifier = SQLIdentifier.quote(this.getDbType(), table); + var sql = SQL.of("select * from " + schemaIdentifier + "." + tableIdentifier); return this.createPlan(sql, sqlQueryParams); } } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2Datasource.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2Datasource.java index 210603f5..05c66ffb 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2Datasource.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2Datasource.java @@ -4,8 +4,11 @@ import org.jumpserver.chen.framework.datasource.DatasourceFactory; import org.jumpserver.chen.framework.datasource.base.BaseDatasource; import org.jumpserver.chen.framework.datasource.entity.DBConnectInfo; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; +import org.jumpserver.chen.framework.datasource.plan.ExecutionPlanDialect; public class DB2Datasource extends BaseDatasource { + private final ExecutionPlanDialect executionPlanDialect = new Db2ExecutionPlanDialect(); static { DatasourceFactory.Register(DB2Datasource.class); @@ -13,6 +16,7 @@ public class DB2Datasource extends BaseDatasource { public DB2Datasource(DBConnectInfo dbConnectInfo) { this.connectionManager = new DB2ConnectionManager(dbConnectInfo, this); + this.metadataCatalog = new MetadataCatalog(this.connectionManager, new DB2MetadataProvider(this.connectionManager)); this.resourceBrowser = new DB2ResourceBrowser(this.connectionManager); this.actionHandler = new DB2ActionHandler(); } @@ -27,4 +31,9 @@ public String getName() { public DbType getDruidDbType() { return DbType.db2; } + + @Override + public ExecutionPlanDialect getExecutionPlanDialect() { + return this.executionPlanDialect; + } } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2MetadataProvider.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2MetadataProvider.java new file mode 100644 index 00000000..7771fb5f --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2MetadataProvider.java @@ -0,0 +1,275 @@ +package org.jumpserver.chen.modules.db2; + +import org.jumpserver.chen.framework.datasource.ConnectionManager; +import org.jumpserver.chen.framework.datasource.metadata.BaseDatabaseMetadataProvider; +import org.jumpserver.chen.framework.datasource.metadata.ColumnMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ConstraintMetadata; +import org.jumpserver.chen.framework.datasource.metadata.IndexMetadata; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCapabilities; +import org.jumpserver.chen.framework.datasource.metadata.ObjectProperties; +import org.jumpserver.chen.framework.datasource.metadata.ForeignKeyMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ObjectRef; +import org.jumpserver.chen.framework.datasource.metadata.PrimaryKeyMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ObjectStatistics; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; +import org.jumpserver.chen.framework.datasource.metadata.RelationMetadata; +import org.jumpserver.chen.framework.datasource.metadata.RelationScope; +import org.jumpserver.chen.framework.datasource.metadata.SchemaMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ScopeKind; +import org.jumpserver.chen.framework.datasource.metadata.ScopeProperties; +import org.jumpserver.chen.framework.datasource.metadata.ScopeRef; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class DB2MetadataProvider extends BaseDatabaseMetadataProvider { + + public DB2MetadataProvider(ConnectionManager connectionManager) { + super(connectionManager); + } + + private static final MetadataCapabilities CAPABILITIES = new MetadataCapabilities( + false, true, true, true, true, true, true, true, false, + true, true, false, true, false, true, true, true, false + ); + + private static final String SQL_SCHEMAS = "SELECT RTRIM(SCHEMANAME) AS name FROM syscat.schemata"; + + private static final String SQL_TABLES = """ + SELECT RTRIM(t.tabname) AS name, + NULL AS engine, + CHAR(t.codepage) AS character_set, + NULL AS collation, + t.remarks AS comment + FROM syscat.tables t + WHERE t.tabschema = ? AND t.type = 'T' + ORDER BY t.tabname + """; + + private static final String SQL_TABLE_STATS = """ + WITH index_pages AS ( + SELECT tabschema, tabname, SUM(COALESCE(nleaf, 0)) AS pages + FROM syscat.indexes + GROUP BY tabschema, tabname + ) + SELECT RTRIM(t.tabname) AS name, + t.card AS estimated_rows, + (COALESCE(t.npages, 0) + COALESCE(i.pages, 0)) * ts.pagesize AS total_size_bytes + FROM syscat.tables t + LEFT JOIN syscat.tablespaces ts ON ts.tbspaceid = t.tbspaceid + LEFT JOIN index_pages i ON i.tabschema = t.tabschema AND i.tabname = t.tabname + WHERE t.tabschema = ? AND t.type = 'T' + ORDER BY t.tabname + """; + + private static final String SQL_VIEWS = """ + SELECT RTRIM(tabname) AS name, 'VIEW' AS type, remarks AS comment + FROM syscat.tables + WHERE tabschema = ? AND type = 'V' + ORDER BY tabname + """; + + private static final String SQL_INDEXES = """ + SELECT RTRIM(i.indname) AS name, + RTRIM(i.tabname) AS table_name, + RTRIM(c.colname) AS column_name, + CASE WHEN i.uniquerule IN ('P', 'U') THEN 1 ELSE 0 END AS is_unique, + i.indextype AS method, + NULL AS definition, + c.colorder AS sort_order + FROM syscat.indexes i + LEFT JOIN syscat.indexcoluse c + ON c.indschema = i.indschema AND c.indname = i.indname + WHERE i.tabschema = ? + ORDER BY i.tabname, i.indname, c.colseq + """; + + private static final String SQL_COLUMNS = """ + SELECT RTRIM(COLNAME) AS name, RTRIM(TABNAME) AS table_name, COLNO + 1 AS ordinal, + TYPENAME AS native_type, TYPENAME AS jdbc_type_name, + LENGTH AS size, SCALE AS scale, NULLS AS nullable, + DEFAULT AS default_value, REMARKS AS comment + FROM syscat.COLUMNS + WHERE TABSCHEMA = ? AND TABNAME IN (__IN__) + ORDER BY TABNAME, COLNO + """; + + private static final String SQL_TABLE_INDEXES = """ + SELECT RTRIM(i.indname) AS name, RTRIM(i.tabname) AS table_name, + RTRIM(c.colname) AS column_name, + CASE WHEN i.uniquerule IN ('P', 'U') THEN 1 ELSE 0 END AS is_unique, + i.indextype AS method, NULL AS definition, c.colorder AS sort_order + FROM syscat.indexes i + LEFT JOIN syscat.indexcoluse c ON c.indschema = i.indschema AND c.indname = i.indname + WHERE i.tabschema = ? AND i.tabname IN (__IN__) + ORDER BY i.tabname, i.indname, c.colseq + """; + + @Override + public MetadataCapabilities capabilities() { + return CAPABILITIES; + } + + @Override + public List listSchemas(String catalog) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_SCHEMAS, List.of())) { + result.add(new SchemaMetadata(null, stringValue(row, "name"))); + } + return result; + } + + @Override + public List listRelations(RelationScope scope, Set kinds) throws SQLException { + var result = new ArrayList(); + if (kinds.contains(RelationKind.TABLE)) { + for (var row : query(SQL_TABLES, List.of(scope.schema()))) { + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + stringValue(row, "comment"), + null, + stringValue(row, "character_set"), + null + )); + } + } + if (kinds.contains(RelationKind.VIEW)) { + for (var row : query(SQL_VIEWS, List.of(scope.schema()))) { + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.VIEW), + stringValue(row, "comment"), + null, + null, + null + )); + } + } + return result; + } + + @Override + public List listColumns(List relations) throws SQLException { + return loadColumns(SQL_COLUMNS, relations); + } + + @Override + public List listIndexes(RelationScope scope) throws SQLException { + return groupIndexRows(query(SQL_INDEXES, List.of(scope.schema())), scope); + } + + @Override + public List listIndexes(List relations) throws SQLException { + if (relations.isEmpty()) { + return List.of(); + } + var first = relations.get(0); + return groupIndexRows(queryKeys(SQL_TABLE_INDEXES, relations, first.schema()), + new RelationScope(first.catalog(), first.schema())); + } + + @Override + public List listStatistics(RelationScope scope) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_TABLE_STATS, List.of(scope.schema()))) { + result.add(new ObjectStatistics( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + longValue(row, "estimated_rows"), + longValue(row, "total_size_bytes") + )); + } + return result; + } + + private static final String SQL_TABLE_PROPERTIES = """ + SELECT TABNAME, TBSPACE, TABSCHEMA, TYPE, STATUS, COLCOUNT, ACTIVE_BLOCKS, AVGROWSIZE, OWNER, CREATE_TIME + FROM syscat.TABLES + WHERE TABSCHEMA = ? AND TABNAME = ? AND TBSPACE IS NOT NULL + """; + + private static final String SQL_SCHEMA_PROPERTIES = """ + SELECT SCHEMANAME, OWNER, DEFINER, CREATE_TIME, REMARKS + FROM syscat.SCHEMATA + WHERE SCHEMANAME = ? + """; + + @Override + public ObjectProperties objectProperties(ObjectRef ref) throws SQLException { + return loadObjectProperties(ref, SQL_TABLE_PROPERTIES); + } + + @Override + public ScopeProperties scopeProperties(ScopeRef ref) throws SQLException { + if (ref.kind() != ScopeKind.SCHEMA) { + return super.scopeProperties(ref); + } + return loadScopeProperties(ref, SQL_SCHEMA_PROPERTIES, List.of(ref.scope().schema())); + } + + private static final String SQL_PRIMARY_KEYS = """ + SELECT RTRIM(k.tabname) AS table_name, RTRIM(k.colname) AS column_name, RTRIM(t.constname) AS name + FROM syscat.tabconst t + JOIN syscat.keycoluse k ON k.tabschema = t.tabschema AND k.tabname = t.tabname AND k.constname = t.constname + WHERE t.type = 'P' AND t.tabschema = ? AND t.tabname IN (__IN__) + ORDER BY t.tabname, t.constname, k.colseq + """; + + private static final String SQL_FOREIGN_KEYS = """ + SELECT RTRIM(f.tabname) AS table_name, RTRIM(k.colname) AS column_name, RTRIM(f.constname) AS name, + RTRIM(f.reftabschema) AS referenced_schema, RTRIM(f.reftabname) AS referenced_table, + RTRIM(rk.colname) AS referenced_column + FROM syscat.references f + JOIN syscat.keycoluse k ON k.tabschema = f.tabschema AND k.tabname = f.tabname AND k.constname = f.constname + JOIN syscat.keycoluse rk ON rk.tabschema = f.reftabschema AND rk.tabname = f.reftabname + AND rk.constname = f.refkeyname AND rk.colseq = k.colseq + WHERE f.tabschema = ? AND f.tabname IN (__IN__) + ORDER BY f.tabname, f.constname, k.colseq + """; + + private static final String SQL_CONSTRAINTS = """ + SELECT RTRIM(t.tabname) AS table_name, RTRIM(t.constname) AS name, + CASE t.type WHEN 'P' THEN 'PRIMARY KEY' WHEN 'F' THEN 'FOREIGN KEY' + WHEN 'U' THEN 'UNIQUE' WHEN 'K' THEN 'CHECK' END AS constraint_type, + RTRIM(k.colname) AS column_name, RTRIM(r.reftabschema) AS referenced_schema, + RTRIM(r.reftabname) AS referenced_table, RTRIM(rk.colname) AS referenced_column, + ch.text AS definition + FROM syscat.tabconst t + LEFT JOIN syscat.keycoluse k + ON k.tabschema = t.tabschema AND k.tabname = t.tabname AND k.constname = t.constname + LEFT JOIN syscat.references r + ON r.tabschema = t.tabschema AND r.tabname = t.tabname AND r.constname = t.constname + LEFT JOIN syscat.keycoluse rk + ON rk.tabschema = r.reftabschema AND rk.tabname = r.reftabname + AND rk.constname = r.refkeyname AND rk.colseq = k.colseq + LEFT JOIN syscat.checks ch + ON ch.tabschema = t.tabschema AND ch.tabname = t.tabname AND ch.constname = t.constname + WHERE t.type IN ('P', 'F', 'U', 'K') + AND t.tabschema = ? AND t.tabname IN (__IN__) + ORDER BY t.tabname, t.constname, k.colseq + """; + + @Override + public List listPrimaryKeys(List relations) throws SQLException { + var first = relations.get(0); + return groupPrimaryKeys(queryKeys(SQL_PRIMARY_KEYS, relations, first.schema()), + new RelationScope(first.catalog(), first.schema())); + } + + @Override + public List listForeignKeys(List relations) throws SQLException { + var first = relations.get(0); + return groupForeignKeys(queryKeys(SQL_FOREIGN_KEYS, relations, first.schema()), + new RelationScope(first.catalog(), first.schema())); + } + + @Override + public List listConstraints(List relations) throws SQLException { + if (relations.isEmpty()) { + return List.of(); + } + var first = relations.get(0); + return groupConstraints(queryKeys(SQL_CONSTRAINTS, relations, first.schema()), + new RelationScope(first.catalog(), first.schema())); + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2ResourceBrowser.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2ResourceBrowser.java index 11d497b2..c0ddc428 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2ResourceBrowser.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2ResourceBrowser.java @@ -2,49 +2,9 @@ import org.jumpserver.chen.framework.datasource.ConnectionManager; import org.jumpserver.chen.framework.datasource.base.BaseResourceBrowser; -import org.jumpserver.chen.framework.datasource.entity.resource.Field; -import org.jumpserver.chen.framework.datasource.entity.resource.Schema; -import org.jumpserver.chen.framework.datasource.entity.resource.Table; -import org.jumpserver.chen.framework.datasource.entity.resource.View; -import org.jumpserver.chen.framework.datasource.sql.SQL; - -import java.sql.SQLException; -import java.util.List; - public class DB2ResourceBrowser extends BaseResourceBrowser { public DB2ResourceBrowser(ConnectionManager connectionManager) { - super(connectionManager, new DB2SQLHintsHandler(connectionManager)); - } - - private static final String SQL_GET_SCHEMAS = "select SCHEMANAME AS NAME from syscat.schemata"; - - @Override - public List getSchemas() throws SQLException { - return this.getSchemas(SQL.of(SQL_GET_SCHEMAS)); + super(connectionManager); } - - private static final String SQL_GET_TABLES = "select TABNAME from syscat.TABLES where TABSCHEMA = '?' and TYPE = 'T'"; - - @Override - public List
getTables(String schema) throws SQLException { - return this.getTables(SQL.of(SQL_GET_TABLES, schema)); - } - - private static final String SQL_GET_VIEWS = "select TABNAME from syscat.TABLES where TABSCHEMA = '?' and TYPE = 'V'"; - - @Override - public List getViews(String schema) throws SQLException { - return this.getViews(SQL.of(SQL_GET_VIEWS, schema)); - } - - private static final String SQL_GET_FIELDS = "select COLNAME, TYPENAME, LENGTH, DEFAULT, REMARKS from syscat.COLUMNS where TABSCHEMA = '?' and TABNAME = '?'"; - - @Override - public List getFields(String schema, String table) throws SQLException { - return this.getFields(SQL.of(SQL_GET_FIELDS, schema, table)); - } - - - } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2SQLHintsHandler.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2SQLHintsHandler.java deleted file mode 100644 index 2f00b674..00000000 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2SQLHintsHandler.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.jumpserver.chen.modules.db2; - -import org.jumpserver.chen.framework.datasource.ConnectionManager; -import org.jumpserver.chen.framework.datasource.base.BaseSQLHintsHandler; -import org.jumpserver.chen.framework.datasource.entity.resource.Field; -import org.jumpserver.chen.framework.datasource.entity.resource.Table; -import org.jumpserver.chen.framework.datasource.sql.SQL; - -import java.sql.SQLException; -import java.util.List; -import java.util.Map; - -public class DB2SQLHintsHandler extends BaseSQLHintsHandler { - private final ConnectionManager connectionManager; - - public DB2SQLHintsHandler(ConnectionManager connectionManager) { - this.connectionManager = connectionManager; - } - - private static final String SQL_GET_SCHEMA_ALL_FIELDS = "select COLNAME, TABSCHEMA, TABNAME from syscat.COLUMNS where TABSCHEMA = '?'"; - - public List getAllFields(String schema) throws SQLException { - return this.connectionManager - .getSqlActuator() - .getObjects(SQL.of(SQL_GET_SCHEMA_ALL_FIELDS, schema).getSql(), Field.class, Map.of("name", 1, "schema", 2, "table", 3)); - - } - - private static final String SQL_GET_ALL_TABLES = "select TABNAME, TABSCHEMA from syscat.TABLES where TABSCHEMA = '?'"; - - public List
getALlTables(String context) throws SQLException { - return this.connectionManager.getSqlActuator() - .getObjects(SQL.of(SQL_GET_ALL_TABLES, context) - .getSql(), Table.class, Map.of("name", 1, "schema", 2)); - } -} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/Db2ExecutionPlanDialect.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/Db2ExecutionPlanDialect.java new file mode 100644 index 00000000..e7fa7159 --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/Db2ExecutionPlanDialect.java @@ -0,0 +1,767 @@ +package org.jumpserver.chen.modules.db2; + +import org.jumpserver.chen.framework.datasource.analysis.SqlStatementAnalysis; +import org.jumpserver.chen.framework.datasource.plan.BaseExecutionPlanDialect; +import org.jumpserver.chen.framework.datasource.plan.ConnectionInvalidatedException; +import org.jumpserver.chen.framework.datasource.plan.DialectPlanResult; +import org.jumpserver.chen.framework.datasource.plan.ExecutionPlanCapabilities; +import org.jumpserver.chen.framework.datasource.plan.PlanCodes; +import org.jumpserver.chen.framework.datasource.plan.PlanDatabase; +import org.jumpserver.chen.framework.datasource.plan.PlanDiagnostic; +import org.jumpserver.chen.framework.datasource.plan.PlanEffects; +import org.jumpserver.chen.framework.datasource.plan.PlanI18n; +import org.jumpserver.chen.framework.datasource.plan.PlanExecutionContext; +import org.jumpserver.chen.framework.datasource.plan.PlanPrerequisite; +import org.jumpserver.chen.framework.datasource.plan.PlanRawFormat; +import org.jumpserver.chen.framework.datasource.plan.PlanStatus; +import org.jumpserver.chen.framework.datasource.plan.PlanTransactionState; + +import java.security.SecureRandom; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.HexFormat; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +public class Db2ExecutionPlanDialect extends BaseExecutionPlanDialect { + static final String RAW_FORMAT_VERSION = "chen-table-v1"; + private static final SecureRandom RANDOM = new SecureRandom(); + private static final Set SAFE_SCHEMAS = Set.of( + "sysibm", "sysfun", "sysproc", "sysibmadm" + ); + private static final Set SAFE_FUNCTIONS = Set.of( + "abs", "acos", "asin", "atan", "atan2", "avg", "bigint", "ceil", "ceiling", + "char", "coalesce", "concat", "cos", "count", "date", "day", "days", "decimal", + "decode", "degrees", "dense_rank", "double", "exp", "extract", "first_value", + "float", "floor", "greatest", "hex", "hour", "integer", "lag", "last_day", + "last_value", "lead", "least", "length", "ln", "locate", "log", "log10", "lower", + "lpad", "ltrim", "max", "microsecond", "midnight_seconds", "min", "minute", "mod", + "month", "months_between", "nullif", "nth_value", "ntile", "power", "quarter", + "radians", "rank", "regexp_count", "regexp_instr", "regexp_like", "regexp_replace", + "regexp_substr", "replace", "round", "row_number", "rpad", "rtrim", "second", + "sign", "sin", "smallint", "sqrt", "stddev", "stddev_samp", "substring", "sum", + "tan", "time", "timestamp", "translate", "trim", "trunc", "truncate", "upper", + "variance", "var_samp", "varchar", "week", "year" + ); + + private final ExecutionPlanCapabilities capabilities = ExecutionPlanCapabilities.db2(); + + @Override + public PlanDatabase database() { + return PlanDatabase.db2; + } + + @Override + public ExecutionPlanCapabilities capabilities() { + return capabilities; + } + + @Override + public DialectPlanResult explainEstimated(PlanExecutionContext context, SqlStatementAnalysis statement) + throws SQLException { + try { + return explain(context, statement); + } catch (ConnectionInvalidatedException e) { + throw e; + } catch (SQLException e) { + if (isConnectionBroken(e)) { + throw invalidated(context, e, null, null, List.of(), effects( + PlanEffects.AuxiliaryStorage.UNKNOWN, + PlanEffects.TransactionState.UNKNOWN, + PlanEffects.ConnectionDisposition.DISCARD + )); + } + throw e; + } + } + + private DialectPlanResult explain(PlanExecutionContext context, SqlStatementAnalysis statement) + throws SQLException { + List prerequisites = new ArrayList<>(); + PlanDiagnostic unsafe = unsafeFunctions(statement); + if (unsafe != null) { + prerequisites.add(PlanPrerequisite.unmet( + PlanCodes.UNSAFE_TO_ESTIMATE, + unsafe.message(), + PlanI18n.msg("Plan.RemoveUnsafeFunctions", "Remove user-defined or planning-time-unsafe functions and retry") + )); + return unmet(context.serverVersion(), prerequisites, unsafe); + } + + TransactionCheck transaction = checkTransaction(context); + if (transaction.failure() != null) { + prerequisites.add(transaction.failure()); + return unmet( + context.serverVersion(), + prerequisites, + PlanDiagnostic.of(PlanCodes.TRANSACTION_CONTEXT_UNSAFE, transaction.failure().message()) + ); + } + prerequisites.add(PlanPrerequisite.met("transaction", transaction.message())); + + ServerIdentity server; + try { + server = serverIdentity(context); + } catch (SQLException e) { + if (isCancelled(context, e) || isConnectionBroken(e)) { + throw e; + } + PlanDiagnostic diagnostic = sqlDiagnostic( + PlanCodes.UNSUPPORTED_DATABASE, + "The connected server is not a verified DB2 LUW target: " + message(e), + e + ); + prerequisites.add(PlanPrerequisite.unmet( + diagnostic.code(), diagnostic.message(), + "Connect to DB2 for Linux, UNIX, and Windows" + )); + return unmet(context.serverVersion(), prerequisites, diagnostic); + } + prerequisites.add(PlanPrerequisite.met( + "db2-luw", + server.productName() + " " + server.productVersion() + "; service " + server.serviceLevel() + )); + prerequisites.add(PlanPrerequisite.met( + "jdbc-driver", + server.driverName() + " " + server.driverVersion() + )); + + Db2ExplainTables.Resolution resolution; + try { + resolution = Db2ExplainTables.resolve(context); + } catch (SQLException e) { + if (isCancelled(context, e) || isConnectionBroken(e)) { + throw e; + } + String code = classifyTableError(e); + PlanDiagnostic diagnostic = sqlDiagnostic(code, message(e), e); + prerequisites.add(PlanPrerequisite.unmet(code, diagnostic.message(), remediation(code))); + return unmet(server.displayVersion(), prerequisites, diagnostic); + } + if (resolution.failure() != null) { + prerequisites.add(resolution.failure()); + return unmet( + server.displayVersion(), + prerequisites, + PlanDiagnostic.of(resolution.failure().code(), resolution.failure().message()) + ); + } + Db2ExplainTables.Target target = resolution.target(); + prerequisites.add(PlanPrerequisite.met( + "explain-table-schema", + "Authorization ID " + target.identity().authorizationId() + + ", CURRENT SCHEMA " + target.identity().currentSchema() + + ", using complete Explain table set in " + target.schema() + )); + prerequisites.add(PlanPrerequisite.met( + "explain-table-columns", + "DB2 Explain tables have compatible request keys and plan columns" + )); + prerequisites.add(PlanPrerequisite.met( + "explain-table-privileges", + "SELECT, INSERT, and DELETE privileges available for the resolved Explain table set" + )); + prerequisites.add(PlanPrerequisite.met("statement", PlanI18n.msg("Plan.SingleSelect", "Single SELECT"))); + + return generateReadAndCleanup( + context, statement, server.displayVersion(), target, transaction, prerequisites + ); + } + + private DialectPlanResult generateReadAndCleanup( + PlanExecutionContext context, + SqlStatementAnalysis statement, + String serverVersion, + Db2ExplainTables.Target target, + TransactionCheck transaction, + List prerequisites + ) throws SQLException { + int queryNo = positiveQueryNo(); + String queryTag = queryTag(); + String requestIdentity = "QUERYNO=" + queryNo + ", QUERYTAG=" + queryTag; + Timestamp windowStart = Db2PlanAccess.currentTimestamp(context, true); + Timestamp windowEnd = null; + Db2PlanAccess.LocateResult located = null; + Db2PlanAccess.Capture raw = null; + SQLException originalError = null; + boolean explainSucceeded = false; + + try { + context.throwIfCancelled(); + Db2PlanAccess.explain(context, statement, queryNo, queryTag); + explainSucceeded = true; + } catch (SQLException e) { + originalError = e; + } + + try { + windowEnd = Db2PlanAccess.currentTimestamp(context, false); + located = Db2PlanAccess.locate( + context, target, queryNo, queryTag, windowStart, windowEnd + ); + if (located.keys().size() == 1 && located.complete() && explainSucceeded) { + raw = Db2PlanAccess.read(context, target, located); + } else { + raw = new Db2PlanAccess.Capture( + materializedLocator(located, context.maxRawBytes()), + located.statement().truncated() + ); + } + } catch (SQLException e) { + originalError = accumulate(originalError, e); + } + + PlanDiagnostic identityError = null; + if (located != null && explainSucceeded) { + if (located.keys().isEmpty()) { + identityError = PlanDiagnostic.of( + PlanCodes.PLAN_REQUEST_NOT_FOUND, + "DB2 EXPLAIN_STATEMENT contains no row for " + requestIdentity + + " in request window " + windowStart + ".." + windowEnd + ); + } else if (located.keys().size() > 1 || !located.complete()) { + identityError = PlanDiagnostic.of( + PlanCodes.PLAN_REQUEST_AMBIGUOUS, + "DB2 EXPLAIN_STATEMENT does not contain one complete, unique plan request for " + + requestIdentity + + " in request window; no ambiguous or incomplete request was deleted" + ); + } + } + + SQLException cleanupError = null; + Db2PlanAccess.StatementKey key = located != null && located.keys().size() == 1 + ? located.keys().get(0) + : null; + List cleanupKeys = located != null && located.complete() + ? located.requestKeys() + : List.of(); + for (Db2PlanAccess.StatementKey cleanupKey : cleanupKeys) { + try { + Db2PlanAccess.cleanup(context, target, cleanupKey); + } catch (SQLException e) { + cleanupError = accumulate(cleanupError, e); + } + } + + boolean ambiguousResidual = explainSucceeded + && (located == null || located.keys().size() != 1 || !located.complete()); + if (cleanupError != null || ambiguousResidual) { + PlanDiagnostic cleanupDiagnostic = cleanupError == null + ? residualDiagnostic(identityError, requestIdentity, windowStart, windowEnd, located) + : cleanupDiagnostic(cleanupError, requestIdentity, key); + if (!canReuseWithResidual(context, transaction, originalError, cleanupError)) { + throw invalidated( + context, + originalError, + cleanupError, + raw, + prerequisites, + effects( + PlanEffects.AuxiliaryStorage.RESIDUAL, + PlanEffects.TransactionState.UNKNOWN, + PlanEffects.ConnectionDisposition.DISCARD + ), + cleanupDiagnostic, + identityError + ); + } + PlanEffects residualEffects = effects( + PlanEffects.AuxiliaryStorage.RESIDUAL, + transactionEffect(transaction, !cleanupKeys.isEmpty()), + PlanEffects.ConnectionDisposition.REUSE + ); + if (identityError != null) { + return diagnosticResult( + serverVersion, PlanStatus.ERROR, raw, prerequisites, residualEffects, + identityError, List.of(cleanupDiagnostic) + ); + } + if (originalError != null) { + return errorResult( + context, serverVersion, originalError, raw, prerequisites, + residualEffects, List.of(cleanupDiagnostic) + ); + } + return resultFromRaw( + serverVersion, context, raw, prerequisites, residualEffects, + List.of(cleanupDiagnostic) + ); + } + + PlanEffects completedEffects = effects( + cleanupKeys.isEmpty() ? PlanEffects.AuxiliaryStorage.NONE : PlanEffects.AuxiliaryStorage.CLEANED, + transactionEffect(transaction, !cleanupKeys.isEmpty()), + PlanEffects.ConnectionDisposition.REUSE + ); + if (identityError != null) { + return diagnosticResult( + serverVersion, PlanStatus.ERROR, raw, prerequisites, completedEffects, + identityError, List.of() + ); + } + if (originalError != null) { + if (isConnectionBroken(originalError) || isTransactionRollback(originalError)) { + throw invalidated( + context, originalError, null, raw, prerequisites, + effects( + cleanupKeys.isEmpty() + ? PlanEffects.AuxiliaryStorage.UNKNOWN + : PlanEffects.AuxiliaryStorage.CLEANED, + PlanEffects.TransactionState.FAILED, + PlanEffects.ConnectionDisposition.DISCARD + ) + ); + } + return errorResult( + context, serverVersion, originalError, raw, prerequisites, completedEffects, List.of() + ); + } + return resultFromRaw(serverVersion, context, raw, prerequisites, completedEffects, List.of()); + } + + private DialectPlanResult resultFromRaw( + String serverVersion, + PlanExecutionContext context, + Db2PlanAccess.Capture raw, + List prerequisites, + PlanEffects effects, + List extraWarnings + ) { + List warnings = new ArrayList<>(extraWarnings); + if (raw == null) { + warnings.add(PlanDiagnostic.of( + PlanCodes.PLAN_PARSE_FAILED, + "DB2 Explain tables returned no materialized result" + )); + return new DialectPlanResult( + serverVersion, PlanStatus.RAW_ONLY, List.of(), null, PlanRawFormat.TABLE, + RAW_FORMAT_VERSION, false, capabilities, prerequisites, effects, null, warnings + ); + } + if (raw.truncated()) { + warnings.add(PlanDiagnostic.of( + PlanCodes.PLAN_LIMIT_REACHED, + "DB2 Explain table result exceeded a display limit" + )); + return new DialectPlanResult( + serverVersion, PlanStatus.RAW_ONLY, List.of(), raw.json(), PlanRawFormat.TABLE, + RAW_FORMAT_VERSION, true, capabilities, prerequisites, effects, null, warnings + ); + } + Db2PlanParser.ParseResult parsed = Db2PlanParser.parse( + raw.json(), context.maxNodes(), context.maxDepth() + ); + warnings.addAll(parsed.warnings()); + return new DialectPlanResult( + serverVersion, + parsed.structured() ? PlanStatus.SUCCESS : PlanStatus.RAW_ONLY, + parsed.roots(), + raw.json(), + PlanRawFormat.TABLE, + RAW_FORMAT_VERSION, + false, + capabilities, + prerequisites, + effects, + null, + warnings + ); + } + + private DialectPlanResult errorResult( + PlanExecutionContext context, + String serverVersion, + SQLException error, + Db2PlanAccess.Capture raw, + List prerequisites, + PlanEffects effects, + List warnings + ) { + PlanStatus status = isCancelled(context, error) ? PlanStatus.CANCELLED : PlanStatus.ERROR; + String code = status == PlanStatus.CANCELLED ? PlanCodes.CANCELLED : classifyTableError(error); + if (PlanCodes.SQL_ERROR.equals(code) && isTransactionRollback(error)) { + code = PlanCodes.TRANSACTION_CONTEXT_UNSAFE; + } + return diagnosticResult( + serverVersion, status, raw, prerequisites, effects, + sqlDiagnostic(code, message(error), error), warnings + ); + } + + private DialectPlanResult diagnosticResult( + String serverVersion, + PlanStatus status, + Db2PlanAccess.Capture raw, + List prerequisites, + PlanEffects effects, + PlanDiagnostic error, + List warnings + ) { + return new DialectPlanResult( + serverVersion, status, List.of(), raw == null ? null : raw.json(), + raw == null ? null : PlanRawFormat.TABLE, + raw == null ? null : RAW_FORMAT_VERSION, + raw != null && raw.truncated(), capabilities, prerequisites, effects, error, warnings + ); + } + + private TransactionCheck checkTransaction(PlanExecutionContext context) throws SQLException { + if (context.transactionProbeFailed()) { + String detail = context.transactionProbeDetail(); + String message = detail == null || detail.isBlank() + ? "DB2 transaction state probe failed" + : "DB2 transaction state probe failed: " + detail; + return TransactionCheck.failed(PlanPrerequisite.unknown( + PlanCodes.TRANSACTION_CONTEXT_UNSAFE, + message, + "Reconnect and retry from a known transaction state" + )); + } + PlanTransactionState state = context.transactionState(); + boolean autoCommit = context.connection().getAutoCommit(); + if (state == PlanTransactionState.UNKNOWN) { + return TransactionCheck.failed(PlanPrerequisite.unknown( + PlanCodes.TRANSACTION_CONTEXT_UNSAFE, + "DB2 transaction state is unknown", + "Reconnect and retry from an idle auto-commit connection or an active transaction" + )); + } + if (state == PlanTransactionState.TRANSACTION_FAILED) { + return TransactionCheck.failed(PlanPrerequisite.unmet( + PlanCodes.TRANSACTION_CONTEXT_UNSAFE, + "The current DB2 transaction is not usable", + PlanI18n.msg("Plan.FinishTxAndRetry", "Finish the existing transaction and retry") + )); + } + if (state == PlanTransactionState.MANUAL_COMMIT_IDLE) { + return TransactionCheck.failed(PlanPrerequisite.unmet( + PlanCodes.TRANSACTION_CONTEXT_UNSAFE, + "A manual-commit idle connection cannot start DB2 Explain auxiliary DML", + "Use an idle auto-commit connection or retry inside an already-active transaction" + )); + } + if (state == PlanTransactionState.AUTO_COMMIT && !autoCommit) { + return TransactionCheck.failed(PlanPrerequisite.unknown( + PlanCodes.TRANSACTION_CONTEXT_UNSAFE, + "DB2 transaction state conflicts with JDBC auto-commit state", + "Reconnect and retry from a known transaction state" + )); + } + if (state == PlanTransactionState.TRANSACTION_ACTIVE && autoCommit) { + return TransactionCheck.failed(PlanPrerequisite.unmet( + PlanCodes.TRANSACTION_CONTEXT_UNSAFE, + "An active DB2 unit of work with JDBC auto-commit enabled is unsafe for auxiliary DML", + "Finish the transaction and retry from a known state" + )); + } + return new TransactionCheck( + state, + autoCommit, + state == PlanTransactionState.TRANSACTION_ACTIVE + ? "DB2 Explain DML will participate in the active unit of work" + : "Idle auto-commit connection", + null + ); + } + + private static ServerIdentity serverIdentity(PlanExecutionContext context) throws SQLException { + DatabaseMetaData metadata = context.connection().getMetaData(); + String serviceLevel; + try (Db2PlanJdbc.Tracked tracked = Db2PlanJdbc.statement(context, true); + ResultSet resultSet = tracked.statement().executeQuery( + "SELECT SERVICE_LEVEL FROM SYSIBMADM.ENV_INST_INFO FETCH FIRST 1 ROW ONLY" + )) { + if (!resultSet.next()) { + throw new SQLException("SYSIBMADM.ENV_INST_INFO returned no DB2 LUW service level"); + } + serviceLevel = resultSet.getString(1); + } + return new ServerIdentity( + metadata.getDatabaseProductName(), + metadata.getDatabaseProductVersion(), + serviceLevel, + metadata.getDriverName(), + metadata.getDriverVersion() + ); + } + + static PlanDiagnostic unsafeFunctions(SqlStatementAnalysis statement) { + for (String rawName : statement.functionNames()) { + String name = rawName == null ? "" : rawName.trim(); + if (name.isEmpty()) continue; + String normalized = name.replace("\"", "").toLowerCase(Locale.ROOT); + int dot = normalized.lastIndexOf('.'); + String owner = dot < 0 ? "" : normalized.substring(0, dot); + String function = dot < 0 ? normalized : normalized.substring(dot + 1); + if ((!owner.isEmpty() && !SAFE_SCHEMAS.contains(owner)) || !SAFE_FUNCTIONS.contains(function)) { + return PlanDiagnostic.of( + PlanCodes.UNSAFE_TO_ESTIMATE, + "Function " + name + " is not a known side-effect-free DB2 LUW builtin" + ); + } + } + return null; + } + + private DialectPlanResult unmet( + String serverVersion, + List prerequisites, + PlanDiagnostic error + ) { + return new DialectPlanResult( + serverVersion, PlanStatus.PREREQUISITES_UNMET, List.of(), null, null, null, + false, capabilities, prerequisites, PlanEffects.unchangedReuse(), error, List.of() + ); + } + + private static boolean canReuseWithResidual( + PlanExecutionContext context, + TransactionCheck transaction, + SQLException original, + SQLException cleanup + ) { + if (transaction.participates() || transaction.state() != PlanTransactionState.AUTO_COMMIT + || !transaction.autoCommit() || context.isCancelled() + || isConnectionBroken(original) || isConnectionBroken(cleanup) + || isTransactionRollback(original) || isTransactionRollback(cleanup)) { + return false; + } + try { + return context.connection().getAutoCommit() && context.connection().isValid(2); + } catch (SQLException e) { + return false; + } + } + + private ConnectionInvalidatedException invalidated( + PlanExecutionContext context, + SQLException original, + SQLException cleanup, + Db2PlanAccess.Capture raw, + List prerequisites, + PlanEffects effects, + PlanDiagnostic... extraWarnings + ) { + PlanDiagnostic originalDiagnostic = original == null + ? null + : sqlDiagnostic( + isCancelled(context, original) ? PlanCodes.CANCELLED : classifyTableError(original), + message(original), original + ); + PlanDiagnostic cleanupDiagnostic = cleanup == null + ? null + : cleanupDiagnostic(cleanup, "unknown request", null); + List warnings = new ArrayList<>(); + if (cleanupDiagnostic != null) warnings.add(cleanupDiagnostic); + if (extraWarnings != null) { + for (PlanDiagnostic warning : extraWarnings) { + if (warning != null) warnings.add(warning); + } + } + PlanDiagnostic primary = originalDiagnostic; + if (primary == null && extraWarnings != null) { + for (PlanDiagnostic diagnostic : extraWarnings) { + if (diagnostic != null && !PlanCodes.AUXILIARY_CLEANUP_FAILED.equals(diagnostic.code())) { + primary = diagnostic; + break; + } + } + } + if (primary == null) { + primary = cleanupDiagnostic == null + ? PlanDiagnostic.of(PlanCodes.CONNECTION_INVALIDATED, "DB2 connection cannot be reused") + : cleanupDiagnostic; + } + DialectPlanResult partial = diagnosticResult( + context.serverVersion(), PlanStatus.CONNECTION_INVALIDATED, raw, prerequisites, + effects, primary, warnings + ); + Throwable cause = original != null ? original : cleanup; + return new ConnectionInvalidatedException( + primary.message(), + cause, + primary, + cleanupDiagnostic, + partial + ); + } + + private ConnectionInvalidatedException invalidated( + PlanExecutionContext context, + SQLException original, + SQLException cleanup, + Db2PlanAccess.Capture raw, + List prerequisites, + PlanEffects effects + ) { + return invalidated(context, original, cleanup, raw, prerequisites, effects, new PlanDiagnostic[0]); + } + + private static PlanDiagnostic residualDiagnostic( + PlanDiagnostic identityError, + String requestIdentity, + Timestamp start, + Timestamp end, + Db2PlanAccess.LocateResult located + ) { + String candidates = located == null + ? "locator unavailable" + : located.keys().isEmpty() + ? "no full statement key" + : located.keys().stream().map(Db2PlanAccess.StatementKey::identity).toList().toString(); + return PlanDiagnostic.of( + PlanCodes.AUXILIARY_CLEANUP_FAILED, + "DB2 Explain request could not be cleaned without risking another request: " + + requestIdentity + ", window=" + start + ".." + end + ", candidates=" + candidates + + (identityError == null ? "" : ", reason=" + identityError.message()) + ); + } + + private static PlanDiagnostic cleanupDiagnostic( + SQLException error, + String requestIdentity, + Db2PlanAccess.StatementKey key + ) { + return new PlanDiagnostic( + PlanCodes.AUXILIARY_CLEANUP_FAILED, + "Failed to delete DB2 Explain rows for " + requestIdentity + + (key == null ? "" : ", statementKey=" + key.identity()) + + ": " + message(error), + error.getSQLState(), + Integer.toString(error.getErrorCode()) + ); + } + + private static String materializedLocator(Db2PlanAccess.LocateResult located, int maxRawBytes) { + com.google.gson.JsonObject envelope = new com.google.gson.JsonObject(); + com.google.gson.JsonArray sets = new com.google.gson.JsonArray(); + sets.add(located.statement().json()); + envelope.add("resultSets", sets); + envelope.addProperty("truncated", located.statement().truncated()); + String json = org.jumpserver.chen.framework.datasource.plan.ExecutionPlanJson.GSON.toJson(envelope); + if (json.getBytes(java.nio.charset.StandardCharsets.UTF_8).length <= maxRawBytes) { + return json; + } + com.google.gson.JsonObject minimal = new com.google.gson.JsonObject(); + minimal.add("resultSets", new com.google.gson.JsonArray()); + minimal.addProperty("truncated", true); + return org.jumpserver.chen.framework.datasource.plan.ExecutionPlanJson.GSON.toJson(minimal); + } + + private static PlanEffects.TransactionState transactionEffect(TransactionCheck transaction, boolean wroteRows) { + return transaction.participates() && wroteRows + ? PlanEffects.TransactionState.PARTICIPATED + : PlanEffects.TransactionState.UNCHANGED; + } + + private static PlanEffects effects( + PlanEffects.AuxiliaryStorage auxiliary, + PlanEffects.TransactionState transaction, + PlanEffects.ConnectionDisposition disposition + ) { + return new PlanEffects(PlanEffects.SessionState.UNCHANGED, transaction, auxiliary, disposition); + } + + private static int positiveQueryNo() { + return RANDOM.nextInt(Integer.MAX_VALUE - 1) + 1; + } + + private static String queryTag() { + byte[] bytes = new byte[8]; + RANDOM.nextBytes(bytes); + return "CHEN" + HexFormat.of().formatHex(bytes).toUpperCase(Locale.ROOT); + } + + private static String classifyTableError(SQLException error) { + if (error == null) return PlanCodes.SQL_ERROR; + String state = error.getSQLState(); + int code = error.getErrorCode(); + if ("42704".equals(state) || code == -204 || code == -219) return PlanCodes.PLAN_TABLE_MISSING; + if ("42703".equals(state) || code == -206) return PlanCodes.PLAN_TABLE_INCOMPATIBLE; + if ((state != null && state.startsWith("425")) || code == -551 || code == -552) { + return PlanCodes.PLAN_PERMISSION_DENIED; + } + return PlanCodes.SQL_ERROR; + } + + private static String remediation(String code) { + return switch (code) { + case PlanCodes.PLAN_TABLE_MISSING -> + "Ask a DBA to install the complete DB2 LUW Explain table set outside Chen"; + case PlanCodes.PLAN_TABLE_INCOMPATIBLE -> + "Ask a DBA to upgrade the DB2 LUW Explain table set outside Chen"; + case PlanCodes.PLAN_PERMISSION_DENIED -> + "Grant catalog access and SELECT, INSERT, and DELETE on the Explain table set"; + default -> "Resolve the DB2 prerequisite error and retry"; + }; + } + + private static boolean isConnectionBroken(SQLException error) { + return error != null && error.getSQLState() != null && error.getSQLState().startsWith("08"); + } + + private static boolean isTransactionRollback(SQLException error) { + return error != null && error.getSQLState() != null && error.getSQLState().startsWith("40"); + } + + private static boolean isCancelled(PlanExecutionContext context, SQLException error) { + String message = error == null ? null : error.getMessage(); + return context.isCancelled() + || (error != null && "57014".equals(error.getSQLState())) + || (message != null && message.toLowerCase(Locale.ROOT).contains("cancel")); + } + + private static PlanDiagnostic sqlDiagnostic(String code, String message, SQLException error) { + return new PlanDiagnostic( + code, + message, + error == null ? null : error.getSQLState(), + error == null ? null : Integer.toString(error.getErrorCode()) + ); + } + + private static SQLException accumulate(SQLException current, SQLException next) { + if (current == null) return next; + current.addSuppressed(next); + return current; + } + + private static String message(SQLException error) { + return error == null || error.getMessage() == null + ? "DB2 execution plan operation failed" + : error.getMessage(); + } + + private record TransactionCheck( + PlanTransactionState state, + boolean autoCommit, + String message, + PlanPrerequisite failure + ) { + static TransactionCheck failed(PlanPrerequisite failure) { + return new TransactionCheck(PlanTransactionState.UNKNOWN, false, null, failure); + } + + boolean participates() { + return state == PlanTransactionState.TRANSACTION_ACTIVE; + } + } + + private record ServerIdentity( + String productName, + String productVersion, + String serviceLevel, + String driverName, + String driverVersion + ) { + String displayVersion() { + return productName + " " + productVersion + " (" + serviceLevel + ")"; + } + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/Db2ExplainTables.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/Db2ExplainTables.java new file mode 100644 index 00000000..e58764ac --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/Db2ExplainTables.java @@ -0,0 +1,420 @@ +package org.jumpserver.chen.modules.db2; + +import org.jumpserver.chen.framework.datasource.plan.PlanCodes; +import org.jumpserver.chen.framework.datasource.plan.PlanExecutionContext; +import org.jumpserver.chen.framework.datasource.plan.PlanPrerequisite; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +final class Db2ExplainTables { + static final List TABLES = List.of( + "EXPLAIN_INSTANCE", + "EXPLAIN_STATEMENT", + "EXPLAIN_OPERATOR", + "EXPLAIN_STREAM", + "EXPLAIN_OBJECT", + "EXPLAIN_PREDICATE", + "EXPLAIN_ARGUMENT", + "EXPLAIN_DIAGNOSTIC", + "EXPLAIN_DIAGNOSTIC_DATA" + ); + static final List STATEMENT_KEY = List.of( + "EXPLAIN_REQUESTER", "EXPLAIN_TIME", "SOURCE_NAME", "SOURCE_SCHEMA", + "SOURCE_VERSION", "EXPLAIN_LEVEL", "STMTNO", "SECTNO" + ); + static final List INSTANCE_KEY = STATEMENT_KEY.subList(0, 5); + + private static final Map> REQUIRED_COLUMNS = requiredColumns(); + private static final Set CHARACTER_KEY_COLUMNS = Set.of( + "EXPLAIN_REQUESTER", "SOURCE_NAME", "SOURCE_SCHEMA", "SOURCE_VERSION", "EXPLAIN_LEVEL", + "QUERYTAG", "OPERATOR_TYPE", "SOURCE_TYPE", "TARGET_TYPE", "OBJECT_SCHEMA", "OBJECT_NAME", + "OBJECT_TYPE", "PREDICATE_TEXT", "ARGUMENT_TYPE", "ARGUMENT_VALUE", "TOKEN" + ); + private static final Set NUMERIC_KEY_COLUMNS = Set.of( + "STMTNO", "SECTNO", "QUERYNO", "OPERATOR_ID", "STREAM_ID", "SOURCE_ID", "TARGET_ID", + "TOTAL_COST", "STREAM_COUNT", "PREDICATE_ID", "DIAGNOSTIC_ID", "CODE", "ORDINAL" + ); + private static final Set TIMESTAMP_COLUMNS = Set.of( + "EXPLAIN_TIME", "CREATE_TIME" + ); + + private Db2ExplainTables() { + } + + static Resolution resolve(PlanExecutionContext context) throws SQLException { + Identity identity = identity(context); + SchemaInventory auth = inventory(context, identity.authorizationId()); + if (auth.presentCount() == TABLES.size()) { + return validate(context, identity, auth); + } + if (auth.presentCount() != 0) { + return failed( + identity, + PlanCodes.PLAN_TABLE_INCOMPATIBLE, + "Authorization schema " + quote(identity.authorizationId()) + + " contains only part of the required DB2 Explain table set: " + + String.join(", ", auth.presentTables()), + "Install or upgrade the complete DB2 LUW Explain table set outside Chen" + ); + } + + SchemaInventory systools = inventory(context, "SYSTOOLS"); + if (systools.presentCount() == TABLES.size()) { + return validate(context, identity, systools); + } + if (systools.presentCount() != 0) { + return failed( + identity, + PlanCodes.PLAN_TABLE_INCOMPATIBLE, + "SYSTOOLS contains only part of the required DB2 Explain table set: " + + String.join(", ", systools.presentTables()), + "Install or upgrade the complete SYSTOOLS Explain table set outside Chen" + ); + } + return failed( + identity, + PlanCodes.PLAN_TABLE_MISSING, + "No complete DB2 LUW Explain table set exists in authorization schema " + + quote(identity.authorizationId()) + " or SYSTOOLS", + "Ask a DBA to install compatible Explain tables; Chen never creates them" + ); + } + + private static Resolution validate( + PlanExecutionContext context, + Identity identity, + SchemaInventory inventory + ) throws SQLException { + for (String table : TABLES) { + String objectType = inventory.objectTypes().get(table); + if (!"T".equals(objectType) && !"A".equals(objectType)) { + return failed( + identity, + PlanCodes.PLAN_TABLE_INCOMPATIBLE, + inventory.qualified(table) + " is not a table or alias", + "Replace it with a compatible DB2 LUW Explain table or alias" + ); + } + } + + Map> columns = columns(context, inventory.schema()); + for (Map.Entry> requirement : REQUIRED_COLUMNS.entrySet()) { + Map actual = columns.getOrDefault(requirement.getKey(), Map.of()); + Set missing = new LinkedHashSet<>(requirement.getValue()); + missing.removeAll(actual.keySet()); + if (!missing.isEmpty()) { + return failed( + identity, + PlanCodes.PLAN_TABLE_INCOMPATIBLE, + inventory.qualified(requirement.getKey()) + " is missing required columns: " + + String.join(", ", missing), + "Upgrade the DB2 LUW Explain table set outside Chen" + ); + } + for (String name : requirement.getValue()) { + String type = actual.get(name); + if (CHARACTER_KEY_COLUMNS.contains(name) && !isCharacter(type)) { + return incompatibleColumn(identity, inventory, requirement.getKey(), name, type); + } + if (TIMESTAMP_COLUMNS.contains(name) && !isTimestamp(type)) { + return incompatibleColumn(identity, inventory, requirement.getKey(), name, type); + } + if (NUMERIC_KEY_COLUMNS.contains(name) && !isNumeric(type)) { + return incompatibleColumn(identity, inventory, requirement.getKey(), name, type); + } + } + } + + Set missingPrivileges = missingPrivileges(context, identity, inventory); + if (!missingPrivileges.isEmpty()) { + return failed( + identity, + PlanCodes.PLAN_PERMISSION_DENIED, + "Missing DB2 Explain table privileges: " + String.join(", ", missingPrivileges), + "Grant SELECT, INSERT, and DELETE on the resolved Explain table set" + ); + } + return new Resolution( + new Target(inventory.schema(), identity), + null + ); + } + + private static Identity identity(PlanExecutionContext context) throws SQLException { + String sql = "VALUES (SESSION_USER, CURRENT SCHEMA)"; + try (Db2PlanJdbc.Tracked tracked = Db2PlanJdbc.statement(context, true); + ResultSet resultSet = tracked.statement().executeQuery(sql)) { + if (!resultSet.next()) { + throw new SQLException("DB2 session identity query returned no row"); + } + return new Identity(upper(resultSet.getString(1)), upper(resultSet.getString(2))); + } + } + + private static SchemaInventory inventory(PlanExecutionContext context, String schema) throws SQLException { + String placeholders = String.join(",", TABLES.stream().map(ignored -> "?").toList()); + String sql = "SELECT TABNAME, TYPE FROM SYSCAT.TABLES WHERE TABSCHEMA = ? AND TABNAME IN (" + + placeholders + ")"; + Map types = new LinkedHashMap<>(); + try (Db2PlanJdbc.Tracked tracked = + Db2PlanJdbc.preparedStatement(context, sql, true)) { + PreparedStatement statement = tracked.statement(); + statement.setString(1, schema); + for (int i = 0; i < TABLES.size(); i++) { + statement.setString(i + 2, TABLES.get(i)); + } + try (ResultSet resultSet = statement.executeQuery()) { + while (resultSet.next()) { + types.put(upper(resultSet.getString(1)), upper(resultSet.getString(2))); + } + } + } + return new SchemaInventory(schema, types); + } + + private static Map> columns( + PlanExecutionContext context, + String schema + ) throws SQLException { + String placeholders = String.join(",", TABLES.stream().map(ignored -> "?").toList()); + String sql = "SELECT TABNAME, COLNAME, TYPENAME FROM SYSCAT.COLUMNS " + + "WHERE TABSCHEMA = ? AND TABNAME IN (" + placeholders + ") ORDER BY TABNAME, COLNO"; + Map> result = new LinkedHashMap<>(); + try (Db2PlanJdbc.Tracked tracked = + Db2PlanJdbc.preparedStatement(context, sql, true)) { + PreparedStatement statement = tracked.statement(); + statement.setString(1, schema); + for (int i = 0; i < TABLES.size(); i++) { + statement.setString(i + 2, TABLES.get(i)); + } + try (ResultSet resultSet = statement.executeQuery()) { + while (resultSet.next()) { + result.computeIfAbsent(upper(resultSet.getString(1)), ignored -> new LinkedHashMap<>()) + .put(upper(resultSet.getString(2)), upper(resultSet.getString(3))); + } + } + } + return result; + } + + private static Set missingPrivileges( + PlanExecutionContext context, + Identity identity, + SchemaInventory inventory + ) throws SQLException { + Set authIds = new HashSet<>(); + authIds.add(identity.authorizationId()); + authIds.add("PUBLIC"); + String groupsSql = "SELECT \"GROUP\" FROM TABLE " + + "(SYSPROC.AUTH_LIST_GROUPS_FOR_AUTHID(?)) AS G"; + try (Db2PlanJdbc.Tracked tracked = + Db2PlanJdbc.preparedStatement(context, groupsSql, true)) { + tracked.statement().setString(1, identity.authorizationId()); + try (ResultSet resultSet = tracked.statement().executeQuery()) { + while (resultSet.next()) { + authIds.add(upper(resultSet.getString(1))); + } + } + } catch (SQLException ignored) { + // Direct ownership and grants are still checked. Runtime SQL errors remain classified. + } + + if (hasDataAccess(context, authIds)) { + return Set.of(); + } + + Map byTable = new HashMap<>(); + String placeholders = String.join(",", authIds.stream().map(ignored -> "?").toList()); + String tablePlaceholders = String.join(",", TABLES.stream().map(ignored -> "?").toList()); + String sql = "SELECT t.TABNAME, t.OWNER, a.CONTROLAUTH, a.SELECTAUTH, a.INSERTAUTH, a.DELETEAUTH " + + "FROM SYSCAT.TABLES t LEFT JOIN SYSCAT.TABAUTH a " + + "ON a.TABSCHEMA=t.TABSCHEMA AND a.TABNAME=t.TABNAME AND a.GRANTEE IN (" + + placeholders + ") WHERE t.TABSCHEMA=? AND t.TABNAME IN (" + tablePlaceholders + ")"; + try (Db2PlanJdbc.Tracked tracked = + Db2PlanJdbc.preparedStatement(context, sql, true)) { + int index = 1; + for (String authId : authIds) { + tracked.statement().setString(index++, authId); + } + tracked.statement().setString(index++, inventory.schema()); + for (String table : TABLES) { + tracked.statement().setString(index++, table); + } + try (ResultSet resultSet = tracked.statement().executeQuery()) { + while (resultSet.next()) { + String table = upper(resultSet.getString(1)); + String owner = upper(resultSet.getString(2)); + Privileges privileges = byTable.computeIfAbsent(table, ignored -> new Privileges()); + if (identity.authorizationId().equals(owner)) { + privileges.all = true; + } + privileges.merge( + resultSet.getString(3), resultSet.getString(4), + resultSet.getString(5), resultSet.getString(6) + ); + } + } + } + + Set missing = new LinkedHashSet<>(); + for (String table : TABLES) { + Privileges privilege = byTable.getOrDefault(table, new Privileges()); + if (!privilege.select) missing.add(table + ":SELECT"); + if (!privilege.insert) missing.add(table + ":INSERT"); + if (!privilege.delete) missing.add(table + ":DELETE"); + } + return missing; + } + + private static boolean hasDataAccess(PlanExecutionContext context, Set authIds) throws SQLException { + String placeholders = String.join(",", authIds.stream().map(ignored -> "?").toList()); + String sql = "SELECT 1 FROM SYSCAT.DBAUTH WHERE GRANTEE IN (" + placeholders + + ") AND DATAACCESSAUTH='Y' FETCH FIRST 1 ROW ONLY"; + try (Db2PlanJdbc.Tracked tracked = + Db2PlanJdbc.preparedStatement(context, sql, true)) { + int index = 1; + for (String authId : authIds) { + tracked.statement().setString(index++, authId); + } + try (ResultSet resultSet = tracked.statement().executeQuery()) { + return resultSet.next(); + } + } + } + + static String quote(String identifier) { + return "\"" + identifier.replace("\"", "\"\"") + "\""; + } + + static String qualified(String schema, String table) { + return quote(schema) + "." + quote(table); + } + + private static Resolution incompatibleColumn( + Identity identity, + SchemaInventory inventory, + String table, + String column, + String type + ) { + return failed( + identity, + PlanCodes.PLAN_TABLE_INCOMPATIBLE, + inventory.qualified(table) + " column " + column + " has incompatible type " + type, + "Upgrade the DB2 LUW Explain table set outside Chen" + ); + } + + private static boolean isCharacter(String type) { + return type != null && (type.contains("CHAR") || type.contains("CLOB")); + } + + private static boolean isTimestamp(String type) { + return type != null && type.contains("TIMESTAMP"); + } + + private static boolean isNumeric(String type) { + return type != null && (type.contains("INT") || type.contains("DECIMAL") + || type.contains("NUMERIC") || type.contains("DOUBLE") || type.contains("REAL")); + } + + private static String upper(String value) { + return value == null ? null : value.trim().toUpperCase(Locale.ROOT); + } + + private static Resolution failed(Identity identity, String code, String message, String remediation) { + return new Resolution(null, PlanPrerequisite.unmet(code, message, remediation)); + } + + private static Map> requiredColumns() { + Map> result = new LinkedHashMap<>(); + result.put("EXPLAIN_INSTANCE", Set.copyOf(INSTANCE_KEY)); + result.put("EXPLAIN_STATEMENT", with(STATEMENT_KEY, "QUERYNO", "QUERYTAG")); + result.put("EXPLAIN_OPERATOR", with(STATEMENT_KEY, "OPERATOR_ID", "OPERATOR_TYPE", "TOTAL_COST")); + result.put("EXPLAIN_STREAM", with( + STATEMENT_KEY, "STREAM_ID", "SOURCE_TYPE", "SOURCE_ID", "TARGET_TYPE", "TARGET_ID", + "OBJECT_SCHEMA", "OBJECT_NAME", "STREAM_COUNT" + )); + result.put("EXPLAIN_OBJECT", with( + STATEMENT_KEY, "OBJECT_SCHEMA", "OBJECT_NAME", "OBJECT_TYPE", "CREATE_TIME" + )); + result.put("EXPLAIN_PREDICATE", with( + STATEMENT_KEY, "OPERATOR_ID", "PREDICATE_ID", "PREDICATE_TEXT" + )); + result.put("EXPLAIN_ARGUMENT", with( + STATEMENT_KEY, "OPERATOR_ID", "ARGUMENT_TYPE", "ARGUMENT_VALUE" + )); + result.put("EXPLAIN_DIAGNOSTIC", with(STATEMENT_KEY, "DIAGNOSTIC_ID", "CODE")); + result.put("EXPLAIN_DIAGNOSTIC_DATA", with( + STATEMENT_KEY, "DIAGNOSTIC_ID", "ORDINAL", "TOKEN" + )); + return Map.copyOf(result); + } + + private static Set with(List base, String... extra) { + Set result = new LinkedHashSet<>(base); + result.addAll(List.of(extra)); + return Set.copyOf(result); + } + + record Identity(String authorizationId, String currentSchema) { + } + + record Target(String schema, Identity identity) { + String qualified(String table) { + return Db2ExplainTables.qualified(schema, table); + } + } + + record Resolution(Target target, PlanPrerequisite failure) { + } + + private record SchemaInventory(String schema, Map objectTypes) { + int presentCount() { + return objectTypes.size(); + } + + List presentTables() { + return new ArrayList<>(objectTypes.keySet()); + } + + String qualified(String table) { + return Db2ExplainTables.qualified(schema, table); + } + } + + private static final class Privileges { + private boolean all; + private boolean select; + private boolean insert; + private boolean delete; + + private void merge(String control, String select, String insert, String delete) { + all |= granted(control); + this.select |= granted(select); + this.insert |= granted(insert); + this.delete |= granted(delete); + if (all) { + this.select = true; + this.insert = true; + this.delete = true; + } + } + + private static boolean granted(String value) { + return "Y".equalsIgnoreCase(value) || "G".equalsIgnoreCase(value); + } + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/Db2PlanAccess.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/Db2PlanAccess.java new file mode 100644 index 00000000..0657478e --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/Db2PlanAccess.java @@ -0,0 +1,590 @@ +package org.jumpserver.chen.modules.db2; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import org.jumpserver.chen.framework.datasource.analysis.SqlStatementAnalysis; +import org.jumpserver.chen.framework.datasource.plan.ExecutionPlanJson; +import org.jumpserver.chen.framework.datasource.plan.PlanExecutionContext; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + +final class Db2PlanAccess { + private static final int MAX_REQUEST_STATEMENT_KEYS = 8; + private static final List CAPTURE_TABLES = List.of( + "EXPLAIN_INSTANCE", + "EXPLAIN_OPERATOR", + "EXPLAIN_STREAM", + "EXPLAIN_OBJECT", + "EXPLAIN_PREDICATE", + "EXPLAIN_ARGUMENT", + "EXPLAIN_DIAGNOSTIC", + "EXPLAIN_DIAGNOSTIC_DATA" + ); + private static final List DELETE_ORDER = List.of( + "EXPLAIN_DIAGNOSTIC_DATA", + "EXPLAIN_DIAGNOSTIC", + "EXPLAIN_ARGUMENT", + "EXPLAIN_PREDICATE", + "EXPLAIN_STREAM", + "EXPLAIN_OBJECT", + "EXPLAIN_OPERATOR", + "EXPLAIN_STATEMENT" + ); + + private Db2PlanAccess() { + } + + static void explain( + PlanExecutionContext context, + SqlStatementAnalysis statement, + int queryNo, + String queryTag + ) throws SQLException { + String sql = explainSql(statement.sql(), queryNo, queryTag); + try (Db2PlanJdbc.Tracked tracked = Db2PlanJdbc.statement(context, true)) { + tracked.statement().execute(sql); + } + } + + static String explainSql(String sql, int queryNo, String queryTag) { + if (queryNo <= 0) { + throw new IllegalArgumentException("DB2 EXPLAIN QUERYNO must be positive"); + } + if (queryTag == null || queryTag.isBlank() + || queryTag.getBytes(StandardCharsets.US_ASCII).length > 20 + || !StandardCharsets.US_ASCII.newEncoder().canEncode(queryTag)) { + throw new IllegalArgumentException("DB2 EXPLAIN QUERYTAG must be non-empty ASCII and at most 20 bytes"); + } + return "EXPLAIN PLAN SET QUERYNO=" + queryNo + " SET QUERYTAG='" + + queryTag.replace("'", "''") + "' FOR " + sql; + } + + static LocateResult locate( + PlanExecutionContext context, + Db2ExplainTables.Target target, + int queryNo, + String queryTag, + Timestamp windowStart, + Timestamp windowEnd + ) throws SQLException { + // DB2 LUW normally writes both an original ('O') and a plan ('P') statement row. + // Locate one unique plan row first, then collect every full statement key belonging + // to that same request instance so cleanup removes both layers without a broad delete. + String sql = "SELECT " + selectColumns(Db2ExplainTables.STATEMENT_KEY) + + " FROM " + target.qualified("EXPLAIN_STATEMENT") + + " WHERE EXPLAIN_REQUESTER=? AND QUERYNO=? AND RTRIM(QUERYTAG)=?" + + " AND EXPLAIN_TIME BETWEEN ? AND ?" + + " AND EXPLAIN_LEVEL='P'" + + " FETCH FIRST 2 ROWS ONLY"; + TableMaterialization planKeysTable; + try (Db2PlanJdbc.Tracked tracked = + Db2PlanJdbc.preparedStatement(context, sql, false)) { + PreparedStatement statement = tracked.statement(); + statement.setString(1, target.identity().authorizationId()); + statement.setInt(2, queryNo); + statement.setString(3, queryTag); + statement.setTimestamp(4, windowStart); + statement.setTimestamp(5, windowEnd); + try (ResultSet resultSet = statement.executeQuery()) { + planKeysTable = materialize("EXPLAIN_STATEMENT", resultSet, context.maxRawBytes(), 2); + } + } + + List planKeys = statementKeys(planKeysTable); + if (planKeys.size() != 1) { + return new LocateResult(planKeys, List.of(), planKeysTable, false); + } + + StatementKey planKey = planKeys.get(0); + String requestWhere = whereKey(Db2ExplainTables.INSTANCE_KEY) + + " AND QUERYNO=? AND RTRIM(QUERYTAG)=?"; + String requestKeysSql = "SELECT " + selectColumns(Db2ExplainTables.STATEMENT_KEY) + + " FROM " + target.qualified("EXPLAIN_STATEMENT") + + " WHERE " + requestWhere + + " ORDER BY EXPLAIN_LEVEL, STMTNO, SECTNO" + + " FETCH FIRST " + (MAX_REQUEST_STATEMENT_KEYS + 1) + " ROWS ONLY"; + TableMaterialization requestKeysTable; + try (Db2PlanJdbc.Tracked tracked = + Db2PlanJdbc.preparedStatement(context, requestKeysSql, false)) { + bindRequest(tracked.statement(), planKey, queryNo, queryTag); + try (ResultSet resultSet = tracked.statement().executeQuery()) { + requestKeysTable = materialize( + "EXPLAIN_STATEMENT", resultSet, context.maxRawBytes(), + MAX_REQUEST_STATEMENT_KEYS + 1 + ); + } + } + List requestKeys = statementKeys(requestKeysTable); + boolean complete = !requestKeysTable.truncated() + && requestKeys.size() <= MAX_REQUEST_STATEMENT_KEYS + && requestKeys.contains(planKey); + + String statementSql = "SELECT * FROM " + target.qualified("EXPLAIN_STATEMENT") + + " WHERE " + requestWhere + + " ORDER BY EXPLAIN_LEVEL, STMTNO, SECTNO"; + TableMaterialization statementTable; + try (Db2PlanJdbc.Tracked tracked = + Db2PlanJdbc.preparedStatement(context, statementSql, false)) { + bindRequest(tracked.statement(), planKey, queryNo, queryTag); + try (ResultSet resultSet = tracked.statement().executeQuery()) { + statementTable = materialize( + "EXPLAIN_STATEMENT", resultSet, context.maxRawBytes(), context.maxNodes() + ); + } + } + return new LocateResult(planKeys, requestKeys, statementTable, complete); + } + + static Timestamp currentTimestamp(PlanExecutionContext context, boolean useRequestDeadline) + throws SQLException { + try (Db2PlanJdbc.Tracked tracked = + Db2PlanJdbc.statement(context, useRequestDeadline); + ResultSet resultSet = tracked.statement().executeQuery("VALUES CURRENT TIMESTAMP")) { + if (!resultSet.next()) { + throw new SQLException("DB2 current timestamp query returned no row"); + } + return resultSet.getTimestamp(1); + } + } + + static Capture read( + PlanExecutionContext context, + Db2ExplainTables.Target target, + LocateResult located + ) throws SQLException { + if (located.keys().size() != 1) { + return capture(List.of(located.statement()), located.statement().truncated(), context.maxRawBytes()); + } + StatementKey key = located.keys().get(0); + List tables = new ArrayList<>(); + tables.add(located.statement()); + boolean truncated = located.statement().truncated(); + int remainingRows = context.maxNodes(); + + for (String table : CAPTURE_TABLES) { + boolean instance = "EXPLAIN_INSTANCE".equals(table); + String where = whereKey(instance ? Db2ExplainTables.INSTANCE_KEY : Db2ExplainTables.STATEMENT_KEY); + String sql = "SELECT * FROM " + target.qualified(table) + " WHERE " + where + + orderBy(table); + TableMaterialization materialized; + try (Db2PlanJdbc.Tracked tracked = + Db2PlanJdbc.preparedStatement(context, sql, true)) { + bindKey(tracked.statement(), key, instance); + try (ResultSet resultSet = tracked.statement().executeQuery()) { + materialized = materialize( + table, + resultSet, + context.maxRawBytes(), + Math.max(0, remainingRows) + ); + } + } + tables.add(materialized); + remainingRows -= materialized.rows().size(); + truncated |= materialized.truncated() || remainingRows <= 0; + } + return capture(tables, truncated, context.maxRawBytes()); + } + + static void cleanup( + PlanExecutionContext context, + Db2ExplainTables.Target target, + StatementKey key + ) throws SQLException { + SQLException failure = null; + for (String table : DELETE_ORDER) { + String sql = "DELETE FROM " + target.qualified(table) + " WHERE " + + whereKey(Db2ExplainTables.STATEMENT_KEY); + try (Db2PlanJdbc.Tracked tracked = + Db2PlanJdbc.preparedStatement(context, sql, false)) { + bindKey(tracked.statement(), key, false); + tracked.statement().executeUpdate(); + } catch (SQLException e) { + failure = accumulate(failure, e); + } + } + + if (failure == null) { + String sql = "DELETE FROM " + target.qualified("EXPLAIN_INSTANCE") + " I WHERE " + + whereKey("I", Db2ExplainTables.INSTANCE_KEY) + + " AND NOT EXISTS (SELECT 1 FROM " + target.qualified("EXPLAIN_STATEMENT") + + " S WHERE " + joinInstanceKey("S", "I") + ")"; + try (Db2PlanJdbc.Tracked tracked = + Db2PlanJdbc.preparedStatement(context, sql, false)) { + bindKey(tracked.statement(), key, true); + tracked.statement().executeUpdate(); + } catch (SQLException e) { + failure = accumulate(failure, e); + } + } + + if (failure == null) { + failure = verifyClean(context, target, key); + } + if (failure != null) { + throw failure; + } + } + + private static SQLException verifyClean( + PlanExecutionContext context, + Db2ExplainTables.Target target, + StatementKey key + ) { + SQLException failure = null; + for (String table : DELETE_ORDER) { + String sql = "SELECT COUNT(*) FROM " + target.qualified(table) + " WHERE " + + whereKey(Db2ExplainTables.STATEMENT_KEY); + try (Db2PlanJdbc.Tracked tracked = + Db2PlanJdbc.preparedStatement(context, sql, false)) { + bindKey(tracked.statement(), key, false); + try (ResultSet resultSet = tracked.statement().executeQuery()) { + if (!resultSet.next() || resultSet.getLong(1) != 0L) { + failure = accumulate(failure, new SQLException( + "DB2 Explain cleanup verification found residual rows in " + table + )); + } + } + } catch (SQLException e) { + failure = accumulate(failure, e); + } + } + return failure; + } + + private static TableMaterialization materialize( + String name, + ResultSet resultSet, + int maxRawBytes, + int maxRows + ) throws SQLException { + ResultSetMetaData metadata = resultSet.getMetaData(); + JsonArray columns = new JsonArray(); + List names = new ArrayList<>(); + for (int index = 1; index <= metadata.getColumnCount(); index++) { + JsonObject column = new JsonObject(); + String columnName = metadata.getColumnLabel(index); + names.add(columnName); + column.addProperty("name", columnName); + column.addProperty("jdbcType", metadata.getColumnType(index)); + column.addProperty("type", metadata.getColumnTypeName(index)); + columns.add(column); + } + + List rows = new ArrayList<>(); + boolean truncated = false; + int bytes = jsonBytes(columns); + while (resultSet.next()) { + if (rows.size() >= maxRows) { + truncated = true; + break; + } + JsonArray row = new JsonArray(); + boolean cellTruncated = false; + for (int index = 1; index <= metadata.getColumnCount(); index++) { + Cell cell = jsonValue(resultSet, metadata.getColumnType(index), index, maxRawBytes); + row.add(cell.value()); + cellTruncated |= cell.truncated(); + } + int rowBytes = jsonBytes(row); + if ((long) bytes + rowBytes + 1L > maxRawBytes) { + truncated = true; + break; + } + rows.add(row); + bytes += rowBytes + 1; + truncated |= cellTruncated; + if (cellTruncated) { + break; + } + } + return new TableMaterialization(name, columns, List.copyOf(names), List.copyOf(rows), truncated); + } + + private static Capture capture( + List tables, + boolean alreadyTruncated, + int maxRawBytes + ) { + JsonObject envelope = new JsonObject(); + JsonArray resultSets = new JsonArray(); + envelope.add("resultSets", resultSets); + envelope.addProperty("truncated", alreadyTruncated); + boolean truncated = alreadyTruncated; + for (TableMaterialization table : tables) { + JsonObject json = table.json(); + resultSets.add(json); + if (jsonBytes(envelope) > maxRawBytes) { + resultSets.remove(resultSets.size() - 1); + truncated = true; + break; + } + } + envelope.addProperty("truncated", truncated); + String raw = ExecutionPlanJson.GSON.toJson(envelope); + if (raw.getBytes(StandardCharsets.UTF_8).length > maxRawBytes) { + JsonObject minimal = new JsonObject(); + minimal.add("resultSets", new JsonArray()); + minimal.addProperty("truncated", true); + return new Capture(ExecutionPlanJson.GSON.toJson(minimal), true); + } + return new Capture(raw, truncated); + } + + private static Cell jsonValue(ResultSet resultSet, int jdbcType, int index, int maxBytes) + throws SQLException { + if (isNumeric(jdbcType)) { + BigDecimal value = resultSet.getBigDecimal(index); + return new Cell(value == null ? JsonNull.INSTANCE : new JsonPrimitive(value), false); + } + if (jdbcType == Types.BOOLEAN || jdbcType == Types.BIT) { + boolean value = resultSet.getBoolean(index); + return new Cell(resultSet.wasNull() ? JsonNull.INSTANCE : new JsonPrimitive(value), false); + } + if (isBinary(jdbcType)) { + try (InputStream input = resultSet.getBinaryStream(index)) { + if (input == null) { + return new Cell(JsonNull.INSTANCE, false); + } + byte[] value = input.readNBytes(maxBytes + 1); + boolean truncated = value.length > maxBytes; + int length = Math.min(value.length, maxBytes); + return new Cell(new JsonPrimitive(Base64.getEncoder().encodeToString( + length == value.length ? value : java.util.Arrays.copyOf(value, length) + )), truncated); + } catch (IOException e) { + throw new SQLException("Failed to read DB2 binary Explain value", e); + } + } + if (isCharacter(jdbcType)) { + try (Reader reader = resultSet.getCharacterStream(index)) { + if (reader == null) { + return new Cell(JsonNull.INSTANCE, false); + } + char[] buffer = new char[Math.min(8192, Math.max(1, maxBytes))]; + StringBuilder value = new StringBuilder(); + int read; + boolean truncated = false; + while ((read = reader.read(buffer)) >= 0) { + int remaining = maxBytes - value.length(); + if (remaining <= 0) { + truncated = true; + break; + } + value.append(buffer, 0, Math.min(read, remaining)); + if (read > remaining) { + truncated = true; + break; + } + } + return new Cell(new JsonPrimitive(value.toString()), truncated); + } catch (IOException e) { + throw new SQLException("Failed to read DB2 character Explain value", e); + } + } + String value = resultSet.getString(index); + return new Cell(value == null ? JsonNull.INSTANCE : new JsonPrimitive(value), false); + } + + private static String whereKey(List columns) { + return String.join(" AND ", columns.stream().map(column -> Db2ExplainTables.quote(column) + "=?").toList()); + } + + private static String selectColumns(List columns) { + return String.join(", ", columns.stream().map(Db2ExplainTables::quote).toList()); + } + + private static String whereKey(String alias, List columns) { + return String.join(" AND ", columns.stream() + .map(column -> alias + "." + Db2ExplainTables.quote(column) + "=?").toList()); + } + + private static String joinInstanceKey(String left, String right) { + return String.join(" AND ", Db2ExplainTables.INSTANCE_KEY.stream() + .map(column -> left + "." + Db2ExplainTables.quote(column) + "=" + + right + "." + Db2ExplainTables.quote(column)).toList()); + } + + private static void bindKey(PreparedStatement statement, StatementKey key, boolean instance) + throws SQLException { + statement.setString(1, key.explainRequester()); + statement.setTimestamp(2, key.explainTime()); + statement.setString(3, key.sourceName()); + statement.setString(4, key.sourceSchema()); + statement.setString(5, key.sourceVersion()); + if (!instance) { + statement.setString(6, key.explainLevel()); + statement.setInt(7, key.stmtNo()); + statement.setInt(8, key.sectNo()); + } + } + + private static void bindRequest( + PreparedStatement statement, + StatementKey key, + int queryNo, + String queryTag + ) throws SQLException { + bindKey(statement, key, true); + statement.setInt(6, queryNo); + statement.setString(7, queryTag); + } + + private static List statementKeys(TableMaterialization table) throws SQLException { + List keys = new ArrayList<>(); + for (JsonArray row : table.rows()) { + keys.add(new StatementKey( + table.text(row, "EXPLAIN_REQUESTER"), + table.timestamp(row, "EXPLAIN_TIME"), + table.text(row, "SOURCE_NAME"), + table.text(row, "SOURCE_SCHEMA"), + table.text(row, "SOURCE_VERSION"), + table.text(row, "EXPLAIN_LEVEL"), + table.integer(row, "STMTNO"), + table.integer(row, "SECTNO") + )); + } + return List.copyOf(keys); + } + + private static String orderBy(String table) { + return switch (table) { + case "EXPLAIN_OPERATOR" -> " ORDER BY OPERATOR_ID"; + case "EXPLAIN_STREAM" -> " ORDER BY STREAM_ID"; + case "EXPLAIN_OBJECT" -> " ORDER BY OBJECT_SCHEMA, OBJECT_NAME, OBJECT_TYPE, CREATE_TIME"; + case "EXPLAIN_PREDICATE" -> " ORDER BY OPERATOR_ID, PREDICATE_ID"; + case "EXPLAIN_ARGUMENT" -> " ORDER BY OPERATOR_ID, ARGUMENT_TYPE"; + case "EXPLAIN_DIAGNOSTIC" -> " ORDER BY DIAGNOSTIC_ID"; + case "EXPLAIN_DIAGNOSTIC_DATA" -> " ORDER BY DIAGNOSTIC_ID, ORDINAL"; + default -> ""; + }; + } + + private static boolean isNumeric(int type) { + return type == Types.BIGINT || type == Types.DECIMAL || type == Types.DOUBLE + || type == Types.FLOAT || type == Types.INTEGER || type == Types.NUMERIC + || type == Types.REAL || type == Types.SMALLINT || type == Types.TINYINT; + } + + private static boolean isBinary(int type) { + return type == Types.BINARY || type == Types.VARBINARY || type == Types.LONGVARBINARY + || type == Types.BLOB; + } + + private static boolean isCharacter(int type) { + return type == Types.CHAR || type == Types.VARCHAR || type == Types.LONGVARCHAR + || type == Types.NCHAR || type == Types.NVARCHAR || type == Types.LONGNVARCHAR + || type == Types.CLOB || type == Types.NCLOB; + } + + private static int jsonBytes(JsonElement value) { + return ExecutionPlanJson.GSON.toJson(value).getBytes(StandardCharsets.UTF_8).length; + } + + private static SQLException accumulate(SQLException current, SQLException next) { + if (current == null) { + return next; + } + current.addSuppressed(next); + return current; + } + + record StatementKey( + String explainRequester, + Timestamp explainTime, + String sourceName, + String sourceSchema, + String sourceVersion, + String explainLevel, + int stmtNo, + int sectNo + ) { + String identity() { + return explainRequester + "@" + explainTime + "/" + sourceSchema + "." + sourceName + + ":" + sourceVersion + "/" + explainLevel + "/" + stmtNo + "/" + sectNo; + } + } + + record LocateResult( + List keys, + List requestKeys, + TableMaterialization statement, + boolean complete + ) { + } + + record Capture(String json, boolean truncated) { + } + + private record Cell(JsonElement value, boolean truncated) { + } + + record TableMaterialization( + String name, + JsonArray columns, + List columnNames, + List rows, + boolean truncated + ) { + JsonObject json() { + JsonObject table = new JsonObject(); + table.addProperty("name", name); + table.add("columns", columns.deepCopy()); + JsonArray values = new JsonArray(); + rows.forEach(row -> values.add(row.deepCopy())); + table.add("rows", values); + return table; + } + + String text(JsonArray row, String column) throws SQLException { + JsonElement value = value(row, column); + return value == null || value.isJsonNull() ? null : value.getAsString().trim(); + } + + int integer(JsonArray row, String column) throws SQLException { + JsonElement value = value(row, column); + if (value == null || value.isJsonNull()) { + throw new SQLException("DB2 Explain statement key column " + column + " is null"); + } + return value.getAsInt(); + } + + Timestamp timestamp(JsonArray row, String column) throws SQLException { + String value = text(row, column); + if (value == null) { + throw new SQLException("DB2 Explain statement key column " + column + " is null"); + } + try { + return Timestamp.valueOf(value); + } catch (IllegalArgumentException e) { + throw new SQLException("Invalid DB2 Explain timestamp " + value, e); + } + } + + private JsonElement value(JsonArray row, String column) throws SQLException { + for (int index = 0; index < columnNames.size(); index++) { + if (column.equalsIgnoreCase(columnNames.get(index))) { + return row.get(index); + } + } + throw new SQLException("DB2 Explain result is missing column " + column); + } + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/Db2PlanJdbc.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/Db2PlanJdbc.java new file mode 100644 index 00000000..04aafa61 --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/Db2PlanJdbc.java @@ -0,0 +1,79 @@ +package org.jumpserver.chen.modules.db2; + +import org.jumpserver.chen.framework.datasource.plan.PlanExecutionContext; +import org.jumpserver.chen.framework.datasource.plan.PlanLimits; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; + +final class Db2PlanJdbc { + private Db2PlanJdbc() { + } + + static Tracked statement(PlanExecutionContext context, boolean useRequestDeadline) + throws SQLException { + if (useRequestDeadline) { + context.throwIfCancelled(); + } + return track(context.connection().createStatement(), context, useRequestDeadline); + } + + static Tracked preparedStatement( + PlanExecutionContext context, + String sql, + boolean useRequestDeadline + ) throws SQLException { + if (useRequestDeadline) { + context.throwIfCancelled(); + } + return track(context.connection().prepareStatement(sql), context, useRequestDeadline); + } + + private static Tracked track( + T statement, + PlanExecutionContext context, + boolean useRequestDeadline + ) throws SQLException { + context.registerStatement(statement); + try { + if (useRequestDeadline) { + context.throwIfCancelled(); + } + long remainingMillis = useRequestDeadline + ? Math.max(1L, context.deadlineMillis() - System.currentTimeMillis()) + : PlanLimits.CLEANUP_TIMEOUT_MS; + long seconds = Math.max(1L, (remainingMillis + 999L) / 1_000L); + statement.setQueryTimeout((int) Math.min(Integer.MAX_VALUE, seconds)); + return new Tracked<>(statement, context); + } catch (SQLException e) { + context.unregisterStatement(statement); + try { + statement.close(); + } catch (SQLException closeError) { + e.addSuppressed(closeError); + } + throw e; + } + } + + static final class Tracked implements AutoCloseable { + private final T statement; + private final PlanExecutionContext context; + + private Tracked(T statement, PlanExecutionContext context) { + this.statement = statement; + this.context = context; + } + + T statement() { + return statement; + } + + @Override + public void close() throws SQLException { + context.unregisterStatement(statement); + statement.close(); + } + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/Db2PlanParser.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/Db2PlanParser.java new file mode 100644 index 00000000..4d3021ad --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/Db2PlanParser.java @@ -0,0 +1,594 @@ +package org.jumpserver.chen.modules.db2; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.jumpserver.chen.framework.datasource.plan.NormalizedNodeType; +import org.jumpserver.chen.framework.datasource.plan.PlanCodes; +import org.jumpserver.chen.framework.datasource.plan.PlanDiagnostic; +import org.jumpserver.chen.framework.datasource.plan.PlanNode; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +public final class Db2PlanParser { + public static final String ROWS_MEANING = "DB2 estimated stream cardinality (STREAM_COUNT)"; + public static final String COST_MEANING = "DB2 cumulative optimizer cost in timerons (TOTAL_COST)"; + + private Db2PlanParser() { + } + + public static ParseResult parse(String rawTableJson, int maxNodes, int maxDepth) { + List warnings = new ArrayList<>(); + if (rawTableJson == null || rawTableJson.isBlank()) { + return ParseResult.failed(warnings, "Empty DB2 Explain table result"); + } + + Map tables; + try { + tables = tables(rawTableJson); + } catch (IllegalArgumentException e) { + return ParseResult.failed(warnings, e.getMessage()); + } + TableData operatorsTable = tables.get("EXPLAIN_OPERATOR"); + TableData streamsTable = tables.get("EXPLAIN_STREAM"); + if (operatorsTable == null || streamsTable == null) { + return ParseResult.failed(warnings, "DB2 Explain envelope is missing OPERATOR or STREAM data"); + } + + Map operators = new LinkedHashMap<>(); + for (Row row : operatorsTable.rows()) { + String id = row.text("OPERATOR_ID"); + if (id == null) { + return ParseResult.failed(warnings, "DB2 EXPLAIN_OPERATOR row has no OPERATOR_ID"); + } + if (operators.putIfAbsent(id, row) != null) { + return ParseResult.failed(warnings, "DB2 EXPLAIN_OPERATOR contains duplicate OPERATOR_ID " + id); + } + } + if (operators.isEmpty()) { + return ParseResult.failed(warnings, "DB2 EXPLAIN_OPERATOR returned no rows for this request"); + } + + List streams = streamsTable.rows(); + Map> childrenByTarget = new HashMap<>(); + Map> streamsByOperator = new HashMap<>(); + Map parentCountBySource = new HashMap<>(); + Map edgeCount = new HashMap<>(); + Set operatorSources = new HashSet<>(); + AtomicBoolean incomplete = new AtomicBoolean(false); + + for (Row stream : streams) { + String sourceType = upper(stream.text("SOURCE_TYPE")); + String targetType = upper(stream.text("TARGET_TYPE")); + String sourceId = stream.text("SOURCE_ID"); + String targetId = stream.text("TARGET_ID"); + if ("O".equals(sourceType) && sourceId != null) { + streamsByOperator.computeIfAbsent(sourceId, ignored -> new ArrayList<>()).add(stream); + } + if ("O".equals(targetType) && targetId != null) { + streamsByOperator.computeIfAbsent(targetId, ignored -> new ArrayList<>()).add(stream); + } + if ("O".equals(sourceType) && "O".equals(targetType)) { + if (!operators.containsKey(sourceId) || !operators.containsKey(targetId)) { + warnings.add(PlanDiagnostic.of( + PlanCodes.PLAN_PARSE_FAILED, + "DB2 stream " + defaultText(stream.text("STREAM_ID"), "?") + + " references a missing operator" + )); + incomplete.set(true); + continue; + } + childrenByTarget.computeIfAbsent(targetId, ignored -> new ArrayList<>()).add(stream); + operatorSources.add(sourceId); + parentCountBySource.merge(sourceId, 1, Integer::sum); + edgeCount.merge(sourceId + "->" + targetId, 1, Integer::sum); + } + } + + Comparator streamOrder = Comparator + .comparing((Row row) -> row.decimal("STREAM_ID"), Comparator.nullsLast(Comparator.naturalOrder())) + .thenComparing(row -> defaultText(row.text("SOURCE_ID"), "")); + childrenByTarget.values().forEach(rows -> rows.sort(streamOrder)); + streamsByOperator.values().forEach(rows -> rows.sort(streamOrder)); + + List rootIds = operators.keySet().stream() + .filter(id -> !operatorSources.contains(id)) + .sorted(Db2PlanParser::compareNativeIds) + .toList(); + if (rootIds.isEmpty()) { + warnings.add(PlanDiagnostic.of( + PlanCodes.PLAN_PARSE_FAILED, + "DB2 Explain operator graph has no root; a cycle is present" + )); + return new ParseResult(List.of(), warnings, false); + } + + Map> predicates = group(tables.get("EXPLAIN_PREDICATE"), "OPERATOR_ID"); + Map> arguments = group(tables.get("EXPLAIN_ARGUMENT"), "OPERATOR_ID"); + Map> objects = objects(tables.get("EXPLAIN_OBJECT")); + AtomicInteger displayIds = new AtomicInteger(); + AtomicInteger displayedNodes = new AtomicInteger(); + List roots = new ArrayList<>(); + for (String rootId : rootIds) { + PlanNode root = convert( + rootId, + null, + operators, + childrenByTarget, + streamsByOperator, + parentCountBySource, + edgeCount, + predicates, + arguments, + objects, + displayIds, + displayedNodes, + 1, + maxNodes, + maxDepth, + new LinkedHashSet<>(), + warnings, + incomplete + ); + if (root != null) { + roots.add(root); + } + } + + Set reachable = new HashSet<>(); + collectReachable(rootIds, childrenByTarget, reachable); + if (!reachable.containsAll(operators.keySet())) { + Set disconnected = new LinkedHashSet<>(operators.keySet()); + disconnected.removeAll(reachable); + warnings.add(PlanDiagnostic.of( + PlanCodes.PLAN_PARSE_FAILED, + "DB2 Explain graph has disconnected operators: " + String.join(", ", disconnected) + )); + incomplete.set(true); + } + if (incomplete.get() || roots.isEmpty()) { + return new ParseResult(List.of(), warnings, false); + } + return new ParseResult(roots, warnings, true); + } + + private static PlanNode convert( + String nativeId, + Row incomingEdge, + Map operators, + Map> childrenByTarget, + Map> streamsByOperator, + Map parentCountBySource, + Map edgeCount, + Map> predicatesByOperator, + Map> argumentsByOperator, + Map> objectsByName, + AtomicInteger displayIds, + AtomicInteger displayedNodes, + int depth, + int maxNodes, + int maxDepth, + Set path, + List warnings, + AtomicBoolean incomplete + ) { + if (displayedNodes.incrementAndGet() > maxNodes) { + addLimitWarning(warnings, "DB2 execution plan exceeded the node limit"); + incomplete.set(true); + return null; + } + if (depth > maxDepth) { + addLimitWarning(warnings, "DB2 execution plan exceeded the depth limit"); + incomplete.set(true); + return null; + } + if (!path.add(nativeId)) { + warnings.add(PlanDiagnostic.of( + PlanCodes.PLAN_PARSE_FAILED, + "DB2 Explain graph contains a cycle at operator " + nativeId + )); + incomplete.set(true); + return null; + } + + Row operator = operators.get(nativeId); + if (operator == null) { + incomplete.set(true); + return null; + } + String nativeOperator = defaultText(operator.text("OPERATOR_TYPE"), "UNKNOWN").trim(); + Map attributes = new LinkedHashMap<>(); + copyRow(attributes, "Operator.", operator, Set.copyOf(Db2ExplainTables.STATEMENT_KEY)); + + if (incomingEdge != null) { + copyRow(attributes, "IncomingStream.", incomingEdge, Set.copyOf(Db2ExplainTables.STATEMENT_KEY)); + String source = incomingEdge.text("SOURCE_ID"); + String target = incomingEdge.text("TARGET_ID"); + if (parentCountBySource.getOrDefault(source, 0) > 1) { + attributes.put("Shared Subplan", "true"); + } + if (edgeCount.getOrDefault(source + "->" + target, 0) > 1) { + attributes.put("Repeated Edge", "true"); + } + } + + List relatedStreams = streamsByOperator.getOrDefault(nativeId, List.of()); + ObjectInfo objectInfo = objectInfo(nativeId, relatedStreams, objectsByName, attributes); + Map predicates = predicates(predicatesByOperator.getOrDefault(nativeId, List.of()), attributes); + arguments(argumentsByOperator.getOrDefault(nativeId, List.of()), attributes); + + Row cardinalityStream = cardinalityStream(nativeId, incomingEdge, relatedStreams); + BigDecimal rows = nonNegative(cardinalityStream == null ? null : cardinalityStream.decimal("STREAM_COUNT")); + BigDecimal cost = nonNegative(operator.decimal("TOTAL_COST")); + + List children = new ArrayList<>(); + for (Row edge : childrenByTarget.getOrDefault(nativeId, List.of())) { + String childId = edge.text("SOURCE_ID"); + PlanNode child = convert( + childId, + edge, + operators, + childrenByTarget, + streamsByOperator, + parentCountBySource, + edgeCount, + predicatesByOperator, + argumentsByOperator, + objectsByName, + displayIds, + displayedNodes, + depth + 1, + maxNodes, + maxDepth, + new LinkedHashSet<>(path), + warnings, + incomplete + ); + if (child != null) { + children.add(child); + } + } + + return new PlanNode( + "db2-" + displayIds.incrementAndGet(), + nativeId, + mapNodeType(nativeOperator), + nativeOperator, + null, + nativeOperator, + objectInfo.detail(), + objectInfo.table(), + objectInfo.relation(), + rows, + cost, + nonNegative(operator.decimal("FIRST_ROW_COST")), + rows == null ? null : ROWS_MEANING, + cost == null ? null : COST_MEANING, + predicates, + attributes, + children + ); + } + + private static ObjectInfo objectInfo( + String operatorId, + List streams, + Map> objects, + Map attributes + ) { + String firstTable = null; + String firstRelation = null; + int streamIndex = 0; + int objectIndex = 0; + for (Row stream : streams) { + boolean belongs = ("D".equalsIgnoreCase(stream.text("SOURCE_TYPE")) + && operatorId.equals(stream.text("TARGET_ID"))) + || ("D".equalsIgnoreCase(stream.text("TARGET_TYPE")) + && operatorId.equals(stream.text("SOURCE_ID"))); + if (!belongs) { + continue; + } + streamIndex++; + copyRow(attributes, "ObjectStream[" + streamIndex + "].", stream, Set.copyOf(Db2ExplainTables.STATEMENT_KEY)); + String schema = stream.text("OBJECT_SCHEMA"); + String name = stream.text("OBJECT_NAME"); + if (name != null && firstTable == null) { + firstTable = name; + firstRelation = schema == null ? name : schema + "." + name; + } + for (Row object : objects.getOrDefault(objectKey(schema, name), List.of())) { + objectIndex++; + copyRow(attributes, "Object[" + objectIndex + "].", object, Set.copyOf(Db2ExplainTables.STATEMENT_KEY)); + } + } + String detail = firstRelation == null ? null : "Object: " + firstRelation; + return new ObjectInfo(firstTable, firstRelation, detail); + } + + private static Map predicates(List rows, Map attributes) { + Map predicates = new LinkedHashMap<>(); + int index = 0; + for (Row row : rows) { + index++; + String id = defaultText(row.text("PREDICATE_ID"), Integer.toString(index)); + String how = row.text("HOW_APPLIED"); + String when = row.text("WHEN_EVALUATED"); + String label = "Predicate[" + id + "]"; + if (how != null) label += " " + how; + if (when != null) label += "@" + when; + putUnique(predicates, label, row.text("PREDICATE_TEXT")); + copyRow(attributes, "Predicate[" + index + "].", row, Set.copyOf(Db2ExplainTables.STATEMENT_KEY)); + } + return predicates; + } + + private static void arguments(List rows, Map attributes) { + int index = 0; + for (Row row : rows) { + index++; + String type = defaultText(row.text("ARGUMENT_TYPE"), Integer.toString(index)); + String value = firstNonBlank(row.text("LONG_ARGUMENT_VALUE"), row.text("ARGUMENT_VALUE")); + putUnique(attributes, "Argument[" + type + "]", value); + copyRow(attributes, "Argument[" + index + "].", row, Set.copyOf(Db2ExplainTables.STATEMENT_KEY)); + } + } + + private static Row cardinalityStream(String operatorId, Row incomingEdge, List streams) { + if (incomingEdge != null && "O".equalsIgnoreCase(incomingEdge.text("SOURCE_TYPE")) + && operatorId.equals(incomingEdge.text("SOURCE_ID"))) { + return incomingEdge; + } + for (Row stream : streams) { + if ("O".equalsIgnoreCase(stream.text("SOURCE_TYPE")) + && operatorId.equals(stream.text("SOURCE_ID"))) { + return stream; + } + } + for (Row stream : streams) { + if ("O".equalsIgnoreCase(stream.text("TARGET_TYPE")) + && operatorId.equals(stream.text("TARGET_ID"))) { + return stream; + } + } + return null; + } + + static NormalizedNodeType mapNodeType(String operator) { + String value = operator == null ? "" : operator.trim().toUpperCase(Locale.ROOT); + if (value.contains("HSJOIN")) return NormalizedNodeType.HASH_JOIN; + if (value.contains("MSJOIN")) return NormalizedNodeType.MERGE_JOIN; + if (value.contains("NLJOIN")) return NormalizedNodeType.NESTED_LOOP; + if (value.contains("JOIN")) return NormalizedNodeType.JOIN; + if (value.contains("IXSCAN") || value.contains("RIDSCN")) return NormalizedNodeType.INDEX_SCAN; + if (value.contains("TBSCAN") || value.contains("FETCH")) return NormalizedNodeType.SCAN; + if (value.contains("SORT")) return NormalizedNodeType.SORT; + if (value.contains("GRPBY") || value.contains("AGG")) return NormalizedNodeType.AGGREGATE; + if (value.contains("FILTER")) return NormalizedNodeType.FILTER; + if (value.contains("ROWNUM") || value.contains("LIMIT")) return NormalizedNodeType.LIMIT; + if (value.contains("UNION")) return NormalizedNodeType.UNION; + if (value.contains("TEMP") || value.contains("SPOOL")) return NormalizedNodeType.MATERIALIZE; + if (value.contains("TQ") || value.contains("PARALLEL")) return NormalizedNodeType.PARALLEL; + if (value.contains("RETURN") || value.contains("GENROW") || value.contains("PROJECT")) { + return NormalizedNodeType.PROJECTION; + } + return NormalizedNodeType.OTHER; + } + + private static Map tables(String raw) { + JsonElement parsed; + try { + parsed = JsonParser.parseString(raw); + } catch (RuntimeException e) { + throw new IllegalArgumentException("DB2 Explain table envelope is not valid JSON"); + } + if (!parsed.isJsonObject()) { + throw new IllegalArgumentException("DB2 Explain table envelope is not an object"); + } + JsonElement resultSetsElement = parsed.getAsJsonObject().get("resultSets"); + if (resultSetsElement == null || !resultSetsElement.isJsonArray()) { + throw new IllegalArgumentException("DB2 Explain table envelope has no resultSets array"); + } + Map result = new LinkedHashMap<>(); + for (JsonElement element : resultSetsElement.getAsJsonArray()) { + if (!element.isJsonObject()) { + throw new IllegalArgumentException("DB2 Explain result set is not an object"); + } + JsonObject object = element.getAsJsonObject(); + String name = text(object.get("name")); + JsonElement columnsElement = object.get("columns"); + JsonElement rowsElement = object.get("rows"); + if (name == null || columnsElement == null || !columnsElement.isJsonArray() + || rowsElement == null || !rowsElement.isJsonArray()) { + throw new IllegalArgumentException("DB2 Explain result set metadata is invalid"); + } + List columns = new ArrayList<>(); + for (JsonElement columnElement : columnsElement.getAsJsonArray()) { + if (!columnElement.isJsonObject()) { + throw new IllegalArgumentException("DB2 Explain column metadata is invalid"); + } + String column = text(columnElement.getAsJsonObject().get("name")); + if (column == null) { + throw new IllegalArgumentException("DB2 Explain column has no name"); + } + columns.add(upper(column)); + } + List rows = new ArrayList<>(); + for (JsonElement rowElement : rowsElement.getAsJsonArray()) { + if (!rowElement.isJsonArray() || rowElement.getAsJsonArray().size() != columns.size()) { + throw new IllegalArgumentException("DB2 Explain row width does not match its columns"); + } + Map fields = new LinkedHashMap<>(); + for (int index = 0; index < columns.size(); index++) { + fields.put(columns.get(index), rowElement.getAsJsonArray().get(index)); + } + rows.add(new Row(fields)); + } + result.put(upper(name), new TableData(List.copyOf(rows))); + } + return result; + } + + private static Map> group(TableData table, String column) { + Map> result = new HashMap<>(); + if (table == null) { + return result; + } + for (Row row : table.rows()) { + String key = row.text(column); + if (key != null) { + result.computeIfAbsent(key, ignored -> new ArrayList<>()).add(row); + } + } + return result; + } + + private static Map> objects(TableData table) { + Map> result = new HashMap<>(); + if (table == null) { + return result; + } + for (Row row : table.rows()) { + result.computeIfAbsent( + objectKey(row.text("OBJECT_SCHEMA"), row.text("OBJECT_NAME")), + ignored -> new ArrayList<>() + ).add(row); + } + return result; + } + + private static void collectReachable( + List roots, + Map> childrenByTarget, + Set reached + ) { + List pending = new ArrayList<>(roots); + while (!pending.isEmpty()) { + String id = pending.remove(pending.size() - 1); + if (!reached.add(id)) { + continue; + } + for (Row edge : childrenByTarget.getOrDefault(id, List.of())) { + String child = edge.text("SOURCE_ID"); + if (child != null) pending.add(child); + } + } + } + + private static void copyRow( + Map target, + String prefix, + Row row, + Set excluded + ) { + row.fields().forEach((name, value) -> { + if (!excluded.contains(name)) { + putUnique(target, prefix + name, text(value)); + } + }); + } + + private static void putUnique(Map values, String key, String value) { + if (value == null || value.isBlank()) { + return; + } + String candidate = key; + int suffix = 2; + while (values.containsKey(candidate)) { + candidate = key + "[" + suffix++ + "]"; + } + values.put(candidate, value); + } + + private static BigDecimal nonNegative(BigDecimal value) { + return value != null && value.signum() >= 0 ? value : null; + } + + private static int compareNativeIds(String left, String right) { + try { + return new BigDecimal(left).compareTo(new BigDecimal(right)); + } catch (RuntimeException ignored) { + return left.compareTo(right); + } + } + + private static String objectKey(String schema, String name) { + return defaultText(schema, "") + "\u0000" + defaultText(name, ""); + } + + private static String text(JsonElement value) { + if (value == null || value.isJsonNull() || !value.isJsonPrimitive()) { + return null; + } + String result = value.getAsString(); + return result == null || result.isBlank() ? null : result.trim(); + } + + private static String upper(String value) { + return value == null ? null : value.trim().toUpperCase(Locale.ROOT); + } + + private static String defaultText(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } + + private static String firstNonBlank(String... values) { + for (String value : values) { + if (value != null && !value.isBlank()) return value; + } + return null; + } + + private static void addLimitWarning(List warnings, String message) { + if (warnings.stream().noneMatch(warning -> PlanCodes.PLAN_LIMIT_REACHED.equals(warning.code()))) { + warnings.add(PlanDiagnostic.of(PlanCodes.PLAN_LIMIT_REACHED, message)); + } + } + + private record TableData(List rows) { + } + + private record Row(Map fields) { + String text(String name) { + return Db2PlanParser.text(fields.get(name)); + } + + BigDecimal decimal(String name) { + JsonElement value = fields.get(name); + if (value == null || value.isJsonNull() || !value.isJsonPrimitive()) return null; + try { + return value.getAsBigDecimal(); + } catch (RuntimeException e) { + return null; + } + } + } + + private record ObjectInfo(String table, String relation, String detail) { + } + + public record ParseResult(List roots, List warnings, boolean structured) { + public ParseResult { + roots = roots == null ? List.of() : List.copyOf(roots); + warnings = warnings == null ? List.of() : List.copyOf(warnings); + } + + static ParseResult failed(List warnings, String message) { + List result = new ArrayList<>(warnings); + result.add(PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, message)); + return new ParseResult(List.of(), result, false); + } + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/mariadb/MariaDBConnectionManager.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/mariadb/MariaDBConnectionManager.java index fa709908..382aa70e 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/mariadb/MariaDBConnectionManager.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/mariadb/MariaDBConnectionManager.java @@ -1,5 +1,6 @@ package org.jumpserver.chen.modules.mariadb; +import org.apache.commons.lang3.StringUtils; import org.jumpserver.chen.framework.datasource.Datasource; import org.jumpserver.chen.framework.datasource.base.BaseConnectionManager; import org.jumpserver.chen.framework.datasource.entity.DBConnectInfo; @@ -46,9 +47,18 @@ public String getDisplayJDBCUrl() { return this.getConnectInfo().toDisplayJDBCUrl(jdbcUrlTemplate); } + @Override + public String getDatabaseContextKey() { + // MariaDB 的对象树使用 schema 节点表示 database,连接池默认库也要取这个值。 + return "schema"; + } @Override public String getJDBCUrl(String database) { - return this.jdbcUrl; + if (StringUtils.isBlank(database)) { + return this.jdbcUrl; + } + // 连接池不能依赖上一条连接执行过 USE,这里显式把 database 放进 JDBC URL。 + return this.getConnectInfo().toJDBCUrl(jdbcUrlTemplate, database); } } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/mariadb/MariaDBDatasource.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/mariadb/MariaDBDatasource.java index 7eb034d8..fce5bc99 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/mariadb/MariaDBDatasource.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/mariadb/MariaDBDatasource.java @@ -4,9 +4,13 @@ import org.jumpserver.chen.framework.datasource.DatasourceFactory; import org.jumpserver.chen.framework.datasource.base.BaseDatasource; import org.jumpserver.chen.framework.datasource.entity.DBConnectInfo; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; +import org.jumpserver.chen.framework.datasource.plan.ExecutionPlanDialect; +import org.jumpserver.chen.modules.mysql.MysqlMetadataProvider; import org.jumpserver.chen.modules.mysql.MysqlActionHandler; public class MariaDBDatasource extends BaseDatasource { + private final ExecutionPlanDialect executionPlanDialect = new MariaDBExecutionPlanDialect(); static { DatasourceFactory.Register(MariaDBDatasource.class); @@ -14,10 +18,16 @@ public class MariaDBDatasource extends BaseDatasource { public MariaDBDatasource(DBConnectInfo dbConnectInfo) { this.connectionManager = new MariaDBConnectionManager(dbConnectInfo, this); + this.metadataCatalog = new MetadataCatalog(this.connectionManager, new MysqlMetadataProvider(this.connectionManager)); this.resourceBrowser = new MariaDBResourceBrowser(this.connectionManager); this.actionHandler = new MysqlActionHandler(); } + @Override + public ExecutionPlanDialect getExecutionPlanDialect() { + return this.executionPlanDialect; + } + @Override public String getName() { return "mariadb"; diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/mariadb/MariaDBExecutionPlanDialect.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/mariadb/MariaDBExecutionPlanDialect.java new file mode 100644 index 00000000..773ab9dc --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/mariadb/MariaDBExecutionPlanDialect.java @@ -0,0 +1,415 @@ +package org.jumpserver.chen.modules.mariadb; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.jumpserver.chen.framework.datasource.analysis.SqlStatementAnalysis; +import org.jumpserver.chen.framework.datasource.plan.BaseExecutionPlanDialect; +import org.jumpserver.chen.framework.datasource.plan.ConnectionInvalidatedException; +import org.jumpserver.chen.framework.datasource.plan.DialectPlanResult; +import org.jumpserver.chen.framework.datasource.plan.ExecutionPlanCapabilities; +import org.jumpserver.chen.framework.datasource.plan.NormalizedNodeType; +import org.jumpserver.chen.framework.datasource.plan.PlanCodes; +import org.jumpserver.chen.framework.datasource.plan.PlanDatabase; +import org.jumpserver.chen.framework.datasource.plan.PlanDiagnostic; +import org.jumpserver.chen.framework.datasource.plan.PlanEffects; +import org.jumpserver.chen.framework.datasource.plan.PlanI18n; +import org.jumpserver.chen.framework.datasource.plan.PlanExecutionContext; +import org.jumpserver.chen.framework.datasource.plan.PlanNode; +import org.jumpserver.chen.framework.datasource.plan.PlanPrerequisite; +import org.jumpserver.chen.framework.datasource.plan.PlanRawFormat; +import org.jumpserver.chen.framework.datasource.plan.PlanStatus; +import org.jumpserver.chen.framework.datasource.plan.PlanTransactionState; + +import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +public class MariaDBExecutionPlanDialect extends BaseExecutionPlanDialect { + private static final Gson TABLE_GSON = new GsonBuilder().serializeNulls().create(); + private static final Set SAFE_SCHEMAS = Set.of("mysql", "sys", "information_schema", "performance_schema"); + private static final Set BUILTIN_FUNCTIONS = Set.of( + "count", "sum", "avg", "min", "max", "ifnull", "if", "nullif", "coalesce", "greatest", "least", + "now", "curdate", "curtime", "current_date", "current_time", "current_timestamp", + "concat", "concat_ws", "length", "char_length", "substring", "substr", "left", "right", "trim", + "replace", "lower", "upper", "cast", "convert", "abs", "ceil", "floor", "round", "mod", + "row_number", "rank", "dense_rank", "exists", "group_concat", "date_format" + ); + private static final Set UNSAFE_FUNCTIONS = Set.of( + "load_file", "sleep", "benchmark", "sys_exec", "sys_eval" + ); + + private final ExecutionPlanCapabilities capabilities = ExecutionPlanCapabilities.mariadb(); + + @Override + public PlanDatabase database() { + return PlanDatabase.mariadb; + } + + @Override + public ExecutionPlanCapabilities capabilities() { + return capabilities; + } + + @Override + public DialectPlanResult explainEstimated(PlanExecutionContext context, SqlStatementAnalysis statement) + throws SQLException { + List prerequisites = new ArrayList<>(); + PlanDiagnostic unsafe = unsafeFunctions(statement); + if (unsafe != null) { + prerequisites.add(PlanPrerequisite.unmet( + PlanCodes.UNSAFE_TO_ESTIMATE, + unsafe.message(), + PlanI18n.msg("Plan.RemoveUnsafeFunctions", "Remove user-defined or planning-time-unsafe functions and retry") + )); + return unmet(context, prerequisites, unsafe); + } + if (context.transactionState() == PlanTransactionState.TRANSACTION_FAILED) { + prerequisites.add(PlanPrerequisite.unmet( + PlanCodes.TRANSACTION_CONTEXT_UNSAFE, + PlanI18n.msg("Plan.MariaDBTransactionAborted", "The current MariaDB transaction is already aborted"), + PlanI18n.msg("Plan.RollbackThenRetry", "Rollback or finish the existing transaction, then retry") + )); + return unmet( + context, + prerequisites, + PlanDiagnostic.of(PlanCodes.TRANSACTION_CONTEXT_UNSAFE, PlanI18n.msg("Plan.CannotEstimateFailedTransaction", "Cannot estimate a plan in a failed transaction")) + ); + } + prerequisites.add(PlanPrerequisite.met("statement", PlanI18n.msg("Plan.SingleSelect", "Single SELECT"))); + PlanEffects effects = PlanEffects.unchangedReuse(); + try { + return explainJson(context, statement, prerequisites, effects); + } catch (SQLException e) { + if (!isJsonUnsupported(e)) { + throwIfInvalid(context, e); + throw e; + } + return explainClassic(context, statement, prerequisites, effects); + } + } + + private DialectPlanResult explainJson( + PlanExecutionContext context, + SqlStatementAnalysis statement, + List prerequisites, + PlanEffects effects + ) throws SQLException { + BoundedRaw raw = executeExplain(context, "EXPLAIN FORMAT=JSON " + statement.sql()); + if (raw.truncated()) { + return rawOnly(context, prerequisites, effects, raw.text(), PlanRawFormat.JSON, true, + PlanDiagnostic.of(PlanCodes.PLAN_LIMIT_REACHED, PlanI18n.msg("Plan.RawPlanExceededDisplayLimit", "Raw plan exceeded the display limit"))); + } + MariaDBPlanParser.ParseResult parsed = MariaDBPlanParser.parse(raw.text(), context.maxNodes(), context.maxDepth()); + if (!parsed.structured()) { + return rawOnly(context, prerequisites, effects, raw.text(), PlanRawFormat.JSON, false, + parsed.warnings().isEmpty() + ? PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, PlanI18n.msg("Plan.MariaDBJsonNormalizeFailed", "MariaDB plan JSON could not be normalized")) + : parsed.warnings().get(parsed.warnings().size() - 1)); + } + return new DialectPlanResult( + context.serverVersion(), + PlanStatus.SUCCESS, + parsed.roots(), + raw.text(), + PlanRawFormat.JSON, + MariaDBPlanParser.RAW_FORMAT_VERSION, + false, + capabilities, + prerequisites, + effects, + null, + parsed.warnings() + ); + } + + private DialectPlanResult explainClassic( + PlanExecutionContext context, + SqlStatementAnalysis statement, + List prerequisites, + PlanEffects effects + ) throws SQLException { + Connection connection = context.connection(); + Statement jdbcStatement = null; + ResultSet resultSet = null; + try { + context.throwIfCancelled(); + jdbcStatement = connection.createStatement(); + context.registerStatement(jdbcStatement); + resultSet = jdbcStatement.executeQuery("EXPLAIN " + statement.sql()); + TableEnvelope envelope = TableEnvelope.from(resultSet); + String rawText = TABLE_GSON.toJson(envelope); + BoundedRaw bounded = boundRaw(rawText, context.maxRawBytes()); + List roots = envelope.toNodes(); + if (bounded.truncated() || roots.isEmpty()) { + return rawOnly( + context, + prerequisites, + effects, + bounded.text(), + PlanRawFormat.TABLE, + bounded.truncated(), + PlanDiagnostic.of( + bounded.truncated() ? PlanCodes.PLAN_LIMIT_REACHED : PlanCodes.PLAN_PARSE_FAILED, + bounded.truncated() ? "Raw plan exceeded the display limit" : "Classic EXPLAIN produced no rows" + ) + ); + } + return new DialectPlanResult( + context.serverVersion(), + PlanStatus.SUCCESS, + roots, + bounded.text(), + PlanRawFormat.TABLE, + "chen-table-v1", + false, + capabilities, + prerequisites, + effects, + null, + List.of() + ); + } catch (SQLException e) { + throwIfInvalid(context, e); + throw e; + } finally { + closeQuietly(resultSet); + context.unregisterStatement(jdbcStatement); + closeQuietly(jdbcStatement); + } + } + + private BoundedRaw executeExplain(PlanExecutionContext context, String sql) throws SQLException { + Connection connection = context.connection(); + Statement jdbcStatement = null; + ResultSet resultSet = null; + try { + context.throwIfCancelled(); + jdbcStatement = connection.createStatement(); + context.registerStatement(jdbcStatement); + resultSet = jdbcStatement.executeQuery(sql); + String value = resultSet.next() ? resultSet.getString(1) : ""; + return boundRaw(value, context.maxRawBytes()); + } finally { + closeQuietly(resultSet); + context.unregisterStatement(jdbcStatement); + closeQuietly(jdbcStatement); + } + } + + static boolean isJsonUnsupported(SQLException error) { + String message = error.getMessage() == null ? "" : error.getMessage().toLowerCase(Locale.ROOT); + return message.contains("format") && (message.contains("json") || message.contains("explain")); + } + + static PlanDiagnostic unsafeFunctions(SqlStatementAnalysis statement) { + for (String rawName : statement.functionNames()) { + String name = rawName == null ? "" : rawName.trim(); + if (name.isEmpty()) { + continue; + } + String lower = name.toLowerCase(Locale.ROOT).replace("\"", "").replace("`", ""); + int dot = lower.lastIndexOf('.'); + String schema = dot < 0 ? "" : lower.substring(0, dot); + String function = dot < 0 ? lower : lower.substring(dot + 1); + if (UNSAFE_FUNCTIONS.contains(function)) { + return PlanDiagnostic.of(PlanCodes.UNSAFE_TO_ESTIMATE, PlanI18n.msg("Plan.FunctionNotSafeToEstimate", "Function %s is not safe to estimate", name)); + } + if (!schema.isEmpty() && !SAFE_SCHEMAS.contains(schema)) { + return PlanDiagnostic.of(PlanCodes.UNSAFE_TO_ESTIMATE, PlanI18n.msg("Plan.FunctionOutsideMariaDBCatalogs", "Function %s is outside known MariaDB catalogs", name)); + } + if (schema.isEmpty() && !BUILTIN_FUNCTIONS.contains(function)) { + return PlanDiagnostic.of(PlanCodes.UNSAFE_TO_ESTIMATE, PlanI18n.msg("Plan.FunctionNotMariaDBBuiltin", "Function %s is not a known MariaDB builtin", name)); + } + } + return null; + } + + private DialectPlanResult rawOnly( + PlanExecutionContext context, + List prerequisites, + PlanEffects effects, + String rawText, + PlanRawFormat format, + boolean truncated, + PlanDiagnostic warning + ) { + return new DialectPlanResult( + context.serverVersion(), + PlanStatus.RAW_ONLY, + List.of(), + rawText, + format, + format == PlanRawFormat.JSON ? MariaDBPlanParser.RAW_FORMAT_VERSION : "chen-table-v1", + truncated, + capabilities, + prerequisites, + effects, + null, + warning == null ? List.of() : List.of(warning) + ); + } + + private DialectPlanResult unmet( + PlanExecutionContext context, + List prerequisites, + PlanDiagnostic error + ) { + return new DialectPlanResult( + context.serverVersion(), + PlanStatus.PREREQUISITES_UNMET, + List.of(), + null, + null, + null, + false, + capabilities, + prerequisites, + PlanEffects.unchangedReuse(), + error, + List.of() + ); + } + + private void throwIfInvalid(PlanExecutionContext context, SQLException error) throws SQLException { + String state = error.getSQLState(); + if (state != null && state.startsWith("08")) { + throw new ConnectionInvalidatedException( + error.getMessage(), + error, + new PlanDiagnostic(PlanCodes.CONNECTION_INVALIDATED, error.getMessage(), error.getSQLState(), Integer.toString(error.getErrorCode())), + null, + new DialectPlanResult( + context.serverVersion(), + PlanStatus.CONNECTION_INVALIDATED, + List.of(), + null, + null, + null, + false, + capabilities, + List.of(), + PlanEffects.discard(PlanEffects.SessionState.UNKNOWN, PlanEffects.TransactionState.UNKNOWN), + new PlanDiagnostic(PlanCodes.CONNECTION_INVALIDATED, error.getMessage(), error.getSQLState(), Integer.toString(error.getErrorCode())), + List.of() + ) + ); + } + } + + static final class TableEnvelope { + final List resultSets; + + TableEnvelope(List resultSets) { + this.resultSets = resultSets; + } + + static TableEnvelope from(ResultSet resultSet) throws SQLException { + ResultSetMetaData meta = resultSet.getMetaData(); + List columns = new ArrayList<>(); + for (int i = 1; i <= meta.getColumnCount(); i++) { + columns.add(new Column(meta.getColumnLabel(i), meta.getColumnTypeName(i))); + } + List> rows = new ArrayList<>(); + while (resultSet.next()) { + List row = new ArrayList<>(); + for (int i = 1; i <= columns.size(); i++) { + row.add(resultSet.getString(i)); + } + rows.add(row); + } + return new TableEnvelope(List.of(new ResultSetEnvelope("EXPLAIN", columns, rows))); + } + + List toNodes() { + if (resultSets.isEmpty()) { + return List.of(); + } + ResultSetEnvelope set = resultSets.get(0); + int tableIdx = set.indexOf("table"); + int typeIdx = set.indexOf("type"); + int rowsIdx = set.indexOf("rows"); + int extraIdx = set.indexOf("Extra"); + List children = new ArrayList<>(); + int id = 1; + for (List row : set.rows) { + String access = typeIdx >= 0 ? row.get(typeIdx) : "row"; + String table = tableIdx >= 0 ? row.get(tableIdx) : null; + BigDecimal rows = null; + try { + if (rowsIdx >= 0 && row.get(rowsIdx) != null) { + rows = new BigDecimal(row.get(rowsIdx)); + } + } catch (NumberFormatException ignored) { + } + Map predicates = new LinkedHashMap<>(); + if (extraIdx >= 0 && row.get(extraIdx) != null) { + predicates.put("Extra", row.get(extraIdx)); + } + children.add(new PlanNode( + "n" + id++, + null, + MariaDBPlanParser.mapAccess(access, access), + access == null ? "row" : access, + null, + access, + extraIdx >= 0 ? row.get(extraIdx) : null, + table, + table, + rows, + null, + null, + rows != null ? MariaDBPlanParser.ROWS_MEANING : null, + null, + predicates, + Map.of(), + List.of() + )); + } + if (children.isEmpty()) { + return List.of(); + } + return List.of(new PlanNode( + "n0", + null, + NormalizedNodeType.OTHER, + "EXPLAIN", + null, + "EXPLAIN", + null, + null, + null, + null, + null, + null, + null, + null, + Map.of(), + Map.of(), + children + )); + } + } + + private record ResultSetEnvelope(String name, List columns, List> rows) { + int indexOf(String label) { + for (int i = 0; i < columns.size(); i++) { + if (label.equalsIgnoreCase(columns.get(i).name)) { + return i; + } + } + return -1; + } + } + + private record Column(String name, String type) { + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/mariadb/MariaDBPlanParser.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/mariadb/MariaDBPlanParser.java new file mode 100644 index 00000000..0132a5c8 --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/mariadb/MariaDBPlanParser.java @@ -0,0 +1,324 @@ +package org.jumpserver.chen.modules.mariadb; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.jumpserver.chen.framework.datasource.plan.NormalizedNodeType; +import org.jumpserver.chen.framework.datasource.plan.PlanCodes; +import org.jumpserver.chen.framework.datasource.plan.PlanDiagnostic; +import org.jumpserver.chen.framework.datasource.plan.PlanNode; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +public final class MariaDBPlanParser { + public static final String RAW_FORMAT_VERSION = "mariadb-explain-json"; + public static final String ROWS_MEANING = "MariaDB estimated rows"; + public static final String COST_MEANING = "MariaDB cost"; + private static final Set ANALYZE_KEYS = Set.of("r_rows", "r_filtered", "r_loops", "r_total_time_ms"); + + private MariaDBPlanParser() { + } + + public static ParseResult parse(String rawJson, int maxNodes, int maxDepth) { + List warnings = new ArrayList<>(); + if (rawJson == null || rawJson.isBlank()) { + return ParseResult.failed(warnings, PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, "Empty MariaDB plan JSON")); + } + JsonElement root; + try { + root = JsonParser.parseString(rawJson); + } catch (RuntimeException e) { + return ParseResult.failed(warnings, PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, "MariaDB plan JSON is not valid")); + } + JsonObject object = asObject(root); + if (object == null || !object.has("query_block")) { + return ParseResult.failed(warnings, PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, "Unrecognized MariaDB EXPLAIN JSON shape")); + } + AtomicInteger ids = new AtomicInteger(1); + AtomicInteger nodeCount = new AtomicInteger(0); + List roots = new ArrayList<>(); + try { + PlanNode node = convert(object.getAsJsonObject("query_block"), "query_block", ids, nodeCount, 1, maxNodes, maxDepth); + if (node != null) { + roots.add(node); + } + } catch (LimitReachedException e) { + warnings.add(PlanDiagnostic.of(PlanCodes.PLAN_LIMIT_REACHED, e.getMessage())); + } + if (roots.isEmpty()) { + return ParseResult.failed(warnings, PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, "MariaDB plan JSON produced no nodes")); + } + return new ParseResult(roots, warnings, true); + } + + private static PlanNode convert( + JsonObject object, + String nativeOperator, + AtomicInteger ids, + AtomicInteger nodeCount, + int depth, + int maxNodes, + int maxDepth + ) { + if (object == null) { + return null; + } + if (nodeCount.incrementAndGet() > maxNodes || depth > maxDepth) { + throw new LimitReachedException("Execution plan exceeded size limits"); + } + JsonObject table = nested(object, "table"); + if (table != null && !"table".equals(nativeOperator) && !object.has("nested_loop") && !object.has("block-nl-join")) { + List children = new ArrayList<>(); + children.add(convert(table, "table", ids, nodeCount, depth + 1, maxNodes, maxDepth)); + addChildren(object, ids, nodeCount, depth, maxNodes, maxDepth, children, true); + return node(ids, nativeOperator, mapNodeType(nativeOperator), text(object, "message"), null, null, decimal(object, "cost"), children, object); + } + + JsonObject source = "table".equals(nativeOperator) && table != null ? table : object; + String tableName = text(source, "table_name"); + String access = text(source, "access_type"); + String operator = access != null ? access : nativeOperator; + BigDecimal rows = decimal(source, "rows"); + BigDecimal cost = firstDecimal(source, "cost", "read_cost", "prefix_cost"); + List children = new ArrayList<>(); + addChildren(source, ids, nodeCount, depth, maxNodes, maxDepth, children, false); + if (source != object) { + addChildren(object, ids, nodeCount, depth, maxNodes, maxDepth, children, true); + } + Map predicates = new LinkedHashMap<>(); + put(predicates, "attached_condition", text(source, "attached_condition")); + put(predicates, "message", text(source, "message")); + Map attributes = new LinkedHashMap<>(); + put(attributes, "key", text(source, "key")); + put(attributes, "access_type", access); + put(attributes, "select_id", text(source, "select_id")); + return new PlanNode( + "n" + ids.getAndIncrement(), + null, + mapAccess(access, operator), + operator, + null, + operator, + text(source, "message"), + tableName, + tableName, + rows, + cost, + null, + rows != null ? ROWS_MEANING : null, + cost != null ? COST_MEANING : null, + predicates, + attributes, + children + ); + } + + private static void addChildren( + JsonObject object, + AtomicInteger ids, + AtomicInteger nodeCount, + int depth, + int maxNodes, + int maxDepth, + List children, + boolean skipTable + ) { + addNamed(object, "filesort", "filesort", ids, nodeCount, depth, maxNodes, maxDepth, children); + addNamed(object, "temporary_table", "temporary_table", ids, nodeCount, depth, maxNodes, maxDepth, children); + addNamed(object, "materialized", "materialized", ids, nodeCount, depth, maxNodes, maxDepth, children); + addNamed(object, "expression_cache", "expression_cache", ids, nodeCount, depth, maxNodes, maxDepth, children); + addNamed(object, "block-nl-join", "block-nl-join", ids, nodeCount, depth, maxNodes, maxDepth, children); + if (!skipTable) { + addNamed(object, "table", "table", ids, nodeCount, depth, maxNodes, maxDepth, children); + } + JsonElement nestedLoop = object.get("nested_loop"); + if (nestedLoop != null && nestedLoop.isJsonArray()) { + for (JsonElement item : nestedLoop.getAsJsonArray()) { + JsonObject child = asObject(item); + if (child == null) { + continue; + } + JsonObject nestedTable = nested(child, "table"); + children.add(convert( + nestedTable != null ? nestedTable : child, + nestedTable != null ? "table" : "nested_loop", + ids, + nodeCount, + depth + 1, + maxNodes, + maxDepth + )); + } + } + JsonElement subs = object.get("attached_subqueries"); + if (subs != null && subs.isJsonArray()) { + for (JsonElement item : subs.getAsJsonArray()) { + JsonObject child = asObject(item); + if (child == null) { + continue; + } + JsonObject block = nested(child, "query_block"); + children.add(convert(block != null ? block : child, "attached_subquery", ids, nodeCount, depth + 1, maxNodes, maxDepth)); + } + } + } + + private static void addNamed( + JsonObject object, + String key, + String operator, + AtomicInteger ids, + AtomicInteger nodeCount, + int depth, + int maxNodes, + int maxDepth, + List children + ) { + JsonObject child = nested(object, key); + if (child != null) { + children.add(convert(child, operator, ids, nodeCount, depth + 1, maxNodes, maxDepth)); + } + } + + static NormalizedNodeType mapAccess(String access, String nativeOperator) { + String value = (access != null ? access : nativeOperator); + if (value == null) { + return NormalizedNodeType.OTHER; + } + String lower = value.toLowerCase(Locale.ROOT); + if (lower.contains("block-nl") || lower.contains("nested")) { + return NormalizedNodeType.NESTED_LOOP; + } + if (lower.contains("eq_ref") || lower.contains("ref") || lower.contains("range") || lower.contains("index")) { + return NormalizedNodeType.INDEX_SCAN; + } + if (lower.contains("scan") || lower.equals("all") || lower.equals("const") || lower.equals("system")) { + return NormalizedNodeType.SCAN; + } + if (lower.contains("filesort") || lower.contains("sort")) { + return NormalizedNodeType.SORT; + } + if (lower.contains("group") || lower.contains("aggregate")) { + return NormalizedNodeType.AGGREGATE; + } + if (lower.contains("materializ") || lower.contains("temporary")) { + return NormalizedNodeType.MATERIALIZE; + } + if (lower.contains("cache") || lower.contains("expression")) { + return NormalizedNodeType.OTHER; + } + if (lower.contains("subquery") || lower.contains("query_block")) { + return NormalizedNodeType.SUBQUERY; + } + return NormalizedNodeType.OTHER; + } + + static NormalizedNodeType mapNodeType(String nativeOperator) { + return mapAccess(null, nativeOperator); + } + + private static PlanNode node( + AtomicInteger ids, + String nativeOperator, + NormalizedNodeType type, + String detail, + String table, + BigDecimal rows, + BigDecimal cost, + List children, + JsonObject source + ) { + return new PlanNode( + "n" + ids.getAndIncrement(), + null, + type, + nativeOperator, + null, + nativeOperator, + detail, + table, + table, + rows, + cost, + null, + rows != null ? ROWS_MEANING : null, + cost != null ? COST_MEANING : null, + Map.of(), + Map.of(), + children + ); + } + + private static JsonObject nested(JsonObject object, String key) { + JsonElement element = object.get(key); + return element != null && element.isJsonObject() ? element.getAsJsonObject() : null; + } + + private static JsonObject asObject(JsonElement element) { + return element != null && element.isJsonObject() ? element.getAsJsonObject() : null; + } + + private static void put(Map map, String key, String value) { + if (value != null && !value.isBlank() && !ANALYZE_KEYS.contains(key)) { + map.put(key, value); + } + } + + private static String text(JsonObject object, String key) { + if (object == null || !object.has(key) || object.get(key).isJsonNull() || ANALYZE_KEYS.contains(key)) { + return null; + } + JsonElement element = object.get(key); + return element.isJsonPrimitive() ? element.getAsString() : element.toString(); + } + + private static BigDecimal firstDecimal(JsonObject object, String... keys) { + for (String key : keys) { + BigDecimal value = decimal(object, key); + if (value != null) { + return value; + } + } + return null; + } + + private static BigDecimal decimal(JsonObject object, String key) { + if (object == null || ANALYZE_KEYS.contains(key) || !object.has(key) || object.get(key).isJsonNull()) { + return null; + } + try { + JsonElement element = object.get(key); + if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber()) { + return element.getAsBigDecimal(); + } + String text = element.getAsString(); + if (text == null || text.isBlank()) { + return null; + } + return new BigDecimal(text); + } catch (RuntimeException e) { + return null; + } + } + + public record ParseResult(List roots, List warnings, boolean structured) { + static ParseResult failed(List warnings, PlanDiagnostic error) { + List all = new ArrayList<>(warnings); + all.add(error); + return new ParseResult(List.of(), all, false); + } + } + + private static final class LimitReachedException extends RuntimeException { + private LimitReachedException(String message) { + super(message); + } + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlActionHandler.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlActionHandler.java index 0109e6de..e080be31 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlActionHandler.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlActionHandler.java @@ -58,19 +58,6 @@ public List getTableActions(TreeNode node) { ); } - private static final String SQL_SELECT_SCHEMA_DETAIL = "select * from information_schema.SCHEMATA where SCHEMA_NAME = '?'"; - - public EventEmitter onSchemaProperties(TreeNode node) throws SQLException { - return this.onShowObjectProperties("schema", SQL_SELECT_SCHEMA_DETAIL, node); - } - - private static final String SQL_SELECT_TABLE_DETAIL = "select TABLE_NAME,TABLE_SCHEMA,TABLE_TYPE,ENGINE,AVG_ROW_LENGTH,DATA_LENGTH,MAX_DATA_LENGTH,CREATE_TIME,TABLE_COLLATION from information_schema.TABLES WHERE TABLE_NAME = '?'"; - - public EventEmitter onTableProperties(TreeNode node) throws SQLException { - return this.onShowObjectProperties("table", SQL_SELECT_TABLE_DETAIL, node); - } - - @Override public EventEmitter handleForm(FormData formData) throws SQLException { return null; diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlConnectionManager.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlConnectionManager.java index cc83f291..20c5e4d3 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlConnectionManager.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlConnectionManager.java @@ -108,8 +108,18 @@ public String getDisplayJDBCUrl() { return this.getConnectInfo().toDisplayJDBCUrl(jdbcUrlTemplate); } + @Override + public String getDatabaseContextKey() { + // MySQL 的对象树使用 schema 节点表示 database,连接池默认库也要取这个值。 + return "schema"; + } + @Override public String getJDBCUrl(String database) { - return this.jdbcUrl; + if (StringUtils.isBlank(database)) { + return this.jdbcUrl; + } + // 连接池不能依赖上一条连接执行过 USE,这里显式把 database 放进 JDBC URL。 + return this.getConnectInfo().toJDBCUrl(jdbcUrlTemplate, database); } } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlDatasource.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlDatasource.java index 5d5fdd2a..e1838e32 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlDatasource.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlDatasource.java @@ -4,8 +4,11 @@ import org.jumpserver.chen.framework.datasource.DatasourceFactory; import org.jumpserver.chen.framework.datasource.base.BaseDatasource; import org.jumpserver.chen.framework.datasource.entity.DBConnectInfo; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; +import org.jumpserver.chen.framework.datasource.plan.ExecutionPlanDialect; public class MysqlDatasource extends BaseDatasource { + private final ExecutionPlanDialect executionPlanDialect = new MysqlExecutionPlanDialect(); static { DatasourceFactory.Register(MysqlDatasource.class); @@ -13,10 +16,16 @@ public class MysqlDatasource extends BaseDatasource { public MysqlDatasource(DBConnectInfo dbConnectInfo) { this.connectionManager = new MysqlConnectionManager(dbConnectInfo, this); + this.metadataCatalog = new MetadataCatalog(this.connectionManager, new MysqlMetadataProvider(this.connectionManager)); this.resourceBrowser = new MysqlResourceBrowser(this.connectionManager); this.actionHandler = new MysqlActionHandler(); } + @Override + public ExecutionPlanDialect getExecutionPlanDialect() { + return this.executionPlanDialect; + } + @Override public String getName() { return "mysql"; diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlExecutionPlanDialect.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlExecutionPlanDialect.java new file mode 100644 index 00000000..b4be5c64 --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlExecutionPlanDialect.java @@ -0,0 +1,457 @@ +package org.jumpserver.chen.modules.mysql; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.jumpserver.chen.framework.datasource.analysis.SqlStatementAnalysis; +import org.jumpserver.chen.framework.datasource.plan.BaseExecutionPlanDialect; +import org.jumpserver.chen.framework.datasource.plan.ConnectionInvalidatedException; +import org.jumpserver.chen.framework.datasource.plan.DialectPlanResult; +import org.jumpserver.chen.framework.datasource.plan.ExecutionPlanCapabilities; +import org.jumpserver.chen.framework.datasource.plan.NormalizedNodeType; +import org.jumpserver.chen.framework.datasource.plan.PlanCodes; +import org.jumpserver.chen.framework.datasource.plan.PlanDatabase; +import org.jumpserver.chen.framework.datasource.plan.PlanDiagnostic; +import org.jumpserver.chen.framework.datasource.plan.PlanEffects; +import org.jumpserver.chen.framework.datasource.plan.PlanI18n; +import org.jumpserver.chen.framework.datasource.plan.PlanExecutionContext; +import org.jumpserver.chen.framework.datasource.plan.PlanNode; +import org.jumpserver.chen.framework.datasource.plan.PlanPrerequisite; +import org.jumpserver.chen.framework.datasource.plan.PlanRawFormat; +import org.jumpserver.chen.framework.datasource.plan.PlanStatus; +import org.jumpserver.chen.framework.datasource.plan.PlanTransactionState; + +import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +public class MysqlExecutionPlanDialect extends BaseExecutionPlanDialect { + private static final Gson TABLE_GSON = new GsonBuilder().serializeNulls().create(); + private static final Set SAFE_SCHEMAS = Set.of("mysql", "sys", "information_schema", "performance_schema"); + private static final Set BUILTIN_FUNCTIONS = Set.of( + "count", "sum", "avg", "min", "max", "ifnull", "if", "nullif", "coalesce", "greatest", "least", + "now", "curdate", "curtime", "current_date", "current_time", "current_timestamp", "unix_timestamp", + "from_unixtime", "date_format", "str_to_date", "datediff", "timestampdiff", "date_add", "date_sub", + "concat", "concat_ws", "length", "char_length", "substring", "substr", "left", "right", "trim", + "ltrim", "rtrim", "replace", "lower", "upper", "cast", "convert", "abs", "ceil", "ceiling", "floor", + "round", "mod", "md5", "sha1", "sha2", "json_extract", "json_unquote", "json_object", "json_array", + "row_number", "rank", "dense_rank", "lag", "lead", "exists", "group_concat", "year", "month", "day", + "hour", "minute", "second", "values", "interval" + ); + private static final Set UNSAFE_FUNCTIONS = Set.of( + "load_file", "sleep", "benchmark", "sys_exec", "sys_eval", "sys_get", "sys_set", + "updatexml", "extractvalue" + ); + + private final ExecutionPlanCapabilities capabilities = ExecutionPlanCapabilities.mysql(); + + @Override + public PlanDatabase database() { + return PlanDatabase.mysql; + } + + @Override + public ExecutionPlanCapabilities capabilities() { + return capabilities; + } + + @Override + public DialectPlanResult explainEstimated(PlanExecutionContext context, SqlStatementAnalysis statement) + throws SQLException { + List prerequisites = new ArrayList<>(); + PlanDiagnostic unsafe = unsafeFunctions(statement); + if (unsafe != null) { + prerequisites.add(PlanPrerequisite.unmet( + PlanCodes.UNSAFE_TO_ESTIMATE, + unsafe.message(), + PlanI18n.msg("Plan.RemoveUnsafeFunctions", "Remove user-defined or planning-time-unsafe functions and retry") + )); + return unmet(context, prerequisites, unsafe); + } + if (context.transactionState() == PlanTransactionState.TRANSACTION_FAILED) { + prerequisites.add(PlanPrerequisite.unmet( + PlanCodes.TRANSACTION_CONTEXT_UNSAFE, + PlanI18n.msg("Plan.MysqlTransactionAborted", "The current MySQL transaction is already aborted"), + PlanI18n.msg("Plan.RollbackThenRetry", "Rollback or finish the existing transaction, then retry") + )); + return unmet( + context, + prerequisites, + PlanDiagnostic.of(PlanCodes.TRANSACTION_CONTEXT_UNSAFE, PlanI18n.msg("Plan.CannotEstimateFailedTransaction", "Cannot estimate a plan in a failed transaction")) + ); + } + + prerequisites.add(PlanPrerequisite.met("statement", PlanI18n.msg("Plan.SingleSelect", "Single SELECT"))); + PlanEffects effects = PlanEffects.unchangedReuse(); + if (jsonSupported(context.serverVersion())) { + try { + return explainJson(context, statement, prerequisites, effects); + } catch (SQLException e) { + if (!isJsonUnsupported(e)) { + throwIfInvalid(context, e); + throw e; + } + } + } + return explainClassic(context, statement, prerequisites, effects); + } + + private DialectPlanResult explainJson( + PlanExecutionContext context, + SqlStatementAnalysis statement, + List prerequisites, + PlanEffects effects + ) throws SQLException { + BoundedRaw raw = executeExplain(context, "EXPLAIN FORMAT=JSON " + statement.sql()); + if (raw.truncated()) { + return rawOnly(context, prerequisites, effects, raw.text(), PlanRawFormat.JSON, MysqlPlanParser.V1, true, + PlanDiagnostic.of(PlanCodes.PLAN_LIMIT_REACHED, PlanI18n.msg("Plan.RawPlanExceededDisplayLimit", "Raw plan exceeded the display limit"))); + } + MysqlPlanParser.ParseResult parsed = MysqlPlanParser.parse(raw.text(), context.maxNodes(), context.maxDepth()); + if (!parsed.structured()) { + return rawOnly(context, prerequisites, effects, raw.text(), PlanRawFormat.JSON, parsed.rawFormatVersion(), false, + parsed.warnings().isEmpty() + ? PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, PlanI18n.msg("Plan.MysqlJsonNormalizeFailed", "MySQL plan JSON could not be normalized")) + : parsed.warnings().get(parsed.warnings().size() - 1)); + } + return new DialectPlanResult( + context.serverVersion(), + PlanStatus.SUCCESS, + parsed.roots(), + raw.text(), + PlanRawFormat.JSON, + parsed.rawFormatVersion(), + false, + capabilities, + prerequisites, + effects, + null, + parsed.warnings() + ); + } + + private DialectPlanResult explainClassic( + PlanExecutionContext context, + SqlStatementAnalysis statement, + List prerequisites, + PlanEffects effects + ) throws SQLException { + Connection connection = context.connection(); + Statement jdbcStatement = null; + ResultSet resultSet = null; + try { + context.throwIfCancelled(); + jdbcStatement = connection.createStatement(); + context.registerStatement(jdbcStatement); + resultSet = jdbcStatement.executeQuery("EXPLAIN " + statement.sql()); + TableEnvelope envelope = TableEnvelope.from(resultSet); + String rawText = TABLE_GSON.toJson(envelope); + BoundedRaw bounded = boundRaw(rawText, context.maxRawBytes()); + List roots = envelope.toNodes(); + if (bounded.truncated() || roots.isEmpty()) { + return rawOnly( + context, + prerequisites, + effects, + bounded.text(), + PlanRawFormat.TABLE, + "chen-table-v1", + bounded.truncated(), + PlanDiagnostic.of( + bounded.truncated() ? PlanCodes.PLAN_LIMIT_REACHED : PlanCodes.PLAN_PARSE_FAILED, + bounded.truncated() ? PlanI18n.msg("Plan.RawPlanExceededDisplayLimit", "Raw plan exceeded the display limit") : PlanI18n.msg("Plan.ClassicExplainEmpty", "Classic EXPLAIN produced no rows") + ) + ); + } + return new DialectPlanResult( + context.serverVersion(), + PlanStatus.SUCCESS, + roots, + bounded.text(), + PlanRawFormat.TABLE, + "chen-table-v1", + false, + capabilities, + prerequisites, + effects, + null, + List.of() + ); + } catch (SQLException e) { + throwIfInvalid(context, e); + throw e; + } finally { + closeQuietly(resultSet); + context.unregisterStatement(jdbcStatement); + closeQuietly(jdbcStatement); + } + } + + private BoundedRaw executeExplain(PlanExecutionContext context, String sql) throws SQLException { + Connection connection = context.connection(); + Statement jdbcStatement = null; + ResultSet resultSet = null; + try { + context.throwIfCancelled(); + jdbcStatement = connection.createStatement(); + context.registerStatement(jdbcStatement); + resultSet = jdbcStatement.executeQuery(sql); + String value = resultSet.next() ? resultSet.getString(1) : ""; + return boundRaw(value, context.maxRawBytes()); + } finally { + closeQuietly(resultSet); + context.unregisterStatement(jdbcStatement); + closeQuietly(jdbcStatement); + } + } + + static boolean jsonSupported(String serverVersion) { + if (serverVersion == null || serverVersion.isBlank()) { + return true; + } + String numeric = serverVersion.replaceAll("[^0-9.].*$", ""); + String[] parts = numeric.split("\\."); + try { + int major = Integer.parseInt(parts[0]); + int minor = parts.length > 1 ? Integer.parseInt(parts[1]) : 0; + int patch = parts.length > 2 ? Integer.parseInt(parts[2]) : 0; + if (major > 5) { + return true; + } + if (major < 5) { + return false; + } + if (minor > 6) { + return true; + } + if (minor < 6) { + return false; + } + return patch >= 5; + } catch (NumberFormatException e) { + return true; + } + } + + static boolean isJsonUnsupported(SQLException error) { + String message = error.getMessage() == null ? "" : error.getMessage().toLowerCase(Locale.ROOT); + return message.contains("format") && (message.contains("json") || message.contains("explain")); + } + + static PlanDiagnostic unsafeFunctions(SqlStatementAnalysis statement) { + for (String rawName : statement.functionNames()) { + String name = rawName == null ? "" : rawName.trim(); + if (name.isEmpty()) { + continue; + } + String lower = name.toLowerCase(Locale.ROOT).replace("\"", "").replace("`", ""); + int dot = lower.lastIndexOf('.'); + String schema = dot < 0 ? "" : lower.substring(0, dot); + String function = dot < 0 ? lower : lower.substring(dot + 1); + if (UNSAFE_FUNCTIONS.contains(function)) { + return PlanDiagnostic.of(PlanCodes.UNSAFE_TO_ESTIMATE, PlanI18n.msg("Plan.FunctionNotSafeToEstimate", "Function %s is not safe to estimate", name)); + } + if (!schema.isEmpty() && !SAFE_SCHEMAS.contains(schema)) { + return PlanDiagnostic.of(PlanCodes.UNSAFE_TO_ESTIMATE, PlanI18n.msg("Plan.FunctionOutsideMysqlCatalogs", "Function %s is outside known MySQL catalogs", name)); + } + if (schema.isEmpty() && !BUILTIN_FUNCTIONS.contains(function)) { + return PlanDiagnostic.of(PlanCodes.UNSAFE_TO_ESTIMATE, PlanI18n.msg("Plan.FunctionNotMysqlBuiltin", "Function %s is not a known MySQL builtin", name)); + } + } + return null; + } + + private DialectPlanResult rawOnly( + PlanExecutionContext context, + List prerequisites, + PlanEffects effects, + String rawText, + PlanRawFormat format, + String version, + boolean truncated, + PlanDiagnostic warning + ) { + return new DialectPlanResult( + context.serverVersion(), + PlanStatus.RAW_ONLY, + List.of(), + rawText, + format, + version, + truncated, + capabilities, + prerequisites, + effects, + null, + warning == null ? List.of() : List.of(warning) + ); + } + + private DialectPlanResult unmet( + PlanExecutionContext context, + List prerequisites, + PlanDiagnostic error + ) { + return new DialectPlanResult( + context.serverVersion(), + PlanStatus.PREREQUISITES_UNMET, + List.of(), + null, + null, + null, + false, + capabilities, + prerequisites, + PlanEffects.unchangedReuse(), + error, + List.of() + ); + } + + private void throwIfInvalid(PlanExecutionContext context, SQLException error) throws SQLException { + String state = error.getSQLState(); + if (state != null && state.startsWith("08")) { + throw new ConnectionInvalidatedException( + error.getMessage(), + error, + new PlanDiagnostic(PlanCodes.CONNECTION_INVALIDATED, error.getMessage(), error.getSQLState(), Integer.toString(error.getErrorCode())), + null, + new DialectPlanResult( + context.serverVersion(), + PlanStatus.CONNECTION_INVALIDATED, + List.of(), + null, + null, + null, + false, + capabilities, + List.of(), + PlanEffects.discard(PlanEffects.SessionState.UNKNOWN, PlanEffects.TransactionState.UNKNOWN), + new PlanDiagnostic(PlanCodes.CONNECTION_INVALIDATED, error.getMessage(), error.getSQLState(), Integer.toString(error.getErrorCode())), + List.of() + ) + ); + } + } + + static final class TableEnvelope { + final List resultSets; + + TableEnvelope(List resultSets) { + this.resultSets = resultSets; + } + + static TableEnvelope from(ResultSet resultSet) throws SQLException { + ResultSetMetaData meta = resultSet.getMetaData(); + List columns = new ArrayList<>(); + for (int i = 1; i <= meta.getColumnCount(); i++) { + columns.add(new Column(meta.getColumnLabel(i), meta.getColumnTypeName(i))); + } + List> rows = new ArrayList<>(); + while (resultSet.next()) { + List row = new ArrayList<>(); + for (int i = 1; i <= columns.size(); i++) { + row.add(resultSet.getString(i)); + } + rows.add(row); + } + return new TableEnvelope(List.of(new ResultSetEnvelope("EXPLAIN", columns, rows))); + } + + List toNodes() { + if (resultSets.isEmpty()) { + return List.of(); + } + ResultSetEnvelope set = resultSets.get(0); + int tableIdx = set.indexOf("table"); + int typeIdx = set.indexOf("type"); + int rowsIdx = set.indexOf("rows"); + int extraIdx = set.indexOf("Extra"); + int keyIdx = set.indexOf("key"); + List children = new ArrayList<>(); + int id = 1; + for (List row : set.rows) { + String access = typeIdx >= 0 ? row.get(typeIdx) : "row"; + String table = tableIdx >= 0 ? row.get(tableIdx) : null; + Map attributes = new LinkedHashMap<>(); + if (keyIdx >= 0 && row.get(keyIdx) != null) { + attributes.put("key", row.get(keyIdx)); + } + Map predicates = new LinkedHashMap<>(); + if (extraIdx >= 0 && row.get(extraIdx) != null) { + predicates.put("Extra", row.get(extraIdx)); + } + BigDecimal rows = null; + try { + if (rowsIdx >= 0 && row.get(rowsIdx) != null) { + rows = new BigDecimal(row.get(rowsIdx)); + } + } catch (NumberFormatException ignored) { + } + children.add(new PlanNode( + "n" + id++, + null, + MysqlPlanParser.mapAccess(access, access), + access == null ? "row" : access, + null, + access, + extraIdx >= 0 ? row.get(extraIdx) : null, + table, + table, + rows, + null, + null, + rows != null ? MysqlPlanParser.ROWS_ESTIMATED : null, + null, + predicates, + attributes, + List.of() + )); + } + if (children.isEmpty()) { + return List.of(); + } + return List.of(new PlanNode( + "n0", + null, + NormalizedNodeType.OTHER, + "EXPLAIN", + null, + "EXPLAIN", + null, + null, + null, + null, + null, + null, + null, + null, + Map.of(), + Map.of(), + children + )); + } + } + + private record ResultSetEnvelope(String name, List columns, List> rows) { + int indexOf(String label) { + for (int i = 0; i < columns.size(); i++) { + if (label.equalsIgnoreCase(columns.get(i).name)) { + return i; + } + } + return -1; + } + } + + private record Column(String name, String type) { + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlMetadataProvider.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlMetadataProvider.java new file mode 100644 index 00000000..d8dcd9b3 --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlMetadataProvider.java @@ -0,0 +1,291 @@ +package org.jumpserver.chen.modules.mysql; + +import org.jumpserver.chen.framework.datasource.ConnectionManager; +import org.jumpserver.chen.framework.datasource.metadata.BaseDatabaseMetadataProvider; +import org.jumpserver.chen.framework.datasource.metadata.ColumnMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ConstraintMetadata; +import org.jumpserver.chen.framework.datasource.metadata.IndexMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ForeignKeyMetadata; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCapabilities; +import org.jumpserver.chen.framework.datasource.metadata.ObjectProperties; +import org.jumpserver.chen.framework.datasource.metadata.ObjectRef; +import org.jumpserver.chen.framework.datasource.metadata.ObjectStatistics; +import org.jumpserver.chen.framework.datasource.metadata.PrimaryKeyMetadata; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; +import org.jumpserver.chen.framework.datasource.metadata.RelationMetadata; +import org.jumpserver.chen.framework.datasource.metadata.RelationScope; +import org.jumpserver.chen.framework.datasource.metadata.SchemaMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ScopeKind; +import org.jumpserver.chen.framework.datasource.metadata.ScopeProperties; +import org.jumpserver.chen.framework.datasource.metadata.ScopeRef; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class MysqlMetadataProvider extends BaseDatabaseMetadataProvider { + + public MysqlMetadataProvider(ConnectionManager connectionManager) { + super(connectionManager); + } + + private static final MetadataCapabilities CAPABILITIES = new MetadataCapabilities( + false, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, false, true, true + ); + + private static final String SQL_SCHEMAS = "SELECT SCHEMA_NAME AS name FROM INFORMATION_SCHEMA.SCHEMATA"; + + private static final String SQL_TABLES = """ + SELECT t.TABLE_NAME AS name, + t.ENGINE AS engine, + c.CHARACTER_SET_NAME AS character_set, + t.TABLE_COLLATION AS collation, + t.TABLE_COMMENT AS comment + FROM INFORMATION_SCHEMA.TABLES t + LEFT JOIN INFORMATION_SCHEMA.COLLATION_CHARACTER_SET_APPLICABILITY c + ON c.COLLATION_NAME = t.TABLE_COLLATION + WHERE t.TABLE_SCHEMA = ? AND t.TABLE_TYPE <> 'VIEW' + ORDER BY t.TABLE_NAME + """; + + private static final String SQL_TABLE_STATS = """ + SELECT TABLE_NAME AS name, + TABLE_ROWS AS estimated_rows, + COALESCE(DATA_LENGTH, 0) + COALESCE(INDEX_LENGTH, 0) AS total_size_bytes + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = ? AND TABLE_TYPE <> 'VIEW' + """; + + private static final String SQL_VIEWS = """ + SELECT TABLE_NAME AS name, 'VIEW' AS type, NULL AS comment + FROM INFORMATION_SCHEMA.VIEWS + WHERE TABLE_SCHEMA = ? + ORDER BY TABLE_NAME + """; + + private static final String SQL_INDEXES = """ + SELECT INDEX_NAME AS name, + TABLE_NAME AS table_name, + COLUMN_NAME AS column_name, + CASE WHEN NON_UNIQUE = 0 THEN 1 ELSE 0 END AS is_unique, + INDEX_TYPE AS method, + NULL AS definition, + COLLATION AS sort_order + FROM INFORMATION_SCHEMA.STATISTICS + WHERE TABLE_SCHEMA = ? + ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX + """; + + private static final String SQL_COLUMNS = """ + SELECT COLUMN_NAME AS name, TABLE_NAME AS table_name, ORDINAL_POSITION AS ordinal, + COLUMN_TYPE AS native_type, DATA_TYPE AS jdbc_type_name, + COALESCE(CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, DATETIME_PRECISION) AS size, + NUMERIC_SCALE AS scale, IS_NULLABLE AS nullable, + COLUMN_DEFAULT AS default_value, COLUMN_COMMENT AS comment + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = ? AND TABLE_NAME IN (__IN__) + ORDER BY TABLE_NAME, ORDINAL_POSITION + """; + + private static final String SQL_TABLE_INDEXES = """ + SELECT INDEX_NAME AS name, + TABLE_NAME AS table_name, + COLUMN_NAME AS column_name, + CASE WHEN NON_UNIQUE = 0 THEN 1 ELSE 0 END AS is_unique, + INDEX_TYPE AS method, + NULL AS definition, + COLLATION AS sort_order + FROM INFORMATION_SCHEMA.STATISTICS + WHERE TABLE_SCHEMA = ? AND TABLE_NAME IN (__IN__) + ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX + """; + + @Override + public MetadataCapabilities capabilities() { + return CAPABILITIES; + } + + @Override + public List listSchemas(String catalog) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_SCHEMAS, List.of())) { + result.add(new SchemaMetadata(null, stringValue(row, "name"))); + } + return result; + } + + @Override + public List listRelations(RelationScope scope, Set kinds) throws SQLException { + var result = new ArrayList(); + if (kinds.contains(RelationKind.TABLE)) { + for (var row : query(SQL_TABLES, List.of(scope.schema()))) { + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + stringValue(row, "comment"), + stringValue(row, "engine"), + stringValue(row, "character_set"), + stringValue(row, "collation") + )); + } + } + if (kinds.contains(RelationKind.VIEW)) { + for (var row : query(SQL_VIEWS, List.of(scope.schema()))) { + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.VIEW), + stringValue(row, "comment"), + null, + null, + null + )); + } + } + return result; + } + + @Override + public List listColumns(List relations) throws SQLException { + return loadColumns(SQL_COLUMNS, relations); + } + + @Override + public List listIndexes(RelationScope scope) throws SQLException { + return groupIndexRows(query(SQL_INDEXES, List.of(scope.schema())), scope); + } + + @Override + public List listIndexes(List relations) throws SQLException { + if (relations.isEmpty()) { + return List.of(); + } + var first = relations.get(0); + return groupIndexRows(queryKeys(SQL_TABLE_INDEXES, relations, first.schema()), + new RelationScope(first.catalog(), first.schema())); + } + + @Override + public List listStatistics(RelationScope scope) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_TABLE_STATS, List.of(scope.schema()))) { + result.add(new ObjectStatistics( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + longValue(row, "estimated_rows"), + longValue(row, "total_size_bytes") + )); + } + return result; + } + + @Override + public String getSchemaDefinition(RelationScope scope) throws SQLException { + var rows = query("SHOW CREATE DATABASE " + quoteIdentifier(scope.schema()), List.of()); + if (rows.isEmpty()) { + return null; + } + var values = rows.get(0).values().iterator(); + values.next(); + return values.hasNext() ? String.valueOf(values.next()) : null; + } + + @Override + public String getTableDefinition(ObjectRef relation) throws SQLException { + var sql = "SHOW CREATE TABLE " + quoteIdentifier(relation.schema()) + "." + quoteIdentifier(relation.name()); + var rows = query(sql, List.of()); + if (rows.isEmpty()) { + return null; + } + var values = rows.get(0).values().iterator(); + values.next(); + return values.hasNext() ? String.valueOf(values.next()) : null; + } + + private static final String SQL_TABLE_PROPERTIES = """ + SELECT TABLE_NAME, TABLE_SCHEMA, TABLE_TYPE, ENGINE, AVG_ROW_LENGTH, DATA_LENGTH, + MAX_DATA_LENGTH, CREATE_TIME, TABLE_COLLATION + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? + """; + + private static final String SQL_SCHEMA_PROPERTIES = """ + SELECT CATALOG_NAME, SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME, SQL_PATH + FROM INFORMATION_SCHEMA.SCHEMATA + WHERE SCHEMA_NAME = ? + """; + + @Override + public ObjectProperties objectProperties(ObjectRef ref) throws SQLException { + return loadObjectProperties(ref, SQL_TABLE_PROPERTIES); + } + + @Override + public ScopeProperties scopeProperties(ScopeRef ref) throws SQLException { + if (ref.kind() != ScopeKind.SCHEMA) { + return super.scopeProperties(ref); + } + return loadScopeProperties(ref, SQL_SCHEMA_PROPERTIES, List.of(ref.scope().schema())); + } + + private static final String SQL_PRIMARY_KEYS = """ + SELECT k.TABLE_NAME AS table_name, k.COLUMN_NAME AS column_name, k.CONSTRAINT_NAME AS name + FROM information_schema.KEY_COLUMN_USAGE k + JOIN information_schema.TABLE_CONSTRAINTS t + ON k.CONSTRAINT_SCHEMA = t.CONSTRAINT_SCHEMA AND k.CONSTRAINT_NAME = t.CONSTRAINT_NAME AND k.TABLE_NAME = t.TABLE_NAME + WHERE t.CONSTRAINT_TYPE = 'PRIMARY KEY' AND t.TABLE_SCHEMA = ? AND k.TABLE_NAME IN (__IN__) + ORDER BY k.TABLE_NAME, k.ORDINAL_POSITION + """; + + private static final String SQL_FOREIGN_KEYS = """ + SELECT k.TABLE_NAME AS table_name, k.COLUMN_NAME AS column_name, k.CONSTRAINT_NAME AS name, + k.REFERENCED_TABLE_SCHEMA AS referenced_schema, k.REFERENCED_TABLE_NAME AS referenced_table, + k.REFERENCED_COLUMN_NAME AS referenced_column + FROM information_schema.KEY_COLUMN_USAGE k + WHERE k.TABLE_SCHEMA = ? AND k.REFERENCED_TABLE_NAME IS NOT NULL AND k.TABLE_NAME IN (__IN__) + ORDER BY k.TABLE_NAME, k.CONSTRAINT_NAME, k.ORDINAL_POSITION + """; + + private static final String SQL_CONSTRAINTS = """ + SELECT tc.TABLE_NAME AS table_name, tc.CONSTRAINT_NAME AS name, + tc.CONSTRAINT_TYPE AS constraint_type, k.COLUMN_NAME AS column_name, + k.REFERENCED_TABLE_SCHEMA AS referenced_schema, + k.REFERENCED_TABLE_NAME AS referenced_table, + k.REFERENCED_COLUMN_NAME AS referenced_column, + NULL AS definition + FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc + LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE k + ON k.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA + AND k.TABLE_NAME = tc.TABLE_NAME AND k.CONSTRAINT_NAME = tc.CONSTRAINT_NAME + WHERE tc.TABLE_SCHEMA = ? AND tc.TABLE_NAME IN (__IN__) + AND tc.CONSTRAINT_TYPE IN ('PRIMARY KEY', 'FOREIGN KEY', 'UNIQUE', 'CHECK') + ORDER BY tc.TABLE_NAME, tc.CONSTRAINT_NAME, k.ORDINAL_POSITION + """; + + @Override + public List listPrimaryKeys(List relations) throws SQLException { + var first = relations.get(0); + return groupPrimaryKeys(queryKeys(SQL_PRIMARY_KEYS, relations, first.schema()), + new RelationScope(first.catalog(), first.schema())); + } + + @Override + public List listForeignKeys(List relations) throws SQLException { + var first = relations.get(0); + return groupForeignKeys(queryKeys(SQL_FOREIGN_KEYS, relations, first.schema()), + new RelationScope(first.catalog(), first.schema())); + } + + @Override + public List listConstraints(List relations) throws SQLException { + if (relations.isEmpty()) { + return List.of(); + } + var first = relations.get(0); + return groupConstraints(queryKeys(SQL_CONSTRAINTS, relations, first.schema()), + new RelationScope(first.catalog(), first.schema())); + } + + @Override + protected String quoteIdentifier(String value) { + return "`" + value.replace("`", "``") + "`"; + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlPlanParser.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlPlanParser.java new file mode 100644 index 00000000..9adb7ed3 --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlPlanParser.java @@ -0,0 +1,569 @@ +package org.jumpserver.chen.modules.mysql; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.jumpserver.chen.framework.datasource.plan.NormalizedNodeType; +import org.jumpserver.chen.framework.datasource.plan.PlanCodes; +import org.jumpserver.chen.framework.datasource.plan.PlanDiagnostic; +import org.jumpserver.chen.framework.datasource.plan.PlanNode; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +public final class MysqlPlanParser { + public static final String V1 = "mysql-explain-json-v1"; + public static final String V2 = "mysql-explain-json-v2"; + public static final String ROWS_EXAMINED = "rows examined per scan"; + public static final String ROWS_PRODUCED = "rows produced per join"; + public static final String ROWS_ESTIMATED = "MySQL estimated rows"; + public static final String COST_PREFIX = "MySQL prefix cost"; + public static final String COST_QUERY = "MySQL query cost"; + public static final String COST_TOTAL = "MySQL estimated total cost"; + + private MysqlPlanParser() { + } + + public static ParseResult parse(String rawJson, int maxNodes, int maxDepth) { + List warnings = new ArrayList<>(); + if (rawJson == null || rawJson.isBlank()) { + return ParseResult.failed(warnings, PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, "Empty MySQL plan JSON"), null); + } + JsonElement root; + try { + root = JsonParser.parseString(rawJson); + } catch (RuntimeException e) { + return ParseResult.failed(warnings, PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, "MySQL plan JSON is not valid"), null); + } + JsonObject planRoot = unwrapPlanRoot(root); + String version = detectVersion(planRoot); + if (version == null) { + return ParseResult.failed(warnings, PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, "Unrecognized MySQL EXPLAIN JSON shape"), null); + } + AtomicInteger ids = new AtomicInteger(1); + AtomicInteger nodeCount = new AtomicInteger(0); + List roots = new ArrayList<>(); + try { + if (V1.equals(version)) { + for (JsonObject block : queryBlocks(planRoot)) { + PlanNode node = convertV1(block, "query_block", ids, nodeCount, 1, maxNodes, maxDepth); + if (node != null) { + roots.add(node); + } + } + } else { + PlanNode node = convertV2(planRoot, ids, nodeCount, 1, maxNodes, maxDepth); + if (node != null) { + roots.add(node); + } + } + } catch (LimitReachedException e) { + warnings.add(PlanDiagnostic.of(PlanCodes.PLAN_LIMIT_REACHED, e.getMessage())); + } + if (roots.isEmpty()) { + return ParseResult.failed(warnings, PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, "MySQL plan JSON produced no nodes"), version); + } + return new ParseResult(roots, warnings, true, version); + } + + static JsonObject unwrapPlanRoot(JsonElement root) { + if (root != null && root.isJsonArray() && !root.getAsJsonArray().isEmpty()) { + root = root.getAsJsonArray().get(0); + } + JsonObject object = asObject(root); + if (object == null) { + return null; + } + if (object.has("query_block") || object.has("operation") || object.has("inputs")) { + return object; + } + for (String key : List.of("query_plan", "plan", "EXPLAIN")) { + JsonElement nested = object.get(key); + if (nested != null) { + JsonObject inner = unwrapPlanRoot(nested); + if (inner != null) { + return inner; + } + } + } + return object; + } + + static String detectVersion(JsonElement root) { + JsonObject object = unwrapPlanRoot(root); + if (object == null) { + return null; + } + if (object.has("query_block")) { + return V1; + } + if (object.has("operation") || object.has("inputs")) { + return V2; + } + return null; + } + + private static List queryBlocks(JsonElement root) { + List blocks = new ArrayList<>(); + JsonObject object = unwrapPlanRoot(root); + if (object == null) { + return blocks; + } + JsonElement block = object.get("query_block"); + if (block != null && block.isJsonObject()) { + blocks.add(block.getAsJsonObject()); + } + return blocks; + } + + private static PlanNode convertV1( + JsonObject object, + String nativeOperator, + AtomicInteger ids, + AtomicInteger nodeCount, + int depth, + int maxNodes, + int maxDepth + ) { + if (nodeCount.incrementAndGet() > maxNodes || depth > maxDepth) { + throw new LimitReachedException("Execution plan exceeded size limits"); + } + JsonObject table = nestedObject(object, "table"); + JsonObject costInfo = nestedObject(object, "cost_info"); + if (table != null && !"table".equals(nativeOperator) && !hasOperationWrapper(object)) { + List children = new ArrayList<>(); + children.add(convertV1(table, "table", ids, nodeCount, depth + 1, maxNodes, maxDepth)); + addV1Children(object, ids, nodeCount, depth, maxNodes, maxDepth, children, true); + return node( + ids, + nativeOperator, + mapNodeType(nativeOperator), + text(object, "message"), + null, + null, + null, + decimal(costInfo, "query_cost"), + COST_QUERY, + predicates(object), + attributes(object), + children + ); + } + if (table != null && "table".equals(nativeOperator)) { + object = table; + } + String tableName = text(object, "table_name"); + String access = text(object, "access_type"); + String operator = access != null ? access : nativeOperator; + BigDecimal produced = decimal(object, "rows_produced_per_join"); + BigDecimal examined = decimal(object, "rows_examined_per_scan"); + BigDecimal rows = produced != null ? produced : examined; + String rowsMeaning = produced != null ? ROWS_PRODUCED : (examined != null ? ROWS_EXAMINED : null); + JsonObject tableCost = nestedObject(object, "cost_info"); + BigDecimal cost = decimal(tableCost, "prefix_cost"); + if (cost == null) { + cost = decimal(costInfo, "query_cost"); + } + String costMeaning = tableCost != null && tableCost.has("prefix_cost") ? COST_PREFIX : COST_QUERY; + List children = new ArrayList<>(); + addV1Children(object, ids, nodeCount, depth, maxNodes, maxDepth, children, false); + Map predicates = predicates(object); + putPredicate(predicates, "attached_condition", text(object, "attached_condition")); + putPredicate(predicates, "using_condition", text(object, "used_columns")); + return node( + ids, + operator, + mapAccess(access, nativeOperator), + text(object, "message"), + tableName, + rows, + rowsMeaning, + cost, + costMeaning, + predicates, + attributes(object), + children + ); + } + + private static boolean hasOperationWrapper(JsonObject object) { + return object.has("ordering_operation") + || object.has("grouping_operation") + || object.has("duplicates_removal") + || object.has("nested_loop") + || object.has("union_result"); + } + + private static void addV1Children( + JsonObject object, + AtomicInteger ids, + AtomicInteger nodeCount, + int depth, + int maxNodes, + int maxDepth, + List children, + boolean skipTable + ) { + addNamedChild(object, "ordering_operation", "ordering_operation", ids, nodeCount, depth, maxNodes, maxDepth, children); + addNamedChild(object, "grouping_operation", "grouping_operation", ids, nodeCount, depth, maxNodes, maxDepth, children); + addNamedChild(object, "duplicates_removal", "duplicates_removal", ids, nodeCount, depth, maxNodes, maxDepth, children); + addNamedChild(object, "buffer_result", "buffer_result", ids, nodeCount, depth, maxNodes, maxDepth, children); + addNamedChild(object, "materialized_from_subquery", "materialized_from_subquery", ids, nodeCount, depth, maxNodes, maxDepth, children); + addNamedChild(object, "union_result", "union_result", ids, nodeCount, depth, maxNodes, maxDepth, children); + addNamedChild(object, "windowing", "windowing", ids, nodeCount, depth, maxNodes, maxDepth, children); + if (!skipTable) { + addNamedChild(object, "table", "table", ids, nodeCount, depth, maxNodes, maxDepth, children); + } + JsonElement nested = object.get("nested_loop"); + if (nested != null && nested.isJsonArray()) { + for (JsonElement item : nested.getAsJsonArray()) { + JsonObject child = asObject(item); + if (child == null) { + continue; + } + JsonObject nestedTable = nestedObject(child, "table"); + children.add(convertV1( + nestedTable != null ? nestedTable : child, + nestedTable != null ? "table" : "nested_loop", + ids, + nodeCount, + depth + 1, + maxNodes, + maxDepth + )); + } + } + addArrayChildren(object, "attached_subqueries", "attached_subquery", ids, nodeCount, depth, maxNodes, maxDepth, children); + addArrayChildren(object, "select_list_subqueries", "select_list_subquery", ids, nodeCount, depth, maxNodes, maxDepth, children); + addArrayChildren(object, "query_specifications", "query_specification", ids, nodeCount, depth, maxNodes, maxDepth, children); + } + + private static void addNamedChild( + JsonObject object, + String key, + String operator, + AtomicInteger ids, + AtomicInteger nodeCount, + int depth, + int maxNodes, + int maxDepth, + List children + ) { + JsonObject child = nestedObject(object, key); + if (child != null) { + children.add(convertV1(child, operator, ids, nodeCount, depth + 1, maxNodes, maxDepth)); + } + } + + private static void addArrayChildren( + JsonObject object, + String key, + String operator, + AtomicInteger ids, + AtomicInteger nodeCount, + int depth, + int maxNodes, + int maxDepth, + List children + ) { + JsonElement element = object.get(key); + if (element == null || !element.isJsonArray()) { + return; + } + for (JsonElement item : element.getAsJsonArray()) { + JsonObject child = asObject(item); + if (child == null) { + continue; + } + JsonObject block = nestedObject(child, "query_block"); + children.add(convertV1(block != null ? block : child, operator, ids, nodeCount, depth + 1, maxNodes, maxDepth)); + } + } + + private static PlanNode convertV2( + JsonObject object, + AtomicInteger ids, + AtomicInteger nodeCount, + int depth, + int maxNodes, + int maxDepth + ) { + if (object == null) { + return null; + } + if (nodeCount.incrementAndGet() > maxNodes || depth > maxDepth) { + throw new LimitReachedException("Execution plan exceeded size limits"); + } + String operation = text(object, "operation"); + if (operation == null) { + operation = text(object, "query_type"); + } + if (operation == null) { + operation = object.has("inputs") ? "query" : "operation"; + } + String access = text(object, "access_type"); + List children = new ArrayList<>(); + addV2Inputs(object, "inputs", ids, nodeCount, depth, maxNodes, maxDepth, children); + addV2Inputs(object, "inputs_from_select_list", ids, nodeCount, depth, maxNodes, maxDepth, children); + Map predicates = new LinkedHashMap<>(); + putPredicate(predicates, "condition", text(object, "condition")); + putPredicate(predicates, "lookup_condition", text(object, "lookup_condition")); + putPredicate(predicates, "hash_condition", text(object, "hash_condition")); + Map attributes = attributes(object); + putPredicate(attributes, "join_type", text(object, "join_type")); + putPredicate(attributes, "join_algorithm", text(object, "join_algorithm")); + putPredicate(attributes, "heading", text(object, "heading")); + putPredicate(attributes, "index_name", text(object, "index_name")); + putPredicate(attributes, "sort_fields", text(object, "sort_fields")); + if (object.has("cte")) { + putPredicate(attributes, "cte", text(object, "cte") == null ? "true" : text(object, "cte")); + } + if (object.has("recursive")) { + putPredicate(attributes, "recursive", text(object, "recursive") == null ? "true" : text(object, "recursive")); + } + String logical = text(object, "join_type"); + PlanNode created = node( + ids, + operation, + mapAccess(access, operation), + text(object, "access_type"), + text(object, "table_name"), + decimal(object, "estimated_rows"), + decimal(object, "estimated_rows") != null ? ROWS_ESTIMATED : null, + decimal(object, "estimated_total_cost"), + decimal(object, "estimated_total_cost") != null ? COST_TOTAL : null, + predicates, + attributes, + children + ); + if (logical == null) { + return created; + } + return new PlanNode( + created.id(), + created.nativeId(), + created.nodeType(), + created.nativeOperator(), + logical, + created.physicalOperator(), + created.detail(), + created.table(), + created.relation(), + created.rows(), + created.cost(), + created.startupCost(), + created.rowsMeaning(), + created.costMeaning(), + created.predicates(), + created.attributes(), + created.children() + ); + } + + static NormalizedNodeType mapAccess(String access, String nativeOperator) { + String value = (access != null ? access : nativeOperator); + if (value == null) { + return NormalizedNodeType.OTHER; + } + String lower = value.toLowerCase(Locale.ROOT); + if (lower.contains("nested") || lower.contains("nlj")) { + return NormalizedNodeType.NESTED_LOOP; + } + if (lower.contains("hash join") || lower.equals("hash")) { + return NormalizedNodeType.HASH_JOIN; + } + if (lower.contains("index") && lower.contains("merge")) { + return NormalizedNodeType.MERGE_JOIN; + } + if (lower.contains("eq_ref") || lower.contains("ref") || lower.contains("range") || lower.contains("index")) { + return NormalizedNodeType.INDEX_SCAN; + } + if (lower.contains("scan") || lower.equals("all") || lower.equals("const") || lower.equals("system")) { + return NormalizedNodeType.SCAN; + } + if (lower.contains("sort") || lower.contains("filesort") || lower.contains("ordering")) { + return NormalizedNodeType.SORT; + } + if (lower.contains("group") || lower.contains("aggregate")) { + return NormalizedNodeType.AGGREGATE; + } + if (lower.contains("union")) { + return NormalizedNodeType.UNION; + } + if (lower.contains("materializ") || lower.contains("temporary")) { + return NormalizedNodeType.MATERIALIZE; + } + if (lower.contains("filter")) { + return NormalizedNodeType.FILTER; + } + if (lower.contains("subquery") || lower.contains("query_block")) { + return NormalizedNodeType.SUBQUERY; + } + if (lower.contains("join")) { + return NormalizedNodeType.JOIN; + } + if (lower.contains("limit")) { + return NormalizedNodeType.LIMIT; + } + if (lower.contains("window")) { + return NormalizedNodeType.AGGREGATE; + } + return NormalizedNodeType.OTHER; + } + + static NormalizedNodeType mapNodeType(String nativeOperator) { + return mapAccess(null, nativeOperator); + } + + private static void addV2Inputs( + JsonObject object, + String key, + AtomicInteger ids, + AtomicInteger nodeCount, + int depth, + int maxNodes, + int maxDepth, + List children + ) { + JsonElement inputs = object.get(key); + if (inputs == null || !inputs.isJsonArray()) { + return; + } + for (JsonElement item : inputs.getAsJsonArray()) { + PlanNode child = convertV2(asObject(item), ids, nodeCount, depth + 1, maxNodes, maxDepth); + if (child != null) { + children.add(child); + } + } + } + + private static PlanNode node( + AtomicInteger ids, + String nativeOperator, + NormalizedNodeType type, + String detail, + String table, + BigDecimal rows, + String rowsMeaning, + BigDecimal cost, + String costMeaning, + Map predicates, + Map attributes, + List children + ) { + return new PlanNode( + "n" + ids.getAndIncrement(), + null, + type, + nativeOperator, + null, + nativeOperator, + detail, + table, + table, + rows, + cost, + null, + rowsMeaning, + costMeaning, + predicates, + attributes, + children + ); + } + + private static Map predicates(JsonObject object) { + Map predicates = new LinkedHashMap<>(); + putPredicate(predicates, "attached_condition", text(object, "attached_condition")); + putPredicate(predicates, "message", text(object, "message")); + return predicates; + } + + private static Map attributes(JsonObject object) { + Map attributes = new LinkedHashMap<>(); + putPredicate(attributes, "access_type", text(object, "access_type")); + putPredicate(attributes, "key", text(object, "key")); + putPredicate(attributes, "possible_keys", text(object, "possible_keys")); + putPredicate(attributes, "using_filesort", text(object, "using_filesort")); + putPredicate(attributes, "using_temporary_table", text(object, "using_temporary_table")); + putPredicate(attributes, "select_id", text(object, "select_id")); + return attributes; + } + + private static void putPredicate(Map map, String key, String value) { + if (value != null && !value.isBlank()) { + map.put(key, value); + } + } + + private static JsonObject nestedObject(JsonObject object, String key) { + JsonElement element = object.get(key); + return element != null && element.isJsonObject() ? element.getAsJsonObject() : null; + } + + private static JsonObject asObject(JsonElement element) { + if (element == null || !element.isJsonObject()) { + return null; + } + return element.getAsJsonObject(); + } + + private static String text(JsonObject object, String key) { + if (object == null || !object.has(key) || object.get(key).isJsonNull()) { + return null; + } + JsonElement element = object.get(key); + if (element.isJsonPrimitive()) { + String value = element.getAsString(); + return value == null || value.isBlank() ? null : value; + } + if (element.isJsonArray()) { + List parts = new ArrayList<>(); + for (JsonElement item : element.getAsJsonArray()) { + if (item != null && item.isJsonPrimitive()) { + parts.add(item.getAsString()); + } + } + return parts.isEmpty() ? null : String.join(", ", parts); + } + return element.toString(); + } + + private static BigDecimal decimal(JsonObject object, String key) { + if (object == null || !object.has(key) || object.get(key).isJsonNull()) { + return null; + } + try { + JsonElement element = object.get(key); + if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber()) { + return element.getAsBigDecimal(); + } + String text = element.getAsString(); + if (text == null || text.isBlank()) { + return null; + } + return new BigDecimal(text); + } catch (RuntimeException e) { + return null; + } + } + + public record ParseResult(List roots, List warnings, boolean structured, String rawFormatVersion) { + static ParseResult failed(List warnings, PlanDiagnostic error, String version) { + List all = new ArrayList<>(warnings); + all.add(error); + return new ParseResult(List.of(), all, false, version); + } + } + + private static final class LimitReachedException extends RuntimeException { + private LimitReachedException(String message) { + super(message); + } + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlResourceBrowser.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlResourceBrowser.java index e6803c88..965a988e 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlResourceBrowser.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlResourceBrowser.java @@ -2,49 +2,9 @@ import org.jumpserver.chen.framework.datasource.ConnectionManager; import org.jumpserver.chen.framework.datasource.base.BaseResourceBrowser; -import org.jumpserver.chen.framework.datasource.entity.resource.Field; -import org.jumpserver.chen.framework.datasource.entity.resource.Schema; -import org.jumpserver.chen.framework.datasource.entity.resource.Table; -import org.jumpserver.chen.framework.datasource.entity.resource.View; -import org.jumpserver.chen.framework.datasource.sql.SQL; - -import java.sql.SQLException; -import java.util.List; - public class MysqlResourceBrowser extends BaseResourceBrowser { public MysqlResourceBrowser(ConnectionManager connectionManager) { - super(connectionManager, new MysqlSQLHintsHandler(connectionManager)); - } - - private static final String SQL_GET_SCHEMAS = "SELECT SCHEMA_NAME AS NAME FROM INFORMATION_SCHEMA.SCHEMATA"; - - @Override - public List getSchemas() throws SQLException { - return this.getSchemas(SQL.of(SQL_GET_SCHEMAS)); + super(connectionManager); } - - private static final String SQL_GET_TABLES = "SELECT TABLE_NAME AS NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '?' AND TABLE_TYPE != 'VIEW'"; - - @Override - public List
getTables(String schema) throws SQLException { - return this.getTables(SQL.of(SQL_GET_TABLES, schema)); - } - - private static final String SQL_GET_VIEWS = "SELECT TABLE_NAME AS NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '?' AND TABLE_TYPE = 'VIEW'"; - - @Override - public List getViews(String schema) throws SQLException { - return this.getViews(SQL.of(SQL_GET_VIEWS, schema)); - } - - private static final String SQL_GET_FIELDS = "SELECT COLUMN_NAME AS NAME, COLUMN_TYPE AS TYPE, COLUMN_KEY AS `KEY`, IS_NULLABLE AS `NULLABLE`, COLUMN_DEFAULT AS `DEFAULT`, EXTRA AS EXTRA, COLUMN_COMMENT AS COMMENT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '?' AND TABLE_NAME = '?'"; - - @Override - public List getFields(String schema, String table) throws SQLException { - return this.getFields(SQL.of(SQL_GET_FIELDS, schema, table)); - } - - - } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlSQLHintsHandler.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlSQLHintsHandler.java deleted file mode 100644 index bb4429a7..00000000 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlSQLHintsHandler.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.jumpserver.chen.modules.mysql; - -import org.jumpserver.chen.framework.datasource.ConnectionManager; -import org.jumpserver.chen.framework.datasource.base.BaseSQLHintsHandler; -import org.jumpserver.chen.framework.datasource.entity.resource.Field; -import org.jumpserver.chen.framework.datasource.entity.resource.Table; -import org.jumpserver.chen.framework.datasource.sql.SQL; - -import java.sql.SQLException; -import java.util.List; -import java.util.Map; - -public class MysqlSQLHintsHandler extends BaseSQLHintsHandler { - private final ConnectionManager connectionManager; - - public MysqlSQLHintsHandler(ConnectionManager connectionManager) { - this.connectionManager = connectionManager; - } - - private static final String SQL_GET_SCHEMA_ALL_FIELDS = "SELECT COLUMN_NAME AS NAME, TABLE_SCHEMA,TABLE_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '?'"; - - public List getAllFields(String schema) throws SQLException { - return this.connectionManager - .getSqlActuator() - .getObjects(SQL.of(SQL_GET_SCHEMA_ALL_FIELDS, schema).getSql(), Field.class, Map.of("name", 1, "schema", 2, "table", 3)); - - } - - private static final String SQL_GET_ALL_TABLES = "SELECT TABLE_NAME AS NAME, TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='?' "; - - public List
getALlTables(String context) throws SQLException { - return this.connectionManager.getSqlActuator() - .getObjects(SQL.of(SQL_GET_ALL_TABLES, context) - .getSql(), Table.class, Map.of("name", 1, "schema", 2)); - } -} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleActionHandler.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleActionHandler.java index e3c8d85d..e6d9a409 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleActionHandler.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleActionHandler.java @@ -14,6 +14,28 @@ @Slf4j public class OracleActionHandler extends BaseActionHandler { + @Override + public List getSchemaActions(TreeNode node) { + return List.of( + Action.builder() + .label(MessageUtils.get("Refresh")) + .key("refresh_node") + .divided(true) + .icon("el-icon-refresh") + .build(), + Action.builder() + .label(MessageUtils.get("NewQuery")) + .key("new_query") + .icon("el-icon-search") + .build(), + Action.builder() + .label(MessageUtils.get("ShowProperties")) + .key("show_properties") + .icon("fa fa-align-justify") + .build() + ); + } + @Override public List getTableActions(TreeNode node) { return List.of( @@ -35,12 +57,6 @@ public List getTableActions(TreeNode node) { ); } - private static final String SQL_SELECT_TABLE_DETAIL = "SELECT TABLE_NAME,TABLESPACE_NAME,STATUS,NUM_ROWS,BLOCKS,AVG_ROW_LEN,SAMPLE_SIZE,OWNER FROM ALL_TABLES WHERE TABLESPACE_NAME is not null AND TABLE_NAME = '?'"; - - public EventEmitter onTableProperties(TreeNode node) throws SQLException { - return this.onShowObjectProperties("table", SQL_SELECT_TABLE_DETAIL, node); - } - @Override public EventEmitter handleForm(FormData formData) throws SQLException { return null; diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleActuator.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleActuator.java index 8f18190c..87a72978 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleActuator.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleActuator.java @@ -8,6 +8,7 @@ import org.jumpserver.chen.framework.datasource.sql.SQLExecutePlan; import org.jumpserver.chen.framework.datasource.sql.SQLIdentifier; import org.jumpserver.chen.framework.datasource.sql.SQLQueryParams; +import org.jumpserver.chen.framework.datasource.sql.SqlValidator; import java.sql.Connection; import java.sql.SQLException; @@ -42,7 +43,9 @@ public void changeSchema(String schema) throws SQLException { @Override public SQLExecutePlan createPlan(String schema, String table, SQLQueryParams sqlQueryParams) throws SQLException { - var sql = SQL.of("select * from ?.\"?\"", schema, table); + var schemaIdentifier = SQLIdentifier.quote(this.getDbType(), schema); + var tableIdentifier = SQLIdentifier.quote(this.getDbType(), table); + var sql = SQL.of("select * from " + schemaIdentifier + "." + tableIdentifier); return this.createPlan(sql, sqlQueryParams); } @@ -67,7 +70,7 @@ public SQLExecutePlan createPlan(SQL sql) throws SQLException { @Override public List parseSQL(SQL sql) { var dbType = this.getDbType(); - var statements = SQLUtils.parseStatements(sql.getSql(), dbType); + var statements = SqlValidator.parse(dbType, sql.getSql()); for (var i = 0; i < statements.size() - 1; i++) { if (!statements.get(i).isAfterSemi()) { throw new ParserException("Multiple SQL statements must be separated by semicolons"); diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleDatasource.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleDatasource.java index cdf6d6b5..364851bc 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleDatasource.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleDatasource.java @@ -4,8 +4,11 @@ import org.jumpserver.chen.framework.datasource.DatasourceFactory; import org.jumpserver.chen.framework.datasource.base.BaseDatasource; import org.jumpserver.chen.framework.datasource.entity.DBConnectInfo; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; +import org.jumpserver.chen.framework.datasource.plan.ExecutionPlanDialect; public class OracleDatasource extends BaseDatasource { + private final ExecutionPlanDialect executionPlanDialect = new OracleExecutionPlanDialect(); static { DatasourceFactory.Register(OracleDatasource.class); @@ -13,6 +16,7 @@ public class OracleDatasource extends BaseDatasource { public OracleDatasource(DBConnectInfo dbConnectInfo) { this.connectionManager = new OracleConnectionManager(dbConnectInfo, this); + this.metadataCatalog = new MetadataCatalog(this.connectionManager, new OracleMetadataProvider(this.connectionManager)); this.resourceBrowser = new OracleResourceBrowser(this.connectionManager); this.actionHandler = new OracleActionHandler(); } @@ -27,4 +31,9 @@ public String getName() { public DbType getDruidDbType() { return DbType.oracle; } + + @Override + public ExecutionPlanDialect getExecutionPlanDialect() { + return this.executionPlanDialect; + } } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleExecutionPlanDialect.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleExecutionPlanDialect.java new file mode 100644 index 00000000..6a736a8e --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleExecutionPlanDialect.java @@ -0,0 +1,608 @@ +package org.jumpserver.chen.modules.oracle; + +import org.jumpserver.chen.framework.datasource.analysis.SqlStatementAnalysis; +import org.jumpserver.chen.framework.datasource.plan.BaseExecutionPlanDialect; +import org.jumpserver.chen.framework.datasource.plan.ConnectionInvalidatedException; +import org.jumpserver.chen.framework.datasource.plan.DialectPlanResult; +import org.jumpserver.chen.framework.datasource.plan.ExecutionPlanCapabilities; +import org.jumpserver.chen.framework.datasource.plan.PlanCodes; +import org.jumpserver.chen.framework.datasource.plan.PlanDatabase; +import org.jumpserver.chen.framework.datasource.plan.PlanDiagnostic; +import org.jumpserver.chen.framework.datasource.plan.PlanEffects; +import org.jumpserver.chen.framework.datasource.plan.PlanI18n; +import org.jumpserver.chen.framework.datasource.plan.PlanExecutionContext; +import org.jumpserver.chen.framework.datasource.plan.PlanPrerequisite; +import org.jumpserver.chen.framework.datasource.plan.PlanRawFormat; +import org.jumpserver.chen.framework.datasource.plan.PlanStatus; +import org.jumpserver.chen.framework.datasource.plan.PlanTransactionState; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; +import java.util.regex.Pattern; + +public class OracleExecutionPlanDialect extends BaseExecutionPlanDialect { + static final String RAW_FORMAT_VERSION = "chen-table-v1"; + private static final String ORACLE_19C_BASELINE = "Oracle 19c validated baseline"; + private static final String ORACLE_26AI_23_26_BASELINE = + "Oracle AI Database 26ai 23.26.x validated baseline"; + private static final Pattern ORACLE_DATABASE_PRODUCT = Pattern.compile( + "\\bOracle(?:\\s+AI)?\\s+Database\\b", + Pattern.CASE_INSENSITIVE + ); + private static final Pattern ORACLE_19C_VERSION = Pattern.compile( + "(?:\\b19c\\b|(? SAFE_SCHEMAS = Set.of("sys", "standard"); + private static final Set SAFE_FUNCTIONS = Set.of( + "abs", "acos", "add_months", "ascii", "asin", "atan", "atan2", "avg", "bitand", + "cast", "ceil", "chr", "coalesce", "concat", "convert", "corr", "cos", "cosh", "count", + "covar_pop", "covar_samp", "decode", "dense_rank", "dump", "exp", "extract", "first_value", + "floor", "greatest", "instr", "instrb", "json_array", "json_arrayagg", "json_exists", + "json_object", "json_objectagg", "json_query", "json_value", "lag", "last_day", "last_value", + "lead", "least", "length", "lengthb", "listagg", "ln", "log", "lower", "lpad", "ltrim", + "max", "median", "min", "mod", "months_between", "next_day", "nth_value", "ntile", "nullif", + "nvl", "nvl2", "ora_hash", "percent_rank", "percentile_cont", "percentile_disc", "power", + "rank", "regexp_count", "regexp_instr", "regexp_like", "regexp_replace", "regexp_substr", + "replace", "round", "row_number", "rpad", "rtrim", "sign", "sin", "sinh", "sqrt", + "standard_hash", "stddev", "stddev_pop", "stddev_samp", "substr", "substrb", "sum", "tan", + "tanh", "to_binary_double", "to_binary_float", "to_char", "to_clob", "to_date", "to_number", + "to_timestamp", "to_timestamp_tz", "translate", "trim", "trunc", "upper", "variance", "xmlagg", + "xmlelement", "xmlforest", "xmlserialize" + ); + + private final ExecutionPlanCapabilities capabilities = ExecutionPlanCapabilities.oracle(); + + @Override + public PlanDatabase database() { + return PlanDatabase.oracle; + } + + @Override + public ExecutionPlanCapabilities capabilities() { + return capabilities; + } + + @Override + public DialectPlanResult explainEstimated(PlanExecutionContext context, SqlStatementAnalysis statement) + throws SQLException { + try { + return explain(context, statement); + } catch (ConnectionInvalidatedException e) { + throw e; + } catch (SQLException e) { + if (isConnectionBroken(e)) { + throw invalidated(context, e, null, PlanEffects.discard( + PlanEffects.SessionState.UNKNOWN, + PlanEffects.TransactionState.UNKNOWN + )); + } + throw e; + } + } + + private DialectPlanResult explain(PlanExecutionContext context, SqlStatementAnalysis statement) + throws SQLException { + List prerequisites = new ArrayList<>(); + PlanDiagnostic unsafe = unsafeFunctions(statement); + if (unsafe != null) { + prerequisites.add(PlanPrerequisite.unmet( + PlanCodes.UNSAFE_TO_ESTIMATE, + unsafe.message(), + PlanI18n.msg("Plan.RemoveUnsafeFunctions", "Remove user-defined or planning-time-unsafe functions and retry") + )); + return unmet(context, prerequisites, unsafe); + } + + String validatedVersionBaseline = validatedVersionBaseline(context.serverVersion()); + if (validatedVersionBaseline == null) { + String version = context.serverVersion(); + String message = version == null || version.isBlank() + ? "Oracle server version could not be determined; continuing with runtime capability checks" + : "Oracle server version has not been validated for estimated plans; " + + "continuing with runtime capability checks: " + version; + prerequisites.add(PlanPrerequisite.unknown( + PlanCodes.VERSION_NOT_VALIDATED, + message, + "Verify that the PLAN_TABLE, privilege, and transaction prerequisites pass" + )); + } else { + prerequisites.add(PlanPrerequisite.met("server-version", validatedVersionBaseline)); + } + + TransactionCheck transaction = checkTransaction(context); + if (transaction.failure() != null) { + prerequisites.add(transaction.failure()); + return unmet( + context, + prerequisites, + PlanDiagnostic.of(PlanCodes.TRANSACTION_CONTEXT_UNSAFE, transaction.failure().message()) + ); + } + prerequisites.add(PlanPrerequisite.met("transaction", transaction.message())); + + OraclePlanTable.Resolution resolution = OraclePlanTable.resolve(context); + if (resolution.failure() != null) { + prerequisites.add(resolution.failure()); + return unmet( + context, + prerequisites, + PlanDiagnostic.of(resolution.failure().code(), resolution.failure().message()) + ); + } + OraclePlanTable.Target target = resolution.target(); + prerequisites.add(PlanPrerequisite.met( + "plan-table", + "Using " + target.qualifiedName() + (target.temporary() ? " (global temporary)" : "") + )); + + PlanPrerequisite lifecycleFailure = OraclePlanTable.validateLifecycle(target, transaction.autoCommit()); + if (lifecycleFailure != null) { + prerequisites.add(lifecycleFailure); + return unmet( + context, + prerequisites, + PlanDiagnostic.of(lifecycleFailure.code(), lifecycleFailure.message()) + ); + } + prerequisites.add(PlanPrerequisite.met( + "plan-row-lifecycle", + OraclePlanTable.lifecycleDescription(target) + )); + + PlanPrerequisite columnFailure = OraclePlanTable.validateColumns(target); + if (columnFailure != null) { + prerequisites.add(columnFailure); + return unmet( + context, + prerequisites, + PlanDiagnostic.of(columnFailure.code(), columnFailure.message()) + ); + } + prerequisites.add(PlanPrerequisite.met("plan-table-columns", "PLAN_TABLE has compatible columns")); + + Set missingPrivileges = OraclePlanTable.missingPrivileges(context, target); + if (!missingPrivileges.isEmpty()) { + String message = "Missing PLAN_TABLE privileges: " + String.join(", ", missingPrivileges); + PlanPrerequisite failure = PlanPrerequisite.unmet( + PlanCodes.PLAN_PERMISSION_DENIED, + message, + "Grant SELECT, INSERT, and DELETE on the resolved PLAN_TABLE" + ); + prerequisites.add(failure); + return unmet(context, prerequisites, PlanDiagnostic.of(failure.code(), message)); + } + prerequisites.add(PlanPrerequisite.met("plan-table-privileges", "PLAN_TABLE SELECT/INSERT/DELETE available")); + prerequisites.add(PlanPrerequisite.met("statement", PlanI18n.msg("Plan.SingleSelect", "Single SELECT"))); + + return generateReadAndCleanup(context, statement, target, transaction, prerequisites); + } + + private DialectPlanResult generateReadAndCleanup( + PlanExecutionContext context, + SqlStatementAnalysis statement, + OraclePlanTable.Target target, + TransactionCheck transaction, + List prerequisites + ) throws SQLException { + String statementId = statementId(); + OraclePlanTableAccess.Capture raw = null; + SQLException originalError = null; + boolean generationAttempted = false; + + try { + context.throwIfCancelled(); + generationAttempted = true; + OraclePlanTableAccess.explain(context, statement, target, statementId); + raw = OraclePlanTableAccess.read(context, target, statementId); + } catch (SQLException e) { + originalError = e; + } + + SQLException cleanupError = null; + if (generationAttempted) { + try { + OraclePlanTableAccess.delete(context, target, statementId); + } catch (SQLException e) { + cleanupError = e; + } + } + + if (cleanupError != null) { + if (!canReuseAfterCleanupFailure(context, transaction, originalError, cleanupError)) { + throw invalidatedAfterCleanupFailure( + context, + originalError, + cleanupError, + raw, + prerequisites, + statementId + ); + } + + PlanDiagnostic cleanupWarning = cleanupDiagnostic(cleanupError, statementId); + PlanEffects effects = effects( + PlanEffects.AuxiliaryStorage.RESIDUAL, + PlanEffects.ConnectionDisposition.REUSE, + PlanEffects.TransactionState.UNCHANGED + ); + if (originalError != null) { + return errorResult(context, originalError, raw, prerequisites, effects, List.of(cleanupWarning)); + } + return resultFromRaw(context, raw, prerequisites, effects, List.of(cleanupWarning)); + } + + PlanEffects.TransactionState transactionEffect = transaction.participates() + ? PlanEffects.TransactionState.PARTICIPATED + : PlanEffects.TransactionState.UNCHANGED; + PlanEffects completedEffects = effects( + PlanEffects.AuxiliaryStorage.CLEANED, + PlanEffects.ConnectionDisposition.REUSE, + transactionEffect + ); + if (originalError == null) { + return resultFromRaw(context, raw, prerequisites, completedEffects, List.of()); + } + if (isConnectionBroken(originalError)) { + throw invalidated(context, originalError, null, PlanEffects.discard( + PlanEffects.SessionState.UNKNOWN, + PlanEffects.TransactionState.UNKNOWN + )); + } + return errorResult(context, originalError, raw, prerequisites, completedEffects, List.of()); + } + + private DialectPlanResult resultFromRaw( + PlanExecutionContext context, + OraclePlanTableAccess.Capture raw, + List prerequisites, + PlanEffects effects, + List extraWarnings + ) { + List warnings = new ArrayList<>(extraWarnings); + if (raw == null) { + warnings.add(PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, "Oracle PLAN_TABLE returned no materialized result")); + return new DialectPlanResult( + context.serverVersion(), PlanStatus.RAW_ONLY, List.of(), null, PlanRawFormat.TABLE, + RAW_FORMAT_VERSION, false, capabilities, prerequisites, effects, null, warnings + ); + } + if (raw.truncated()) { + warnings.add(PlanDiagnostic.of(PlanCodes.PLAN_LIMIT_REACHED, "Oracle plan table result exceeded a display limit")); + return new DialectPlanResult( + context.serverVersion(), PlanStatus.RAW_ONLY, List.of(), raw.json(), PlanRawFormat.TABLE, + RAW_FORMAT_VERSION, true, capabilities, prerequisites, effects, null, warnings + ); + } + + OraclePlanParser.ParseResult parsed = OraclePlanParser.parse(raw.json(), context.maxNodes(), context.maxDepth()); + warnings.addAll(parsed.warnings()); + return new DialectPlanResult( + context.serverVersion(), + parsed.structured() ? PlanStatus.SUCCESS : PlanStatus.RAW_ONLY, + parsed.roots(), + raw.json(), + PlanRawFormat.TABLE, + RAW_FORMAT_VERSION, + false, + capabilities, + prerequisites, + effects, + null, + warnings + ); + } + + private DialectPlanResult errorResult( + PlanExecutionContext context, + SQLException error, + OraclePlanTableAccess.Capture raw, + List prerequisites, + PlanEffects effects, + List warnings + ) { + PlanStatus status = isCancelled(context, error) ? PlanStatus.CANCELLED : PlanStatus.ERROR; + String code = status == PlanStatus.CANCELLED ? PlanCodes.CANCELLED : PlanCodes.SQL_ERROR; + return new DialectPlanResult( + context.serverVersion(), status, List.of(), raw == null ? null : raw.json(), + raw == null ? null : PlanRawFormat.TABLE, raw == null ? null : RAW_FORMAT_VERSION, + raw != null && raw.truncated(), capabilities, prerequisites, effects, + sqlDiagnostic(code, error), warnings + ); + } + + private TransactionCheck checkTransaction(PlanExecutionContext context) throws SQLException { + if (context.transactionProbeFailed()) { + String detail = context.transactionProbeDetail(); + String message = detail == null || detail.isBlank() + ? "Oracle transaction state probe failed" + : "Oracle transaction state probe failed: " + detail; + return TransactionCheck.failed(PlanPrerequisite.unknown( + PlanCodes.TRANSACTION_CONTEXT_UNSAFE, + message, + "Reconnect and retry from a known transaction state" + )); + } + + PlanTransactionState state = context.transactionState(); + boolean autoCommit = context.connection().getAutoCommit(); + if (state == PlanTransactionState.UNKNOWN) { + return TransactionCheck.failed(PlanPrerequisite.unknown( + PlanCodes.TRANSACTION_CONTEXT_UNSAFE, + "Oracle transaction state is unknown", + "Reconnect and retry from an idle auto-commit connection or an active transaction" + )); + } + if (state == PlanTransactionState.TRANSACTION_FAILED) { + return TransactionCheck.failed(PlanPrerequisite.unmet( + PlanCodes.TRANSACTION_CONTEXT_UNSAFE, + "The current Oracle transaction is not usable", + PlanI18n.msg("Plan.FinishTxAndRetry", "Finish the existing transaction and retry") + )); + } + if (state == PlanTransactionState.MANUAL_COMMIT_IDLE) { + return TransactionCheck.failed(PlanPrerequisite.unmet( + PlanCodes.TRANSACTION_CONTEXT_UNSAFE, + "A manual-commit idle connection cannot start PLAN_TABLE auxiliary DML", + "Use an idle auto-commit connection or retry inside an already-active transaction" + )); + } + if (state == PlanTransactionState.AUTO_COMMIT && !autoCommit) { + return TransactionCheck.failed(PlanPrerequisite.unknown( + PlanCodes.TRANSACTION_CONTEXT_UNSAFE, + "Oracle transaction state conflicts with JDBC auto-commit state", + "Reconnect and retry from a known transaction state" + )); + } + if (state == PlanTransactionState.TRANSACTION_ACTIVE && autoCommit) { + return TransactionCheck.failed(PlanPrerequisite.unmet( + PlanCodes.TRANSACTION_CONTEXT_UNSAFE, + "An active Oracle transaction with JDBC auto-commit enabled is unsafe for auxiliary DML", + "Finish the transaction and retry from a known state" + )); + } + return new TransactionCheck( + state, + autoCommit, + state == PlanTransactionState.TRANSACTION_ACTIVE + ? "PLAN_TABLE DML will participate in the active transaction" + : "Idle auto-commit connection", + null + ); + } + + private DialectPlanResult unmet( + PlanExecutionContext context, + List prerequisites, + PlanDiagnostic error + ) { + return new DialectPlanResult( + context.serverVersion(), + PlanStatus.PREREQUISITES_UNMET, + List.of(), + null, + null, + null, + false, + capabilities, + prerequisites, + PlanEffects.unchangedReuse(), + error, + List.of() + ); + } + + static PlanDiagnostic unsafeFunctions(SqlStatementAnalysis statement) { + for (String rawName : statement.functionNames()) { + String name = rawName == null ? "" : rawName.trim(); + if (name.isEmpty()) { + continue; + } + String normalized = name.replace("\"", "").toLowerCase(Locale.ROOT); + int dot = normalized.lastIndexOf('.'); + String owner = dot < 0 ? "" : normalized.substring(0, dot); + String function = dot < 0 ? normalized : normalized.substring(dot + 1); + if ((!owner.isEmpty() && !SAFE_SCHEMAS.contains(owner)) || !SAFE_FUNCTIONS.contains(function)) { + return PlanDiagnostic.of( + PlanCodes.UNSAFE_TO_ESTIMATE, + "Function " + name + " is not a known side-effect-free Oracle builtin" + ); + } + } + return null; + } + + private static boolean isConnectionBroken(SQLException error) { + String state = error.getSQLState(); + return state != null && state.startsWith("08"); + } + + private static boolean canReuseAfterCleanupFailure( + PlanExecutionContext context, + TransactionCheck transaction, + SQLException originalError, + SQLException cleanupError + ) { + if (transaction.participates() + || transaction.state() != PlanTransactionState.AUTO_COMMIT + || !transaction.autoCommit() + || context.isCancelled() + || isConnectionBroken(cleanupError) + || (originalError != null && isConnectionBroken(originalError))) { + return false; + } + try { + if (!context.connection().getAutoCommit()) { + return false; + } + String sql = "SELECT DBMS_TRANSACTION.LOCAL_TRANSACTION_ID(FALSE) FROM DUAL"; + try (OraclePlanJdbc.Tracked tracked = OraclePlanJdbc.statement(context, false); + ResultSet resultSet = tracked.statement().executeQuery(sql)) { + if (!resultSet.next()) { + return false; + } + String transactionId = resultSet.getString(1); + return transactionId == null || transactionId.isBlank(); + } + } catch (SQLException probeError) { + cleanupError.addSuppressed(probeError); + return false; + } + } + + static String validatedVersionBaseline(String version) { + if (version == null || version.isBlank() || !ORACLE_DATABASE_PRODUCT.matcher(version).find()) { + return null; + } + if (ORACLE_19C_VERSION.matcher(version).find()) { + return ORACLE_19C_BASELINE; + } + if (ORACLE_26AI_PRODUCT.matcher(version).find() + && ORACLE_26AI_23_26_VERSION.matcher(version).find()) { + return ORACLE_26AI_23_26_BASELINE; + } + return null; + } + + private static boolean isCancelled(PlanExecutionContext context, SQLException error) { + String message = error.getMessage(); + return context.isCancelled() + || error.getErrorCode() == 1013 + || (message != null && (message.contains("ORA-01013") + || message.toLowerCase(Locale.ROOT).contains("cancel"))); + } + + private static String statementId() { + byte[] bytes = new byte[10]; + ThreadLocalRandom.current().nextBytes(bytes); + return "CHEN_" + java.util.HexFormat.of().formatHex(bytes).toUpperCase(Locale.ROOT); + } + + private static PlanEffects effects( + PlanEffects.AuxiliaryStorage auxiliaryStorage, + PlanEffects.ConnectionDisposition disposition, + PlanEffects.TransactionState transactionState + ) { + return new PlanEffects( + PlanEffects.SessionState.UNCHANGED, + transactionState, + auxiliaryStorage, + disposition + ); + } + + private static PlanDiagnostic cleanupDiagnostic(SQLException error, String statementId) { + return new PlanDiagnostic( + PlanCodes.AUXILIARY_CLEANUP_FAILED, + "Failed to delete Oracle PLAN_TABLE rows for request " + statementId + ": " + error.getMessage(), + error.getSQLState(), + Integer.toString(error.getErrorCode()) + ); + } + + private ConnectionInvalidatedException invalidatedAfterCleanupFailure( + PlanExecutionContext context, + SQLException original, + SQLException cleanup, + OraclePlanTableAccess.Capture raw, + List prerequisites, + String statementId + ) { + SQLException cause = original == null ? cleanup : original; + PlanDiagnostic originalDiagnostic = original == null + ? cleanupDiagnostic(cleanup, statementId) + : sqlDiagnostic(isCancelled(context, original) ? PlanCodes.CANCELLED : PlanCodes.SQL_ERROR, original); + PlanDiagnostic cleanupDiagnostic = cleanupDiagnostic(cleanup, statementId); + List warnings = List.of(cleanupDiagnostic); + PlanEffects partialEffects = effects( + PlanEffects.AuxiliaryStorage.RESIDUAL, + PlanEffects.ConnectionDisposition.DISCARD, + PlanEffects.TransactionState.UNKNOWN + ); + DialectPlanResult partial = new DialectPlanResult( + context.serverVersion(), PlanStatus.CONNECTION_INVALIDATED, List.of(), + raw == null ? null : raw.json(), raw == null ? null : PlanRawFormat.TABLE, + raw == null ? null : RAW_FORMAT_VERSION, raw != null && raw.truncated(), capabilities, + prerequisites, partialEffects, originalDiagnostic, warnings + ); + return new ConnectionInvalidatedException( + "Oracle PLAN_TABLE cleanup failed; the connection cannot be reused", + cause, + originalDiagnostic, + cleanupDiagnostic, + partial + ); + } + + private ConnectionInvalidatedException invalidated( + PlanExecutionContext context, + SQLException original, + SQLException cleanup, + PlanEffects effects + ) { + PlanDiagnostic originalDiagnostic = sqlDiagnostic(PlanCodes.CONNECTION_INVALIDATED, original); + PlanDiagnostic cleanupDiagnostic = cleanup == null + ? null + : sqlDiagnostic(PlanCodes.AUXILIARY_CLEANUP_FAILED, cleanup); + DialectPlanResult partial = new DialectPlanResult( + context.serverVersion(), + PlanStatus.CONNECTION_INVALIDATED, + List.of(), + null, + null, + null, + false, + capabilities, + List.of(), + effects, + originalDiagnostic, + cleanupDiagnostic == null ? List.of() : List.of(cleanupDiagnostic) + ); + return new ConnectionInvalidatedException( + original.getMessage(), + original, + originalDiagnostic, + cleanupDiagnostic, + partial + ); + } + + private static PlanDiagnostic sqlDiagnostic(String code, SQLException error) { + return new PlanDiagnostic( + code, + error.getMessage(), + error.getSQLState(), + Integer.toString(error.getErrorCode()) + ); + } + + private record TransactionCheck( + PlanTransactionState state, + boolean autoCommit, + String message, + PlanPrerequisite failure + ) { + static TransactionCheck failed(PlanPrerequisite failure) { + return new TransactionCheck(PlanTransactionState.UNKNOWN, false, null, failure); + } + + boolean participates() { + return state == PlanTransactionState.TRANSACTION_ACTIVE; + } + } + +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleMetadataProvider.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleMetadataProvider.java new file mode 100644 index 00000000..f43cbe7c --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleMetadataProvider.java @@ -0,0 +1,308 @@ +package org.jumpserver.chen.modules.oracle; + +import org.jumpserver.chen.framework.datasource.ConnectionManager; +import org.jumpserver.chen.framework.datasource.metadata.BaseDatabaseMetadataProvider; +import org.jumpserver.chen.framework.datasource.metadata.ColumnMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ConstraintMetadata; +import org.jumpserver.chen.framework.datasource.metadata.IndexMetadata; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCapabilities; +import org.jumpserver.chen.framework.datasource.metadata.ObjectProperties; +import org.jumpserver.chen.framework.datasource.metadata.ForeignKeyMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ObjectRef; +import org.jumpserver.chen.framework.datasource.metadata.PrimaryKeyMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ObjectStatistics; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; +import org.jumpserver.chen.framework.datasource.metadata.RelationMetadata; +import org.jumpserver.chen.framework.datasource.metadata.RelationScope; +import org.jumpserver.chen.framework.datasource.metadata.SchemaMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ScopeKind; +import org.jumpserver.chen.framework.datasource.metadata.ScopeProperties; +import org.jumpserver.chen.framework.datasource.metadata.ScopeRef; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class OracleMetadataProvider extends BaseDatabaseMetadataProvider { + + public OracleMetadataProvider(ConnectionManager connectionManager) { + super(connectionManager); + } + + private static final MetadataCapabilities CAPABILITIES = new MetadataCapabilities( + false, true, true, true, true, true, true, true, false, + true, true, false, false, false, true, true, true, true + ); + + private static final String SQL_SCHEMAS = "SELECT USERNAME AS name FROM ALL_USERS"; + + private static final String SQL_TABLES = """ + SELECT t.table_name AS name, + NULL AS engine, + NULL AS character_set, + NULL AS collation, + c.comments AS "comment" + FROM all_tables t + LEFT JOIN all_tab_comments c + ON c.owner = t.owner AND c.table_name = t.table_name AND c.table_type = 'TABLE' + WHERE t.owner = ? AND t.tablespace_name IS NOT NULL + ORDER BY t.table_name + """; + + private static final String SQL_TABLE_STATS = """ + WITH target_schema AS (SELECT ? AS owner FROM dual), object_segments AS ( + SELECT s.segment_name AS table_name, s.bytes + FROM all_segments s JOIN target_schema x ON x.owner = s.owner + WHERE s.segment_type IN ('TABLE', 'TABLE PARTITION', 'TABLE SUBPARTITION') + UNION ALL + SELECT i.table_name, s.bytes + FROM all_segments s + JOIN all_indexes i ON i.owner = s.owner AND i.index_name = s.segment_name + JOIN target_schema x ON x.owner = i.owner + WHERE s.segment_type IN ('INDEX', 'INDEX PARTITION', 'INDEX SUBPARTITION') + UNION ALL + SELECT l.table_name, s.bytes + FROM all_segments s + JOIN all_lobs l ON l.owner = s.owner AND s.segment_name IN (l.segment_name, l.index_name) + JOIN target_schema x ON x.owner = l.owner + ), object_sizes AS ( + SELECT table_name, SUM(bytes) AS total_size_bytes + FROM object_segments GROUP BY table_name + ) + SELECT t.table_name AS name, + t.num_rows AS estimated_rows, + z.total_size_bytes AS total_size_bytes + FROM all_tables t + JOIN target_schema x ON x.owner = t.owner + LEFT JOIN object_sizes z ON z.table_name = t.table_name + WHERE t.tablespace_name IS NOT NULL + ORDER BY t.table_name + """; + + private static final String SQL_VIEWS = """ + SELECT v.view_name AS name, 'VIEW' AS type, c.comments AS "comment" + FROM all_views v + LEFT JOIN all_tab_comments c + ON c.owner = v.owner AND c.table_name = v.view_name AND c.table_type = 'VIEW' + WHERE v.owner = ? + ORDER BY v.view_name + """; + + private static final String SQL_INDEXES = """ + SELECT i.index_name AS name, + i.table_name AS table_name, + CASE WHEN e.column_expression IS NULL THEN c.column_name END AS column_name, + e.column_expression AS expression, + CASE WHEN i.uniqueness = 'UNIQUE' THEN 1 ELSE 0 END AS is_unique, + i.index_type AS method, + c.descend AS sort_order + FROM all_indexes i + JOIN all_ind_columns c + ON c.index_owner = i.owner AND c.index_name = i.index_name AND c.table_owner = i.table_owner + LEFT JOIN all_ind_expressions e + ON e.index_owner = c.index_owner AND e.index_name = c.index_name + AND e.table_owner = c.table_owner AND e.column_position = c.column_position + WHERE i.owner = ? + ORDER BY i.table_name, i.index_name, c.column_position + """; + + private static final String SQL_COLUMNS = """ + SELECT c.COLUMN_NAME AS name, c.TABLE_NAME AS table_name, c.COLUMN_ID AS ordinal, + c.DATA_TYPE AS native_type, c.DATA_TYPE AS jdbc_type_name, + COALESCE(c.CHAR_LENGTH, c.DATA_PRECISION, c.DATA_LENGTH) AS "size", + c.DATA_SCALE AS scale, c.NULLABLE AS nullable, + c.DATA_DEFAULT AS default_value, cc.COMMENTS AS "comment" + FROM ALL_TAB_COLUMNS c + LEFT JOIN ALL_COL_COMMENTS cc + ON cc.OWNER = c.OWNER AND cc.TABLE_NAME = c.TABLE_NAME AND cc.COLUMN_NAME = c.COLUMN_NAME + WHERE c.OWNER = ? AND c.TABLE_NAME IN (__IN__) + ORDER BY c.TABLE_NAME, c.COLUMN_ID + """; + + private static final String SQL_TABLE_INDEXES = """ + SELECT i.index_name AS name, i.table_name AS table_name, + CASE WHEN e.column_expression IS NULL THEN c.column_name END AS column_name, + e.column_expression AS expression, + CASE WHEN i.uniqueness = 'UNIQUE' THEN 1 ELSE 0 END AS is_unique, + i.index_type AS method, + c.descend AS sort_order + FROM all_indexes i + JOIN all_ind_columns c + ON c.index_owner = i.owner AND c.index_name = i.index_name AND c.table_owner = i.table_owner + LEFT JOIN all_ind_expressions e + ON e.index_owner = c.index_owner AND e.index_name = c.index_name + AND e.table_owner = c.table_owner AND e.column_position = c.column_position + WHERE i.owner = ? AND i.table_name IN (__IN__) + ORDER BY i.table_name, i.index_name, c.column_position + """; + + @Override + public MetadataCapabilities capabilities() { + return CAPABILITIES; + } + + @Override + public List listSchemas(String catalog) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_SCHEMAS, List.of())) { + result.add(new SchemaMetadata(null, stringValue(row, "name"))); + } + return result; + } + + @Override + public List listRelations(RelationScope scope, Set kinds) throws SQLException { + var result = new ArrayList(); + if (kinds.contains(RelationKind.TABLE)) { + for (var row : query(SQL_TABLES, List.of(scope.schema()))) { + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + stringValue(row, "comment"), + null, + null, + null + )); + } + } + if (kinds.contains(RelationKind.VIEW)) { + for (var row : query(SQL_VIEWS, List.of(scope.schema()))) { + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.VIEW), + stringValue(row, "comment"), + null, + null, + null + )); + } + } + return result; + } + + @Override + public List listColumns(List relations) throws SQLException { + return loadColumns(SQL_COLUMNS, relations); + } + + @Override + public List listIndexes(RelationScope scope) throws SQLException { + return groupIndexRows(query(SQL_INDEXES, List.of(scope.schema())), scope); + } + + @Override + public List listIndexes(List relations) throws SQLException { + if (relations.isEmpty()) { + return List.of(); + } + var first = relations.get(0); + return groupIndexRows(queryKeys(SQL_TABLE_INDEXES, relations, first.schema()), + new RelationScope(first.catalog(), first.schema())); + } + + @Override + public String getTableDefinition(ObjectRef relation) throws SQLException { + var objectType = relation.kind() == RelationKind.TABLE ? "TABLE" : "VIEW"; + var rows = query("SELECT DBMS_METADATA.GET_DDL(?, ?, ?) AS definition FROM dual", + List.of(objectType, relation.name(), relation.schema())); + return rows.isEmpty() ? null : stringValue(rows.get(0), "definition"); + } + + @Override + public List listStatistics(RelationScope scope) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_TABLE_STATS, List.of(scope.schema()))) { + result.add(new ObjectStatistics( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + longValue(row, "estimated_rows"), + longValue(row, "total_size_bytes") + )); + } + return result; + } + + private static final String SQL_TABLE_PROPERTIES = """ + SELECT TABLE_NAME, TABLESPACE_NAME, STATUS, NUM_ROWS, BLOCKS, AVG_ROW_LEN, SAMPLE_SIZE, OWNER + FROM ALL_TABLES + WHERE OWNER = ? AND TABLE_NAME = ? AND TABLESPACE_NAME IS NOT NULL + """; + + private static final String SQL_SCHEMA_PROPERTIES = """ + SELECT USERNAME, USER_ID, CREATED + FROM ALL_USERS + WHERE USERNAME = ? + """; + + @Override + public ObjectProperties objectProperties(ObjectRef ref) throws SQLException { + return loadObjectProperties(ref, SQL_TABLE_PROPERTIES); + } + + @Override + public ScopeProperties scopeProperties(ScopeRef ref) throws SQLException { + if (ref.kind() != ScopeKind.SCHEMA) { + return super.scopeProperties(ref); + } + return loadScopeProperties(ref, SQL_SCHEMA_PROPERTIES, List.of(ref.scope().schema())); + } + + private static final String SQL_PRIMARY_KEYS = """ + SELECT c.table_name AS table_name, cc.column_name AS column_name, c.constraint_name AS name + FROM all_constraints c + JOIN all_cons_columns cc ON cc.owner = c.owner AND cc.constraint_name = c.constraint_name AND cc.table_name = c.table_name + WHERE c.constraint_type = 'P' AND c.owner = ? AND c.table_name IN (__IN__) + ORDER BY c.table_name, c.constraint_name, cc.position + """; + + private static final String SQL_FOREIGN_KEYS = """ + SELECT c.table_name AS table_name, cc.column_name AS column_name, c.constraint_name AS name, + rc.owner AS referenced_schema, rc.table_name AS referenced_table, rcc.column_name AS referenced_column + FROM all_constraints c + JOIN all_cons_columns cc ON cc.owner = c.owner AND cc.constraint_name = c.constraint_name AND cc.table_name = c.table_name + JOIN all_constraints rc ON rc.owner = c.r_owner AND rc.constraint_name = c.r_constraint_name + JOIN all_cons_columns rcc ON rcc.owner = rc.owner AND rcc.constraint_name = rc.constraint_name AND rcc.position = cc.position + WHERE c.constraint_type = 'R' AND c.owner = ? AND c.table_name IN (__IN__) + ORDER BY c.table_name, c.constraint_name, cc.position + """; + + private static final String SQL_CONSTRAINTS = """ + SELECT c.table_name AS table_name, c.constraint_name AS name, + CASE c.constraint_type WHEN 'P' THEN 'PRIMARY KEY' WHEN 'R' THEN 'FOREIGN KEY' + WHEN 'U' THEN 'UNIQUE' WHEN 'C' THEN 'CHECK' END AS constraint_type, + cc.column_name AS column_name, rc.owner AS referenced_schema, + rc.table_name AS referenced_table, rcc.column_name AS referenced_column, + c.search_condition AS definition + FROM all_constraints c + LEFT JOIN all_cons_columns cc + ON cc.owner = c.owner AND cc.constraint_name = c.constraint_name AND cc.table_name = c.table_name + LEFT JOIN all_constraints rc ON rc.owner = c.r_owner AND rc.constraint_name = c.r_constraint_name + LEFT JOIN all_cons_columns rcc + ON rcc.owner = rc.owner AND rcc.constraint_name = rc.constraint_name AND rcc.position = cc.position + WHERE c.constraint_type IN ('P', 'R', 'U', 'C') + AND c.owner = ? AND c.table_name IN (__IN__) + ORDER BY c.table_name, c.constraint_name, cc.position + """; + + @Override + public List listPrimaryKeys(List relations) throws SQLException { + var first = relations.get(0); + return groupPrimaryKeys(queryKeys(SQL_PRIMARY_KEYS, relations, first.schema()), + new RelationScope(first.catalog(), first.schema())); + } + + @Override + public List listForeignKeys(List relations) throws SQLException { + var first = relations.get(0); + return groupForeignKeys(queryKeys(SQL_FOREIGN_KEYS, relations, first.schema()), + new RelationScope(first.catalog(), first.schema())); + } + + @Override + public List listConstraints(List relations) throws SQLException { + if (relations.isEmpty()) { + return List.of(); + } + var first = relations.get(0); + return groupConstraints(queryKeys(SQL_CONSTRAINTS, relations, first.schema()), + new RelationScope(first.catalog(), first.schema())); + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OraclePlanJdbc.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OraclePlanJdbc.java new file mode 100644 index 00000000..cec192d1 --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OraclePlanJdbc.java @@ -0,0 +1,87 @@ +package org.jumpserver.chen.modules.oracle; + +import org.jumpserver.chen.framework.datasource.plan.PlanExecutionContext; +import org.jumpserver.chen.framework.datasource.plan.PlanLimits; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; + +final class OraclePlanJdbc { + private OraclePlanJdbc() { + } + + static Tracked statement(PlanExecutionContext context, boolean useRequestDeadline) + throws SQLException { + if (useRequestDeadline) { + context.throwIfCancelled(); + } + return track(context.connection().createStatement(), context, useRequestDeadline); + } + + static Tracked preparedStatement( + PlanExecutionContext context, + String sql, + boolean useRequestDeadline + ) throws SQLException { + if (useRequestDeadline) { + context.throwIfCancelled(); + } + return track(context.connection().prepareStatement(sql), context, useRequestDeadline); + } + + private static Tracked track( + T statement, + PlanExecutionContext context, + boolean useRequestDeadline + ) throws SQLException { + context.registerStatement(statement); + try { + if (useRequestDeadline) { + context.throwIfCancelled(); + } + configureTimeout(statement, context, useRequestDeadline); + return new Tracked<>(statement, context); + } catch (SQLException e) { + context.unregisterStatement(statement); + try { + statement.close(); + } catch (SQLException closeError) { + e.addSuppressed(closeError); + } + throw e; + } + } + + private static void configureTimeout( + Statement statement, + PlanExecutionContext context, + boolean useRequestDeadline + ) throws SQLException { + long remainingMillis = useRequestDeadline + ? Math.max(1L, context.deadlineMillis() - System.currentTimeMillis()) + : PlanLimits.CLEANUP_TIMEOUT_MS; + long seconds = Math.max(1L, (remainingMillis + 999L) / 1_000L); + statement.setQueryTimeout((int) Math.min(Integer.MAX_VALUE, seconds)); + } + + static final class Tracked implements AutoCloseable { + private final T statement; + private final PlanExecutionContext context; + + private Tracked(T statement, PlanExecutionContext context) { + this.statement = statement; + this.context = context; + } + + T statement() { + return statement; + } + + @Override + public void close() throws SQLException { + context.unregisterStatement(statement); + statement.close(); + } + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OraclePlanParser.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OraclePlanParser.java new file mode 100644 index 00000000..3f1ef093 --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OraclePlanParser.java @@ -0,0 +1,381 @@ +package org.jumpserver.chen.modules.oracle; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.jumpserver.chen.framework.datasource.plan.NormalizedNodeType; +import org.jumpserver.chen.framework.datasource.plan.PlanCodes; +import org.jumpserver.chen.framework.datasource.plan.PlanDiagnostic; +import org.jumpserver.chen.framework.datasource.plan.PlanNode; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +public final class OraclePlanParser { + public static final String ROWS_MEANING = "Oracle estimated output cardinality"; + public static final String COST_MEANING = "Oracle optimizer estimated subtree cost"; + + private OraclePlanParser() { + } + + public static ParseResult parse(String rawTableJson, int maxNodes, int maxDepth) { + List warnings = new ArrayList<>(); + if (rawTableJson == null || rawTableJson.isBlank()) { + return ParseResult.failed(warnings, "Empty Oracle plan table result"); + } + + JsonObject envelope; + try { + JsonElement parsed = JsonParser.parseString(rawTableJson); + if (!parsed.isJsonObject()) { + return ParseResult.failed(warnings, "Oracle plan table envelope is not an object"); + } + envelope = parsed.getAsJsonObject(); + } catch (RuntimeException e) { + return ParseResult.failed(warnings, "Oracle plan table envelope is not valid JSON"); + } + + List rows; + try { + rows = rows(envelope); + } catch (IllegalArgumentException e) { + return ParseResult.failed(warnings, e.getMessage()); + } + if (rows.isEmpty()) { + return ParseResult.failed(warnings, "Oracle PLAN_TABLE returned no rows for this request"); + } + if (rows.size() > maxNodes) { + warnings.add(PlanDiagnostic.of(PlanCodes.PLAN_LIMIT_REACHED, "Execution plan exceeded the node limit")); + return new ParseResult(List.of(), warnings, false); + } + + Map byId = new LinkedHashMap<>(); + for (Row row : rows) { + String id = row.text("ID"); + if (id == null) { + return ParseResult.failed(warnings, "Oracle PLAN_TABLE row has no ID"); + } + if (byId.putIfAbsent(id, row) != null) { + return ParseResult.failed(warnings, "Oracle PLAN_TABLE contains duplicate ID " + id); + } + } + + Map> childrenByParent = new HashMap<>(); + List roots = new ArrayList<>(); + for (Row row : rows) { + String parentId = row.text("PARENT_ID"); + if (parentId == null) { + roots.add(row); + } else if (byId.containsKey(parentId)) { + childrenByParent.computeIfAbsent(parentId, ignored -> new ArrayList<>()).add(row); + } else { + roots.add(row); + warnings.add(PlanDiagnostic.of( + PlanCodes.PLAN_PARSE_FAILED, + "Oracle plan node " + row.text("ID") + " references missing parent " + parentId + )); + } + } + + Comparator order = Comparator + .comparing((Row row) -> row.decimal("POSITION"), Comparator.nullsLast(Comparator.naturalOrder())) + .thenComparing(row -> row.decimal("ID"), Comparator.nullsLast(Comparator.naturalOrder())); + roots.sort(order); + childrenByParent.values().forEach(children -> children.sort(order)); + + AtomicInteger displayIds = new AtomicInteger(1); + AtomicInteger nodeCount = new AtomicInteger(); + List parsedRoots = new ArrayList<>(); + for (Row root : roots) { + PlanNode node = convert( + root, + childrenByParent, + displayIds, + nodeCount, + 1, + maxNodes, + maxDepth, + new HashSet<>(), + warnings + ); + if (node != null) { + parsedRoots.add(node); + } + } + boolean incomplete = warnings.stream().anyMatch(warning -> + PlanCodes.PLAN_LIMIT_REACHED.equals(warning.code()) + || PlanCodes.PLAN_PARSE_FAILED.equals(warning.code())); + if (incomplete) { + return new ParseResult(List.of(), warnings, false); + } + if (parsedRoots.isEmpty()) { + return ParseResult.failed(warnings, "Oracle PLAN_TABLE produced no plan roots"); + } + return new ParseResult(parsedRoots, warnings, true); + } + + private static List rows(JsonObject envelope) { + JsonArray resultSets = array(envelope, "resultSets"); + if (resultSets.isEmpty() || !resultSets.get(0).isJsonObject()) { + throw new IllegalArgumentException("Oracle plan table envelope has no result set"); + } + JsonObject resultSet = resultSets.get(0).getAsJsonObject(); + JsonArray columns = array(resultSet, "columns"); + JsonArray values = array(resultSet, "rows"); + List names = new ArrayList<>(); + for (JsonElement element : columns) { + if (!element.isJsonObject()) { + throw new IllegalArgumentException("Oracle plan table column metadata is invalid"); + } + String name = text(element.getAsJsonObject().get("name")); + if (name == null) { + throw new IllegalArgumentException("Oracle plan table column has no name"); + } + names.add(name.toUpperCase(Locale.ROOT)); + } + + List result = new ArrayList<>(); + for (JsonElement element : values) { + if (!element.isJsonArray()) { + throw new IllegalArgumentException("Oracle plan table row is invalid"); + } + JsonArray rowValues = element.getAsJsonArray(); + if (rowValues.size() != names.size()) { + throw new IllegalArgumentException("Oracle plan table row width does not match its columns"); + } + Map fields = new LinkedHashMap<>(); + for (int i = 0; i < names.size(); i++) { + fields.put(names.get(i), rowValues.get(i)); + } + result.add(new Row(fields)); + } + return result; + } + + private static PlanNode convert( + Row row, + Map> childrenByParent, + AtomicInteger displayIds, + AtomicInteger nodeCount, + int depth, + int maxNodes, + int maxDepth, + Set path, + List warnings + ) { + if (nodeCount.incrementAndGet() > maxNodes) { + warnings.add(PlanDiagnostic.of(PlanCodes.PLAN_LIMIT_REACHED, "Execution plan exceeded the node limit")); + return null; + } + if (depth > maxDepth) { + warnings.add(PlanDiagnostic.of(PlanCodes.PLAN_LIMIT_REACHED, "Execution plan exceeded the depth limit")); + return null; + } + + String nativeId = row.text("ID"); + if (!path.add(nativeId)) { + warnings.add(PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, "Oracle plan contains a cycle at node " + nativeId)); + return null; + } + + String operation = defaultText(row.text("OPERATION"), "Unknown"); + String options = row.text("OPTIONS"); + String nativeOperator = options == null ? operation : operation + " " + options; + String owner = row.text("OBJECT_OWNER"); + String table = row.text("OBJECT_NAME"); + String relation = table == null ? null : owner == null ? table : owner + "." + table; + + Map predicates = new LinkedHashMap<>(); + put(predicates, "Access Predicates", row.text("ACCESS_PREDICATES")); + put(predicates, "Filter Predicates", row.text("FILTER_PREDICATES")); + + Map attributes = new LinkedHashMap<>(); + put(attributes, "Options", options); + put(attributes, "Object Owner", owner); + put(attributes, "Object Name", table); + put(attributes, "Object Alias", row.text("OBJECT_ALIAS")); + put(attributes, "Object Type", row.text("OBJECT_TYPE")); + put(attributes, "Optimizer", row.text("OPTIMIZER")); + put(attributes, "Bytes", row.text("BYTES")); + put(attributes, "CPU Cost", row.text("CPU_COST")); + put(attributes, "IO Cost", row.text("IO_COST")); + put(attributes, "Temp Space", row.text("TEMP_SPACE")); + put(attributes, "Projection", row.text("PROJECTION")); + put(attributes, "Time", row.text("TIME")); + put(attributes, "Query Block", row.text("QBLOCK_NAME")); + put(attributes, "Other Tag", row.text("OTHER_TAG")); + put(attributes, "Partition Start", row.text("PARTITION_START")); + put(attributes, "Partition Stop", row.text("PARTITION_STOP")); + put(attributes, "Depth", row.text("DEPTH")); + put(attributes, "Position", row.text("POSITION")); + + List children = new ArrayList<>(); + for (Row child : childrenByParent.getOrDefault(nativeId, List.of())) { + PlanNode parsed = convert( + child, + childrenByParent, + displayIds, + nodeCount, + depth + 1, + maxNodes, + maxDepth, + new HashSet<>(path), + warnings + ); + if (parsed != null) { + children.add(parsed); + } + } + + return new PlanNode( + "n" + displayIds.getAndIncrement(), + nativeId, + mapNodeType(nativeOperator), + nativeOperator, + logicalOperator(operation, options), + operation, + null, + table, + relation, + row.decimal("CARDINALITY"), + row.decimal("COST"), + null, + ROWS_MEANING, + COST_MEANING, + predicates, + attributes, + children + ); + } + + static NormalizedNodeType mapNodeType(String nativeOperator) { + String value = nativeOperator == null ? "" : nativeOperator.toLowerCase(Locale.ROOT); + if (value.contains("index fast full scan") || value.contains("index full scan")) { + return NormalizedNodeType.INDEX_ONLY_SCAN; + } + if (value.contains("index") && value.contains("scan")) { + return NormalizedNodeType.INDEX_SCAN; + } + if (value.contains("hash join")) { + return NormalizedNodeType.HASH_JOIN; + } + if (value.contains("merge join")) { + return NormalizedNodeType.MERGE_JOIN; + } + if (value.contains("nested loop")) { + return NormalizedNodeType.NESTED_LOOP; + } + if (value.contains("join")) { + return NormalizedNodeType.JOIN; + } + if (value.contains("table access") || value.contains("mat_view access") + || value.contains("collection iterator") || value.contains("cluster scan")) { + return NormalizedNodeType.SCAN; + } + if (value.startsWith("sort")) { + return NormalizedNodeType.SORT; + } + if (value.contains("group by") || value.contains("aggregate") || value.startsWith("window")) { + return NormalizedNodeType.AGGREGATE; + } + if (value.contains("stopkey") || value.startsWith("first rows")) { + return NormalizedNodeType.LIMIT; + } + if (value.startsWith("px ") || value.contains("parallel")) { + return NormalizedNodeType.PARALLEL; + } + if (value.contains("union")) { + return NormalizedNodeType.UNION; + } + if (value.contains("temp table transformation") || value.contains("materialize")) { + return NormalizedNodeType.MATERIALIZE; + } + if (value.equals("view") || value.contains("subquery")) { + return NormalizedNodeType.SUBQUERY; + } + if (value.equals("hash") || value.startsWith("hash ")) { + return NormalizedNodeType.HASH; + } + if (value.contains("scan") || value.contains("access")) { + return NormalizedNodeType.SCAN; + } + if (value.contains("projection") || value.equals("select statement")) { + return NormalizedNodeType.PROJECTION; + } + return NormalizedNodeType.OTHER; + } + + private static String logicalOperator(String operation, String options) { + if (operation == null || !operation.toLowerCase(Locale.ROOT).contains("join")) { + return null; + } + return options == null ? "Join" : options; + } + + private static JsonArray array(JsonObject object, String key) { + JsonElement value = object.get(key); + if (value == null || !value.isJsonArray()) { + return new JsonArray(); + } + return value.getAsJsonArray(); + } + + private static String text(JsonElement value) { + if (value == null || value.isJsonNull() || !value.isJsonPrimitive()) { + return null; + } + String text = value.getAsString(); + return text == null || text.isBlank() ? null : text; + } + + private static String defaultText(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } + + private static void put(Map values, String key, String value) { + if (value != null && !value.isBlank()) { + values.put(key, value); + } + } + + private record Row(Map fields) { + String text(String name) { + return OraclePlanParser.text(fields.get(name)); + } + + BigDecimal decimal(String name) { + JsonElement value = fields.get(name); + if (value == null || value.isJsonNull() || !value.isJsonPrimitive()) { + return null; + } + try { + return value.getAsBigDecimal(); + } catch (RuntimeException e) { + return null; + } + } + } + + public record ParseResult(List roots, List warnings, boolean structured) { + public ParseResult { + roots = roots == null ? List.of() : List.copyOf(roots); + warnings = warnings == null ? List.of() : List.copyOf(warnings); + } + + static ParseResult failed(List warnings, String message) { + List result = new ArrayList<>(warnings); + result.add(PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, message)); + return new ParseResult(List.of(), result, false); + } + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OraclePlanTable.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OraclePlanTable.java new file mode 100644 index 00000000..36aeb02f --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OraclePlanTable.java @@ -0,0 +1,368 @@ +package org.jumpserver.chen.modules.oracle; + +import org.jumpserver.chen.framework.datasource.plan.PlanCodes; +import org.jumpserver.chen.framework.datasource.plan.PlanExecutionContext; +import org.jumpserver.chen.framework.datasource.plan.PlanPrerequisite; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +final class OraclePlanTable { + private static final String NAME = "PLAN_TABLE"; + private static final int MAX_SYNONYM_DEPTH = 8; + private static final List PLAN_COLUMNS = List.of( + "STATEMENT_ID", "PLAN_ID", "TIMESTAMP", "REMARKS", "OPERATION", "OPTIONS", + "OBJECT_NODE", "OBJECT_OWNER", "OBJECT_NAME", "OBJECT_ALIAS", "OBJECT_INSTANCE", + "OBJECT_TYPE", "OPTIMIZER", "SEARCH_COLUMNS", "ID", "PARENT_ID", "DEPTH", + "POSITION", "COST", "CARDINALITY", "BYTES", "OTHER_TAG", "PARTITION_START", + "PARTITION_STOP", "PARTITION_ID", "OTHER", "DISTRIBUTION", "CPU_COST", "IO_COST", + "TEMP_SPACE", "ACCESS_PREDICATES", "FILTER_PREDICATES", "PROJECTION", "TIME", + "QBLOCK_NAME", "OTHER_XML" + ); + private static final Set REQUIRED_COLUMNS = Set.of( + "STATEMENT_ID", "OPERATION", "ID", "PARENT_ID", "POSITION", "COST", "CARDINALITY" + ); + private static final Set CHARACTER_COLUMNS = Set.of("STATEMENT_ID", "OPERATION"); + private static final Set NUMBER_COLUMNS = Set.of("ID", "PARENT_ID", "POSITION", "COST", "CARDINALITY"); + private static final Set REQUIRED_PRIVILEGES = Set.of("SELECT", "INSERT", "DELETE"); + + private OraclePlanTable() { + } + + static Resolution resolve(PlanExecutionContext context) throws SQLException { + SessionIdentity identity = sessionIdentity(context); + String owner = identity.currentSchema(); + String name = NAME; + Set visited = new HashSet<>(); + + for (int depth = 0; depth <= MAX_SYNONYM_DEPTH; depth++) { + String key = owner + "." + name; + if (!visited.add(key)) { + return failed( + PlanCodes.PLAN_TABLE_INCOMPATIBLE, + "PLAN_TABLE synonym resolution contains a cycle", + "Repair the PLAN_TABLE synonym chain" + ); + } + + ObjectInfo object = objectInfo(context, owner, name); + if (object != null && object.type() == ObjectType.TABLE) { + if (object.temporary() == null) { + return failed( + PlanCodes.PLAN_TABLE_INCOMPATIBLE, + "PLAN_TABLE table metadata is not accessible", + "Grant access to a compatible PLAN_TABLE target" + ); + } + return Resolution.resolved(new Target( + owner, + name, + object.temporary(), + object.duration(), + tableColumns(context, owner, name), + identity.sessionUser() + )); + } + + ObjectType type = object == null ? null : object.type(); + if (type != null && type != ObjectType.SYNONYM) { + return incompatibleObject(owner, name, type); + } + + Synonym synonym; + if (type == ObjectType.SYNONYM) { + synonym = synonym(context, owner, name); + } else if (depth == 0) { + synonym = synonym(context, "PUBLIC", name); + } else { + synonym = null; + } + if (synonym == null) { + String message = depth == 0 + ? "PLAN_TABLE is not resolvable in the current Oracle schema" + : "PLAN_TABLE synonym target cannot be resolved"; + String remediation = depth == 0 + ? "Create a compatible private PLAN_TABLE or grant access through the standard public synonym" + : "Repair the PLAN_TABLE synonym chain"; + return failed(PlanCodes.PLAN_TABLE_MISSING, message, remediation); + } + if (synonym.databaseLink() != null) { + return failed( + PlanCodes.PLAN_TABLE_INCOMPATIBLE, + "A remote PLAN_TABLE synonym is not supported", + "Point PLAN_TABLE at a compatible table in the current database" + ); + } + owner = synonym.owner(); + name = synonym.name(); + } + return failed( + PlanCodes.PLAN_TABLE_INCOMPATIBLE, + "PLAN_TABLE synonym chain is too deep", + "Point PLAN_TABLE directly at a compatible local table" + ); + } + + static PlanPrerequisite validateLifecycle(Target target, boolean autoCommit) { + if (!target.temporary()) { + return null; + } + String duration = upper(target.duration()); + if (!"SYS$SESSION".equals(duration) && !"SYS$TRANSACTION".equals(duration)) { + return PlanPrerequisite.unmet( + PlanCodes.PLAN_TABLE_INCOMPATIBLE, + "PLAN_TABLE has an unknown temporary-row duration", + "Use a standard Oracle PLAN_TABLE definition" + ); + } + if (autoCommit && "SYS$TRANSACTION".equals(duration)) { + return PlanPrerequisite.unmet( + PlanCodes.TRANSACTION_CONTEXT_UNSAFE, + "PLAN_TABLE deletes rows on commit, so auto-commit would erase the plan before it can be read", + "Use the standard ON COMMIT PRESERVE ROWS PLAN_TABLE or an active transaction" + ); + } + return null; + } + + static PlanPrerequisite validateColumns(Target target) { + Map columns = target.columns(); + Set missing = new LinkedHashSet<>(REQUIRED_COLUMNS); + missing.removeAll(columns.keySet()); + if (!missing.isEmpty()) { + return PlanPrerequisite.unmet( + PlanCodes.PLAN_TABLE_INCOMPATIBLE, + "PLAN_TABLE is missing required columns: " + String.join(", ", missing), + "Replace it with a compatible Oracle PLAN_TABLE definition" + ); + } + for (String name : CHARACTER_COLUMNS) { + if (!isCharacterType(columns.get(name))) { + return incompatibleColumn(name, columns.get(name)); + } + } + for (String name : NUMBER_COLUMNS) { + if (!isNumberType(columns.get(name))) { + return incompatibleColumn(name, columns.get(name)); + } + } + return null; + } + + static Set missingPrivileges(PlanExecutionContext context, Target target) throws SQLException { + Set missing = new LinkedHashSet<>(REQUIRED_PRIVILEGES); + missing.removeAll(loadPrivileges(context, target)); + return missing; + } + + static String lifecycleDescription(Target target) { + if (!target.temporary()) { + return "Regular table rows are isolated by generated STATEMENT_ID and explicitly deleted"; + } + return "Temporary table duration " + target.duration() + " is compatible with this transaction state"; + } + + static String quote(String identifier) { + return "\"" + identifier.replace("\"", "\"\"") + "\""; + } + + private static SessionIdentity sessionIdentity(PlanExecutionContext context) throws SQLException { + String sql = "SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA'), USER FROM DUAL"; + try (OraclePlanJdbc.Tracked tracked = OraclePlanJdbc.statement(context, true); + ResultSet resultSet = tracked.statement().executeQuery(sql)) { + if (!resultSet.next()) { + throw new SQLException("Oracle session identity query returned no row"); + } + return new SessionIdentity(resultSet.getString(1), resultSet.getString(2)); + } + } + + private static ObjectInfo objectInfo(PlanExecutionContext context, String owner, String name) + throws SQLException { + String sql = "SELECT o.OBJECT_TYPE, t.TEMPORARY, t.DURATION FROM ALL_OBJECTS o " + + "LEFT JOIN ALL_TABLES t ON t.OWNER = o.OWNER AND t.TABLE_NAME = o.OBJECT_NAME " + + "WHERE o.OWNER = ? AND o.OBJECT_NAME = ? " + + "AND o.OBJECT_TYPE IN ('TABLE','VIEW','SYNONYM') " + + "ORDER BY CASE o.OBJECT_TYPE WHEN 'TABLE' THEN 1 WHEN 'VIEW' THEN 2 ELSE 3 END"; + try (OraclePlanJdbc.Tracked tracked = OraclePlanJdbc.preparedStatement(context, sql, true)) { + PreparedStatement statement = tracked.statement(); + statement.setString(1, owner); + statement.setString(2, name); + try (ResultSet resultSet = statement.executeQuery()) { + if (!resultSet.next()) { + return null; + } + ObjectType type = ObjectType.valueOf(resultSet.getString(1).toUpperCase(Locale.ROOT)); + String temporary = resultSet.getString(2); + return new ObjectInfo( + type, + temporary == null ? null : "Y".equalsIgnoreCase(temporary), + resultSet.getString(3) + ); + } + } + } + + private static Synonym synonym(PlanExecutionContext context, String owner, String name) throws SQLException { + String sql = "SELECT TABLE_OWNER, TABLE_NAME, DB_LINK FROM ALL_SYNONYMS " + + "WHERE OWNER = ? AND SYNONYM_NAME = ?"; + try (OraclePlanJdbc.Tracked tracked = OraclePlanJdbc.preparedStatement(context, sql, true)) { + PreparedStatement statement = tracked.statement(); + statement.setString(1, owner); + statement.setString(2, name); + try (ResultSet resultSet = statement.executeQuery()) { + return resultSet.next() + ? new Synonym(resultSet.getString(1), resultSet.getString(2), resultSet.getString(3)) + : null; + } + } + } + + private static Map tableColumns( + PlanExecutionContext context, + String owner, + String name + ) throws SQLException { + String sql = "SELECT COLUMN_NAME, DATA_TYPE FROM ALL_TAB_COLUMNS " + + "WHERE OWNER = ? AND TABLE_NAME = ? ORDER BY COLUMN_ID"; + Map columns = new LinkedHashMap<>(); + try (OraclePlanJdbc.Tracked tracked = OraclePlanJdbc.preparedStatement(context, sql, true)) { + PreparedStatement statement = tracked.statement(); + statement.setString(1, owner); + statement.setString(2, name); + try (ResultSet resultSet = statement.executeQuery()) { + while (resultSet.next()) { + columns.put(upper(resultSet.getString(1)), upper(resultSet.getString(2))); + } + } + } + return columns; + } + + private static Set loadPrivileges(PlanExecutionContext context, Target target) throws SQLException { + if (target.owner().equals(target.sessionUser())) { + return REQUIRED_PRIVILEGES; + } + + Set privileges = new HashSet<>(); + String grantsSql = "SELECT DISTINCT PRIVILEGE FROM ALL_TAB_PRIVS " + + "WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? " + + "AND (GRANTEE = ? OR GRANTEE = 'PUBLIC' OR GRANTEE IN (SELECT ROLE FROM SESSION_ROLES))"; + try (OraclePlanJdbc.Tracked tracked = + OraclePlanJdbc.preparedStatement(context, grantsSql, true)) { + PreparedStatement statement = tracked.statement(); + statement.setString(1, target.owner()); + statement.setString(2, target.name()); + statement.setString(3, target.sessionUser()); + try (ResultSet resultSet = statement.executeQuery()) { + while (resultSet.next()) { + privileges.add(upper(resultSet.getString(1))); + } + } + } + + String systemSql = "SELECT PRIVILEGE FROM SESSION_PRIVS " + + "WHERE PRIVILEGE IN ('SELECT ANY TABLE','INSERT ANY TABLE','DELETE ANY TABLE')"; + try (OraclePlanJdbc.Tracked tracked = OraclePlanJdbc.statement(context, true); + ResultSet resultSet = tracked.statement().executeQuery(systemSql)) { + while (resultSet.next()) { + String privilege = upper(resultSet.getString(1)); + if (privilege != null && privilege.endsWith(" ANY TABLE")) { + privileges.add(privilege.substring(0, privilege.length() - " ANY TABLE".length())); + } + } + } + return privileges; + } + + private static PlanPrerequisite incompatibleColumn(String name, String type) { + return PlanPrerequisite.unmet( + PlanCodes.PLAN_TABLE_INCOMPATIBLE, + "PLAN_TABLE column " + name + " has incompatible type " + type, + "Replace it with a compatible Oracle PLAN_TABLE definition" + ); + } + + private static boolean isCharacterType(String type) { + return type != null && (type.contains("CHAR") || "CLOB".equals(type)); + } + + private static boolean isNumberType(String type) { + return type != null && (type.contains("NUMBER") || type.contains("FLOAT") || type.contains("DOUBLE") + || type.contains("INTEGER") || type.contains("DECIMAL")); + } + + private static String upper(String value) { + return value == null ? null : value.toUpperCase(Locale.ROOT); + } + + private static Resolution incompatibleObject(String owner, String name, ObjectType type) { + return failed( + PlanCodes.PLAN_TABLE_INCOMPATIBLE, + "Resolved PLAN_TABLE object " + quote(owner) + "." + quote(name) + " is a " + type, + "Use a compatible Oracle table for PLAN_TABLE" + ); + } + + private static Resolution failed(String code, String message, String remediation) { + return Resolution.failed(PlanPrerequisite.unmet(code, message, remediation)); + } + + private enum ObjectType { + TABLE, + VIEW, + SYNONYM + } + + private record SessionIdentity(String currentSchema, String sessionUser) { + } + + private record Synonym(String owner, String name, String databaseLink) { + } + + private record ObjectInfo(ObjectType type, Boolean temporary, String duration) { + } + + record Target( + String owner, + String name, + boolean temporary, + String duration, + Map columns, + String sessionUser + ) { + String qualifiedName() { + return quote(owner) + "." + quote(name); + } + + List selectedColumns() { + List selected = new ArrayList<>(); + for (String column : PLAN_COLUMNS) { + if (columns.containsKey(column)) { + selected.add(column); + } + } + return selected; + } + } + + record Resolution(Target target, PlanPrerequisite failure) { + static Resolution resolved(Target target) { + return new Resolution(target, null); + } + + static Resolution failed(PlanPrerequisite failure) { + return new Resolution(null, failure); + } + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OraclePlanTableAccess.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OraclePlanTableAccess.java new file mode 100644 index 00000000..c9583758 --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OraclePlanTableAccess.java @@ -0,0 +1,152 @@ +package org.jumpserver.chen.modules.oracle; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import org.jumpserver.chen.framework.datasource.analysis.SqlStatementAnalysis; +import org.jumpserver.chen.framework.datasource.plan.ExecutionPlanJson; +import org.jumpserver.chen.framework.datasource.plan.PlanExecutionContext; + +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; +import java.util.List; +import java.util.stream.Collectors; + +final class OraclePlanTableAccess { + private OraclePlanTableAccess() { + } + + static void explain( + PlanExecutionContext context, + SqlStatementAnalysis statement, + OraclePlanTable.Target target, + String statementId + ) throws SQLException { + String sql = "EXPLAIN PLAN SET STATEMENT_ID = '" + statementId + "' INTO " + + target.qualifiedName() + " FOR " + statement.sql(); + try (OraclePlanJdbc.Tracked tracked = OraclePlanJdbc.statement(context, true)) { + tracked.statement().execute(sql); + } + } + + static Capture read( + PlanExecutionContext context, + OraclePlanTable.Target target, + String statementId + ) throws SQLException { + List columns = target.selectedColumns(); + String selected = columns.stream().map(OraclePlanTable::quote).collect(Collectors.joining(", ")); + String sql = "SELECT " + selected + " FROM " + target.qualifiedName() + + " WHERE " + OraclePlanTable.quote("STATEMENT_ID") + " = ? ORDER BY " + + OraclePlanTable.quote("ID") + ", " + OraclePlanTable.quote("POSITION"); + + try (OraclePlanJdbc.Tracked tracked = + OraclePlanJdbc.preparedStatement(context, sql, true)) { + tracked.statement().setString(1, statementId); + try (ResultSet resultSet = tracked.statement().executeQuery()) { + return capture(resultSet, context.maxRawBytes(), context.maxNodes()); + } + } + } + + static void delete( + PlanExecutionContext context, + OraclePlanTable.Target target, + String statementId + ) throws SQLException { + String sql = "DELETE FROM " + target.qualifiedName() + + " WHERE " + OraclePlanTable.quote("STATEMENT_ID") + " = ?"; + try (OraclePlanJdbc.Tracked tracked = + OraclePlanJdbc.preparedStatement(context, sql, false)) { + tracked.statement().setString(1, statementId); + tracked.statement().executeUpdate(); + } + } + + private static Capture capture(ResultSet resultSet, int maxRawBytes, int maxRows) throws SQLException { + ResultSetMetaData metadata = resultSet.getMetaData(); + JsonObject envelope = new JsonObject(); + JsonArray resultSets = new JsonArray(); + JsonObject table = new JsonObject(); + table.addProperty("name", "PLAN_TABLE"); + JsonArray columns = new JsonArray(); + for (int index = 1; index <= metadata.getColumnCount(); index++) { + JsonObject column = new JsonObject(); + column.addProperty("name", metadata.getColumnLabel(index)); + column.addProperty("type", metadata.getColumnTypeName(index)); + columns.add(column); + } + table.add("columns", columns); + JsonArray rows = new JsonArray(); + table.add("rows", rows); + resultSets.add(table); + envelope.add("resultSets", resultSets); + envelope.addProperty("truncated", false); + + boolean truncated = false; + int rowCount = 0; + int capturedBytes = jsonBytes(envelope); + while (resultSet.next()) { + if (rowCount >= maxRows) { + truncated = true; + break; + } + JsonArray row = new JsonArray(); + for (int index = 1; index <= metadata.getColumnCount(); index++) { + row.add(jsonValue(resultSet, metadata.getColumnType(index), index)); + } + int rowBytes = jsonBytes(row); + int separatorBytes = rows.isEmpty() ? 0 : 1; + if ((long) capturedBytes + separatorBytes + rowBytes > maxRawBytes) { + truncated = true; + break; + } + rows.add(row); + capturedBytes += separatorBytes + rowBytes; + rowCount++; + } + envelope.addProperty("truncated", truncated); + String json = ExecutionPlanJson.GSON.toJson(envelope); + if (json.getBytes(StandardCharsets.UTF_8).length > maxRawBytes) { + JsonObject minimal = new JsonObject(); + minimal.add("resultSets", new JsonArray()); + minimal.addProperty("truncated", true); + return new Capture(ExecutionPlanJson.GSON.toJson(minimal), true); + } + return new Capture(json, truncated); + } + + private static int jsonBytes(JsonElement value) { + return ExecutionPlanJson.GSON.toJson(value).getBytes(StandardCharsets.UTF_8).length; + } + + private static JsonElement jsonValue(ResultSet resultSet, int jdbcType, int index) throws SQLException { + if (isNumericJdbcType(jdbcType)) { + BigDecimal value = resultSet.getBigDecimal(index); + return value == null ? JsonNull.INSTANCE : new JsonPrimitive(value); + } + if (jdbcType == Types.BOOLEAN || jdbcType == Types.BIT) { + boolean value = resultSet.getBoolean(index); + return resultSet.wasNull() ? JsonNull.INSTANCE : new JsonPrimitive(value); + } + String value = resultSet.getString(index); + return value == null ? JsonNull.INSTANCE : new JsonPrimitive(value); + } + + private static boolean isNumericJdbcType(int jdbcType) { + return jdbcType == Types.BIGINT || jdbcType == Types.DECIMAL || jdbcType == Types.DOUBLE + || jdbcType == Types.FLOAT || jdbcType == Types.INTEGER || jdbcType == Types.NUMERIC + || jdbcType == Types.REAL || jdbcType == Types.SMALLINT || jdbcType == Types.TINYINT; + } + + record Capture(String json, boolean truncated) { + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleResourceBrowser.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleResourceBrowser.java index 26c0c478..36e19528 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleResourceBrowser.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleResourceBrowser.java @@ -2,48 +2,9 @@ import org.jumpserver.chen.framework.datasource.ConnectionManager; import org.jumpserver.chen.framework.datasource.base.BaseResourceBrowser; -import org.jumpserver.chen.framework.datasource.entity.resource.Field; -import org.jumpserver.chen.framework.datasource.entity.resource.Schema; -import org.jumpserver.chen.framework.datasource.entity.resource.Table; -import org.jumpserver.chen.framework.datasource.entity.resource.View; -import org.jumpserver.chen.framework.datasource.sql.SQL; - -import java.sql.SQLException; -import java.util.List; - public class OracleResourceBrowser extends BaseResourceBrowser { public OracleResourceBrowser(ConnectionManager connectionManager) { - super(connectionManager, new OracleSQLHintsHandler(connectionManager)); - } - - private static final String SQL_GET_SCHEMAS = "SELECT USERNAME FROM ALL_USERS"; - - @Override - public List getSchemas() throws SQLException { - return this.getSchemas(SQL.of(SQL_GET_SCHEMAS)); - } - - private static final String SQL_GET_TABLES = "SELECT TABLE_NAME FROM ALL_TABLES where TABLESPACE_NAME is not null AND OWNER='?'"; - - @Override - public List
getTables(String schema) throws SQLException { - return this.getTables(SQL.of(SQL_GET_TABLES, schema)); + super(connectionManager); } - - private static final String SQL_GET_VIEWS = "SELECT VIEW_NAME FROM ALL_VIEWS WHERE OWNER= '?'"; - - @Override - public List getViews(String schema) throws SQLException { - return this.getViews(SQL.of(SQL_GET_VIEWS, schema)); - } - - private static final String SQL_GET_FIELDS = "SELECT COLUMN_NAME,DATA_TYPE,DATA_LENGTH,DATA_PRECISION,DATA_SCALE,CHAR_LENGTH,CHAR_USED,DATA_DEFAULT,COMMENTS FROM ALL_TAB_COLUMNS WHERE OWNER='?' AND TABLE_NAME='?'" ; - - @Override - public List getFields(String schema, String table) throws SQLException { - return this.getFields(SQL.of(SQL_GET_FIELDS, schema, table)); - } - - } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleSQLHintsHandler.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleSQLHintsHandler.java deleted file mode 100644 index 14091df6..00000000 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleSQLHintsHandler.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.jumpserver.chen.modules.oracle; - -import org.jumpserver.chen.framework.datasource.ConnectionManager; -import org.jumpserver.chen.framework.datasource.base.BaseSQLHintsHandler; -import org.jumpserver.chen.framework.datasource.entity.resource.Field; -import org.jumpserver.chen.framework.datasource.entity.resource.Table; -import org.jumpserver.chen.framework.datasource.sql.SQL; - -import java.sql.SQLException; -import java.util.List; -import java.util.Map; - -public class OracleSQLHintsHandler extends BaseSQLHintsHandler { - - private final ConnectionManager connectionManager; - - - public OracleSQLHintsHandler(ConnectionManager connectionManager) { - this.connectionManager = connectionManager; - } - - - private static final String SQL_GET_ALL_TABLES = "SELECT TABLE_NAME,OWNER FROM ALL_TABLES WHERE OWNER= '?'"; - - public List
getALlTables(String schema) throws SQLException { - return this.connectionManager.getSqlActuator() - .getObjects(SQL.of(SQL_GET_ALL_TABLES, schema) - .getSql(), Table.class, Map.of("name", 1, "schema", 2)); - } - - private static final String SQL_GET_ALL_FIELDS = "SELECT COLUMN_NAME,OWNER,TABLE_NAME FROM ALL_TAB_COLUMNS WHERE OWNER = '?'"; - - - public List getAllFields(String schema) throws SQLException { - return this.connectionManager - .getSqlActuator() - .getObjects(SQL.of(SQL_GET_ALL_FIELDS, schema).getSql(), Field.class, Map.of("name", 1, "schema", 2, "table", 3)); - - } - - -} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlActionHandler.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlActionHandler.java index b5e6b02c..f54e8110 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlActionHandler.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlActionHandler.java @@ -50,20 +50,6 @@ public List getDatabaseActions(TreeNode node) { ); } - private static final String SQL_SELECT_DATABASE_DETAIL = "SELECT datname,pg_database_size(datname) as size,pg_database_size(datname) - pg_database_size(datname) as size_free,pg_database_size(datname) / pg_database_size(datname) as size_percent FROM pg_database WHERE datname = '?'"; - - public EventEmitter onDatabaseProperties(TreeNode node) throws SQLException { - return this.onShowObjectProperties("database", SQL_SELECT_DATABASE_DETAIL, node); - } - - - private static final String SQL_SELECT_TABLE_DETAIL = "SELECT table_name,table_schema,table_type FROM information_schema.tables WHERE table_name = '?'"; - - public EventEmitter onTableProperties(TreeNode node) throws SQLException { - return this.onShowObjectProperties("table", SQL_SELECT_TABLE_DETAIL, node); - } - - @Override public EventEmitter handleForm(FormData formData) throws SQLException { return null; diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlDatasource.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlDatasource.java index 07fa880c..b0387cce 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlDatasource.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlDatasource.java @@ -4,8 +4,11 @@ import org.jumpserver.chen.framework.datasource.DatasourceFactory; import org.jumpserver.chen.framework.datasource.base.BaseDatasource; import org.jumpserver.chen.framework.datasource.entity.DBConnectInfo; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; +import org.jumpserver.chen.framework.datasource.plan.ExecutionPlanDialect; public class PostgresqlDatasource extends BaseDatasource { + private final ExecutionPlanDialect executionPlanDialect = new PostgresqlExecutionPlanDialect(); static { DatasourceFactory.Register(PostgresqlDatasource.class); @@ -13,10 +16,16 @@ public class PostgresqlDatasource extends BaseDatasource { public PostgresqlDatasource(DBConnectInfo dbConnectInfo) { this.connectionManager = new PostgresqlConnectionManager(dbConnectInfo, this); + this.metadataCatalog = new MetadataCatalog(this.connectionManager, new PostgresqlMetadataProvider(this.connectionManager)); this.resourceBrowser = new PostgresqlResourceBrowser(this.connectionManager); this.actionHandler = new PostgresqlActionHandler(); } + @Override + public ExecutionPlanDialect getExecutionPlanDialect() { + return this.executionPlanDialect; + } + @Override public String getName() { return "postgresql"; diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlExecutionPlanDialect.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlExecutionPlanDialect.java new file mode 100644 index 00000000..2e1d37f9 --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlExecutionPlanDialect.java @@ -0,0 +1,403 @@ +package org.jumpserver.chen.modules.postgresql; + +import org.jumpserver.chen.framework.datasource.analysis.SqlStatementAnalysis; +import org.jumpserver.chen.framework.datasource.plan.BaseExecutionPlanDialect; +import org.jumpserver.chen.framework.datasource.plan.ConnectionInvalidatedException; +import org.jumpserver.chen.framework.datasource.plan.DialectPlanResult; +import org.jumpserver.chen.framework.datasource.plan.ExecutionPlanCapabilities; +import org.jumpserver.chen.framework.datasource.plan.PlanCodes; +import org.jumpserver.chen.framework.datasource.plan.PlanDatabase; +import org.jumpserver.chen.framework.datasource.plan.PlanDiagnostic; +import org.jumpserver.chen.framework.datasource.plan.PlanEffects; +import org.jumpserver.chen.framework.datasource.plan.PlanI18n; +import org.jumpserver.chen.framework.datasource.plan.PlanExecutionContext; +import org.jumpserver.chen.framework.datasource.plan.PlanPrerequisite; +import org.jumpserver.chen.framework.datasource.plan.PlanRawFormat; +import org.jumpserver.chen.framework.datasource.plan.PlanStatus; +import org.jumpserver.chen.framework.datasource.plan.PlanTransactionState; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.HexFormat; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; + +public class PostgresqlExecutionPlanDialect extends BaseExecutionPlanDialect { + static final String RAW_FORMAT_VERSION = "postgresql-explain-json"; + private static final Set SAFE_SCHEMAS = Set.of("pg_catalog", "information_schema"); + private static final Set BUILTIN_FUNCTIONS = Set.of( + "count", "sum", "avg", "min", "max", "coalesce", "nullif", "greatest", "least", + "now", "current_date", "current_time", "current_timestamp", "localtimestamp", "localtime", + "date_trunc", "date_part", "age", "extract", "to_char", "to_date", "to_timestamp", "to_number", + "lower", "upper", "trim", "ltrim", "rtrim", "length", "char_length", "octet_length", + "substring", "substr", "replace", "overlay", "position", "strpos", "concat", "concat_ws", + "format", "left", "right", "repeat", "reverse", "split_part", "regexp_replace", "regexp_split_to_array", + "abs", "ceil", "ceiling", "floor", "round", "trunc", "mod", "power", "sqrt", "sign", + "generate_series", "unnest", "array_agg", "string_agg", "json_agg", "jsonb_agg", "json_build_object", + "jsonb_build_object", "json_build_array", "jsonb_build_array", "row_to_json", "to_json", "to_jsonb", + "row_number", "rank", "dense_rank", "ntile", "lag", "lead", "first_value", "last_value", "nth_value", + "bool_and", "bool_or", "every", "corr", "covar_pop", "covar_samp", "regr_avgx", "stddev", "variance", + "exists", "cast", "text", "int", "int2", "int4", "int8", "float4", "float8", "numeric", "varchar", + "bpchar", "bool", "date", "timestamp", "timestamptz", "uuid", "json", "jsonb", "bytea", + "pg_typeof", "pg_get_expr", "pg_get_userbyid", "current_schema", "current_user", "session_user", + "array_length", "cardinality", "array_append", "array_cat", "array_remove", + "md5", "sha256", "encode", "decode", "btrim", "initcap", "quote_ident", "quote_literal" + ); + private static final Set UNSAFE_FUNCTIONS = Set.of( + "dblink", "dblink_exec", "dblink_connect", "lo_import", "lo_export", "lo_unlink", + "pg_read_file", "pg_read_binary_file", "pg_ls_dir", "pg_stat_file", "pg_execute_server_program", + "pg_reload_conf", "pg_terminate_backend", "pg_cancel_backend", "copy_from", "copy_to" + ); + + private final ExecutionPlanCapabilities capabilities = ExecutionPlanCapabilities.postgresql(); + + @Override + public PlanDatabase database() { + return PlanDatabase.postgresql; + } + + @Override + public ExecutionPlanCapabilities capabilities() { + return capabilities; + } + + @Override + public DialectPlanResult explainEstimated(PlanExecutionContext context, SqlStatementAnalysis statement) + throws SQLException { + List prerequisites = new ArrayList<>(); + PlanDiagnostic unsafe = unsafeFunctions(statement); + if (unsafe != null) { + prerequisites.add(PlanPrerequisite.unmet( + PlanCodes.UNSAFE_TO_ESTIMATE, + unsafe.message(), + PlanI18n.msg("Plan.RemoveUnsafeFunctions", "Remove user-defined or planning-time-unsafe functions and retry") + )); + return unmet(context, prerequisites, unsafe, PlanEffects.unchangedReuse()); + } + + PlanTransactionState txState = context.transactionState(); + if (context.transactionProbeFailed()) { + String detail = context.transactionProbeDetail(); + String message = (detail == null || detail.isBlank()) + ? PlanI18n.msg("Plan.PostgresTxProbeFailed", "PostgreSQL transaction state probe failed") + : PlanI18n.msg("Plan.PostgresTxProbeFailedDetail", "PostgreSQL transaction state probe failed: %s", detail); + prerequisites.add(PlanPrerequisite.unknown( + PlanCodes.TRANSACTION_CONTEXT_UNSAFE, + message, + PlanI18n.msg("Plan.ReconnectWithoutSavepointProbe", "Reconnect and retry without relying on SAVEPOINT probing") + )); + return unmet( + context, + prerequisites, + PlanDiagnostic.of(PlanCodes.TRANSACTION_CONTEXT_UNSAFE, message), + PlanEffects.unchangedReuse() + ); + } + if (txState == PlanTransactionState.UNKNOWN) { + prerequisites.add(PlanPrerequisite.unknown( + PlanCodes.TRANSACTION_CONTEXT_UNSAFE, + PlanI18n.msg("Plan.PostgresTxUnknown", "PostgreSQL transaction state is unknown"), + PlanI18n.msg("Plan.ReconnectWithoutSavepointProbe", "Reconnect and retry without relying on SAVEPOINT probing") + )); + return unmet( + context, + prerequisites, + PlanDiagnostic.of(PlanCodes.TRANSACTION_CONTEXT_UNSAFE, PlanI18n.msg("Plan.PostgresTxUnknown", "PostgreSQL transaction state is unknown")), + PlanEffects.unchangedReuse() + ); + } + if (txState == PlanTransactionState.TRANSACTION_FAILED) { + prerequisites.add(PlanPrerequisite.unmet( + PlanCodes.TRANSACTION_CONTEXT_UNSAFE, + PlanI18n.msg("Plan.PostgresTransactionAborted", "The current PostgreSQL transaction is already aborted"), + PlanI18n.msg("Plan.RollbackThenRetry", "Rollback or finish the existing transaction, then retry") + )); + return unmet( + context, + prerequisites, + PlanDiagnostic.of(PlanCodes.TRANSACTION_CONTEXT_UNSAFE, PlanI18n.msg("Plan.CannotEstimateFailedTransaction", "Cannot estimate a plan in a failed transaction")), + PlanEffects.unchangedReuse() + ); + } + if (txState == PlanTransactionState.MANUAL_COMMIT_IDLE) { + prerequisites.add(PlanPrerequisite.unmet( + PlanCodes.TRANSACTION_CONTEXT_UNSAFE, + PlanI18n.msg("Plan.ManualCommitIdleCannotStart", "A manual-commit idle connection cannot start a plan transaction"), + PlanI18n.msg("Plan.RunExplainAutoCommitOrOpenTx", "Run EXPLAIN while auto-commit idle or inside an already-open transaction") + )); + return unmet( + context, + prerequisites, + PlanDiagnostic.of(PlanCodes.TRANSACTION_CONTEXT_UNSAFE, PlanI18n.msg("Plan.ManualCommitIdleNotUsed", "Manual-commit idle connections are not used for EXPLAIN")), + PlanEffects.unchangedReuse() + ); + } + + prerequisites.add(PlanPrerequisite.met("statement", PlanI18n.msg("Plan.SingleSelect", "Single SELECT"))); + boolean useSavepoint = txState == PlanTransactionState.TRANSACTION_ACTIVE; + if (useSavepoint) { + return explainWithSavepoint(context, statement, prerequisites); + } + return explainDirect(context, statement, prerequisites, PlanEffects.unchangedReuse()); + } + + private DialectPlanResult explainWithSavepoint( + PlanExecutionContext context, + SqlStatementAnalysis statement, + List prerequisites + ) throws SQLException { + Connection connection = context.connection(); + String savepointName = "chen_ep_" + HexFormat.of().formatHex(randomBytes(6)); + try { + context.throwIfCancelled(); + executeSql(context, connection, "SAVEPOINT " + savepointName); + } catch (SQLException e) { + throwIfInvalid(context, e, PlanEffects.unchangedReuse()); + return unmet( + context, + List.of(PlanPrerequisite.unmet( + PlanCodes.TRANSACTION_CONTEXT_UNSAFE, + PlanI18n.msg("Plan.SavepointCreateFailed", "Failed to create a request-scoped SAVEPOINT"), + PlanI18n.msg("Plan.CheckTxAndRetry", "Check the transaction state and retry") + )), + sqlDiagnostic(PlanCodes.TRANSACTION_CONTEXT_UNSAFE, PlanI18n.msg("Plan.FailedToCreateSavepoint", "Failed to create SAVEPOINT"), e), + PlanEffects.unchangedReuse() + ); + } + + SQLException explainError = null; + DialectPlanResult result = null; + try { + result = explainDirect(context, statement, prerequisites, PlanEffects.savepointParticipated()); + } catch (SQLException e) { + explainError = e; + } + + SQLException restoreError = null; + try { + if (explainError != null) { + executeSql(context, connection, "ROLLBACK TO SAVEPOINT " + savepointName); + } + executeSql(context, connection, "RELEASE SAVEPOINT " + savepointName); + } catch (SQLException e) { + restoreError = e; + } + + if (restoreError != null) { + PlanDiagnostic original = explainError == null + ? PlanDiagnostic.of(PlanCodes.SQL_ERROR, PlanI18n.msg("Plan.SavepointRestoreFailed", "SAVEPOINT restore failed after EXPLAIN")) + : sqlDiagnostic(PlanCodes.SQL_ERROR, explainError.getMessage(), explainError); + throw new ConnectionInvalidatedException( + "PostgreSQL SAVEPOINT restore failed", + restoreError, + original, + sqlDiagnostic(PlanCodes.CONNECTION_INVALIDATED, restoreError.getMessage(), restoreError), + result + ); + } + if (explainError != null) { + throwIfInvalid(context, explainError, PlanEffects.savepointParticipated()); + throw explainError; + } + return result; + } + + private DialectPlanResult explainDirect( + PlanExecutionContext context, + SqlStatementAnalysis statement, + List prerequisites, + PlanEffects effects + ) throws SQLException { + context.throwIfCancelled(); + Connection connection = context.connection(); + Statement jdbcStatement = null; + ResultSet resultSet = null; + try { + jdbcStatement = connection.createStatement(); + context.registerStatement(jdbcStatement); + BoundedRaw raw; + try { + resultSet = jdbcStatement.executeQuery("EXPLAIN (FORMAT JSON) " + statement.sql()); + String value = resultSet.next() ? resultSet.getString(1) : ""; + raw = boundRaw(value, context.maxRawBytes()); + } finally { + closeQuietly(resultSet); + context.unregisterStatement(jdbcStatement); + closeQuietly(jdbcStatement); + } + + if (raw.truncated()) { + return new DialectPlanResult( + context.serverVersion(), + PlanStatus.RAW_ONLY, + List.of(), + raw.text(), + PlanRawFormat.JSON, + RAW_FORMAT_VERSION, + true, + capabilities, + prerequisites, + effects, + null, + List.of(PlanDiagnostic.of(PlanCodes.PLAN_LIMIT_REACHED, PlanI18n.msg("Plan.RawPlanExceededDisplayLimit", "Raw plan exceeded the display limit"))) + ); + } + + PostgresqlPlanParser.ParseResult parsed = PostgresqlPlanParser.parse( + raw.text(), + context.maxNodes(), + context.maxDepth() + ); + if (!parsed.structured()) { + return new DialectPlanResult( + context.serverVersion(), + PlanStatus.RAW_ONLY, + List.of(), + raw.text(), + PlanRawFormat.JSON, + RAW_FORMAT_VERSION, + false, + capabilities, + prerequisites, + effects, + null, + parsed.warnings() + ); + } + return new DialectPlanResult( + context.serverVersion(), + PlanStatus.SUCCESS, + parsed.roots(), + raw.text(), + PlanRawFormat.JSON, + RAW_FORMAT_VERSION, + false, + capabilities, + prerequisites, + effects, + null, + parsed.warnings() + ); + } catch (SQLException e) { + throwIfInvalid(context, e, effects); + throw e; + } + } + + private void executeSql(PlanExecutionContext context, Connection connection, String sql) throws SQLException { + Statement statement = null; + try { + statement = connection.createStatement(); + context.registerStatement(statement); + statement.execute(sql); + } finally { + context.unregisterStatement(statement); + closeQuietly(statement); + } + } + + private static void throwIfInvalid(PlanExecutionContext context, SQLException error, PlanEffects effects) + throws SQLException { + if (isConnectionBroken(error)) { + throw new ConnectionInvalidatedException( + error.getMessage(), + error, + sqlDiagnostic(PlanCodes.CONNECTION_INVALIDATED, error.getMessage(), error), + null, + new DialectPlanResult( + context.serverVersion(), + PlanStatus.CONNECTION_INVALIDATED, + List.of(), + null, + null, + null, + false, + ExecutionPlanCapabilities.postgresql(), + List.of(), + PlanEffects.discard(PlanEffects.SessionState.UNKNOWN, PlanEffects.TransactionState.UNKNOWN), + sqlDiagnostic(PlanCodes.CONNECTION_INVALIDATED, error.getMessage(), error), + List.of() + ) + ); + } + } + + private static boolean isConnectionBroken(SQLException error) { + String state = error.getSQLState(); + return state != null && (state.startsWith("08") || "57P01".equals(state) || "57P02".equals(state) || "57P03".equals(state)); + } + + private DialectPlanResult unmet( + PlanExecutionContext context, + List prerequisites, + PlanDiagnostic error, + PlanEffects effects + ) { + return new DialectPlanResult( + context.serverVersion(), + PlanStatus.PREREQUISITES_UNMET, + List.of(), + null, + null, + null, + false, + capabilities, + prerequisites, + effects, + error, + List.of() + ); + } + + static PlanDiagnostic unsafeFunctions(SqlStatementAnalysis statement) { + for (String rawName : statement.functionNames()) { + String name = rawName == null ? "" : rawName.trim(); + if (name.isEmpty()) { + continue; + } + String lower = name.toLowerCase(Locale.ROOT); + int dot = lower.lastIndexOf('.'); + String schema = dot < 0 ? "" : lower.substring(0, dot).replace("\"", ""); + String function = (dot < 0 ? lower : lower.substring(dot + 1)).replace("\"", ""); + if (UNSAFE_FUNCTIONS.contains(function)) { + return PlanDiagnostic.of( + PlanCodes.UNSAFE_TO_ESTIMATE, + PlanI18n.msg("Plan.FunctionNotSafeAtPlanTime", "Function %s is not safe to estimate at planning time", name) + ); + } + if (!schema.isEmpty() && !SAFE_SCHEMAS.contains(schema)) { + return PlanDiagnostic.of( + PlanCodes.UNSAFE_TO_ESTIMATE, + PlanI18n.msg("Plan.FunctionOutsidePgCatalog", "Function %s is outside pg_catalog and cannot be proven side-effect free", name) + ); + } + if (schema.isEmpty() && !BUILTIN_FUNCTIONS.contains(function)) { + return PlanDiagnostic.of( + PlanCodes.UNSAFE_TO_ESTIMATE, + PlanI18n.msg("Plan.FunctionNotPostgresBuiltin", "Function %s is not a known PostgreSQL builtin", name) + ); + } + } + return null; + } + + private static PlanDiagnostic sqlDiagnostic(String code, String message, SQLException error) { + return new PlanDiagnostic( + code, + message == null ? error.getMessage() : message, + error.getSQLState(), + Integer.toString(error.getErrorCode()) + ); + } + + private static byte[] randomBytes(int length) { + byte[] bytes = new byte[length]; + ThreadLocalRandom.current().nextBytes(bytes); + return bytes; + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlMetadataProvider.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlMetadataProvider.java new file mode 100644 index 00000000..c342a289 --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlMetadataProvider.java @@ -0,0 +1,340 @@ +package org.jumpserver.chen.modules.postgresql; + +import org.jumpserver.chen.framework.datasource.ConnectionManager; +import org.jumpserver.chen.framework.datasource.metadata.BaseDatabaseMetadataProvider; +import org.jumpserver.chen.framework.datasource.metadata.CatalogMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ColumnMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ConstraintMetadata; +import org.jumpserver.chen.framework.datasource.metadata.IndexMetadata; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCapabilities; +import org.jumpserver.chen.framework.datasource.metadata.ObjectProperties; +import org.jumpserver.chen.framework.datasource.metadata.ForeignKeyMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ObjectRef; +import org.jumpserver.chen.framework.datasource.metadata.PrimaryKeyMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ObjectStatistics; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; +import org.jumpserver.chen.framework.datasource.metadata.RelationMetadata; +import org.jumpserver.chen.framework.datasource.metadata.RelationScope; +import org.jumpserver.chen.framework.datasource.metadata.SchemaMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ScopeKind; +import org.jumpserver.chen.framework.datasource.metadata.ScopeProperties; +import org.jumpserver.chen.framework.datasource.metadata.ScopeRef; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +public class PostgresqlMetadataProvider extends BaseDatabaseMetadataProvider { + + public PostgresqlMetadataProvider(ConnectionManager connectionManager) { + super(connectionManager); + } + + private static final MetadataCapabilities CAPABILITIES = new MetadataCapabilities( + true, true, true, true, true, true, true, true, false, + true, true, false, false, false, true, true, true, false + ); + + private static final String SQL_CATALOGS = "SELECT DATNAME AS name FROM PG_DATABASE WHERE DATISTEMPLATE = FALSE"; + + private static final String SQL_SCHEMAS = "SELECT SCHEMA_NAME AS name FROM INFORMATION_SCHEMA.SCHEMATA"; + + private static final String SQL_TABLES = """ + SELECT c.relname AS name, + NULL::text AS character_set, + NULL::text AS collation, + obj_description(c.oid, 'pg_class') AS comment + FROM pg_class c + JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE n.nspname = ? AND c.relkind IN ('r', 'p') + ORDER BY c.relname + """; + + private static final String SQL_TABLE_STATS = """ + SELECT c.relname AS name, + c.reltuples::bigint AS estimated_rows, + pg_total_relation_size(c.oid) AS total_size_bytes + FROM pg_class c + JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE n.nspname = ? AND c.relkind IN ('r', 'p') + ORDER BY c.relname + """; + + private static final String SQL_VIEWS = """ + SELECT c.relname AS name, + CASE c.relkind WHEN 'm' THEN 'MATERIALIZED VIEW' ELSE 'VIEW' END AS type, + obj_description(c.oid, 'pg_class') AS comment + FROM pg_class c + JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE n.nspname = ? AND c.relkind IN ('v', 'm') + ORDER BY c.relname + """; + + private static final String SQL_INDEXES = """ + SELECT ic.relname AS name, + tc.relname AS table_name, + a.attname AS column_name, + CASE WHEN k.attnum = 0 THEN pg_get_indexdef(ic.oid, k.position::integer, true) END AS expression, + i.indisunique AS is_unique, + am.amname AS method, + pg_get_indexdef(ic.oid) AS definition, + CASE WHEN (i.indoption[(k.position - 1)::integer] & 1) = 1 THEN 'DESC' ELSE 'ASC' END AS sort_order + FROM pg_index i + JOIN pg_class ic ON ic.oid = i.indexrelid + JOIN pg_class tc ON tc.oid = i.indrelid + JOIN pg_namespace n ON n.oid = tc.relnamespace + JOIN pg_am am ON am.oid = ic.relam + LEFT JOIN LATERAL unnest(i.indkey) WITH ORDINALITY k(attnum, position) ON true + LEFT JOIN pg_attribute a ON a.attrelid = tc.oid AND a.attnum = k.attnum AND k.attnum > 0 + WHERE n.nspname = ? + ORDER BY tc.relname, ic.relname, k.position + """; + + private static final String SQL_COLUMNS = """ + SELECT a.attname AS name, c.relname AS table_name, a.attnum AS ordinal, + CASE WHEN bt.typelem <> 0 AND bt.typlen = -1 THEN bt.typname + WHEN btn.nspname <> 'pg_catalog' THEN bt.typname + ELSE pg_catalog.format_type(bt.oid, NULL) END AS native_type, + CASE WHEN bt.typelem <> 0 AND bt.typlen = -1 THEN 'ARRAY' + WHEN btn.nspname <> 'pg_catalog' THEN 'USER-DEFINED' + ELSE pg_catalog.format_type(bt.oid, NULL) END AS jdbc_type_name, + COALESCE( + information_schema._pg_char_max_length(bt.oid, + CASE WHEN dt.typtype = 'd' THEN dt.typtypmod ELSE a.atttypmod END), + information_schema._pg_numeric_precision(bt.oid, + CASE WHEN dt.typtype = 'd' THEN dt.typtypmod ELSE a.atttypmod END), + information_schema._pg_datetime_precision(bt.oid, + CASE WHEN dt.typtype = 'd' THEN dt.typtypmod ELSE a.atttypmod END) + ) AS size, + information_schema._pg_numeric_scale(bt.oid, + CASE WHEN dt.typtype = 'd' THEN dt.typtypmod ELSE a.atttypmod END) AS scale, + NOT (a.attnotnull OR (dt.typtype = 'd' AND dt.typnotnull)) AS nullable, + CASE WHEN COALESCE(pg_catalog.to_jsonb(a)->>'attgenerated', '') = '' + AND COALESCE(pg_catalog.to_jsonb(a)->>'attidentity', '') = '' + THEN pg_catalog.pg_get_expr(ad.adbin, ad.adrelid) END AS default_value, + pg_catalog.col_description(c.oid, a.attnum) AS comment + FROM pg_catalog.pg_class c + JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid + JOIN pg_catalog.pg_type dt ON dt.oid = a.atttypid + JOIN pg_catalog.pg_type bt ON bt.oid = CASE WHEN dt.typtype = 'd' THEN dt.typbasetype ELSE dt.oid END + JOIN pg_catalog.pg_namespace btn ON btn.oid = bt.typnamespace + LEFT JOIN pg_catalog.pg_attrdef ad ON ad.adrelid = a.attrelid AND ad.adnum = a.attnum + WHERE n.nspname = ? AND c.relname IN (__IN__) + AND c.relkind IN ('r', 'p', 'v', 'm', 'f') + AND a.attnum > 0 AND NOT a.attisdropped + AND NOT pg_catalog.pg_is_other_temp_schema(n.oid) + AND (pg_catalog.pg_has_role(c.relowner, 'USAGE') + OR pg_catalog.has_column_privilege( + c.oid, a.attnum, 'SELECT, INSERT, UPDATE, REFERENCES')) + ORDER BY c.relname, a.attnum + """; + + private static final String SQL_TABLE_INDEXES = """ + SELECT ic.relname AS name, tc.relname AS table_name, a.attname AS column_name, + CASE WHEN k.attnum = 0 THEN pg_get_indexdef(ic.oid, k.position::integer, true) END AS expression, + i.indisunique AS is_unique, am.amname AS method, pg_get_indexdef(ic.oid) AS definition, + CASE WHEN (i.indoption[(k.position - 1)::integer] & 1) = 1 THEN 'DESC' ELSE 'ASC' END AS sort_order + FROM pg_index i + JOIN pg_class ic ON ic.oid = i.indexrelid + JOIN pg_class tc ON tc.oid = i.indrelid + JOIN pg_namespace n ON n.oid = tc.relnamespace + JOIN pg_am am ON am.oid = ic.relam + LEFT JOIN LATERAL unnest(i.indkey) WITH ORDINALITY k(attnum, position) ON true + LEFT JOIN pg_attribute a ON a.attrelid = tc.oid AND a.attnum = k.attnum AND k.attnum > 0 + WHERE n.nspname = ? AND tc.relname IN (__IN__) + ORDER BY tc.relname, ic.relname, k.position + """; + + @Override + public MetadataCapabilities capabilities() { + return CAPABILITIES; + } + + @Override + public List listCatalogs() throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_CATALOGS, List.of())) { + result.add(new CatalogMetadata(stringValue(row, "name"))); + } + return result; + } + + @Override + public List listSchemas(String catalog) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_SCHEMAS, List.of())) { + result.add(new SchemaMetadata(catalog, stringValue(row, "name"))); + } + return result; + } + + @Override + public List listRelations(RelationScope scope, Set kinds) throws SQLException { + var result = new ArrayList(); + if (kinds.contains(RelationKind.TABLE)) { + for (var row : query(SQL_TABLES, List.of(scope.schema()))) { + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + stringValue(row, "comment"), + null, + null, + null + )); + } + } + if (kinds.contains(RelationKind.VIEW) || kinds.contains(RelationKind.MATERIALIZED_VIEW)) { + for (var row : query(SQL_VIEWS, List.of(scope.schema()))) { + var type = stringValue(row, "type"); + var kind = type != null && type.toUpperCase(Locale.ROOT).contains("MATERIALIZED") + ? RelationKind.MATERIALIZED_VIEW + : RelationKind.VIEW; + if (!kinds.contains(kind)) { + continue; + } + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), kind), + stringValue(row, "comment"), + null, + null, + null + )); + } + } + return result; + } + + @Override + public List listColumns(List relations) throws SQLException { + return loadColumns(SQL_COLUMNS, relations); + } + + @Override + public List listIndexes(RelationScope scope) throws SQLException { + return groupIndexRows(query(SQL_INDEXES, List.of(scope.schema())), scope); + } + + @Override + public List listIndexes(List relations) throws SQLException { + if (relations.isEmpty()) { + return List.of(); + } + var first = relations.get(0); + return groupIndexRows(queryKeys(SQL_TABLE_INDEXES, relations, first.schema()), + new RelationScope(first.catalog(), first.schema())); + } + + @Override + public List listStatistics(RelationScope scope) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_TABLE_STATS, List.of(scope.schema()))) { + result.add(new ObjectStatistics( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + longValue(row, "estimated_rows"), + longValue(row, "total_size_bytes") + )); + } + return result; + } + + private static final String SQL_TABLE_PROPERTIES = """ + SELECT table_name, table_schema, table_type + FROM information_schema.tables + WHERE table_schema = ? AND table_name = ? + """; + + private static final String SQL_DATABASE_PROPERTIES = """ + SELECT datname, pg_database_size(datname) AS size, + pg_size_pretty(pg_database_size(datname)) AS size_pretty, + datcollate, datctype, datistemplate, datallowconn, datconnlimit + FROM pg_database + WHERE datname = ? + """; + + @Override + public ObjectProperties objectProperties(ObjectRef ref) throws SQLException { + return loadObjectProperties(ref, SQL_TABLE_PROPERTIES); + } + + @Override + public ScopeProperties scopeProperties(ScopeRef ref) throws SQLException { + if (ref.kind() != ScopeKind.CATALOG) { + return super.scopeProperties(ref); + } + return loadScopeProperties(ref, SQL_DATABASE_PROPERTIES, List.of(ref.scope().catalog())); + } + + private static final String SQL_PRIMARY_KEYS = """ + SELECT tc.relname AS table_name, a.attname AS column_name, con.conname AS name + FROM pg_constraint con + JOIN pg_class tc ON tc.oid = con.conrelid + JOIN pg_namespace n ON n.oid = tc.relnamespace + JOIN LATERAL unnest(con.conkey) WITH ORDINALITY k(attnum, ord) ON true + JOIN pg_attribute a ON a.attrelid = tc.oid AND a.attnum = k.attnum + WHERE con.contype = 'p' AND n.nspname = ? AND tc.relname IN (__IN__) + ORDER BY tc.relname, con.conname, k.ord + """; + + private static final String SQL_FOREIGN_KEYS = """ + SELECT tc.relname AS table_name, a.attname AS column_name, con.conname AS name, + ns.nspname AS referenced_schema, rc.relname AS referenced_table, ra.attname AS referenced_column + FROM pg_constraint con + JOIN pg_class tc ON tc.oid = con.conrelid + JOIN pg_namespace n ON n.oid = tc.relnamespace + JOIN LATERAL unnest(con.conkey) WITH ORDINALITY k(attnum, ord) ON true + JOIN pg_attribute a ON a.attrelid = tc.oid AND a.attnum = k.attnum + JOIN LATERAL unnest(con.confkey) WITH ORDINALITY rk(attnum, ord) ON rk.ord = k.ord + JOIN pg_class rc ON rc.oid = con.confrelid + JOIN pg_namespace ns ON ns.oid = rc.relnamespace + JOIN pg_attribute ra ON ra.attrelid = rc.oid AND ra.attnum = rk.attnum + WHERE con.contype = 'f' AND n.nspname = ? AND tc.relname IN (__IN__) + ORDER BY tc.relname, con.conname, k.ord + """; + + private static final String SQL_CONSTRAINTS = """ + SELECT tc.relname AS table_name, con.conname AS name, + CASE con.contype WHEN 'p' THEN 'PRIMARY KEY' WHEN 'f' THEN 'FOREIGN KEY' + WHEN 'u' THEN 'UNIQUE' WHEN 'c' THEN 'CHECK' END AS constraint_type, + a.attname AS column_name, rn.nspname AS referenced_schema, + rc.relname AS referenced_table, ra.attname AS referenced_column, + pg_get_constraintdef(con.oid, true) AS definition + FROM pg_constraint con + JOIN pg_class tc ON tc.oid = con.conrelid + JOIN pg_namespace n ON n.oid = tc.relnamespace + LEFT JOIN LATERAL unnest(con.conkey) WITH ORDINALITY k(attnum, ord) ON true + LEFT JOIN pg_attribute a ON a.attrelid = tc.oid AND a.attnum = k.attnum + LEFT JOIN LATERAL unnest(con.confkey) WITH ORDINALITY rk(attnum, ord) ON rk.ord = k.ord + LEFT JOIN pg_class rc ON rc.oid = con.confrelid + LEFT JOIN pg_namespace rn ON rn.oid = rc.relnamespace + LEFT JOIN pg_attribute ra ON ra.attrelid = rc.oid AND ra.attnum = rk.attnum + WHERE con.contype IN ('p', 'f', 'u', 'c') + AND n.nspname = ? AND tc.relname IN (__IN__) + ORDER BY tc.relname, con.conname, k.ord + """; + + @Override + public List listPrimaryKeys(List relations) throws SQLException { + var first = relations.get(0); + return groupPrimaryKeys(queryKeys(SQL_PRIMARY_KEYS, relations, first.schema()), + new RelationScope(first.catalog(), first.schema())); + } + + @Override + public List listForeignKeys(List relations) throws SQLException { + var first = relations.get(0); + return groupForeignKeys(queryKeys(SQL_FOREIGN_KEYS, relations, first.schema()), + new RelationScope(first.catalog(), first.schema())); + } + + @Override + public List listConstraints(List relations) throws SQLException { + if (relations.isEmpty()) { + return List.of(); + } + var first = relations.get(0); + return groupConstraints(queryKeys(SQL_CONSTRAINTS, relations, first.schema()), + new RelationScope(first.catalog(), first.schema())); + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlPlanParser.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlPlanParser.java new file mode 100644 index 00000000..0255f571 --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlPlanParser.java @@ -0,0 +1,352 @@ +package org.jumpserver.chen.modules.postgresql; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.jumpserver.chen.framework.datasource.plan.NormalizedNodeType; +import org.jumpserver.chen.framework.datasource.plan.PlanCodes; +import org.jumpserver.chen.framework.datasource.plan.PlanDiagnostic; +import org.jumpserver.chen.framework.datasource.plan.PlanNode; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +public final class PostgresqlPlanParser { + public static final String ROWS_MEANING = "estimated output rows per invocation"; + public static final String COST_MEANING = "PostgreSQL estimated total subtree cost"; + static final String[] PREDICATE_KEYS = { + "Filter", + "Index Cond", + "Recheck Cond", + "Join Filter", + "Hash Cond", + "Merge Cond", + "TID Cond", + "Cache Key", + "One-Time Filter" + }; + private static final String[] ATTRIBUTE_KEYS = { + "Join Type", + "Sort Key", + "Group Key", + "CTE Name", + "Index Name", + "Strategy", + "Subplan Name", + "Parent Relationship", + "Alias", + "Parallel Aware", + "Output" + }; + + private PostgresqlPlanParser() { + } + + public static ParseResult parse(String rawJson, int maxNodes, int maxDepth) { + List warnings = new ArrayList<>(); + if (rawJson == null || rawJson.isBlank()) { + return ParseResult.failed(warnings, PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, "Empty PostgreSQL plan JSON")); + } + JsonElement root; + try { + root = JsonParser.parseString(rawJson); + } catch (RuntimeException e) { + return ParseResult.failed(warnings, PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, "PostgreSQL plan JSON is not valid")); + } + + List planObjects = extractPlans(root); + if (planObjects.isEmpty()) { + return ParseResult.failed(warnings, PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, "PostgreSQL plan JSON has no Plan node")); + } + + AtomicInteger ids = new AtomicInteger(1); + AtomicInteger nodeCount = new AtomicInteger(0); + List roots = new ArrayList<>(); + try { + for (JsonObject planObject : planObjects) { + PlanNode node = convert(planObject, ids, nodeCount, 1, maxNodes, maxDepth, warnings); + if (node != null) { + roots.add(node); + } + } + } catch (LimitReachedException e) { + warnings.add(PlanDiagnostic.of(PlanCodes.PLAN_LIMIT_REACHED, e.getMessage())); + } + + if (roots.isEmpty()) { + return ParseResult.failed(warnings, PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, "PostgreSQL plan JSON produced no nodes")); + } + return new ParseResult(roots, warnings, true); + } + + private static List extractPlans(JsonElement root) { + List plans = new ArrayList<>(); + if (root.isJsonArray()) { + for (JsonElement element : root.getAsJsonArray()) { + collectPlan(element, plans); + } + return plans; + } + collectPlan(root, plans); + return plans; + } + + private static void collectPlan(JsonElement element, List plans) { + if (element == null || !element.isJsonObject()) { + return; + } + JsonObject object = element.getAsJsonObject(); + if (object.has("Plan") && object.get("Plan").isJsonObject()) { + plans.add(object.getAsJsonObject("Plan")); + return; + } + if (object.has("Node Type")) { + plans.add(object); + } + } + + private static PlanNode convert( + JsonObject plan, + AtomicInteger ids, + AtomicInteger nodeCount, + int depth, + int maxNodes, + int maxDepth, + List warnings + ) { + if (nodeCount.incrementAndGet() > maxNodes) { + throw new LimitReachedException("Execution plan exceeded the node limit"); + } + if (depth > maxDepth) { + throw new LimitReachedException("Execution plan exceeded the depth limit"); + } + + String nativeOperator = text(plan, "Node Type"); + if (nativeOperator == null || nativeOperator.isBlank()) { + nativeOperator = "Unknown"; + } + String schema = text(plan, "Schema"); + String relationName = text(plan, "Relation Name"); + String cteName = text(plan, "CTE Name"); + String table = relationName != null ? relationName : cteName; + String relation = relationName; + if (schema != null && !schema.isBlank() && relationName != null) { + relation = schema + "." + relationName; + } else if (relation == null) { + relation = cteName; + } + + Map predicates = new LinkedHashMap<>(); + for (String key : PREDICATE_KEYS) { + String value = text(plan, key); + if (value != null) { + predicates.put(key, value); + } + } + + Map attributes = new LinkedHashMap<>(); + for (String key : ATTRIBUTE_KEYS) { + putAttr(attributes, key, text(plan, key)); + } + + List children = new ArrayList<>(); + addChildren(plan.get("Plans"), ids, nodeCount, depth, maxNodes, maxDepth, warnings, children); + addChildren(plan.get("Subplans"), ids, nodeCount, depth, maxNodes, maxDepth, warnings, children); + addChildren(plan.get("InitPlan"), ids, nodeCount, depth, maxNodes, maxDepth, warnings, children); + + String id = "n" + ids.getAndIncrement(); + String joinType = text(plan, "Join Type"); + + return new PlanNode( + id, + text(plan, "Node Id"), + mapNodeType(nativeOperator), + nativeOperator, + joinType, + nativeOperator, + null, + table, + relation, + decimal(plan, "Plan Rows"), + decimal(plan, "Total Cost"), + decimal(plan, "Startup Cost"), + ROWS_MEANING, + COST_MEANING, + predicates, + attributes, + children + ); + } + + private static void addChildren( + JsonElement element, + AtomicInteger ids, + AtomicInteger nodeCount, + int depth, + int maxNodes, + int maxDepth, + List warnings, + List children + ) { + if (element == null || element.isJsonNull()) { + return; + } + if (element.isJsonObject()) { + JsonObject object = element.getAsJsonObject(); + JsonObject plan = object.has("Plan") && object.get("Plan").isJsonObject() + ? object.getAsJsonObject("Plan") + : object; + PlanNode child = convert(plan, ids, nodeCount, depth + 1, maxNodes, maxDepth, warnings); + if (child != null) { + children.add(child); + } + return; + } + if (!element.isJsonArray()) { + return; + } + JsonArray array = element.getAsJsonArray(); + for (JsonElement childElement : array) { + if (childElement == null || childElement.isJsonNull()) { + continue; + } + if (childElement.isJsonObject()) { + addChildren(childElement, ids, nodeCount, depth, maxNodes, maxDepth, warnings, children); + } + } + } + + static NormalizedNodeType mapNodeType(String nativeOperator) { + String value = nativeOperator == null ? "" : nativeOperator.toLowerCase(Locale.ROOT); + if (value.contains("index only scan")) { + return NormalizedNodeType.INDEX_ONLY_SCAN; + } + if (value.contains("index scan") || value.contains("bitmap")) { + return NormalizedNodeType.INDEX_SCAN; + } + if (value.contains("hash join")) { + return NormalizedNodeType.HASH_JOIN; + } + if (value.contains("merge join")) { + return NormalizedNodeType.MERGE_JOIN; + } + if (value.contains("nested loop")) { + return NormalizedNodeType.NESTED_LOOP; + } + if (value.contains("join")) { + return NormalizedNodeType.JOIN; + } + if (value.contains("cte scan") || value.contains("subquery") || value.contains("initplan") || value.contains("subplan")) { + return NormalizedNodeType.SUBQUERY; + } + if (value.contains("seq scan") || value.contains("foreign scan") || value.contains("function scan") + || value.contains("table function") || value.contains("worktable scan")) { + return NormalizedNodeType.SCAN; + } + if (value.equals("hash") || value.startsWith("hash ")) { + return NormalizedNodeType.HASH; + } + if (value.contains("sort")) { + return NormalizedNodeType.SORT; + } + if (value.contains("aggregate") || value.contains("group") || value.contains("window")) { + return NormalizedNodeType.AGGREGATE; + } + if (value.contains("limit")) { + return NormalizedNodeType.LIMIT; + } + if (value.contains("gather") || value.contains("parallel")) { + return NormalizedNodeType.PARALLEL; + } + if (value.contains("append") || value.contains("union")) { + return NormalizedNodeType.UNION; + } + if (value.contains("materialize")) { + return NormalizedNodeType.MATERIALIZE; + } + if (value.contains("values")) { + return NormalizedNodeType.VALUES; + } + if (value.equals("result") || value.contains("project")) { + return NormalizedNodeType.PROJECTION; + } + if (value.contains("scan")) { + return NormalizedNodeType.SCAN; + } + return NormalizedNodeType.OTHER; + } + + private static void putAttr(Map attributes, String key, String value) { + if (value != null && !value.isBlank()) { + attributes.put(key, value); + } + } + + private static String text(JsonObject object, String key) { + if (!object.has(key) || object.get(key).isJsonNull()) { + return null; + } + JsonElement element = object.get(key); + if (element.isJsonPrimitive()) { + String value = element.getAsString(); + return value == null || value.isBlank() ? null : value; + } + if (element.isJsonArray()) { + List parts = new ArrayList<>(); + for (JsonElement item : element.getAsJsonArray()) { + if (item == null || item.isJsonNull()) { + continue; + } + if (item.isJsonPrimitive()) { + String value = item.getAsString(); + if (value != null && !value.isBlank()) { + parts.add(value); + } + } else { + parts.add(item.toString()); + } + } + return parts.isEmpty() ? null : String.join(", ", parts); + } + return element.toString(); + } + + private static BigDecimal decimal(JsonObject object, String key) { + if (!object.has(key) || object.get(key).isJsonNull()) { + return null; + } + try { + JsonElement element = object.get(key); + if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber()) { + return element.getAsBigDecimal(); + } + String text = element.getAsString(); + if (text == null || text.isBlank()) { + return null; + } + return new BigDecimal(text); + } catch (RuntimeException e) { + return null; + } + } + + public record ParseResult(List roots, List warnings, boolean structured) { + public static ParseResult failed(List warnings, PlanDiagnostic error) { + List all = new ArrayList<>(warnings); + all.add(error); + return new ParseResult(List.of(), all, false); + } + } + + private static final class LimitReachedException extends RuntimeException { + private LimitReachedException(String message) { + super(message); + } + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlResourceBrowser.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlResourceBrowser.java index fb510962..c1791f3d 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlResourceBrowser.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlResourceBrowser.java @@ -1,87 +1,43 @@ package org.jumpserver.chen.modules.postgresql; -import org.apache.commons.lang3.StringUtils; import org.jumpserver.chen.framework.datasource.ConnectionManager; import org.jumpserver.chen.framework.datasource.base.BaseResourceBrowser; -import org.jumpserver.chen.framework.datasource.entity.resource.*; -import org.jumpserver.chen.framework.datasource.sql.SQL; +import org.jumpserver.chen.framework.datasource.entity.resource.Schema; +import org.jumpserver.chen.framework.datasource.entity.resource.TreeNode; import org.jumpserver.chen.framework.utils.TreeUtils; import org.jumpserver.chen.modules.postgresql.entity.Database; import java.sql.SQLException; import java.util.List; -import java.util.Map; - public class PostgresqlResourceBrowser extends BaseResourceBrowser { public PostgresqlResourceBrowser(ConnectionManager connectionManager) { - super(connectionManager, new PostgresqlSQLHintsHandler(connectionManager)); + super(connectionManager); } @Override public List getDatasourceChildNodes(TreeNode parent) throws SQLException { - return this.getDatabases() - .stream() - .map(database -> database.toResourceNode(parent)) - .toList(); - } - - public List getChildNodes(TreeNode node) throws SQLException { - if (node != null) { - var dbName = TreeUtils.getValue(node.getKey(), "database"); - if (StringUtils.isNotEmpty(dbName)) { - this.getConnectionManager().setDatabaseContext(dbName); - } - } - return super.getChildNodes(node); + return this.getDatabases().stream().map(database -> database.toResourceNode(parent)).toList(); } public List getDatabaseChildNodes(TreeNode node) throws SQLException { - return this.getSchemas() - .stream() - .map(schema -> schema.toResourceNode(node)) + var dbName = TreeUtils.getValue(node.getKey(), "database"); + return this.metadataCatalog().listSchemas(dbName).stream() + .map(schema -> { + var entity = new Schema(); + entity.setName(schema.name()); + return entity.toResourceNode(node); + }) .toList(); } - private static final String SQL_GET_DATABASES = "SELECT DATNAME AS NAME FROM PG_DATABASE WHERE DATISTEMPLATE = FALSE"; - public List getDatabases() throws SQLException { - return this.getDatabases(SQL.of(SQL_GET_DATABASES)); - } - - public List getDatabases(SQL sql) throws SQLException { - return this.getConnectionManager() - .getSqlActuator() - .getObjects(sql.getSql(), Database.class, Map.of("name", 1)); - } - - private static final String SQL_GET_SCHEMAS = "SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA"; - - @Override - public List getSchemas() throws SQLException { - return this.getSchemas(SQL.of(SQL_GET_SCHEMAS)); - } - - private static final String SQL_GET_TABLES = " SELECT TABLE_NAME AS NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '?' AND TABLE_TYPE = 'BASE TABLE'"; - - @Override - public List
getTables(String schema) throws SQLException { - return this.getTables(SQL.of(SQL_GET_TABLES, schema)); - } - - private static final String SQL_GET_VIEWS = "SELECT TABLE_NAME AS NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '?' AND TABLE_TYPE = 'VIEW'"; - - @Override - public List getViews(String schema) throws SQLException { - return this.getViews(SQL.of(SQL_GET_VIEWS, schema)); - } - - private static final String SQL_GET_FIELDS = "SELECT COLUMN_NAME AS NAME, COLUMN_TYPE AS TYPE, COLUMN_KEY AS `KEY`, IS_NULLABLE AS `NULLABLE`, COLUMN_DEFAULT AS `DEFAULT`, EXTRA AS EXTRA, COLUMN_COMMENT AS COMMENT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '?' AND TABLE_NAME = '?'"; - - @Override - public List getFields(String schema, String table) throws SQLException { - return this.getFields(SQL.of(SQL_GET_FIELDS, schema, table)); + return this.metadataCatalog().listCatalogs().stream() + .map(catalog -> { + var database = new Database(); + database.setName(catalog.name()); + return database; + }) + .toList(); } - - } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlSQLHintsHandler.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlSQLHintsHandler.java deleted file mode 100644 index c0b970dc..00000000 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlSQLHintsHandler.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.jumpserver.chen.modules.postgresql; - -import org.apache.commons.lang3.StringUtils; -import org.jumpserver.chen.framework.datasource.ConnectionManager; -import org.jumpserver.chen.framework.datasource.base.BaseSQLHintsHandler; -import org.jumpserver.chen.framework.datasource.entity.resource.Field; -import org.jumpserver.chen.framework.datasource.entity.resource.Table; -import org.jumpserver.chen.framework.datasource.sql.SQL; -import org.jumpserver.chen.framework.utils.SqlIdentifierUtils; -import org.jumpserver.chen.framework.utils.TreeUtils; - -import java.sql.SQLException; -import java.util.List; -import java.util.Map; - -public class PostgresqlSQLHintsHandler extends BaseSQLHintsHandler { - - private final ConnectionManager connectionManager; - - - public PostgresqlSQLHintsHandler(ConnectionManager connectionManager) { - this.connectionManager = connectionManager; - } - - - private static final String SQL_GET_ALL_TABLES = "SELECT TABLE_NAME AS NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '?'"; - - public List
getALlTables(String schema) throws SQLException { - return this.connectionManager.getSqlActuator() - .getObjects(SQL.of(SQL_GET_ALL_TABLES, schema) - .getSql(), Table.class, Map.of("name", 1, "schema", 2)); - } - - private static final String SQL_GET_ALL_FIELDS = "SELECT COLUMN_NAME,TABLE_SCHEMA,TABLE_NAME AS NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA= '?'"; - - - public List getAllFields(String schema) throws SQLException { - return this.connectionManager - .getSqlActuator() - .getObjects(SQL.of(SQL_GET_ALL_FIELDS, schema).getSql(), Field.class, Map.of("name", 1, "schema", 2, "table", 3)); - - } - - - @Override - public Map> getHints(String nodeKey, String context) throws SQLException { - - var db = TreeUtils.getValue(nodeKey, "database"); - if (StringUtils.isNotEmpty(db)) { - // nodeKey is client-controlled; reject URL metacharacters before it - // reaches the JDBC URL via setDatabaseContext. - SqlIdentifierUtils.validateDatabaseName(db); - this.connectionManager.setDatabaseContext(db); - } - - return super.getHints(nodeKey, context); - } - -} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerActionHandler.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerActionHandler.java index 8b5f0a71..5892025d 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerActionHandler.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerActionHandler.java @@ -45,12 +45,6 @@ public List getDatabaseActions(TreeNode node) { ); } - private static final String SQL_SELECT_DATABASE_DETAIL = "SELECT name,collation_name FROM sys.databases WHERE name = '?'"; - public EventEmitter onDatabaseProperties(TreeNode node) throws SQLException { - return this.onShowObjectProperties("database", SQL_SELECT_DATABASE_DETAIL, node); - } - - @Override public EventEmitter handleForm(FormData formData) throws SQLException { return null; diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerActuator.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerActuator.java index 65c58af1..d003148e 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerActuator.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerActuator.java @@ -40,7 +40,9 @@ public void changeSchema(String schema) throws SQLException { @Override public SQLExecutePlan createPlan(String schema, String table, SQLQueryParams sqlQueryParams) throws SQLException { - var sql = SQL.of("select * from ?.[?]", schema, table); + var schemaIdentifier = SQLIdentifier.quote(this.getDbType(), schema); + var tableIdentifier = SQLIdentifier.quote(this.getDbType(), table); + var sql = SQL.of("select * from " + schemaIdentifier + "." + tableIdentifier); return this.createPlan(sql, sqlQueryParams); } } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerDatasource.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerDatasource.java index 50a7c85d..9c84eabe 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerDatasource.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerDatasource.java @@ -4,8 +4,11 @@ import org.jumpserver.chen.framework.datasource.DatasourceFactory; import org.jumpserver.chen.framework.datasource.base.BaseDatasource; import org.jumpserver.chen.framework.datasource.entity.DBConnectInfo; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; +import org.jumpserver.chen.framework.datasource.plan.ExecutionPlanDialect; public class SQLServerDatasource extends BaseDatasource { + private final ExecutionPlanDialect executionPlanDialect = new SqlServerExecutionPlanDialect(); static { DatasourceFactory.Register(SQLServerDatasource.class); @@ -13,6 +16,7 @@ public class SQLServerDatasource extends BaseDatasource { public SQLServerDatasource(DBConnectInfo dbConnectInfo) { this.connectionManager = new SQLServerConnectionManager(dbConnectInfo,this); + this.metadataCatalog = new MetadataCatalog(this.connectionManager, new SQLServerMetadataProvider(this.connectionManager)); this.resourceBrowser = new SQLServerResourceBrowser(this.connectionManager); this.actionHandler = new SQLServerActionHandler(); } @@ -28,5 +32,9 @@ public DbType getDruidDbType() { return DbType.sqlserver; } + @Override + public ExecutionPlanDialect getExecutionPlanDialect() { + return this.executionPlanDialect; + } } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerMetadataProvider.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerMetadataProvider.java new file mode 100644 index 00000000..48759129 --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerMetadataProvider.java @@ -0,0 +1,332 @@ +package org.jumpserver.chen.modules.sqlserver; + +import org.jumpserver.chen.framework.datasource.ConnectionManager; +import org.jumpserver.chen.framework.datasource.metadata.BaseDatabaseMetadataProvider; +import org.jumpserver.chen.framework.datasource.metadata.CatalogMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ColumnMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ConstraintMetadata; +import org.jumpserver.chen.framework.datasource.metadata.IndexMetadata; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCapabilities; +import org.jumpserver.chen.framework.datasource.metadata.ScopeKind; +import org.jumpserver.chen.framework.datasource.metadata.ScopeProperties; +import org.jumpserver.chen.framework.datasource.metadata.ScopeRef; +import org.jumpserver.chen.framework.datasource.metadata.ForeignKeyMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ObjectRef; +import org.jumpserver.chen.framework.datasource.metadata.PrimaryKeyMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ObjectStatistics; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; +import org.jumpserver.chen.framework.datasource.metadata.RelationMetadata; +import org.jumpserver.chen.framework.datasource.metadata.RelationScope; +import org.jumpserver.chen.framework.datasource.metadata.SchemaMetadata; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class SQLServerMetadataProvider extends BaseDatabaseMetadataProvider { + + public SQLServerMetadataProvider(ConnectionManager connectionManager) { + super(connectionManager); + } + + private static final MetadataCapabilities CAPABILITIES = new MetadataCapabilities( + true, true, true, true, true, true, true, true, false, + true, true, false, false, false, true, true, true, false + ); + + private static final String SQL_CATALOGS = "SELECT name AS name FROM sys.databases"; + + private static final String SQL_SCHEMAS = "SELECT schema_name AS name FROM INFORMATION_SCHEMA.SCHEMATA"; + + private static final String SQL_TABLES = """ + SELECT t.name AS name, + NULL AS engine, + NULL AS character_set, + NULL AS collation, + CONVERT(nvarchar(max), ep.value) AS comment + FROM sys.tables t + JOIN sys.schemas s ON s.schema_id = t.schema_id + LEFT JOIN sys.extended_properties ep + ON ep.major_id = t.object_id AND ep.minor_id = 0 AND ep.name = 'MS_Description' + WHERE s.name = ? + ORDER BY t.name + """; + + private static final String SQL_TABLE_STATS = """ + WITH row_counts AS ( + SELECT object_id, SUM(rows) AS estimated_rows + FROM sys.partitions + WHERE index_id IN (0, 1) + GROUP BY object_id + ), allocated_sizes AS ( + SELECT p.object_id, SUM(CONVERT(bigint, a.total_pages)) * 8192 AS total_size_bytes + FROM sys.partitions p + JOIN sys.allocation_units a ON a.container_id IN (p.hobt_id, p.partition_id) + GROUP BY p.object_id + ) + SELECT t.name AS name, + r.estimated_rows AS estimated_rows, + z.total_size_bytes AS total_size_bytes + FROM sys.tables t + JOIN sys.schemas s ON s.schema_id = t.schema_id + LEFT JOIN row_counts r ON r.object_id = t.object_id + LEFT JOIN allocated_sizes z ON z.object_id = t.object_id + WHERE s.name = ? + ORDER BY t.name + """; + + private static final String SQL_VIEWS = """ + SELECT v.name AS name, + 'VIEW' AS type, + CONVERT(nvarchar(max), ep.value) AS comment + FROM sys.views v + JOIN sys.schemas s ON s.schema_id = v.schema_id + LEFT JOIN sys.extended_properties ep + ON ep.major_id = v.object_id AND ep.minor_id = 0 AND ep.name = 'MS_Description' + WHERE s.name = ? + ORDER BY v.name + """; + + private static final String SQL_INDEXES = """ + SELECT i.name AS name, + t.name AS table_name, + c.name AS column_name, + i.is_unique AS is_unique, + i.type_desc AS method, + NULL AS definition, + ic.is_included_column AS included, + CASE WHEN ic.is_included_column = 1 THEN NULL + WHEN ic.is_descending_key = 1 THEN 'DESC' ELSE 'ASC' END AS sort_order + FROM sys.indexes i + JOIN sys.tables t ON t.object_id = i.object_id + JOIN sys.schemas s ON s.schema_id = t.schema_id + LEFT JOIN sys.index_columns ic + ON ic.object_id = i.object_id AND ic.index_id = i.index_id + LEFT JOIN sys.columns c + ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE s.name = ? AND i.name IS NOT NULL AND i.is_hypothetical = 0 + ORDER BY t.name, i.name, ic.is_included_column, ic.key_ordinal, ic.index_column_id + """; + + private static final String SQL_COLUMNS = """ + SELECT c.name AS name, o.name AS table_name, c.column_id AS ordinal, + ty.name AS native_type, + CASE WHEN ty.name IN ('timestamp', 'rowversion') THEN 'binary' ELSE ty.name END AS jdbc_type_name, + CASE + WHEN c.max_length = -1 THEN 2147483647 + WHEN ty.name IN ('nchar', 'nvarchar') AND c.max_length > 0 THEN c.max_length / 2 + WHEN ty.name IN ('char', 'varchar', 'binary', 'varbinary') THEN c.max_length + WHEN ty.name IN ('decimal', 'numeric') THEN c.precision + ELSE NULL + END AS size, + CASE WHEN ty.name IN ('decimal', 'numeric', 'time', 'datetime2', 'datetimeoffset') + THEN c.scale ELSE NULL END AS scale, + c.is_nullable AS nullable, dc.definition AS default_value, + CONVERT(nvarchar(max), ep.value) AS comment + FROM sys.columns c + JOIN sys.objects o ON o.object_id = c.object_id AND o.type IN ('U', 'V') + JOIN sys.schemas s ON s.schema_id = o.schema_id + JOIN sys.types ty ON ty.user_type_id = c.user_type_id + LEFT JOIN sys.default_constraints dc ON dc.object_id = c.default_object_id + LEFT JOIN sys.extended_properties ep + ON ep.major_id = c.object_id AND ep.minor_id = c.column_id AND ep.name = 'MS_Description' + WHERE s.name = ? AND o.name IN (__IN__) + ORDER BY o.name, c.column_id + """; + + private static final String SQL_TABLE_INDEXES = """ + SELECT i.name AS name, t.name AS table_name, c.name AS column_name, + i.is_unique AS is_unique, i.type_desc AS method, NULL AS definition, + ic.is_included_column AS included, + CASE WHEN ic.is_included_column = 1 THEN NULL + WHEN ic.is_descending_key = 1 THEN 'DESC' ELSE 'ASC' END AS sort_order + FROM sys.indexes i + JOIN sys.tables t ON t.object_id = i.object_id + JOIN sys.schemas s ON s.schema_id = t.schema_id + LEFT JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id + LEFT JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE s.name = ? AND t.name IN (__IN__) + AND i.name IS NOT NULL AND i.is_hypothetical = 0 + ORDER BY t.name, i.name, ic.is_included_column, ic.key_ordinal, ic.index_column_id + """; + + private static final String SQL_DATABASE_PROPERTIES = """ + SELECT name, collation_name, state_desc, recovery_model_desc, compatibility_level, create_date + FROM sys.databases + WHERE name = ? + """; + + @Override + public MetadataCapabilities capabilities() { + return CAPABILITIES; + } + + @Override + public List listCatalogs() throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_CATALOGS, List.of())) { + result.add(new CatalogMetadata(stringValue(row, "name"))); + } + return result; + } + + @Override + public List listSchemas(String catalog) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_SCHEMAS, List.of())) { + result.add(new SchemaMetadata(catalog, stringValue(row, "name"))); + } + return result; + } + + @Override + public List listRelations(RelationScope scope, Set kinds) throws SQLException { + var result = new ArrayList(); + if (kinds.contains(RelationKind.TABLE)) { + for (var row : query(SQL_TABLES, List.of(scope.schema()))) { + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + stringValue(row, "comment"), + null, + null, + null + )); + } + } + if (kinds.contains(RelationKind.VIEW)) { + for (var row : query(SQL_VIEWS, List.of(scope.schema()))) { + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.VIEW), + stringValue(row, "comment"), + null, + null, + null + )); + } + } + return result; + } + + @Override + public List listColumns(List relations) throws SQLException { + return loadColumns(SQL_COLUMNS, relations); + } + + @Override + public List listIndexes(RelationScope scope) throws SQLException { + return groupIndexRows(query(SQL_INDEXES, List.of(scope.schema())), scope); + } + + @Override + public List listIndexes(List relations) throws SQLException { + if (relations.isEmpty()) { + return List.of(); + } + var first = relations.get(0); + return groupIndexRows(queryKeys(SQL_TABLE_INDEXES, relations, first.schema()), + new RelationScope(first.catalog(), first.schema())); + } + + @Override + public List listStatistics(RelationScope scope) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_TABLE_STATS, List.of(scope.schema()))) { + result.add(new ObjectStatistics( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + longValue(row, "estimated_rows"), + longValue(row, "total_size_bytes") + )); + } + return result; + } + + @Override + public ScopeProperties scopeProperties(ScopeRef ref) throws SQLException { + if (ref.kind() != ScopeKind.CATALOG) { + return super.scopeProperties(ref); + } + return loadScopeProperties(ref, SQL_DATABASE_PROPERTIES, List.of(ref.scope().catalog())); + } + + private static final String SQL_PRIMARY_KEYS = """ + SELECT t.name AS table_name, c.name AS column_name, kc.name AS name + FROM sys.key_constraints kc + JOIN sys.tables t ON t.object_id = kc.parent_object_id + JOIN sys.schemas s ON s.schema_id = t.schema_id + JOIN sys.index_columns ic ON ic.object_id = t.object_id AND ic.index_id = kc.unique_index_id + JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE kc.type = 'PK' AND s.name = ? AND t.name IN (__IN__) + ORDER BY t.name, kc.name, ic.key_ordinal + """; + + private static final String SQL_FOREIGN_KEYS = """ + SELECT t.name AS table_name, c.name AS column_name, fk.name AS name, + rs.name AS referenced_schema, rt.name AS referenced_table, rc.name AS referenced_column + FROM sys.foreign_keys fk + JOIN sys.tables t ON t.object_id = fk.parent_object_id + JOIN sys.schemas s ON s.schema_id = t.schema_id + JOIN sys.foreign_key_columns fkc ON fkc.constraint_object_id = fk.object_id + JOIN sys.columns c ON c.object_id = fkc.parent_object_id AND c.column_id = fkc.parent_column_id + JOIN sys.tables rt ON rt.object_id = fk.referenced_object_id + JOIN sys.schemas rs ON rs.schema_id = rt.schema_id + JOIN sys.columns rc ON rc.object_id = fkc.referenced_object_id AND rc.column_id = fkc.referenced_column_id + WHERE s.name = ? AND t.name IN (__IN__) + ORDER BY t.name, fk.name, fkc.constraint_column_id + """; + + private static final String SQL_CONSTRAINTS = """ + WITH target_tables AS ( + SELECT t.object_id, t.name AS table_name + FROM sys.tables t JOIN sys.schemas s ON s.schema_id = t.schema_id + WHERE s.name = ? AND t.name IN (__IN__) + ) + SELECT t.table_name, kc.name, CASE kc.type WHEN 'PK' THEN 'PRIMARY KEY' ELSE 'UNIQUE' END AS constraint_type, + c.name AS column_name, NULL AS referenced_schema, NULL AS referenced_table, + NULL AS referenced_column, NULL AS definition, ic.key_ordinal AS ordinal + FROM target_tables t + JOIN sys.key_constraints kc ON kc.parent_object_id = t.object_id + JOIN sys.index_columns ic ON ic.object_id = t.object_id AND ic.index_id = kc.unique_index_id + JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id + UNION ALL + SELECT t.table_name, fk.name, 'FOREIGN KEY', c.name, rs.name, rt.name, rc.name, NULL, + fkc.constraint_column_id + FROM target_tables t + JOIN sys.foreign_keys fk ON fk.parent_object_id = t.object_id + JOIN sys.foreign_key_columns fkc ON fkc.constraint_object_id = fk.object_id + JOIN sys.columns c ON c.object_id = fkc.parent_object_id AND c.column_id = fkc.parent_column_id + JOIN sys.tables rt ON rt.object_id = fk.referenced_object_id + JOIN sys.schemas rs ON rs.schema_id = rt.schema_id + JOIN sys.columns rc ON rc.object_id = fkc.referenced_object_id AND rc.column_id = fkc.referenced_column_id + UNION ALL + SELECT t.table_name, cc.name, 'CHECK', c.name, NULL, NULL, NULL, cc.definition, 0 + FROM target_tables t + JOIN sys.check_constraints cc ON cc.parent_object_id = t.object_id + LEFT JOIN sys.columns c ON c.object_id = cc.parent_object_id AND c.column_id = cc.parent_column_id + ORDER BY table_name, name, ordinal + """; + + @Override + public List listPrimaryKeys(List relations) throws SQLException { + var first = relations.get(0); + return groupPrimaryKeys(queryKeys(SQL_PRIMARY_KEYS, relations, first.schema()), + new RelationScope(first.catalog(), first.schema())); + } + + @Override + public List listForeignKeys(List relations) throws SQLException { + var first = relations.get(0); + return groupForeignKeys(queryKeys(SQL_FOREIGN_KEYS, relations, first.schema()), + new RelationScope(first.catalog(), first.schema())); + } + + @Override + public List listConstraints(List relations) throws SQLException { + if (relations.isEmpty()) { + return List.of(); + } + var first = relations.get(0); + return groupConstraints(queryKeys(SQL_CONSTRAINTS, relations, first.schema()), + new RelationScope(first.catalog(), first.schema())); + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerResourceBrowser.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerResourceBrowser.java index e4888bcc..986fb277 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerResourceBrowser.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerResourceBrowser.java @@ -1,89 +1,43 @@ package org.jumpserver.chen.modules.sqlserver; -import org.apache.commons.lang3.StringUtils; import org.jumpserver.chen.framework.datasource.ConnectionManager; import org.jumpserver.chen.framework.datasource.base.BaseResourceBrowser; -import org.jumpserver.chen.framework.datasource.entity.resource.*; -import org.jumpserver.chen.framework.datasource.sql.SQL; +import org.jumpserver.chen.framework.datasource.entity.resource.Schema; +import org.jumpserver.chen.framework.datasource.entity.resource.TreeNode; import org.jumpserver.chen.framework.utils.TreeUtils; import org.jumpserver.chen.modules.postgresql.entity.Database; import java.sql.SQLException; import java.util.List; -import java.util.Map; - public class SQLServerResourceBrowser extends BaseResourceBrowser { public SQLServerResourceBrowser(ConnectionManager connectionManager) { - - super(connectionManager, new SQLServerSQLHintsHandler(connectionManager)); + super(connectionManager); } @Override public List getDatasourceChildNodes(TreeNode parent) throws SQLException { - return this.getDatabases() - .stream() - .map(database -> database.toResourceNode(parent)) - .toList(); - } - - public List getChildNodes(TreeNode node) throws SQLException { - if (node != null) { - var dbName = TreeUtils.getValue(node.getKey(), "database"); - if (StringUtils.isNotEmpty(dbName)) { - this.getConnectionManager().setDatabaseContext(dbName); - } - } - return super.getChildNodes(node); + return this.getDatabases().stream().map(database -> database.toResourceNode(parent)).toList(); } public List getDatabaseChildNodes(TreeNode node) throws SQLException { - return this.getSchemas() - .stream() - .map(schema -> schema.toResourceNode(node)) + var dbName = TreeUtils.getValue(node.getKey(), "database"); + return this.metadataCatalog().listSchemas(dbName).stream() + .map(schema -> { + var entity = new Schema(); + entity.setName(schema.name()); + return entity.toResourceNode(node); + }) .toList(); } - private static final String SQL_GET_DATABASES = "SELECT name FROM sys.databases;"; - public List getDatabases() throws SQLException { - return this.getDatabases(SQL.of(SQL_GET_DATABASES)); - } - - public List getDatabases(SQL sql) throws SQLException { - return this.getConnectionManager() - .getSqlActuator() - .getObjects(sql.getSql(), Database.class, Map.of("name", 1)); - } - - // 查询当前数据库下所有的schema - private static final String SQL_GET_SCHEMAS = "SELECT schema_name FROM INFORMATION_SCHEMA.SCHEMATA"; - - @Override - public List getSchemas() throws SQLException { - return this.getSchemas(SQL.of(SQL_GET_SCHEMAS)); - } - - private static final String SQL_GET_TABLES = " SELECT table_name AS name FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = '?'"; - - @Override - public List
getTables(String schema) throws SQLException { - return this.getTables(SQL.of(SQL_GET_TABLES, schema)); - } - - private static final String SQL_GET_VIEWS = "SELECT table_name AS name FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = '?' AND table_type = 'VIEW'"; - - @Override - public List getViews(String schema) throws SQLException { - return this.getViews(SQL.of(SQL_GET_VIEWS, schema)); - } - - private static final String SQL_GET_FIELDS = "SELECT column_name AS name, column_type AS type, column_key AS `key`, is_nullable AS `nullable`, column_default AS `default`, extra AS extra, column_comment AS comment FROM information_schema.columns WHERE table_schema = '?' AND table_name = '?'"; - - @Override - public List getFields(String schema, String table) throws SQLException { - return this.getFields(SQL.of(SQL_GET_FIELDS, schema, table)); + return this.metadataCatalog().listCatalogs().stream() + .map(catalog -> { + var database = new Database(); + database.setName(catalog.name()); + return database; + }) + .toList(); } - - } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerSQLHintsHandler.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerSQLHintsHandler.java deleted file mode 100644 index dcdc2616..00000000 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerSQLHintsHandler.java +++ /dev/null @@ -1,83 +0,0 @@ -package org.jumpserver.chen.modules.sqlserver; - -import org.jumpserver.chen.framework.datasource.ConnectionManager; -import org.jumpserver.chen.framework.datasource.base.BaseSQLHintsHandler; -import org.jumpserver.chen.framework.datasource.entity.resource.Field; -import org.jumpserver.chen.framework.datasource.entity.resource.Schema; -import org.jumpserver.chen.framework.datasource.entity.resource.Table; -import org.jumpserver.chen.framework.datasource.sql.SQL; - -import java.sql.SQLException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class SQLServerSQLHintsHandler extends BaseSQLHintsHandler { - - private final ConnectionManager connectionManager; - - - public SQLServerSQLHintsHandler(ConnectionManager connectionManager) { - this.connectionManager = connectionManager; - } - - - public List
getALlTables(String schema) throws SQLException { - return null; - } - - public List getAllFields(String schema) throws SQLException { - return null; - } - - private static final String GET_ALL_SCHEMAS = "SELECT schema_name FROM INFORMATION_SCHEMA.SCHEMATA"; - - public List getAllSchemas() throws SQLException { - return this.connectionManager.getSqlActuator() - .getObjects(SQL.of(GET_ALL_SCHEMAS).getSql(), - Schema.class, Map.of("name", 1)); - } - - private static final String GET_ALL_TABLES = "SELECT table_name AS name,table_schema FROM INFORMATION_SCHEMA.TABLES"; - - public List
getAllTables() throws SQLException { - return this.connectionManager.getSqlActuator() - .getObjects(SQL.of(GET_ALL_TABLES).getSql(), - Table.class, Map.of("name", 1, "schema", 2)); - } - - private static final String GET_ALL_FIELDS = "SELECT column_name AS name, table_schema, table_name FROM INFORMATION_SCHEMA.COLUMNS"; - - public List getAllFields() throws SQLException { - return this.connectionManager.getSqlActuator() - .getObjects(SQL.of(GET_ALL_FIELDS).getSql(), - Field.class, Map.of("name", 1, "schema", 2, "table", 3)); - } - - @Override - public Map> getHints(String nodeKey, String context) throws SQLException { - Map> suggestions = new HashMap<>(); - - var schemas = this.getAllSchemas(); - var schemaNames = schemas.stream().map(Schema::getName).toList(); - - var tables = this.getAllTables(); - var fields = this.getAllFields(); - - schemaNames.forEach(schemaName -> { - var tableNames = tables.stream().filter(table -> table.getSchema().equals(schemaName)).map(Table::getName).toList(); - suggestions.put(schemaName, tableNames); - }); - - var tableNames = tables.stream().map(Table::getName).toList(); - - tableNames.forEach(tableName -> { - var fieldNames = fields.stream().filter(field -> field.getTable().equals(tableName)).map(Field::getName).toList(); - suggestions.put(tableName, fieldNames); - }); - - return suggestions; - } - - -} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SqlServerExecutionPlanDialect.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SqlServerExecutionPlanDialect.java new file mode 100644 index 00000000..0bd94704 --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SqlServerExecutionPlanDialect.java @@ -0,0 +1,649 @@ +package org.jumpserver.chen.modules.sqlserver; + +import org.jumpserver.chen.framework.datasource.analysis.SqlStatementAnalysis; +import org.jumpserver.chen.framework.datasource.plan.BaseExecutionPlanDialect; +import org.jumpserver.chen.framework.datasource.plan.ConnectionInvalidatedException; +import org.jumpserver.chen.framework.datasource.plan.DialectPlanResult; +import org.jumpserver.chen.framework.datasource.plan.ExecutionPlanCapabilities; +import org.jumpserver.chen.framework.datasource.plan.PlanCodes; +import org.jumpserver.chen.framework.datasource.plan.PlanDatabase; +import org.jumpserver.chen.framework.datasource.plan.PlanDiagnostic; +import org.jumpserver.chen.framework.datasource.plan.PlanEffects; +import org.jumpserver.chen.framework.datasource.plan.PlanI18n; +import org.jumpserver.chen.framework.datasource.plan.PlanExecutionContext; +import org.jumpserver.chen.framework.datasource.plan.PlanPrerequisite; +import org.jumpserver.chen.framework.datasource.plan.PlanRawFormat; +import org.jumpserver.chen.framework.datasource.plan.PlanStatus; +import org.jumpserver.chen.framework.datasource.plan.PlanTransactionState; + +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.regex.Pattern; + +public class SqlServerExecutionPlanDialect extends BaseExecutionPlanDialect { + static final String SHOWPLAN_ON = "SET SHOWPLAN_XML ON"; + static final String SHOWPLAN_OFF = "SET SHOWPLAN_XML OFF"; + static final String STATE_PROBE = + "SELECT CAST(431205 AS int) AS chen_showplan_state_probe, XACT_STATE() AS chen_xact_state"; + static final String PERMISSION_PROBE = + "SELECT HAS_PERMS_BY_NAME(DB_NAME(), 'DATABASE', 'SHOWPLAN') AS chen_has_showplan"; + private static final int MAX_PROBE_BYTES = 128 * 1024; + private static final Pattern EXTERNAL_ROWSET = Pattern.compile( + "(?i)\\b(?:OPENROWSET|OPENQUERY|OPENDATASOURCE)\\s*\\(" + ); + private static final Set SAFE_SCHEMAS = Set.of("sys"); + private static final Set SAFE_FUNCTIONS = Set.of( + "abs", "acos", "asin", "atan", "atn2", "avg", "ceiling", "count", "degrees", + "exp", "floor", "log", "log10", "max", "min", "pi", "power", "radians", "rand", + "round", "sign", "sin", "sqrt", "square", "sum", "tan", "checksum", "binary_checksum", + "cast", "convert", "try_cast", "try_convert", "parse", "try_parse", "coalesce", "isnull", + "nullif", "iif", "choose", "case", "current_timestamp", "getdate", "getutcdate", + "sysdatetime", "sysutcdatetime", "sysdatetimeoffset", "dateadd", "datediff", "datediff_big", + "datefromparts", "datetimefromparts", "datetime2fromparts", "datetimeoffsetfromparts", + "datename", "datepart", "day", "eomonth", "isdate", "month", "smalldatetimefromparts", + "timefromparts", "year", "char", "concat", "concat_ws", "difference", "format", "left", + "len", "lower", "ltrim", "nchar", "patindex", "quotename", "replace", "replicate", + "reverse", "right", "rtrim", "soundex", "space", "str", "string_agg", "string_escape", + "string_split", "stuff", "substring", "translate", "trim", "unicode", "upper", + "json_query", "json_value", "isjson", "json_modify", "row_number", "rank", "dense_rank", + "ntile", "lag", "lead", "first_value", "last_value", "percent_rank", "percentile_cont", + "percentile_disc", "cume_dist", "newid", "newsequentialid", "host_name", "db_name", + "schema_name", "object_name", "type_name", "user_name", "suser_sname", "original_login", + "has_perms_by_name", "xact_state", "grouping", "grouping_id" + ); + + private final ExecutionPlanCapabilities capabilities = ExecutionPlanCapabilities.sqlserver(); + + @Override + public PlanDatabase database() { + return PlanDatabase.sqlserver; + } + + @Override + public ExecutionPlanCapabilities capabilities() { + return capabilities; + } + + @Override + public DialectPlanResult explainEstimated(PlanExecutionContext context, SqlStatementAnalysis statement) + throws SQLException { + List prerequisites = new ArrayList<>(); + PlanDiagnostic unsafe = unsafeStatement(statement); + if (unsafe != null) { + prerequisites.add(PlanPrerequisite.unmet( + PlanCodes.UNSAFE_TO_ESTIMATE, + unsafe.message(), + PlanI18n.msg("Plan.RemoveUdfOrExternalRowset", "Remove user-defined functions or external rowset access and retry") + )); + return unmet(context, prerequisites, unsafe, PlanEffects.unchangedReuse()); + } + + addVersionAndDriverDiagnostics(context, prerequisites); + PlanTransactionState transactionState = validateTransaction(context, prerequisites); + if (transactionState == null) { + PlanPrerequisite failure = prerequisites.get(prerequisites.size() - 1); + return unmet( + context, + prerequisites, + PlanDiagnostic.of(failure.code(), failure.message()), + PlanEffects.unchangedReuse() + ); + } + + Probe initial; + try { + initial = probeShowplan(context, true); + } catch (SQLException e) { + if (isConnectionBroken(e)) { + throw invalidated(context, e, null, null, prerequisites); + } + if (isCancelled(context, e)) { + throw e; + } + String message = "Unable to determine the initial SHOWPLAN_XML state: " + safeMessage(e); + prerequisites.add(PlanPrerequisite.unknown( + PlanCodes.SESSION_STATE_UNSAFE, + message, + "Reconnect and retry from a known SHOWPLAN_XML OFF session" + )); + return unmet(context, prerequisites, sqlDiagnostic(PlanCodes.SESSION_STATE_UNSAFE, message, e), + PlanEffects.unchangedReuse()); + } + + if (initial.mode() == ShowplanMode.ON) { + String message = "SHOWPLAN_XML is already enabled; Chen will not alter the existing session state"; + prerequisites.add(PlanPrerequisite.unmet( + PlanCodes.SESSION_STATE_UNSAFE, + message, + "Turn SHOWPLAN_XML OFF outside Chen or reconnect, then retry" + )); + return unmet(context, prerequisites, PlanDiagnostic.of(PlanCodes.SESSION_STATE_UNSAFE, message), + PlanEffects.unchangedReuse()); + } + if (initial.mode() != ShowplanMode.OFF || initial.xactState() == null) { + String message = "The initial SHOWPLAN_XML or SQL Server transaction state could not be confirmed"; + prerequisites.add(PlanPrerequisite.unknown( + PlanCodes.SESSION_STATE_UNSAFE, + message, + "Reconnect and retry from a known SHOWPLAN_XML OFF session" + )); + return unmet(context, prerequisites, PlanDiagnostic.of(PlanCodes.SESSION_STATE_UNSAFE, message), + PlanEffects.unchangedReuse()); + } + if (!transactionMatches(transactionState, initial.xactState())) { + SQLException inconsistency = new SQLException( + "SQL Server transaction probes disagree before SHOWPLAN_XML was changed", "HY000" + ); + throw invalidated(context, inconsistency, null, null, prerequisites); + } + prerequisites.add(PlanPrerequisite.met("showplan-state", "Initial SHOWPLAN_XML state is OFF")); + prerequisites.add(PlanPrerequisite.met("transaction", transactionDescription(transactionState))); + + Permission permission; + try { + permission = probePermission(context); + } catch (SQLException e) { + if (isConnectionBroken(e)) { + throw invalidated(context, e, null, null, prerequisites); + } + if (isCancelled(context, e)) { + throw e; + } + String message = "Unable to verify SHOWPLAN permission in the current database: " + safeMessage(e); + prerequisites.add(PlanPrerequisite.unknown( + PlanCodes.PLAN_PERMISSION_DENIED, + message, + "Grant SHOWPLAN in every referenced database and reconnect" + )); + return unmet(context, prerequisites, sqlDiagnostic(PlanCodes.PLAN_PERMISSION_DENIED, message, e), + PlanEffects.unchangedReuse()); + } + if (permission != Permission.GRANTED) { + String message = permission == Permission.DENIED + ? "SHOWPLAN permission is missing in the current database" + : "SHOWPLAN permission in the current database could not be confirmed"; + PlanPrerequisite prerequisite = permission == Permission.DENIED + ? PlanPrerequisite.unmet( + PlanCodes.PLAN_PERMISSION_DENIED, + message, + "Grant SHOWPLAN in every database referenced by the query" + ) + : PlanPrerequisite.unknown( + PlanCodes.PLAN_PERMISSION_DENIED, + message, + "Grant SHOWPLAN in every database referenced by the query and reconnect" + ); + prerequisites.add(prerequisite); + return unmet(context, prerequisites, PlanDiagnostic.of(PlanCodes.PLAN_PERMISSION_DENIED, message), + PlanEffects.unchangedReuse()); + } + prerequisites.add(PlanPrerequisite.met( + "showplan-permission", + "Current database SHOWPLAN permission is available; referenced databases are verified during compilation" + )); + prerequisites.add(PlanPrerequisite.met("statement", PlanI18n.msg("Plan.SingleSelect", "Single SELECT"))); + + return executeShowplan(context, statement, transactionState, prerequisites); + } + + private DialectPlanResult executeShowplan( + PlanExecutionContext context, + SqlStatementAnalysis statement, + PlanTransactionState initialTransaction, + List prerequisites + ) throws SQLException { + boolean onAttempted = false; + boolean onConfirmed = false; + String failureStage = "SET SHOWPLAN_XML ON failed: "; + SQLException originalError = null; + SqlServerPlanJdbc.PlanCapture capture = null; + + try { + try (SqlServerPlanJdbc.TrackedStatement on = SqlServerPlanJdbc.requestStatement(context)) { + onAttempted = true; + SqlServerPlanJdbc.executeAndDrain(on.statement(), SHOWPLAN_ON); + onConfirmed = true; + } + failureStage = "SQL Server plan compilation or result read failed: "; + context.throwIfCancelled(); + try (SqlServerPlanJdbc.TrackedStatement query = SqlServerPlanJdbc.requestStatement(context)) { + capture = SqlServerPlanJdbc.executePlan( + query.statement(), + statement.sql(), + context.maxRawBytes() + ); + } + } catch (SQLException e) { + originalError = e; + } + + SQLException restoreError = null; + Probe restored = null; + if (onAttempted) { + try { + restored = restoreAndVerify(context); + if (restored.mode() != ShowplanMode.OFF || restored.xactState() == null) { + restoreError = new SQLException("SHOWPLAN_XML OFF could not be verified", "HY000"); + } else if (!transactionRestored(initialTransaction, restored.xactState())) { + restoreError = new SQLException( + "SQL Server transaction state changed while restoring SHOWPLAN_XML", "HY000" + ); + } + } catch (SQLException e) { + restoreError = e; + } + } + + if (restoreError != null) { + throw invalidated(context, originalError, restoreError, capture, prerequisites); + } + if (originalError != null && isConnectionBroken(originalError)) { + throw invalidated(context, originalError, null, capture, prerequisites); + } + + PlanEffects.TransactionState transactionEffect = restored != null && restored.xactState() == -1 + ? PlanEffects.TransactionState.FAILED + : PlanEffects.TransactionState.UNCHANGED; + PlanEffects effects = new PlanEffects( + onAttempted ? PlanEffects.SessionState.RESTORED : PlanEffects.SessionState.UNCHANGED, + transactionEffect, + PlanEffects.AuxiliaryStorage.NONE, + PlanEffects.ConnectionDisposition.REUSE + ); + + if (originalError != null) { + return errorResult(context, originalError, capture, prerequisites, effects, failureStage); + } + if (!onConfirmed) { + SQLException error = new SQLException("SHOWPLAN_XML ON was not confirmed", "HY000"); + return errorResult(context, error, capture, prerequisites, effects, "SET SHOWPLAN_XML ON failed: "); + } + if (transactionEffect == PlanEffects.TransactionState.FAILED) { + SQLException error = new SQLException( + "The active SQL Server transaction became uncommittable while compiling the plan", "25000" + ); + return errorResult( + context, + error, + capture, + prerequisites, + effects, + "SQL Server plan compilation changed the transaction state: " + ); + } + return resultFromCapture(context, capture, prerequisites, effects); + } + + private Probe restoreAndVerify(PlanExecutionContext context) throws SQLException { + try (SqlServerPlanJdbc.TrackedStatement off = SqlServerPlanJdbc.cleanupStatement(context)) { + SqlServerPlanJdbc.executeAndDrain(off.statement(), SHOWPLAN_OFF); + } + return probeShowplan(context, false); + } + + private Probe probeShowplan(PlanExecutionContext context, boolean requestDeadline) throws SQLException { + SqlServerPlanJdbc.ProbeCapture capture; + if (requestDeadline) { + try (SqlServerPlanJdbc.TrackedStatement statement = SqlServerPlanJdbc.requestStatement(context)) { + capture = SqlServerPlanJdbc.executeProbe(statement.statement(), STATE_PROBE, MAX_PROBE_BYTES); + } + } else { + try (SqlServerPlanJdbc.TrackedStatement statement = SqlServerPlanJdbc.cleanupStatement(context)) { + capture = SqlServerPlanJdbc.executeProbe(statement.statement(), STATE_PROBE, MAX_PROBE_BYTES); + } + } + if (capture.truncated() || capture.rows().size() != 1 || capture.rows().get(0).isEmpty()) { + return new Probe(ShowplanMode.UNKNOWN, null); + } + List row = capture.rows().get(0); + String first = row.get(0); + if (SqlServerPlanParser.isShowPlanXml(first, MAX_PROBE_BYTES)) { + return new Probe(ShowplanMode.ON, null); + } + if (!"431205".equals(first) || row.size() < 2) { + return new Probe(ShowplanMode.UNKNOWN, null); + } + try { + return new Probe(ShowplanMode.OFF, Integer.parseInt(row.get(1))); + } catch (NumberFormatException e) { + return new Probe(ShowplanMode.OFF, null); + } + } + + private Permission probePermission(PlanExecutionContext context) throws SQLException { + SqlServerPlanJdbc.ProbeCapture capture; + try (SqlServerPlanJdbc.TrackedStatement statement = SqlServerPlanJdbc.requestStatement(context)) { + capture = SqlServerPlanJdbc.executeProbe(statement.statement(), PERMISSION_PROBE, 128); + } + if (capture.truncated() || capture.rows().size() != 1 || capture.rows().get(0).isEmpty()) { + return Permission.UNKNOWN; + } + return switch (capture.rows().get(0).get(0).toLowerCase(Locale.ROOT)) { + case "1", "true" -> Permission.GRANTED; + case "0", "false" -> Permission.DENIED; + default -> Permission.UNKNOWN; + }; + } + + private DialectPlanResult resultFromCapture( + PlanExecutionContext context, + SqlServerPlanJdbc.PlanCapture capture, + List prerequisites, + PlanEffects effects + ) { + List warnings = new ArrayList<>(); + if (capture == null || capture.documents().isEmpty()) { + warnings.add(PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, "SQL Server returned no Showplan XML document")); + return new DialectPlanResult( + context.serverVersion(), PlanStatus.RAW_ONLY, List.of(), capture == null ? null : capture.rawText(), + PlanRawFormat.XML, SqlServerPlanParser.RAW_FORMAT_VERSION, + capture != null && capture.truncated(), capabilities, + prerequisites, effects, null, warnings + ); + } + if (capture.documents().size() > 1) { + warnings.add(PlanDiagnostic.of( + "MULTIPLE_SHOWPLAN_DOCUMENTS", + "SQL Server returned multiple Showplan XML documents; all were normalized in result order" + )); + } + if (capture.truncated()) { + warnings.add(PlanDiagnostic.of(PlanCodes.PLAN_LIMIT_REACHED, "SQL Server Showplan XML exceeded a capture limit")); + return new DialectPlanResult( + context.serverVersion(), PlanStatus.RAW_ONLY, List.of(), capture.rawText(), PlanRawFormat.XML, + SqlServerPlanParser.RAW_FORMAT_VERSION, true, capabilities, prerequisites, effects, null, warnings + ); + } + + SqlServerPlanParser.ParseResult parsed = SqlServerPlanParser.parse( + capture.documents(), + context.maxRawBytes(), + context.maxNodes(), + context.maxDepth() + ); + warnings.addAll(parsed.warnings()); + return new DialectPlanResult( + context.serverVersion(), + parsed.structured() ? PlanStatus.SUCCESS : PlanStatus.RAW_ONLY, + parsed.roots(), + capture.rawText(), + PlanRawFormat.XML, + SqlServerPlanParser.RAW_FORMAT_VERSION, + false, + capabilities, + prerequisites, + effects, + null, + warnings + ); + } + + private DialectPlanResult errorResult( + PlanExecutionContext context, + SQLException error, + SqlServerPlanJdbc.PlanCapture capture, + List prerequisites, + PlanEffects effects, + String stage + ) { + PlanStatus status; + String code; + if (isCancelled(context, error)) { + status = PlanStatus.CANCELLED; + code = PlanCodes.CANCELLED; + } else if (isPermissionDenied(error)) { + status = PlanStatus.PREREQUISITES_UNMET; + code = PlanCodes.PLAN_PERMISSION_DENIED; + prerequisites.add(PlanPrerequisite.unmet( + code, + safeMessage(error), + "Grant query permissions and SHOWPLAN in every database referenced by the SELECT" + )); + } else { + status = PlanStatus.ERROR; + code = PlanCodes.SQL_ERROR; + } + return new DialectPlanResult( + context.serverVersion(), status, List.of(), capture == null ? null : capture.rawText(), + capture == null ? null : PlanRawFormat.XML, + capture == null ? null : SqlServerPlanParser.RAW_FORMAT_VERSION, + capture != null && capture.truncated(), capabilities, prerequisites, effects, + sqlDiagnostic(code, stage + safeMessage(error), error), List.of() + ); + } + + private ConnectionInvalidatedException invalidated( + PlanExecutionContext context, + SQLException original, + SQLException cleanup, + SqlServerPlanJdbc.PlanCapture capture, + List prerequisites + ) { + PlanDiagnostic originalDiagnostic = original == null ? null : sqlDiagnostic( + isCancelled(context, original) ? PlanCodes.CANCELLED : PlanCodes.SQL_ERROR, + safeMessage(original), + original + ); + PlanDiagnostic cleanupDiagnostic = cleanup == null ? null : sqlDiagnostic( + PlanCodes.SESSION_RESTORE_FAILED, + "SHOWPLAN_XML session recovery could not be confirmed: " + safeMessage(cleanup), + cleanup + ); + PlanDiagnostic error = originalDiagnostic != null + ? originalDiagnostic + : (cleanupDiagnostic == null + ? PlanDiagnostic.of(PlanCodes.CONNECTION_INVALIDATED, "SQL Server connection is not reusable") + : cleanupDiagnostic); + List warnings = cleanupDiagnostic == null ? List.of() : List.of(cleanupDiagnostic); + DialectPlanResult partial = new DialectPlanResult( + context.serverVersion(), PlanStatus.CONNECTION_INVALIDATED, List.of(), + capture == null ? null : capture.rawText(), capture == null ? null : PlanRawFormat.XML, + capture == null ? null : SqlServerPlanParser.RAW_FORMAT_VERSION, + capture != null && capture.truncated(), capabilities, prerequisites, + PlanEffects.discard(PlanEffects.SessionState.UNKNOWN, PlanEffects.TransactionState.UNKNOWN), + error, warnings + ); + SQLException cause = cleanup != null ? cleanup : original; + return new ConnectionInvalidatedException( + "SQL Server SHOWPLAN_XML session could not be safely reused", + cause, + originalDiagnostic, + cleanupDiagnostic, + partial + ); + } + + private PlanTransactionState validateTransaction( + PlanExecutionContext context, + List prerequisites + ) { + if (context.transactionProbeFailed()) { + String detail = context.transactionProbeDetail(); + prerequisites.add(PlanPrerequisite.unknown( + PlanCodes.TRANSACTION_CONTEXT_UNSAFE, + detail == null || detail.isBlank() + ? "SQL Server transaction state probe failed" + : "SQL Server transaction state probe failed: " + detail, + "Reconnect and retry from a known transaction state" + )); + return null; + } + PlanTransactionState state = context.transactionState(); + if (state == PlanTransactionState.AUTO_COMMIT || state == PlanTransactionState.TRANSACTION_ACTIVE) { + return state; + } + String message = switch (state) { + case MANUAL_COMMIT_IDLE -> + "A manual-commit idle connection is not probed because the probe could start a transaction"; + case TRANSACTION_FAILED -> "The current SQL Server transaction is already uncommittable"; + default -> "The current SQL Server transaction state is unknown"; + }; + prerequisites.add(state == PlanTransactionState.UNKNOWN + ? PlanPrerequisite.unknown( + PlanCodes.TRANSACTION_CONTEXT_UNSAFE, message, "Reconnect and retry from a known transaction state") + : PlanPrerequisite.unmet( + PlanCodes.TRANSACTION_CONTEXT_UNSAFE, message, "Finish the transaction or reconnect, then retry")); + return null; + } + + private void addVersionAndDriverDiagnostics( + PlanExecutionContext context, + List prerequisites + ) { + String version = context.serverVersion(); + if (version == null || version.isBlank()) { + prerequisites.add(PlanPrerequisite.unknown( + PlanCodes.VERSION_NOT_VALIDATED, + "SQL Server version could not be determined; runtime SHOWPLAN checks will be used", + "Record the SQL Server product version for compatibility diagnostics" + )); + } else { + prerequisites.add(PlanPrerequisite.met("server-version", version)); + } + try { + DatabaseMetaData metadata = context.connection().getMetaData(); + String driver = metadata == null ? null : metadata.getDriverVersion(); + if (driver == null || driver.isBlank()) { + prerequisites.add(PlanPrerequisite.unknown( + PlanCodes.VERSION_NOT_VALIDATED, + "SQL Server JDBC driver version could not be determined", + "Record the JDBC driver version for compatibility diagnostics" + )); + } else { + prerequisites.add(PlanPrerequisite.met("jdbc-driver-version", driver)); + } + } catch (SQLException e) { + prerequisites.add(PlanPrerequisite.unknown( + PlanCodes.VERSION_NOT_VALIDATED, + "SQL Server JDBC driver version probe failed: " + safeMessage(e), + "Record the JDBC driver version for compatibility diagnostics" + )); + } + } + + static PlanDiagnostic unsafeStatement(SqlStatementAnalysis statement) { + if (EXTERNAL_ROWSET.matcher(statement.sql()).find()) { + return PlanDiagnostic.of( + PlanCodes.UNSAFE_TO_ESTIMATE, + "External SQL Server rowset providers are not safe to estimate" + ); + } + for (String rawName : statement.functionNames()) { + String name = rawName == null ? "" : rawName.trim(); + if (name.isBlank()) { + continue; + } + String normalized = name.replace("[", "").replace("]", "").replace("\"", "") + .toLowerCase(Locale.ROOT); + int dot = normalized.lastIndexOf('.'); + String schema = dot < 0 ? "" : normalized.substring(0, dot); + String function = dot < 0 ? normalized : normalized.substring(dot + 1); + if ((!schema.isEmpty() && !SAFE_SCHEMAS.contains(schema)) || !SAFE_FUNCTIONS.contains(function)) { + return PlanDiagnostic.of( + PlanCodes.UNSAFE_TO_ESTIMATE, + "Function " + name + " is not a known SQL Server builtin safe for estimated plans" + ); + } + } + return null; + } + + private DialectPlanResult unmet( + PlanExecutionContext context, + List prerequisites, + PlanDiagnostic error, + PlanEffects effects + ) { + return new DialectPlanResult( + context.serverVersion(), PlanStatus.PREREQUISITES_UNMET, List.of(), null, null, null, + false, capabilities, prerequisites, effects, error, List.of() + ); + } + + private static boolean transactionMatches(PlanTransactionState expected, int actual) { + return (expected == PlanTransactionState.AUTO_COMMIT && actual == 0) + || (expected == PlanTransactionState.TRANSACTION_ACTIVE && actual == 1); + } + + private static boolean transactionRestored(PlanTransactionState expected, int actual) { + if (expected == PlanTransactionState.AUTO_COMMIT) { + return actual == 0; + } + return expected == PlanTransactionState.TRANSACTION_ACTIVE && (actual == 1 || actual == -1); + } + + private static String transactionDescription(PlanTransactionState state) { + return state == PlanTransactionState.TRANSACTION_ACTIVE + ? "Existing active transaction will not be committed or rolled back" + : "Idle auto-commit session"; + } + + private static boolean isPermissionDenied(SQLException error) { + for (SQLException current = error; current != null; current = current.getNextException()) { + if (current.getErrorCode() == 229 || current.getErrorCode() == 262) { + return true; + } + String message = safeMessage(current).toLowerCase(Locale.ROOT); + if (message.contains("permission") + && (message.contains("showplan") || message.contains("select") || message.contains("denied"))) { + return true; + } + } + return false; + } + + private static boolean isCancelled(PlanExecutionContext context, SQLException error) { + if (context.isCancelled()) { + return true; + } + for (SQLException current = error; current != null; current = current.getNextException()) { + String message = safeMessage(current).toLowerCase(Locale.ROOT); + if ("57014".equals(current.getSQLState()) + || message.contains("cancel") + || message.contains("timed out") + || message.contains("timeout")) { + return true; + } + } + return false; + } + + private static boolean isConnectionBroken(SQLException error) { + for (SQLException current = error; current != null; current = current.getNextException()) { + String state = current.getSQLState(); + if (state != null && state.startsWith("08")) { + return true; + } + } + return false; + } + + private static PlanDiagnostic sqlDiagnostic(String code, String message, SQLException error) { + return new PlanDiagnostic(code, message, error.getSQLState(), Integer.toString(error.getErrorCode())); + } + + private static String safeMessage(SQLException error) { + return error.getMessage() == null || error.getMessage().isBlank() + ? error.getClass().getSimpleName() + : error.getMessage(); + } + + private enum ShowplanMode { + ON, + OFF, + UNKNOWN + } + + private enum Permission { + GRANTED, + DENIED, + UNKNOWN + } + + private record Probe(ShowplanMode mode, Integer xactState) { + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SqlServerPlanJdbc.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SqlServerPlanJdbc.java new file mode 100644 index 00000000..c2364504 --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SqlServerPlanJdbc.java @@ -0,0 +1,303 @@ +package org.jumpserver.chen.modules.sqlserver; + +import org.jumpserver.chen.framework.datasource.plan.PlanExecutionContext; +import org.jumpserver.chen.framework.datasource.plan.PlanLimits; + +import java.io.IOException; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.SQLXML; +import java.sql.Statement; +import java.sql.Types; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +final class SqlServerPlanJdbc { + private static final int MAX_RESULTS = 64; + private static final int MAX_XML_DOCUMENTS = 16; + + private SqlServerPlanJdbc() { + } + + static TrackedStatement requestStatement(PlanExecutionContext context) throws SQLException { + context.throwIfCancelled(); + Statement statement = context.connection().createStatement(); + context.registerStatement(statement); + try { + configureTimeout(statement, Math.max(1L, context.deadlineMillis() - System.currentTimeMillis())); + context.throwIfCancelled(); + return new TrackedStatement(statement, context, true); + } catch (SQLException e) { + context.unregisterStatement(statement); + closeAfterFailure(statement, e); + throw e; + } + } + + static TrackedStatement cleanupStatement(PlanExecutionContext context) throws SQLException { + Statement statement = context.connection().createStatement(); + try { + configureTimeout(statement, PlanLimits.CLEANUP_TIMEOUT_MS); + return new TrackedStatement(statement, context, false); + } catch (SQLException e) { + closeAfterFailure(statement, e); + throw e; + } + } + + static void executeAndDrain(Statement statement, String sql) throws SQLException { + boolean resultSet = statement.execute(sql); + int traversed = 0; + while (true) { + if (++traversed > MAX_RESULTS) { + throw new SQLException("SQL Server returned too many JDBC results", "HY000"); + } + if (resultSet) { + try (ResultSet ignored = statement.getResultSet()) { + // SET SHOWPLAN_XML should not return rows. Closing any unexpected result is enough + // before advancing because the Microsoft driver supports CLOSE_CURRENT_RESULT. + } + } else if (statement.getUpdateCount() == -1) { + return; + } + resultSet = statement.getMoreResults(Statement.CLOSE_CURRENT_RESULT); + } + } + + static ProbeCapture executeProbe(Statement statement, String sql, int maxBytes) throws SQLException { + boolean resultSet = statement.execute(sql); + int traversed = 0; + List> rows = new ArrayList<>(); + boolean truncated = false; + while (true) { + if (++traversed > MAX_RESULTS) { + throw new SQLException("SQL Server probe returned too many JDBC results", "HY000"); + } + if (resultSet) { + try (ResultSet current = statement.getResultSet()) { + if (current != null) { + ResultSetMetaData metadata = current.getMetaData(); + int columns = metadata == null ? 1 : Math.max(1, metadata.getColumnCount()); + while (current.next()) { + List row = new ArrayList<>(columns); + for (int column = 1; column <= columns; column++) { + BoundedText value = readProbeValue(current, column, maxBytes); + row.add(value.text()); + truncated |= value.truncated(); + } + rows.add(List.copyOf(row)); + if (rows.size() >= MAX_XML_DOCUMENTS) { + truncated = true; + break; + } + } + } + } + } else if (statement.getUpdateCount() == -1) { + return new ProbeCapture(List.copyOf(rows), truncated); + } + resultSet = statement.getMoreResults(Statement.CLOSE_CURRENT_RESULT); + } + } + + static PlanCapture executePlan(Statement statement, String sql, int maxRawBytes) throws SQLException { + boolean resultSet = statement.execute(sql); + int traversed = 0; + List documents = new ArrayList<>(); + boolean truncated = false; + int capturedBytes = 0; + while (true) { + if (++traversed > MAX_RESULTS) { + throw new SQLException("SQL Server returned too many JDBC results", "HY000"); + } + if (resultSet) { + try (ResultSet current = statement.getResultSet()) { + while (!truncated && current != null && current.next()) { + if (documents.size() >= MAX_XML_DOCUMENTS) { + truncated = true; + break; + } + int remainingBytes = Math.max(0, maxRawBytes - capturedBytes); + BoundedText document = readPlanXml(current, remainingBytes); + if (document.text() != null && !document.text().isBlank()) { + documents.add(document.text()); + capturedBytes += document.text().getBytes(StandardCharsets.UTF_8).length; + } + truncated |= document.truncated(); + if (document.truncated()) { + break; + } + } + } + } else if (statement.getUpdateCount() == -1) { + return capture(documents, truncated, maxRawBytes); + } + resultSet = statement.getMoreResults(Statement.CLOSE_CURRENT_RESULT); + } + } + + private static PlanCapture capture(List documents, boolean truncated, int maxRawBytes) { + String rawText; + if (documents.isEmpty()) { + rawText = ""; + } else if (documents.size() == 1) { + rawText = documents.get(0); + } else { + StringBuilder joined = new StringBuilder(); + for (int index = 0; index < documents.size(); index++) { + if (index > 0) { + joined.append("\n\n"); + } + joined.append(documents.get(index)); + } + rawText = joined.toString(); + } + BoundedText bounded = bound(rawText, maxRawBytes); + return new PlanCapture(List.copyOf(documents), bounded.text(), truncated || bounded.truncated()); + } + + private static BoundedText readPlanXml(ResultSet resultSet, int maxBytes) throws SQLException { + ResultSetMetaData metadata = resultSet.getMetaData(); + boolean xmlColumn = false; + if (metadata != null) { + try { + String typeName = metadata.getColumnTypeName(1); + xmlColumn = metadata.getColumnType(1) == Types.SQLXML + || (typeName != null && "xml".equals(typeName.toLowerCase(Locale.ROOT))); + } catch (SQLException ignored) { + // The value is still readable through the JDBC character-stream mapping. + } + } + if (xmlColumn) { + try { + SQLXML sqlxml = resultSet.getSQLXML(1); + if (sqlxml == null) { + return new BoundedText("", false); + } + return readAndFree(sqlxml, maxBytes); + } catch (SQLFeatureNotSupportedException ignored) { + // Older compatible drivers may expose the XML column only as a character stream. + } + } + Reader reader = resultSet.getCharacterStream(1); + if (reader != null) { + return read(reader, maxBytes); + } + return bound(resultSet.getString(1), maxBytes); + } + + private static BoundedText readProbeValue(ResultSet resultSet, int column, int maxBytes) throws SQLException { + Object value = resultSet.getObject(column); + if (value instanceof SQLXML sqlxml) { + return readAndFree(sqlxml, maxBytes); + } + return bound(value == null ? null : value.toString(), maxBytes); + } + + private static BoundedText readAndFree(SQLXML sqlxml, int maxBytes) throws SQLException { + BoundedText value = null; + SQLException failure = null; + try { + value = read(sqlxml.getCharacterStream(), maxBytes); + } catch (SQLException e) { + failure = e; + } + try { + sqlxml.free(); + } catch (SQLException e) { + if (failure != null) { + failure.addSuppressed(e); + } else { + failure = e; + } + } + if (failure != null) { + throw failure; + } + return value; + } + + private static BoundedText read(Reader reader, int maxBytes) throws SQLException { + if (reader == null) { + return new BoundedText("", false); + } + int characterLimit = Math.max(0, maxBytes) + 1; + StringBuilder value = new StringBuilder(Math.min(characterLimit, 8192)); + char[] buffer = new char[4096]; + try (reader) { + while (value.length() < characterLimit) { + int read = reader.read(buffer, 0, Math.min(buffer.length, characterLimit - value.length())); + if (read < 0) { + break; + } + value.append(buffer, 0, read); + } + } catch (IOException e) { + throw new SQLException("Failed to read SQL Server XML plan", "HY000", e); + } + return bound(value.toString(), maxBytes); + } + + private static BoundedText bound(String value, int maxBytes) { + if (value == null) { + return new BoundedText("", false); + } + byte[] bytes = value.getBytes(StandardCharsets.UTF_8); + int safeMaxBytes = Math.max(0, maxBytes); + if (bytes.length <= safeMaxBytes) { + return new BoundedText(value, false); + } + return new BoundedText(new String(bytes, 0, safeMaxBytes, StandardCharsets.UTF_8), true); + } + + private static void configureTimeout(Statement statement, long timeoutMillis) throws SQLException { + long seconds = Math.max(1L, (timeoutMillis + 999L) / 1_000L); + statement.setQueryTimeout((int) Math.min(Integer.MAX_VALUE, seconds)); + } + + private static void closeAfterFailure(Statement statement, SQLException error) { + try { + statement.close(); + } catch (SQLException closeError) { + error.addSuppressed(closeError); + } + } + + record ProbeCapture(List> rows, boolean truncated) { + } + + record PlanCapture(List documents, String rawText, boolean truncated) { + } + + private record BoundedText(String text, boolean truncated) { + } + + static final class TrackedStatement implements AutoCloseable { + private final Statement statement; + private final PlanExecutionContext context; + private final boolean registered; + + private TrackedStatement(Statement statement, PlanExecutionContext context, boolean registered) { + this.statement = statement; + this.context = context; + this.registered = registered; + } + + Statement statement() { + return statement; + } + + @Override + public void close() throws SQLException { + if (registered) { + context.unregisterStatement(statement); + } + statement.close(); + } + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SqlServerPlanParser.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SqlServerPlanParser.java new file mode 100644 index 00000000..dc646b24 --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SqlServerPlanParser.java @@ -0,0 +1,399 @@ +package org.jumpserver.chen.modules.sqlserver; + +import org.jumpserver.chen.framework.datasource.plan.NormalizedNodeType; +import org.jumpserver.chen.framework.datasource.plan.PlanCodes; +import org.jumpserver.chen.framework.datasource.plan.PlanDiagnostic; +import org.jumpserver.chen.framework.datasource.plan.PlanNode; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.helpers.DefaultHandler; + +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.StringReader; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +public final class SqlServerPlanParser { + public static final String RAW_FORMAT_VERSION = "sqlserver-showplan-xml"; + public static final String ROWS_MEANING = "SQL Server estimated output rows"; + public static final String COST_MEANING = "SQL Server estimated total subtree cost"; + + private SqlServerPlanParser() { + } + + public static ParseResult parse(String rawXml, int maxRawBytes, int maxNodes, int maxDepth) { + return parse(List.of(rawXml == null ? "" : rawXml), maxRawBytes, maxNodes, maxDepth); + } + + public static ParseResult parse(List documents, int maxRawBytes, int maxNodes, int maxDepth) { + List warnings = new ArrayList<>(); + List roots = new ArrayList<>(); + AtomicInteger ids = new AtomicInteger(); + AtomicInteger nodes = new AtomicInteger(); + boolean limited = false; + long totalRawBytes = 0L; + + if (documents == null || documents.isEmpty()) { + return ParseResult.failed(warnings, "SQL Server returned no Showplan XML documents"); + } + for (String rawXml : documents) { + if (rawXml == null || rawXml.isBlank()) { + warnings.add(PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, "SQL Server returned an empty Showplan XML document")); + continue; + } + int documentBytes = rawXml.getBytes(StandardCharsets.UTF_8).length; + totalRawBytes += documentBytes; + if (documentBytes > maxRawBytes || totalRawBytes > maxRawBytes) { + warnings.add(PlanDiagnostic.of(PlanCodes.PLAN_LIMIT_REACHED, "SQL Server Showplan XML exceeded the raw size limit")); + limited = true; + continue; + } + + Document document; + try { + document = parseDocument(rawXml); + } catch (Exception e) { + warnings.add(PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, "SQL Server Showplan XML is not valid or safe to parse")); + continue; + } + if (!"ShowPlanXML".equals(localName(document.getDocumentElement()))) { + warnings.add(PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, "XML document is not a SQL Server ShowPlanXML document")); + continue; + } + + List documentRoots = new ArrayList<>(); + findRootRelOps(document.getDocumentElement(), documentRoots); + if (documentRoots.isEmpty()) { + warnings.add(PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, "SQL Server Showplan XML contains no RelOp nodes")); + continue; + } + for (Element root : documentRoots) { + if (nodes.get() >= maxNodes) { + limited = true; + break; + } + roots.add(convert(root, ids, nodes, 1, maxNodes, maxDepth, warnings)); + } + } + + if (limited && warnings.stream().noneMatch(warning -> PlanCodes.PLAN_LIMIT_REACHED.equals(warning.code()))) { + warnings.add(PlanDiagnostic.of(PlanCodes.PLAN_LIMIT_REACHED, "SQL Server execution plan exceeded node or depth limits")); + } + if (roots.isEmpty()) { + return new ParseResult(List.of(), List.copyOf(warnings), false); + } + return new ParseResult(List.copyOf(roots), List.copyOf(warnings), true); + } + + static boolean isShowPlanXml(String rawXml, int maxBytes) { + if (rawXml == null || rawXml.isBlank() || rawXml.getBytes(StandardCharsets.UTF_8).length > maxBytes) { + return false; + } + if (!rawXml.stripLeading().startsWith("<")) { + return false; + } + try { + return "ShowPlanXML".equals(localName(parseDocument(rawXml).getDocumentElement())); + } catch (Exception e) { + return false; + } + } + + private static Document parseDocument(String xml) throws Exception { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + factory.setXIncludeAware(false); + factory.setExpandEntityReferences(false); + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + factory.setFeature("http://xml.org/sax/features/external-general-entities", false); + factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + DocumentBuilder builder = factory.newDocumentBuilder(); + builder.setEntityResolver((publicId, systemId) -> new InputSource(new StringReader(""))); + builder.setErrorHandler(new DefaultHandler()); + return builder.parse(new InputSource(new StringReader(xml))); + } + + private static void findRootRelOps(Element element, List roots) { + if ("RelOp".equals(localName(element))) { + roots.add(element); + return; + } + for (Element child : childElements(element)) { + findRootRelOps(child, roots); + } + } + + private static PlanNode convert( + Element relOp, + AtomicInteger ids, + AtomicInteger nodes, + int depth, + int maxNodes, + int maxDepth, + List warnings + ) { + nodes.incrementAndGet(); + int displayId = ids.incrementAndGet(); + String nativeId = attribute(relOp, "NodeId"); + String logical = attribute(relOp, "LogicalOp"); + String physical = attribute(relOp, "PhysicalOp"); + String nativeOperator = firstNonBlank(physical, logical, "RelOp"); + Map attributes = elementAttributes(relOp); + Map predicates = new LinkedHashMap<>(); + ObjectInfo object = collectOwnedDetails(relOp, attributes, predicates); + + List children = new ArrayList<>(); + List childRelOps = new ArrayList<>(); + findNearestChildRelOps(relOp, relOp, childRelOps); + if (depth >= maxDepth && !childRelOps.isEmpty()) { + addLimitWarning(warnings, "SQL Server execution plan exceeded the depth limit"); + } else { + for (Element child : childRelOps) { + if (nodes.get() >= maxNodes) { + addLimitWarning(warnings, "SQL Server execution plan exceeded the node limit"); + break; + } + children.add(convert(child, ids, nodes, depth + 1, maxNodes, maxDepth, warnings)); + } + } + + BigDecimal rows = decimal(attribute(relOp, "EstimateRows")); + BigDecimal cost = decimal(attribute(relOp, "EstimatedTotalSubtreeCost")); + String detail = object.relation() == null ? null : "Object: " + object.relation(); + return new PlanNode( + "sqlserver-" + displayId, + nativeId, + mapNodeType(logical, physical), + nativeOperator, + emptyToNull(logical), + emptyToNull(physical), + detail, + object.table(), + object.relation(), + rows, + cost, + null, + rows == null ? null : ROWS_MEANING, + cost == null ? null : COST_MEANING, + predicates, + attributes, + children + ); + } + + private static ObjectInfo collectOwnedDetails( + Element relOp, + Map attributes, + Map predicates + ) { + ObjectInfo object = new ObjectInfo(null, null); + List stack = new ArrayList<>(childElements(relOp)); + int objectIndex = 0; + int scalarIndex = 0; + while (!stack.isEmpty()) { + Element element = stack.remove(stack.size() - 1); + if ("RelOp".equals(localName(element))) { + continue; + } + String name = localName(element); + if ("Object".equals(name)) { + objectIndex++; + NamedNodeMap objectAttributes = element.getAttributes(); + for (int index = 0; index < objectAttributes.getLength(); index++) { + Node attribute = objectAttributes.item(index); + putUnique(attributes, "Object[" + objectIndex + "]." + localName(attribute), attribute.getNodeValue()); + } + if (object.table() == null) { + String table = emptyToNull(attribute(element, "Table")); + object = new ObjectInfo(table, qualifiedObjectName(element)); + } + } + if ("ScalarOperator".equals(name)) { + String scalar = emptyToNull(attribute(element, "ScalarString")); + if (scalar != null) { + scalarIndex++; + String predicateName = predicateAncestor(element, relOp); + if (predicateName == null) { + putUnique(attributes, "ScalarOperator[" + scalarIndex + "]", scalar); + } else { + putUnique(predicates, predicateName, scalar); + } + } + } + stack.addAll(childElements(element)); + } + return object; + } + + private static String predicateAncestor(Element scalar, Element relOp) { + Node current = scalar.getParentNode(); + while (current instanceof Element element && current != relOp) { + String name = localName(element); + if (name.toLowerCase(Locale.ROOT).contains("predicate") || "ProbeResidual".equals(name)) { + return name; + } + current = current.getParentNode(); + } + return null; + } + + private static void findNearestChildRelOps(Element root, Element current, List children) { + for (Element child : childElements(current)) { + if (child != root && "RelOp".equals(localName(child))) { + children.add(child); + } else { + findNearestChildRelOps(root, child, children); + } + } + } + + private static String qualifiedObjectName(Element object) { + List parts = new ArrayList<>(); + for (String name : List.of("Database", "Schema", "Table")) { + String value = emptyToNull(attribute(object, name)); + if (value != null) { + parts.add(value); + } + } + return parts.isEmpty() ? null : String.join(".", parts); + } + + private static Map elementAttributes(Element element) { + Map values = new LinkedHashMap<>(); + NamedNodeMap attributes = element.getAttributes(); + for (int index = 0; index < attributes.getLength(); index++) { + Node attribute = attributes.item(index); + values.put(localName(attribute), attribute.getNodeValue()); + } + return values; + } + + private static List childElements(Element element) { + List children = new ArrayList<>(); + NodeList nodes = element.getChildNodes(); + for (int index = 0; index < nodes.getLength(); index++) { + if (nodes.item(index) instanceof Element child) { + children.add(child); + } + } + return children; + } + + private static String attribute(Element element, String name) { + if (element.hasAttribute(name)) { + return element.getAttribute(name); + } + NamedNodeMap attributes = element.getAttributes(); + for (int index = 0; index < attributes.getLength(); index++) { + Node attribute = attributes.item(index); + if (name.equals(localName(attribute))) { + return attribute.getNodeValue(); + } + } + return ""; + } + + private static String localName(Node node) { + if (node == null) { + return ""; + } + String local = node.getLocalName(); + if (local != null) { + return local; + } + String name = node.getNodeName(); + int colon = name == null ? -1 : name.indexOf(':'); + return colon < 0 ? name : name.substring(colon + 1); + } + + private static BigDecimal decimal(String value) { + if (value == null || value.isBlank()) { + return null; + } + try { + return new BigDecimal(value); + } catch (NumberFormatException e) { + return null; + } + } + + static NormalizedNodeType mapNodeType(String logical, String physical) { + String value = (firstNonBlank(physical, "") + " " + firstNonBlank(logical, "")) + .toLowerCase(Locale.ROOT); + if (value.contains("nested loops")) return NormalizedNodeType.NESTED_LOOP; + if (value.contains("hash match") && value.contains("join")) return NormalizedNodeType.HASH_JOIN; + if (value.contains("merge join")) return NormalizedNodeType.MERGE_JOIN; + if (value.contains("join")) return NormalizedNodeType.JOIN; + if (value.contains("index scan") || value.contains("index seek")) return NormalizedNodeType.INDEX_SCAN; + if (value.contains("table scan") || value.contains("remote scan")) return NormalizedNodeType.SCAN; + if (value.contains("sort")) return NormalizedNodeType.SORT; + if (value.contains("aggregate")) return NormalizedNodeType.AGGREGATE; + if (value.contains("filter")) return NormalizedNodeType.FILTER; + if (value.contains("top")) return NormalizedNodeType.LIMIT; + if (value.contains("concatenation")) return NormalizedNodeType.UNION; + if (value.contains("constant scan")) return NormalizedNodeType.VALUES; + if (value.contains("spool")) return NormalizedNodeType.MATERIALIZE; + if (value.contains("compute scalar") || value.contains("sequence project")) return NormalizedNodeType.PROJECTION; + if (value.contains("parallelism")) return NormalizedNodeType.PARALLEL; + if (value.contains("hash match")) return NormalizedNodeType.HASH; + return NormalizedNodeType.OTHER; + } + + private static void addLimitWarning(List warnings, String message) { + if (warnings.stream().noneMatch(warning -> PlanCodes.PLAN_LIMIT_REACHED.equals(warning.code()))) { + warnings.add(PlanDiagnostic.of(PlanCodes.PLAN_LIMIT_REACHED, message)); + } + } + + private static void putUnique(Map values, String key, String value) { + if (value == null || value.isBlank()) { + return; + } + String candidate = key; + int suffix = 2; + while (values.containsKey(candidate)) { + candidate = key + "[" + suffix++ + "]"; + } + values.put(candidate, value); + } + + private static String emptyToNull(String value) { + return value == null || value.isBlank() ? null : value; + } + + private static String firstNonBlank(String... values) { + for (String value : values) { + if (value != null && !value.isBlank()) { + return value; + } + } + return ""; + } + + private record ObjectInfo(String table, String relation) { + } + + public record ParseResult(List roots, List warnings, boolean structured) { + private static ParseResult failed(List warnings, String message) { + List all = new ArrayList<>(warnings); + all.add(PlanDiagnostic.of(PlanCodes.PLAN_PARSE_FAILED, message)); + return new ParseResult(List.of(), List.copyOf(all), false); + } + } +} diff --git a/backend/modules/src/test/java/org/jumpserver/chen/modules/DataViewQualifiedTableNameTest.java b/backend/modules/src/test/java/org/jumpserver/chen/modules/DataViewQualifiedTableNameTest.java new file mode 100644 index 00000000..1bc31c1d --- /dev/null +++ b/backend/modules/src/test/java/org/jumpserver/chen/modules/DataViewQualifiedTableNameTest.java @@ -0,0 +1,127 @@ +package org.jumpserver.chen.modules; + +import com.alibaba.druid.DbType; +import com.alibaba.druid.sql.SQLUtils; +import org.junit.Test; +import org.jumpserver.chen.framework.datasource.ConnectionManager; +import org.jumpserver.chen.framework.datasource.Datasource; +import org.jumpserver.chen.framework.datasource.sql.SQLActuator; +import org.jumpserver.chen.framework.utils.PageUtils; +import org.jumpserver.chen.modules.dameng.DMActuator; +import org.jumpserver.chen.modules.db2.DB2Actuator; +import org.jumpserver.chen.modules.oracle.OracleActuator; +import org.jumpserver.chen.modules.sqlserver.SQLServerActuator; + +import java.lang.reflect.Proxy; +import java.util.List; +import java.util.function.Function; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * DataView 打开表时按 schema/table 生成 {@code select * from }。 + * 未加引号的标识符一旦含 {@code #}(如达梦内部表 {@code ##HISTOGRAMS_TABLE}),Druid 会把 + * {@code ##} 词法解析成 VARIANT,抛 ParserException 并连带关掉整个 console websocket。 + * 这里锁定各方言都必须产出自身可解析的带引号限定名。 + */ +public class DataViewQualifiedTableNameTest { + + private record Dialect(String name, DbType dbType, Function factory, + Function expectQuoted) { + } + + private static final List DIALECTS = List.of( + new Dialect("dm", DbType.dm, DMActuator::new, name -> "\"" + name + "\""), + new Dialect("db2", DbType.db2, DB2Actuator::new, name -> "\"" + name + "\""), + new Dialect("oracle", DbType.oracle, OracleActuator::new, name -> "\"" + name + "\""), + new Dialect("sqlserver", DbType.sqlserver, SQLServerActuator::new, name -> "[" + name + "]") + ); + + @Test + public void generatesQuotedQualifiedNameForOrdinaryTable() throws Exception { + assertQualifiedName("SYSDBA", "ORDERS", "select * from %s.%s"); + } + + /** + * 回归用例:达梦内部表 {@code ##HISTOGRAMS_TABLE} 修复前生成 + * {@code select * from SYSDBA.##HISTOGRAMS_TABLE},在 column 22 处解析失败。 + */ + @Test + public void generatesParseableQualifiedNameForHashPrefixedTable() throws Exception { + assertQualifiedName("SYSDBA", "##HISTOGRAMS_TABLE", "select * from %s.%s"); + } + + /** 关键字表名、大小写敏感表名与特殊字符表名同样必须被正确引用。 */ + @Test + public void quotesKeywordCaseSensitiveAndSpecialCharacterNames() throws Exception { + assertQualifiedName("SYSDBA", "ORDER", "select * from %s.%s"); + assertQualifiedName("SYSDBA", "MixedCaseTable", "select * from %s.%s"); + assertQualifiedName("SYSDBA", "with space", "select * from %s.%s"); + assertQualifiedName("SYSDBA", "semi;colon--", "select * from %s.%s"); + } + + /** + * 标识符内部的引号字符必须按各方言规则转义,不能提前闭合引用。 + * 注:SQL Server 的 {@code []]} 转义是本仓库内嵌 Druid 的词法限制(既有 SQLIdentifier 行为, + * 与本次 # 号修复无关),故此处只对采用 {@code ""} 双写转义的方言断言可 round-trip。 + */ + @Test + public void escapesEmbeddedQuoteCharacters() throws Exception { + for (var dialect : DIALECTS) { + if (dialect.dbType() == DbType.sqlserver) { + continue; // 已知 Druid 限制:见方法注释 + } + var actuator = dialect.factory().apply(connectionManager(dialect.dbType())); + var expected = "\"we\"\"ird\""; + + var plan = actuator.createPlan("SYSDBA", "we\"ird", null); + var sql = plan.getTargetSQL(); + + assertTrue(dialect.name() + " should escape embedded quote: " + sql, sql.contains(expected)); + assertParses(dialect, sql); + } + } + + private static void assertQualifiedName(String schema, String table, String template) throws Exception { + for (var dialect : DIALECTS) { + var actuator = dialect.factory().apply(connectionManager(dialect.dbType())); + + // 与 DataViewConsole.createDataView 相同的调用方式:先取未加 limit 的基准 SQL。 + var plan = actuator.createPlan(schema, table, null); + var sql = plan.getTargetSQL(); + + assertEquals(dialect.name(), + String.format(template, + dialect.expectQuoted().apply(schema), + dialect.expectQuoted().apply(table)), + sql); + + // 修复前此处抛 ParserException: pos .., token VARIANT + assertParses(dialect, sql); + + // DataView 分页会重新解析并回写 SQL,引用必须在 round-trip 中保留。 + var paged = PageUtils.limit(sql, dialect.dbType(), 0, 50); + assertTrue(dialect.name() + " lost quoting while paging: " + paged, + paged.contains(dialect.expectQuoted().apply(table))); + assertParses(dialect, paged); + } + } + + private static void assertParses(Dialect dialect, String sql) { + SQLUtils.parseSingleStatement(sql, dialect.dbType().name()); + } + + private static ConnectionManager connectionManager(DbType dbType) { + var datasource = (Datasource) Proxy.newProxyInstance( + Datasource.class.getClassLoader(), + new Class[]{Datasource.class}, + (proxy, method, args) -> "getDruidDbType".equals(method.getName()) ? dbType : null + ); + return (ConnectionManager) Proxy.newProxyInstance( + ConnectionManager.class.getClassLoader(), + new Class[]{ConnectionManager.class}, + (proxy, method, args) -> "getDatasource".equals(method.getName()) ? datasource : null + ); + } +} diff --git a/backend/modules/src/test/java/org/jumpserver/chen/modules/dameng/DmMetadataProviderRelationTest.java b/backend/modules/src/test/java/org/jumpserver/chen/modules/dameng/DmMetadataProviderRelationTest.java new file mode 100644 index 00000000..29de70bb --- /dev/null +++ b/backend/modules/src/test/java/org/jumpserver/chen/modules/dameng/DmMetadataProviderRelationTest.java @@ -0,0 +1,156 @@ +package org.jumpserver.chen.modules.dameng; + +import org.junit.Test; +import org.jumpserver.chen.framework.datasource.ConnectionManager; +import org.jumpserver.chen.framework.datasource.metadata.ObjectRef; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; +import org.jumpserver.chen.framework.datasource.metadata.RelationScope; +import org.jumpserver.chen.framework.datasource.sql.SQLActuator; + +import java.lang.reflect.Proxy; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Pattern; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class DmMetadataProviderRelationTest { + private static final Pattern UNSAFE_DM_ALIAS = + Pattern.compile("(?i)\\bAS\\s+(?:comment|collation|type)\\b"); + + @Test + public void listTablesUsesReservedWordSafeCommentAliasAndMapsRows() throws Exception { + var capturedSql = new AtomicReference(); + var provider = new DmMetadataProvider(connectionManager(capturedSql)); + + var tables = provider.listRelations(new RelationScope(null, "SYSDBA"), Set.of(RelationKind.TABLE)); + + assertEquals(1, tables.size()); + assertEquals(RelationKind.TABLE, tables.get(0).ref().kind()); + assertEquals("ORDERS", tables.get(0).ref().name()); + assertEquals("order table", tables.get(0).comment()); + assertRelationProjection(capturedSql.get(), "all_tables", "t.table_name"); + assertFalse(capturedSql.get(), normalized(capturedSql.get()).contains(" as engine")); + assertFalse(capturedSql.get(), normalized(capturedSql.get()).contains(" as character_set")); + assertFalse(capturedSql.get(), normalized(capturedSql.get()).contains(" as collation")); + } + + @Test + public void listViewsUsesReservedWordSafeCommentAliasAndMapsRows() throws Exception { + var capturedSql = new AtomicReference(); + var provider = new DmMetadataProvider(connectionManager(capturedSql)); + + var views = provider.listRelations(new RelationScope(null, "SYSDBA"), Set.of(RelationKind.VIEW)); + + assertEquals(1, views.size()); + assertEquals(RelationKind.VIEW, views.get(0).ref().kind()); + assertEquals("ACTIVE_ORDERS", views.get(0).ref().name()); + assertEquals("active orders", views.get(0).comment()); + assertRelationProjection(capturedSql.get(), "all_views", "v.view_name"); + assertFalse(capturedSql.get(), normalized(capturedSql.get()).contains(" as type")); + } + + @Test + public void listColumnsMapsObjectCommentWithoutReservedAlias() throws Exception { + var capturedSql = new AtomicReference(); + var provider = new DmMetadataProvider(connectionManager(capturedSql)); + var owner = new ObjectRef(null, "SYSDBA", "ORDERS", RelationKind.TABLE); + + var columns = provider.listColumns(List.of(owner)); + + assertEquals(1, columns.size()); + assertEquals("ID", columns.get(0).name()); + assertEquals("primary key", columns.get(0).comment()); + assertReservedWordSafeAliases(capturedSql.get()); + assertTrue(capturedSql.get(), normalized(capturedSql.get()).contains("from all_tab_columns")); + } + + @Test + public void metadataQueriesDoNotUseKnownDamengKeywordAliases() throws Exception { + for (var field : DmMetadataProvider.class.getDeclaredFields()) { + if (!field.getName().startsWith("SQL_") + || field.getType() != String.class + || !Modifier.isStatic(field.getModifiers())) { + continue; + } + field.setAccessible(true); + assertReservedWordSafeAliases((String) field.get(null)); + } + } + + private static void assertRelationProjection(String sql, String source, String nameExpression) { + var normalized = normalized(sql); + assertTrue(sql, normalized.contains("select " + nameExpression + " as name, c.comments as object_comment")); + assertTrue(sql, normalized.contains("from " + source)); + assertTrue(sql, normalized.contains(" as object_comment")); + assertTrue(sql, sql.contains("?")); + assertFalse(sql, sql.contains("'?'")); + assertReservedWordSafeAliases(sql); + } + + private static void assertReservedWordSafeAliases(String sql) { + assertFalse(sql, normalized(sql).contains("as \"comment\"")); + assertFalse(sql, UNSAFE_DM_ALIAS.matcher(sql).find()); + } + + private static String normalized(String sql) { + return sql.toLowerCase(Locale.ROOT).replaceAll("\\s+", " ").trim(); + } + + private static ConnectionManager connectionManager(AtomicReference capturedSql) { + var actuator = (SQLActuator) Proxy.newProxyInstance( + SQLActuator.class.getClassLoader(), + new Class[]{SQLActuator.class}, + (proxy, method, args) -> { + if (!"queryRows".equals(method.getName())) { + return null; + } + var sql = (String) args[0]; + capturedSql.set(sql); + var normalized = sql.toLowerCase(Locale.ROOT); + if (normalized.contains("from all_tables")) { + return List.of(row("name", "ORDERS", "object_comment", "order table")); + } + if (normalized.contains("from all_views")) { + return List.of(row("name", "ACTIVE_ORDERS", "object_comment", "active orders")); + } + if (normalized.contains("from all_tab_columns")) { + return List.of(row( + "name", "ID", + "table_name", "ORDERS", + "ordinal", 1, + "native_type", "BIGINT", + "jdbc_type_name", "BIGINT", + "size", 19, + "scale", 0, + "nullable", "N", + "default_value", null, + "object_comment", "primary key" + )); + } + return new ArrayList<>(); + } + ); + return (ConnectionManager) Proxy.newProxyInstance( + ConnectionManager.class.getClassLoader(), + new Class[]{ConnectionManager.class}, + (proxy, method, args) -> "getSqlActuator".equals(method.getName()) ? actuator : null + ); + } + + private static Map row(Object... values) { + var row = new LinkedHashMap(); + for (int i = 0; i < values.length; i += 2) { + row.put(String.valueOf(values[i]), values[i + 1]); + } + return row; + } +} diff --git a/backend/web/pom.xml b/backend/web/pom.xml index 7ed3b596..98d5110c 100644 --- a/backend/web/pom.xml +++ b/backend/web/pom.xml @@ -31,26 +31,6 @@ ${maven.compiler.target} - - org.apache.maven.plugins - maven-antrun-plugin - - - main-class-placement - generate-resources - - - - - - - - - run - - - - diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/ai/AgentWebSocketHandler.java b/backend/web/src/main/java/org/jumpserver/chen/web/ai/AgentWebSocketHandler.java new file mode 100644 index 00000000..7698a3a9 --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/ai/AgentWebSocketHandler.java @@ -0,0 +1,562 @@ +package org.jumpserver.chen.web.ai; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import jakarta.annotation.PreDestroy; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.jumpserver.chen.framework.session.SessionManager; +import org.jumpserver.chen.framework.session.impl.JMSSession; +import org.jumpserver.chen.framework.ws.io.PacketIO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import java.io.IOException; +import java.net.SocketTimeoutException; +import java.sql.SQLTimeoutException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +@Component +@Slf4j +public class AgentWebSocketHandler extends TextWebSocketHandler { + private static final Gson GSON = new Gson(); + private static final int PROTOCOL_VERSION = 1; + private static final int MAX_CLIENT_MESSAGE_BYTES = 320 * 1024; + private static final int MAX_TOOL_RESULT_BYTES = 256 * 1024; + private static final String AGENT_BINDING_META_KEY = "com.jumpserver/agent"; + private static final String SQL_CONTEXT_META_KEY = "com.jumpserver/sqlContext"; + private static final String SQL_OPERATION_META_KEY = "com.jumpserver/sqlOperation"; + private static final String FINAL_RESULT_META_KEY = "com.jumpserver/finalResult"; + private static final Set OPERATIONS = Set.of("generate", "explain", "repair"); + + private final SqlAgentToolService toolService; + private final Map tasks = new ConcurrentHashMap<>(); + private final Duration toolTimeout; + private final ScheduledThreadPoolExecutor deadlineExecutor = new ScheduledThreadPoolExecutor(1, runnable -> { + Thread thread = new Thread(runnable, "chen-agent-deadline"); + thread.setDaemon(true); + return thread; + }); + private final ThreadPoolExecutor toolExecutor = new ThreadPoolExecutor( + 2, + 4, + 60, + TimeUnit.SECONDS, + new ArrayBlockingQueue<>(100), + runnable -> { + Thread thread = new Thread(runnable, "chen-agent-tool"); + thread.setDaemon(true); + return thread; + }, + new ThreadPoolExecutor.AbortPolicy() + ); + + @Autowired + public AgentWebSocketHandler(SqlAgentToolService toolService) { + this(toolService, Duration.ofSeconds(60)); + } + + AgentWebSocketHandler(SqlAgentToolService toolService, Duration toolTimeout) { + this.toolService = toolService; + this.toolTimeout = toolTimeout; + this.toolExecutor.allowCoreThreadTimeOut(true); + this.deadlineExecutor.setRemoveOnCancelPolicy(true); + } + + @Override + public void afterConnectionEstablished(WebSocketSession webSocket) { + String token = (String) webSocket.getAttributes().get("token"); + SessionManager.setContext(token); + JMSSession session = currentSession(token); + if (session == null) { + sendProtocolError(webSocket, "session_closed", "The database session is not active"); + return; + } + sendManifest(webSocket, session); + } + + @Override + protected void handleTextMessage(WebSocketSession webSocket, TextMessage message) { + String token = (String) webSocket.getAttributes().get("token"); + SessionManager.setContext(token); + if (message.getPayloadLength() > MAX_CLIENT_MESSAGE_BYTES) { + sendProtocolError(webSocket, "invalid_request", "Agent tool request is too large"); + return; + } + JMSSession session = currentSession(token); + if (session == null) { + sendProtocolError(webSocket, "session_closed", "The database session is not active"); + closeWebSocket(webSocket); + return; + } + + JsonObject packet; + try { + packet = JsonParser.parseString(message.getPayload()).getAsJsonObject(); + } catch (RuntimeException e) { + sendProtocolError(webSocket, "invalid_request", "Invalid agent tool request"); + return; + } + String type = stringValue(packet, "type", 64); + switch (type) { + case "mcp.request" -> handleToolRequest(webSocket, token, session, packet); + case "mcp.cancel" -> handleToolCancel(webSocket, session, packet); + default -> sendProtocolError(webSocket, "invalid_request", "Unsupported agent tool request"); + } + } + + private void sendManifest(WebSocketSession webSocket, JMSSession session) { + var identity = session.getJmsSession(); + var connectInfo = session.getDatasource().getConnectInfo(); + Map context = new LinkedHashMap<>(); + context.put("session_kind", "sql_editor"); + context.put("interaction_mode", "draft_only"); + context.put("command_language", "sql"); + context.put("dialect", StringUtils.defaultString(connectInfo.getDbType()).toLowerCase(Locale.ROOT)); + context.put("protocol", identity.getProtocol()); + context.put("asset_id", identity.getAssetId()); + context.put("asset_name", identity.getAsset()); + context.put("platform_type", StringUtils.defaultString(connectInfo.getDbType())); + context.put("database", StringUtils.defaultString(connectInfo.getDb())); + + Map manifest = new LinkedHashMap<>(); + manifest.put("version", PROTOCOL_VERSION); + manifest.put("resource_session_id", identity.getId()); + manifest.put("profile", "sql"); + manifest.put("revision", 1); + manifest.put("context", context); + manifest.put("tools", toolDefinitions()); + new PacketIO(webSocket).sendPacket("mcp.manifest", manifest); + } + + void handleToolRequest( + WebSocketSession webSocket, + String token, + JMSSession session, + JsonObject packet + ) { + String requestID = ""; + try { + JsonObject request = rpcData(packet, session); + requestID = stringValue(request, "id", 128); + if (requestID.isBlank() || !"2.0".equals(stringValue(request, "jsonrpc", 8)) + || !"tools/call".equals(stringValue(request, "method", 32))) { + throw new IllegalArgumentException("Invalid MCP tool request"); + } + JsonObject params = objectValue(request, "params"); + String toolName = stringValue(params, "name", 128).toLowerCase(Locale.ROOT); + JsonObject arguments = objectValue(params, "arguments"); + JsonObject metadata = objectValue(params, "_meta"); + validateBinding(metadata, session); + JsonElement context = metadata.get(SQL_CONTEXT_META_KEY); + String operation = stringValue(metadata, SQL_OPERATION_META_KEY, 32).toLowerCase(Locale.ROOT); + if (context == null || !context.isJsonObject() || !OPERATIONS.contains(operation)) { + throw new IllegalArgumentException("Invalid SQL editor binding"); + } + + String taskKey = taskKey(webSocket, requestID); + ToolTask task = new ToolTask(webSocket, session, requestID, toolName, () -> { + try { + SessionManager.setContext(token); + var resolved = toolService.resolveRequestContext(session, GSON.toJson(context), operation); + String resultJSON = toolService.execute(session, resolved, toolName, GSON.toJson(arguments)); + if (resultJSON.length() > MAX_TOOL_RESULT_BYTES) { + throw new IllegalStateException("Database tool result is too large"); + } + JsonParser.parseString(resultJSON).getAsJsonObject(); + return resultJSON; + } finally { + SessionManager.setContext(null); + } + }); + if (tasks.putIfAbsent(taskKey, task) != null) { + sendToolError(webSocket, session, requestID, -32600, "Duplicate MCP request id"); + return; + } + try { + // Include queue time in the deadline, and retain the bounded worker pool even if JDBC ignores interruption. + task.setDeadline(deadlineExecutor.schedule(() -> task.cancelAs("timeout"), + toolTimeout.toMillis(), TimeUnit.MILLISECONDS)); + if (!task.isDone()) toolExecutor.execute(task); + } catch (RejectedExecutionException e) { + task.reject(e); + } + } catch (IllegalArgumentException e) { + sendToolError(webSocket, session, requestID, -32602, e.getMessage()); + } + } + + void handleToolCancel(WebSocketSession webSocket, JMSSession session, JsonObject packet) { + String requestID = ""; + try { + JsonObject request = rpcData(packet, session); + if (!"2.0".equals(stringValue(request, "jsonrpc", 8)) + || !"notifications/cancelled".equals(stringValue(request, "method", 64))) { + throw new IllegalArgumentException("Invalid MCP cancellation"); + } + requestID = stringValue(objectValue(request, "params"), "requestId", 128); + if (requestID.isBlank()) { + throw new IllegalArgumentException("Invalid MCP cancellation id"); + } + ToolTask task = tasks.get(taskKey(webSocket, requestID)); + boolean cancelled = task != null && task.cancelAs("cancelled"); + sendRPC(webSocket, session, "mcp.cancel_result", Map.of( + "jsonrpc", "2.0", + "id", requestID, + "result", Map.of("cancelled", cancelled) + )); + } catch (IllegalArgumentException e) { + sendToolError(webSocket, session, requestID, -32602, e.getMessage()); + } + } + + private final class ToolTask extends FutureTask { + private final WebSocketSession webSocket; + private final JMSSession session; + private final String requestID; + private final String toolName; + private volatile ScheduledFuture deadline; + private String cancellationStatus = "cancelled"; + + ToolTask(WebSocketSession webSocket, JMSSession session, String requestID, + String toolName, Callable callable) { + super(callable); + this.webSocket = webSocket; + this.session = session; + this.requestID = requestID; + this.toolName = toolName; + } + + void setDeadline(ScheduledFuture deadline) { + this.deadline = deadline; + if (isDone()) deadline.cancel(false); + } + + synchronized boolean cancelAs(String status) { + if (isDone()) return false; + cancellationStatus = status; + return cancel(true); + } + + void reject(RejectedExecutionException cause) { + setException(cause); + } + + @Override + protected void done() { + tasks.remove(taskKey(webSocket, requestID), this); + if (deadline != null) deadline.cancel(false); + toolExecutor.remove(this); + // FutureTask selects one terminal outcome; an interrupted driver returning late cannot send another result. + if (!webSocket.isOpen()) return; + try { + if (isCancelled()) { + sendOutcome(cancellationStatus); + } else { + sendToolResult(webSocket, session, requestID, get()); + } + } catch (ExecutionException e) { + Throwable cause = e.getCause(); + String outcome = expectedOutcome(cause); + if (outcome != null) { + sendOutcome(outcome); + } else if (cause instanceof RejectedExecutionException) { + sendToolError(webSocket, session, requestID, -32000, "Database tool queue is full"); + } else if (cause instanceof IllegalArgumentException || cause instanceof IllegalStateException) { + sendToolError(webSocket, session, requestID, -32602, cause.getMessage()); + } else { + log.warn("Chen database agent tool failed, sessionId={}, tool={}", + session.getJmsSession().getId(), toolName, cause); + sendToolError(webSocket, session, requestID, -32603, "Database tool failed"); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + sendOutcome("cancelled"); + } + } + + private void sendOutcome(String status) { + log.debug("Chen agent tool finished, sessionId={}, tool={}, status={}", + session.getJmsSession().getId(), toolName, status); + sendRPC(webSocket, session, "mcp.response", Map.of( + "jsonrpc", "2.0", "id", requestID, + "result", Map.of( + "isError", true, + "content", List.of(Map.of("type", "text", "text", + "timeout".equals(status) ? "Database tool timed out" : "Database tool was cancelled")), + "_meta", Map.of(AGENT_BINDING_META_KEY, Map.of("status", status, "code", "tool_" + status)) + ) + )); + } + } + + private static String expectedOutcome(Throwable cause) { + // Drivers may wrap timeout and interruption exceptions in SQLException. + for (int depth = 0; cause != null && depth < 16; depth++, cause = cause.getCause()) { + if (cause instanceof SQLTimeoutException || cause instanceof SocketTimeoutException + || cause instanceof TimeoutException) return "timeout"; + if (cause instanceof InterruptedException || cause instanceof CancellationException) return "cancelled"; + } + return null; + } + + private static JsonObject rpcData(JsonObject packet, JMSSession session) { + if (numberValue(packet, "version") != PROTOCOL_VERSION + || !session.getJmsSession().getId().equals(stringValue(packet, "resource_session_id", 128))) { + throw new IllegalArgumentException("MCP resource binding does not match"); + } + JsonElement data = packet.get("data"); + if (data != null && data.isJsonPrimitive() && data.getAsJsonPrimitive().isString()) { + data = JsonParser.parseString(data.getAsString()); + } + if (data == null || !data.isJsonObject()) { + throw new IllegalArgumentException("Invalid MCP payload"); + } + return data.getAsJsonObject(); + } + + private static void validateBinding(JsonObject metadata, JMSSession session) { + JsonElement value = metadata.get(AGENT_BINDING_META_KEY); + if (value == null || !value.isJsonObject()) { + throw new IllegalArgumentException("Agent tool binding is missing"); + } + JsonObject binding = value.getAsJsonObject(); + if (!session.getJmsSession().getId().equals(stringValue(binding, "resource_session_id", 128)) + || numberValue(binding, "revision") != 1 + || stringValue(binding, "tool_call_id", 128).isBlank()) { + throw new IllegalArgumentException("Agent tool binding does not match"); + } + } + + private void sendToolResult( + WebSocketSession webSocket, + JMSSession session, + String requestID, + String resultJSON + ) { + JsonObject structuredContent = JsonParser.parseString(resultJSON).getAsJsonObject(); + Map result = new LinkedHashMap<>(); + result.put("content", List.of(Map.of("type", "text", "text", resultJSON))); + result.put("structuredContent", structuredContent); + sendRPC(webSocket, session, "mcp.response", Map.of( + "jsonrpc", "2.0", "id", requestID, "result", result + )); + } + + private static void sendToolError( + WebSocketSession webSocket, + JMSSession session, + String requestID, + int code, + String message + ) { + sendRPC(webSocket, session, "mcp.response", Map.of( + "jsonrpc", "2.0", + "id", StringUtils.defaultString(requestID), + "error", Map.of("code", code, "message", StringUtils.defaultIfBlank(message, "Database tool failed")) + )); + } + + private static void sendRPC( + WebSocketSession webSocket, + JMSSession session, + String type, + Map response + ) { + Map data = new LinkedHashMap<>(); + data.put("version", PROTOCOL_VERSION); + data.put("resource_session_id", session.getJmsSession().getId()); + data.putAll(response); + new PacketIO(webSocket).sendPacket(type, data); + } + + static List> toolDefinitions() { + List> tools = new ArrayList<>(); + tools.add(tool( + "read_sql_context", + "Read SQL editor context", + "Read Chen-verified dialect, connection scope, active editor target, SQL analysis and last error. " + + "This never reads business rows.", + "{\"type\":\"object\",\"additionalProperties\":false,\"maxProperties\":0}", + false + )); + tools.add(tool( + "inspect_schema", + "Inspect database schema", + "Inspect bounded table, column, key, index and comment metadata in the active schema. " + + "Provide one literal table-name query or up to eight exact tables; query * performs bounded discovery. " + + "This never reads business rows.", + "{\"type\":\"object\",\"additionalProperties\":false," + + "\"anyOf\":[{\"required\":[\"query\"]},{\"required\":[\"tables\"]}]," + + "\"properties\":{" + + "\"query\":{\"type\":\"string\",\"minLength\":1,\"maxLength\":1024," + + "\"description\":\"Literal case-insensitive table-name query; use * only for bounded discovery\"}," + + "\"schema\":{\"type\":\"string\",\"maxLength\":1024," + + "\"description\":\"Optional schema override within the verified editor scope\"}," + + "\"tables\":{\"type\":\"array\",\"minItems\":1,\"maxItems\":8," + + "\"description\":\"Exact table or view names to inspect\"," + + "\"items\":{\"type\":\"string\",\"minLength\":1,\"maxLength\":1024}}}}", + true + )); + tools.add(tool( + "validate_sql", + "Validate SQL draft", + "Parse SQL locally in Chen with Druid and return statement count, type and referenced objects. " + + "valid=false means Chen cannot parse the SQL, not that it is illegal or unsafe. " + + "This never executes SQL.", + "{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"sql\"]," + + "\"properties\":{\"sql\":{\"type\":\"string\",\"minLength\":1,\"maxLength\":131072," + + "\"description\":\"One SQL draft to parse locally without execution\"}}}", + false + )); + Map proposalTool = tool( + "propose_sql", + "Propose SQL draft", + "Prepare SQL as a draft for explicit user review, and wait for the " + + "user to apply or reject it. Query only accepts SQL Chen can parse with Druid; " + + "unparseable vendor-native SQL must be proposed in Console, which inserts it with an explicit notice. " + + "This never executes SQL.", + "{\"type\":\"object\",\"additionalProperties\":false," + + "\"required\":[\"sql\",\"explanation\"],\"properties\":{" + + "\"sql\":{\"type\":\"string\",\"minLength\":1,\"maxLength\":131072," + + "\"description\":\"Complete SQL in the verified dialect\"}," + + "\"explanation\":{\"type\":\"string\",\"maxLength\":4096," + + "\"description\":\"Concise explanation for the user reviewing the draft\"}}}", + false + ); + proposalTool.put("_meta", Map.of(FINAL_RESULT_META_KEY, true)); + tools.add(proposalTool); + return tools; + } + + private static Map tool( + String name, + String title, + String description, + String inputSchema, + boolean openWorldHint + ) { + Map definition = new LinkedHashMap<>(); + definition.put("name", name); + definition.put("title", title); + definition.put("description", description); + definition.put("inputSchema", JsonParser.parseString(inputSchema).getAsJsonObject()); + definition.put("annotations", Map.of( + "readOnlyHint", true, + "destructiveHint", false, + "idempotentHint", true, + "openWorldHint", openWorldHint + )); + return definition; + } + + private JMSSession currentSession(String token) { + var session = SessionManager.getSession(token); + if (!(session instanceof JMSSession jmsSession) + || jmsSession.isClosing() || !jmsSession.isActive()) { + return null; + } + return jmsSession; + } + + private static JsonObject objectValue(JsonObject object, String name) { + JsonElement value = object.get(name); + if (value == null || !value.isJsonObject()) { + throw new IllegalArgumentException("Invalid " + name); + } + return value.getAsJsonObject(); + } + + private static String stringValue(JsonObject object, String name, int maximum) { + try { + JsonElement value = object.get(name); + String result = value == null || value.isJsonNull() ? "" : value.getAsString(); + return result.length() <= maximum ? result : ""; + } catch (RuntimeException e) { + return ""; + } + } + + private static String stringValue(JsonObject object, String name) { + return stringValue(object, name, 128); + } + + private static int numberValue(JsonObject object, String name) { + try { + return object.get(name).getAsInt(); + } catch (RuntimeException e) { + return 0; + } + } + + private static String taskKey(WebSocketSession webSocket, String requestID) { + return webSocket.getId() + "\u0000" + requestID; + } + + private static void sendProtocolError(WebSocketSession webSocket, String code, String message) { + new PacketIO(webSocket).sendPacket("ai_error", Map.of("code", code, "message", message)); + } + + @Override + public void afterConnectionClosed(WebSocketSession webSocket, CloseStatus status) { + cancelSocketTasks(webSocket); + } + + @Override + public void handleTransportError(WebSocketSession webSocket, Throwable exception) { + log.debug("Chen agent tool websocket closed, websocketId={}", webSocket.getId(), exception); + cancelSocketTasks(webSocket); + } + + private void cancelSocketTasks(WebSocketSession webSocket) { + String prefix = webSocket.getId() + "\u0000"; + for (Map.Entry entry : tasks.entrySet()) { + if (entry.getKey().startsWith(prefix) && tasks.remove(entry.getKey(), entry.getValue())) { + entry.getValue().cancelAs("cancelled"); + } + } + } + + @PreDestroy + public void shutdown() { + for (ToolTask task : tasks.values()) { + task.cancelAs("cancelled"); + } + tasks.clear(); + deadlineExecutor.shutdownNow(); + toolExecutor.shutdownNow(); + } + + private static void closeWebSocket(WebSocketSession webSocket) { + try { + if (webSocket.isOpen()) { + webSocket.close(CloseStatus.POLICY_VIOLATION); + } + } catch (IOException ignored) { + } + } +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/ai/SqlAgentToolService.java b/backend/web/src/main/java/org/jumpserver/chen/web/ai/SqlAgentToolService.java new file mode 100644 index 00000000..8b933a34 --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/ai/SqlAgentToolService.java @@ -0,0 +1,1035 @@ +package org.jumpserver.chen.web.ai; + +import com.alibaba.druid.DbType; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.apache.commons.lang3.StringUtils; +import org.jumpserver.chen.framework.console.QueryConsole; +import org.jumpserver.chen.framework.datasource.ConnectionManager; +import org.jumpserver.chen.framework.datasource.Datasource; +import org.jumpserver.chen.framework.datasource.entity.DatasourceInfo; +import org.jumpserver.chen.framework.datasource.entity.resource.ResourceNodeSnapshot; +import org.jumpserver.chen.framework.datasource.metadata.ColumnMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ForeignKeyMetadata; +import org.jumpserver.chen.framework.datasource.metadata.IndexMetadata; +import org.jumpserver.chen.framework.datasource.metadata.IndexPart; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; +import org.jumpserver.chen.framework.datasource.metadata.ObjectRef; +import org.jumpserver.chen.framework.datasource.metadata.PrimaryKeyMetadata; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; +import org.jumpserver.chen.framework.datasource.metadata.RelationMetadata; +import org.jumpserver.chen.framework.datasource.metadata.RelationScope; +import org.jumpserver.chen.framework.datasource.sql.SqlValidationResult; +import org.jumpserver.chen.framework.datasource.sql.SqlValidator; +import org.jumpserver.chen.framework.session.Session; +import org.springframework.stereotype.Service; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +@Service +public class SqlAgentToolService { + static final String CONSOLE_UNPARSEABLE_NOTICE = "Chen cannot validate this SQL with Druid.\n\n" + + "It may use database-native syntax.\n\n" + + "Please review it before execution."; + private static final Gson GSON = new Gson(); + private static final int MAX_CONTEXT_BYTES = 256 * 1024; + private static final int MAX_SQL_BYTES = 128 * 1024; + private static final int MAX_TOOL_ARGUMENT_BYTES = MAX_SQL_BYTES + 8 * 1024; + private static final int MAX_IDENTIFIER_BYTES = 1024; + static final int MAX_INSPECT_TABLES = 8; + static final int MAX_DISCOVER_TABLES = 100; + static final String DISCOVER_TABLES_QUERY = "*"; + private static final int MAX_INSPECT_COLUMNS = 96; + private static final int MAX_INSPECT_RELATIONS = 48; + static final List INSPECT_SCHEMA_DATA_CATEGORIES = List.of( + "connection_metadata", "tables", "columns", "primary_keys", + "foreign_keys", "indexes", "comments", "default_values" + ); + private static final Set CONTEXT_NODE_TYPES = + Set.of("datasource", "database", "schema", "table", "view"); + private static final Set TOOL_NAMES = + Set.of("read_sql_context", "inspect_schema", "validate_sql", "propose_sql"); + private static final Set INSPECT_KINDS = + Set.of(RelationKind.TABLE, RelationKind.VIEW, RelationKind.MATERIALIZED_VIEW); + private static final Set WORKSPACE_TAB_KINDS = + Set.of("query", "console", "data-view", "database", "none"); + private static final Set PROPOSAL_TARGETS = + Set.of("selection", "document", "new_query"); + + public AgentRequestContext resolveRequestContext(Session session, String contextJson, String operation) { + if (session == null || session.isClosing() || !session.isActive()) { + throw new IllegalArgumentException("The database session is not active"); + } + if (contextJson == null || contextJson.isBlank() || contextJson.length() > MAX_CONTEXT_BYTES) { + throw new IllegalArgumentException("Invalid SQL editor context"); + } + JsonObject submitted; + try { + submitted = JsonParser.parseString(contextJson).getAsJsonObject(); + } catch (RuntimeException e) { + throw new IllegalArgumentException("Invalid SQL editor context", e); + } + + String nodeKey = boundedString(submitted, "nodeKey", MAX_IDENTIFIER_BYTES, true); + String consoleId = boundedString(submitted, "consoleId", 256, false); + String workspaceTabId = boundedString(submitted, "workspaceTabId", 256, false); + String workspaceTabKind = boundedString(submitted, "workspaceTabKind", 32, false); + if (workspaceTabKind.isBlank()) { + workspaceTabKind = "none"; + } + boolean requiresConsole = !"none".equals(workspaceTabKind) && !"database".equals(workspaceTabKind); + if (!WORKSPACE_TAB_KINDS.contains(workspaceTabKind) + || (requiresConsole && consoleId.isBlank())) { + throw new IllegalArgumentException("Invalid database workspace context"); + } + var console = consoleId.isBlank() ? null : session.getConsoles().get(consoleId); + if (consoleId.isBlank() ? requiresConsole + : console == null || !nodeKey.equals(console.getNodeKey())) { + throw new IllegalArgumentException("Invalid database console context"); + } + + ResourceNodeSnapshot node; + if (console == null) { + node = session.getDatasource().getResourceBrowser().getIndexedNode(nodeKey); + } else { + var context = console.getContext(); + node = new ResourceNodeSnapshot( + context.nodeKey(), context.nodeType(), context.database(), context.schema(), context.table(), null + ); + } + if (node == null || !CONTEXT_NODE_TYPES.contains(node.type())) { + throw new IllegalArgumentException("Invalid database resource context"); + } + + String database = node.database(); + String schema = node.schema(); + String currentContext = ""; + ConnectionManager manager = session.getDatasource().getConnectionManager(); + if (console instanceof QueryConsole queryConsole) { + currentContext = queryConsole.getCurrentContext(); + if (StringUtils.isNotBlank(currentContext)) { + if (StringUtils.equals(manager.getContextKey(), manager.getDatabaseContextKey())) { + database = currentContext; + } else { + schema = currentContext; + } + } + } + String displaySchema = schema; + schema = normalizeMetadataSchema(database, schema); + + String documentSql = boundedString(submitted, "documentSql", MAX_SQL_BYTES, false); + int selectionFrom = nonNegativeInt(submitted, "selectionFrom"); + int selectionTo = nonNegativeInt(submitted, "selectionTo"); + if (selectionFrom > documentSql.length() || selectionTo > documentSql.length() + || selectionTo < selectionFrom) { + throw new IllegalArgumentException("Invalid SQL editor selection"); + } + boolean hasSelection = selectionTo > selectionFrom; + String selectedSql = hasSelection ? documentSql.substring(selectionFrom, selectionTo) : ""; + String tabId = boundedString(submitted, "tabId", 256, false); + String proposalTarget = boundedString(submitted, "proposalTarget", 32, true); + if (!PROPOSAL_TARGETS.contains(proposalTarget) + || ("selection".equals(proposalTarget) && !hasSelection) + || (("selection".equals(proposalTarget) || "document".equals(proposalTarget)) + && tabId.isBlank())) { + throw new IllegalArgumentException("Invalid SQL proposal target"); + } + long revision = submitted.has("revision") ? submitted.get("revision").getAsLong() : 0; + if (revision < 0) { + throw new IllegalArgumentException("Invalid SQL editor revision"); + } + + Datasource datasource = session.getDatasource(); + JsonObject lastError = "repair".equals(operation) ? sanitizeLastError(submitted.get("lastError")) : null; + if (lastError != null && hasSelection) { + lastError.addProperty("sql", selectedSql); + } + String dialect = StringUtils.defaultString(datasource.getConnectInfo().getDbType()) + .toLowerCase(Locale.ROOT); + String targetSql = hasSelection ? selectedSql : documentSql; + Map sqlAnalysis = StringUtils.isBlank(targetSql) + ? null : validateSQL(datasource.getDruidDbType(), targetSql); + JsonObject sanitized = new JsonObject(); + sanitized.addProperty("dialect", dialect); + addNullableString(sanitized, "database", database); + addNullableString(sanitized, "schema", schema); + addNullableString(sanitized, "displaySchema", displaySchema); + sanitized.addProperty("nodeKey", node.key()); + sanitized.addProperty("nodeType", node.type()); + addNullableString(sanitized, "table", node.table()); + sanitized.addProperty("consoleId", consoleId); + sanitized.addProperty("paneId", boundedString(submitted, "paneId", 256, false)); + sanitized.addProperty("tabId", tabId); + sanitized.addProperty("proposalTarget", proposalTarget); + sanitized.addProperty("workspaceTabId", workspaceTabId); + sanitized.addProperty("workspaceTabKind", workspaceTabKind); + sanitized.addProperty("currentContext", currentContext); + sanitized.addProperty("revision", revision); + sanitized.addProperty("selectionFrom", selectionFrom); + sanitized.addProperty("selectionTo", selectionTo); + sanitized.addProperty("selectedSql", selectedSql); + sanitized.addProperty("documentSql", hasSelection ? "" : documentSql); + sanitized.add("connectionContext", connectionContext( + dialect, database, displaySchema, schema, currentContext, + manager.getContextKey(), manager.getDatabaseContextKey(), node + )); + if (sqlAnalysis != null) { + sanitized.add("currentSqlAnalysis", GSON.toJsonTree(sqlAnalysis)); + sanitized.add("referencedTables", GSON.toJsonTree(sqlAnalysis.get("tables"))); + } else { + sanitized.add("referencedTables", GSON.toJsonTree(List.of())); + } + if (lastError != null) { + sanitized.add("lastError", lastError); + } + String sanitizedJson = GSON.toJson(sanitized); + if (sanitizedJson.length() > MAX_CONTEXT_BYTES) { + throw new IllegalArgumentException("SQL editor context is too large"); + } + return new AgentRequestContext( + dialect, database, schema, node.table(), node.key(), + consoleId, currentContext, + sanitizedJson + ); + } + + public String execute(Session session, AgentRequestContext context, String toolName, String argumentsJson) + throws SQLException { + if (session == null || context == null || session.isClosing() || !session.isActive()) { + throw new IllegalStateException("The database session is not active"); + } + assertRequestContextCurrent(session, context); + toolName = StringUtils.defaultString(toolName).trim().toLowerCase(Locale.ROOT); + if (!TOOL_NAMES.contains(toolName)) { + throw new IllegalArgumentException("Unsupported SQL assistant tool"); + } + if (argumentsJson == null || argumentsJson.length() > MAX_TOOL_ARGUMENT_BYTES) { + throw new IllegalArgumentException("Invalid SQL assistant tool arguments"); + } + JsonObject arguments = parseToolArguments(argumentsJson); + + Datasource datasource = session.getDatasource(); + ConnectionManager connectionManager = datasource.getConnectionManager(); + connectionManager.setDatabaseContext(StringUtils.defaultString(context.database())); + Object result = switch (toolName) { + case "read_sql_context" -> Map.of( + "kind", "sql_context", + "context", JsonParser.parseString(context.sanitizedJson()).getAsJsonObject() + ); + case "inspect_schema" -> Map.of( + "kind", "schema", + "schema", inspectSchema(datasource, context, arguments) + ); + case "validate_sql" -> Map.of( + "kind", "validation", + "analysis", SqlValidator.validate( + datasource.getDruidDbType(), boundedString(arguments, "sql", MAX_SQL_BYTES, true) + ).toAnalysisMap() + ); + case "propose_sql" -> proposeSQL(datasource, context, arguments); + default -> throw new IllegalArgumentException("Unsupported SQL assistant tool"); + }; + return GSON.toJson(result); + } + + private static Map proposeSQL( + Datasource datasource, + AgentRequestContext context, + JsonObject arguments + ) { + String sql = boundedString(arguments, "sql", MAX_SQL_BYTES, true).trim(); + String explanation = boundedString(arguments, "explanation", 4 * 1024, false).trim(); + JsonObject editor = JsonParser.parseString(context.sanitizedJson()).getAsJsonObject(); + boolean consoleWorkspace = "console".equals(boundedString(editor, "workspaceTabKind", 32, false)); + SqlValidationResult validation = SqlValidator.validate(datasource.getDruidDbType(), sql); + if (!validation.parseable()) { + if (!consoleWorkspace) { + throw new IllegalArgumentException(SqlValidator.queryUnsupportedMessage()); + } + explanation = appendNotice(explanation, CONSOLE_UNPARSEABLE_NOTICE); + } + Map analysis = validation.parseable() ? validation.toAnalysisMap() : null; + int selectionFrom = editor.get("selectionFrom").getAsInt(); + int selectionTo = editor.get("selectionTo").getAsInt(); + String target = editor.get("proposalTarget").getAsString(); + String originalSQL = "selection".equals(target) + ? editor.get("selectedSql").getAsString() + : "document".equals(target) ? editor.get("documentSql").getAsString() : ""; + if (!"new_query".equals(target) && sql.equals(originalSQL.trim())) { + throw new IllegalArgumentException("The SQL proposal does not change the current editor target"); + } + + Map base = new LinkedHashMap<>(); + for (String field : List.of( + "paneId", "tabId", "workspaceTabId", "workspaceTabKind", "currentContext", + "revision", "selectionFrom", "selectionTo", "nodeKey", "database", "schema" + )) { + JsonElement value = editor.get(field); + if (value != null && !value.isJsonNull()) { + base.put(field, GSON.fromJson(value, Object.class)); + } + } + base.put("target", target); + + Map proposal = new LinkedHashMap<>(); + proposal.put("sql", sql); + proposal.put("originalSql", originalSQL); + proposal.put("explanation", explanation); + if (analysis != null) { + proposal.put("analysis", analysis); + } + proposal.put("base", base); + + Map result = new LinkedHashMap<>(); + result.put("kind", "proposal"); + if (analysis != null) { + result.put("analysis", analysis); + } + result.put("proposal", proposal); + return result; + } + + private static String appendNotice(String explanation, String notice) { + return explanation.isBlank() ? notice : explanation + "\n\n" + notice; + } + + MetadataApprovalScope resolveMetadataApprovalScope( + AgentRequestContext context, + String argumentsJson + ) { + if (context == null) { + throw new IllegalArgumentException("Invalid SQL editor context"); + } + return resolveMetadataApprovalScope(context, parseToolArguments(argumentsJson)); + } + + private MetadataApprovalScope resolveMetadataApprovalScope( + AgentRequestContext context, + JsonObject arguments + ) { + String query = boundedString(arguments, "query", MAX_IDENTIFIER_BYTES, false).trim(); + List requestedTables = boundedStringList(arguments, "tables", MAX_INSPECT_TABLES); + if (query.isBlank() && requestedTables.isEmpty()) { + throw new IllegalArgumentException("Schema inspection query or tables are required"); + } + boolean discovery = DISCOVER_TABLES_QUERY.equals(query); + if (discovery && !requestedTables.isEmpty()) { + throw new IllegalArgumentException("Table discovery cannot include explicit tables"); + } + + String activeSchema = normalizeMetadataSchema(context.database(), context.schema()); + if (isBlockedSystemScope(context.dialect(), context.database(), activeSchema)) { + throw new IllegalArgumentException(blockedSystemScopeMessage( + context.dialect(), context.database(), activeSchema + )); + } + if (requiresSchemaScope(context.dialect()) && activeSchema.isBlank()) { + throw new IllegalArgumentException("Schema inspection requires an active schema"); + } + if (requiresDatabaseScope(context.dialect()) && StringUtils.isBlank(context.database())) { + throw new IllegalArgumentException("Schema inspection requires an active database"); + } + String requestedSchema = normalizeMetadataSchema( + context.database(), boundedString(arguments, "schema", MAX_IDENTIFIER_BYTES, false) + ); + if (StringUtils.isNotBlank(requestedSchema) + && !requestedSchema.equalsIgnoreCase(activeSchema)) { + throw new IllegalArgumentException("Schema inspection must stay in the active schema"); + } + + List tables = new ArrayList<>(requestedTables.size()); + for (String requestedTable : requestedTables) { + List parts = qualifiedIdentifierParts(requestedTable); + validateTableQualifier(parts, context.database(), activeSchema); + tables.add(parts.get(parts.size() - 1)); + } + return new MetadataApprovalScope( + StringUtils.defaultString(context.database()), + StringUtils.defaultString(activeSchema), + context.nodeKey(), + List.copyOf(tables), + query, + discovery + ); + } + + private static JsonObject parseToolArguments(String argumentsJson) { + if (argumentsJson == null || argumentsJson.length() > MAX_TOOL_ARGUMENT_BYTES) { + throw new IllegalArgumentException("Invalid SQL assistant tool arguments"); + } + try { + return JsonParser.parseString(argumentsJson).getAsJsonObject(); + } catch (RuntimeException e) { + throw new IllegalArgumentException("Invalid SQL assistant tool arguments", e); + } + } + + static Map validateSQL(DbType dbType, String sql) { + return SqlValidator.validate(dbType, sql).toAnalysisMap(); + } + + private Map inspectSchema( + Datasource datasource, AgentRequestContext context, JsonObject arguments + ) throws SQLException { + MetadataApprovalScope approvedScope = resolveMetadataApprovalScope(context, arguments); + MetadataCatalog catalog = datasource.getMetadataCatalog(); + if (catalog == null) { + throw new IllegalStateException("Database metadata catalog is not available"); + } + RelationScope scope = relationScope(context, approvedScope.schema()); + List available = catalog.listRelations(scope, INSPECT_KINDS).stream() + .filter(relation -> isRelationWithinScope(relation.ref(), scope, context.dialect())) + .toList(); + + Map candidates = new LinkedHashMap<>(); + List missingTables = new ArrayList<>(); + for (String requestedTable : approvedScope.tables()) { + String table = unqualifiedTableName(requestedTable); + RelationMetadata match = findRelation(available, table); + if (match == null) { + missingTables.add(requestedTable); + } else { + candidates.putIfAbsent(metadataKey(match.ref()), match); + } + } + + String query = approvedScope.query(); + int maximumTables = approvedScope.discovery() ? MAX_DISCOVER_TABLES : MAX_INSPECT_TABLES; + boolean truncated = false; + if (!query.isBlank()) { + String needle = approvedScope.discovery() ? "" : query.toLowerCase(Locale.ROOT); + List matches = available.stream() + .filter(relation -> needle.isEmpty() + || relation.ref().name().toLowerCase(Locale.ROOT).contains(needle)) + .filter(relation -> !candidates.containsKey(metadataKey(relation.ref()))) + .toList(); + truncated = candidates.size() + matches.size() > maximumTables; + for (RelationMetadata match : matches) { + if (candidates.size() >= maximumTables) { + break; + } + candidates.putIfAbsent(metadataKey(match.ref()), match); + } + } + + Set allowedTables = candidates.values().stream() + .map(relation -> relation.ref().name().toLowerCase(Locale.ROOT)) + .collect(java.util.stream.Collectors.toUnmodifiableSet()); + List> objects = new ArrayList<>(candidates.size()); + if (approvedScope.discovery()) { + for (RelationMetadata relation : candidates.values()) { + objects.add(tableInfo(relation)); + } + } else { + objects.addAll(describeRelations( + catalog, scope, List.copyOf(candidates.values()), allowedTables, context.dialect() + )); + } + + Map result = new LinkedHashMap<>(); + result.put("connection", inspectConnectionBanner(datasource, context)); + result.put("requestedScope", metadataScopeMap(context.database(), approvedScope.schema())); + result.put("resolvedScope", metadataScopeMap(scope.catalog(), scope.schema())); + result.put("objects", objects); + result.put("missingTables", missingTables); + result.put("matchCount", objects.size()); + result.put("discovery", approvedScope.discovery()); + result.put("truncated", truncated); + return result; + } + + private List> describeRelations( + MetadataCatalog catalog, + RelationScope scope, + List relations, + Set allowedTables, + String dialect + ) throws SQLException { + List refs = relations.stream().map(RelationMetadata::ref).toList(); + Map> columnsByOwner = new LinkedHashMap<>(); + for (ColumnMetadata column : catalog.listColumns(refs)) { + columnsByOwner.computeIfAbsent(metadataKey(column.owner()), ignored -> new ArrayList<>()).add(column); + } + Map> primaryKeysByOwner = new LinkedHashMap<>(); + for (PrimaryKeyMetadata primaryKey : catalog.listPrimaryKeys(refs)) { + primaryKeysByOwner.computeIfAbsent(metadataKey(primaryKey.owner()), ignored -> new LinkedHashSet<>()) + .addAll(primaryKey.columns()); + } + Map> foreignKeysByOwner = new LinkedHashMap<>(); + for (ForeignKeyMetadata foreignKey : catalog.listForeignKeys(refs)) { + foreignKeysByOwner.computeIfAbsent(metadataKey(foreignKey.owner()), ignored -> new ArrayList<>()) + .add(foreignKey); + } + Map> indexesByOwner = new LinkedHashMap<>(); + for (IndexMetadata index : catalog.listIndexes(scope)) { + indexesByOwner.computeIfAbsent(metadataKey(index.owner()), ignored -> new ArrayList<>()).add(index); + } + + List> objects = new ArrayList<>(relations.size()); + for (RelationMetadata relation : relations) { + String key = metadataKey(relation.ref()); + List> columns = serializeColumns( + columnsByOwner.getOrDefault(key, List.of()), + primaryKeysByOwner.getOrDefault(key, Set.of()) + ); + Map item = tableInfo(relation); + item.put("columns", columns); + item.put("foreignKeys", serializeForeignKeys( + foreignKeysByOwner.getOrDefault(key, List.of()), + scope, allowedTables, dialect + )); + item.put("indexes", serializeIndexes(indexesByOwner.getOrDefault(key, List.of()))); + item.put("truncated", columns.size() == MAX_INSPECT_COLUMNS); + objects.add(item); + } + return objects; + } + + private static List> serializeColumns( + List columns, Set primaryKeys + ) { + Set primaryKeyNames = primaryKeys.stream() + .map(name -> name.toLowerCase(Locale.ROOT)) + .collect(java.util.stream.Collectors.toUnmodifiableSet()); + List> result = new ArrayList<>(); + for (ColumnMetadata column : columns) { + if (result.size() >= MAX_INSPECT_COLUMNS) { + break; + } + Map item = new LinkedHashMap<>(); + item.put("name", column.name()); + item.put("type", column.nativeType()); + item.put("jdbcType", column.jdbcType()); + item.put("size", column.size()); + item.put("scale", column.scale()); + item.put("nullable", column.nullable()); + item.put("defaultValue", column.defaultValue()); + item.put("comment", column.comment()); + item.put("position", column.ordinal()); + item.put("primaryKey", primaryKeyNames.contains(StringUtils.defaultString(column.name()).toLowerCase(Locale.ROOT))); + result.add(item); + } + return result; + } + + private static List> serializeForeignKeys( + List foreignKeys, + RelationScope scope, + Set allowedTables, + String dialect + ) { + List> result = new ArrayList<>(); + for (ForeignKeyMetadata foreignKey : foreignKeys) { + ObjectRef referenced = foreignKey.referenced(); + if (referenced == null + || !isRelationWithinScope(referenced, scope, dialect) + || !allowedTables.contains(StringUtils.defaultString(referenced.name()).toLowerCase(Locale.ROOT))) { + continue; + } + List columns = foreignKey.columns() == null ? List.of() : foreignKey.columns(); + List referencedColumns = foreignKey.referencedColumns() == null + ? List.of() : foreignKey.referencedColumns(); + int count = Math.min(columns.size(), referencedColumns.size()); + for (int index = 0; index < count && result.size() < MAX_INSPECT_RELATIONS; index++) { + Map item = new LinkedHashMap<>(); + item.put("name", foreignKey.name()); + item.put("column", columns.get(index)); + item.put("referencedSchema", referenced.schema()); + item.put("referencedTable", referenced.name()); + item.put("referencedColumn", referencedColumns.get(index)); + result.add(item); + } + if (result.size() >= MAX_INSPECT_RELATIONS) { + break; + } + } + return result; + } + + private static List> serializeIndexes(List indexes) { + List> result = new ArrayList<>(); + for (IndexMetadata index : indexes) { + List parts = index.parts() == null ? List.of() : index.parts(); + for (IndexPart part : parts) { + String column = part.columnName() != null ? part.columnName() : part.expression(); + if (column == null) { + continue; + } + Map item = new LinkedHashMap<>(); + item.put("name", index.name()); + item.put("column", column); + item.put("unique", index.unique()); + item.put("position", part.ordinal()); + result.add(item); + if (result.size() >= MAX_INSPECT_RELATIONS) { + return result; + } + } + } + return result; + } + + private static RelationMetadata findRelation(List relations, String table) { + for (RelationMetadata relation : relations) { + if (table.equalsIgnoreCase(relation.ref().name())) { + return relation; + } + } + return null; + } + + private static Map tableInfo(RelationMetadata relation) { + ObjectRef ref = relation.ref(); + Map result = new LinkedHashMap<>(); + result.put("database", ref.catalog()); + result.put("schema", ref.schema()); + result.put("name", ref.name()); + result.put("type", jdbcRelationType(ref.kind())); + result.put("comment", relation.comment()); + return result; + } + + private static String jdbcRelationType(RelationKind kind) { + if (kind == RelationKind.MATERIALIZED_VIEW) { + return "MATERIALIZED VIEW"; + } + if (kind == RelationKind.VIEW) { + return "VIEW"; + } + return "TABLE"; + } + + private static boolean isRelationWithinScope(ObjectRef ref, RelationScope scope, String dialect) { + if (ref == null) { + return false; + } + String database = StringUtils.defaultString(ref.catalog()); + String schema = StringUtils.defaultString(ref.schema()); + if (StringUtils.isNotBlank(scope.catalog()) && !scope.catalog().equalsIgnoreCase(database)) { + return false; + } + if (StringUtils.isNotBlank(scope.schema()) && !scope.schema().equalsIgnoreCase(schema)) { + return false; + } + return !isBlockedSystemScope(dialect, database, schema); + } + + private static String metadataKey(ObjectRef ref) { + return (StringUtils.defaultString(ref.catalog()) + "\u0000" + + StringUtils.defaultString(ref.schema()) + "\u0000" + + StringUtils.defaultString(ref.name())).toLowerCase(Locale.ROOT); + } + + private static RelationScope relationScope(AgentRequestContext context, String requestedSchema) { + String database = StringUtils.trimToNull(context.database()); + String schema = StringUtils.trimToNull(normalizeMetadataSchema( + StringUtils.defaultString(database), + StringUtils.defaultIfBlank(requestedSchema, context.schema()) + )); + String dialect = StringUtils.defaultString(context.dialect()).toLowerCase(Locale.ROOT); + if ("mysql".equals(dialect) || "mariadb".equals(dialect)) { + // MySQL/MariaDB providers query INFORMATION_SCHEMA by scope.schema(), + // matching ResourceBrowser.resolveScope(catalog=null, schema=database). + return new RelationScope(null, schema != null ? schema : database); + } + if ("oracle".equals(dialect) || "dameng".equals(dialect) || "dm".equals(dialect)) { + return new RelationScope(null, schema); + } + return new RelationScope(database, schema); + } + + private static Map inspectConnectionBanner(Datasource datasource, AgentRequestContext context) { + DatasourceInfo info = datasource.getInfo(); + Map result = new LinkedHashMap<>(); + result.put("dialect", context.dialect()); + result.put("databaseProduct", info == null ? "" : StringUtils.defaultString(info.getDbType())); + result.put("databaseVersion", info == null ? "" : StringUtils.defaultString(info.getVersion())); + result.put("driverName", info == null ? "" : StringUtils.defaultString(info.getDriverClassName())); + result.put("driverVersion", info == null ? "" : StringUtils.defaultString(info.getDriverVersion())); + result.put("identifierQuote", identifierQuote(context.dialect())); + return result; + } + + static String normalizeMetadataSchema(String database, String schema) { + String normalized = StringUtils.trimToEmpty(schema); + String catalog = StringUtils.trimToEmpty(database); + if (StringUtils.isNotBlank(catalog) + && StringUtils.startsWithIgnoreCase(normalized, catalog + ".")) { + normalized = normalized.substring(catalog.length() + 1); + } + return normalized; + } + + private static void assertRequestContextCurrent(Session session, AgentRequestContext expected) { + ResourceNodeSnapshot node; + String currentContext = ""; + if (StringUtils.isBlank(expected.consoleId())) { + node = session.getDatasource().getResourceBrowser().getIndexedNode(expected.nodeKey()); + } else { + var console = session.getConsoles().get(expected.consoleId()); + if (console == null || !expected.nodeKey().equals(console.getNodeKey())) { + throw new IllegalStateException("The database workspace context has changed"); + } + var consoleContext = console.getContext(); + node = new ResourceNodeSnapshot( + consoleContext.nodeKey(), consoleContext.nodeType(), consoleContext.database(), + consoleContext.schema(), consoleContext.table(), null + ); + if (console instanceof QueryConsole queryConsole) { + currentContext = queryConsole.getCurrentContext(); + } + } + if (node == null || !expected.nodeKey().equals(node.key())) { + throw new IllegalStateException("The database workspace context has changed"); + } + + String database = node.database(); + String schema = node.schema(); + ConnectionManager manager = session.getDatasource().getConnectionManager(); + if (StringUtils.isNotBlank(currentContext)) { + if (StringUtils.equals(manager.getContextKey(), manager.getDatabaseContextKey())) { + database = currentContext; + } else { + schema = currentContext; + } + } + schema = normalizeMetadataSchema(database, schema); + if (!StringUtils.equals(database, expected.database()) + || !StringUtils.equalsIgnoreCase(schema, expected.schema()) + || !StringUtils.equals(currentContext, expected.currentContext())) { + throw new IllegalStateException("The database workspace context has changed"); + } + } + + private static String blockedSystemScopeMessage(String dialect, String database, String schema) { + if (isBlockedSystemSchema(dialect, schema)) { + return "The active schema '" + normalizePolicyIdentifier(schema) + + "' is a protected system schema; table metadata is unavailable in this scope"; + } + if (isBlockedSystemDatabase(dialect, database)) { + return "The active database '" + normalizePolicyIdentifier(database) + + "' is a protected system database; table metadata is unavailable in this scope"; + } + return "The active database scope contains protected system objects"; + } + + private static boolean isBlockedSystemScope(String dialect, String database, String schema) { + return isBlockedSystemDatabase(dialect, database) || isBlockedSystemSchema(dialect, schema); + } + + private static boolean isBlockedSystemDatabase(String dialect, String database) { + String value = normalizePolicyIdentifier(database); + String db = StringUtils.defaultString(dialect).toLowerCase(Locale.ROOT); + if (value.isBlank()) { + return false; + } + if ("information_schema".equals(value)) { + return true; + } + return switch (db) { + case "mysql", "mariadb" -> Set.of("mysql", "performance_schema", "sys").contains(value); + case "sqlserver" -> Set.of("master", "model", "msdb", "tempdb").contains(value); + case "clickhouse" -> "system".equals(value); + default -> false; + }; + } + + private static boolean isBlockedSystemSchema(String dialect, String schema) { + String value = normalizePolicyIdentifier(schema); + String db = StringUtils.defaultString(dialect).toLowerCase(Locale.ROOT); + if (value.isBlank()) { + return false; + } + if ("information_schema".equals(value)) { + return true; + } + return switch (db) { + case "postgresql", "postgres" -> value.equals("pg_catalog") + || value.equals("pg_toast") + || value.startsWith("pg_temp_") + || value.startsWith("pg_toast_temp_"); + case "sqlserver" -> value.equals("sys"); + case "oracle" -> Set.of( + "sys", "system", "xdb", "mdsys", "ctxsys", "audsys" + ).contains(value); + case "db2" -> Set.of( + "sysibm", "syscat", "sysstat", "sysfun", "sysproc", "systools" + ).contains(value); + case "clickhouse" -> value.equals("system"); + case "dm", "dameng" -> Set.of("sys", "system", "sysauditor").contains(value); + default -> false; + }; + } + + private static String normalizePolicyIdentifier(String value) { + String normalized = StringUtils.trimToEmpty(value); + if (normalized.length() >= 2 && ((normalized.startsWith("\"") && normalized.endsWith("\"")) + || (normalized.startsWith("`") && normalized.endsWith("`")) + || (normalized.startsWith("[") && normalized.endsWith("]")))) { + normalized = normalized.substring(1, normalized.length() - 1); + } + return normalized.toLowerCase(Locale.ROOT); + } + + private static boolean requiresSchemaScope(String dialect) { + return !Set.of("mysql", "mariadb").contains( + StringUtils.defaultString(dialect).toLowerCase(Locale.ROOT) + ); + } + + private static boolean requiresDatabaseScope(String dialect) { + return Set.of("mysql", "mariadb", "clickhouse", "sqlserver").contains( + StringUtils.defaultString(dialect).toLowerCase(Locale.ROOT) + ); + } + + private static JsonObject connectionContext( + String dialect, + String database, + String displaySchema, + String resolvedSchema, + String currentContext, + String contextKey, + String databaseContextKey, + ResourceNodeSnapshot node + ) { + JsonObject result = new JsonObject(); + result.addProperty("dialect", StringUtils.defaultString(dialect)); + addNullableString(result, "database", database); + addNullableString(result, "displaySchema", displaySchema); + addNullableString(result, "resolvedSchema", resolvedSchema); + addNullableString(result, "currentContext", currentContext); + result.addProperty("contextKey", StringUtils.defaultString(contextKey)); + result.addProperty("databaseContextKey", StringUtils.defaultString(databaseContextKey)); + result.addProperty("nodeType", StringUtils.defaultString(node.type())); + addNullableString(result, "table", node.table()); + result.addProperty("identifierQuote", identifierQuote(dialect)); + result.addProperty("draftOnly", true); + result.addProperty("businessRowAccess", false); + return result; + } + + private static String identifierQuote(String dialect) { + return switch (StringUtils.defaultString(dialect).toLowerCase(Locale.ROOT)) { + case "mysql", "mariadb", "clickhouse" -> "`"; + case "sqlserver" -> "[]"; + default -> "\""; + }; + } + + private static Map metadataScopeMap(String catalog, String schema) { + Map result = new LinkedHashMap<>(); + result.put("catalog", StringUtils.defaultString(catalog)); + result.put("schema", StringUtils.defaultString(schema)); + return result; + } + + static JsonObject sanitizeLastError(JsonElement value) { + if (value == null || value.isJsonNull()) { + return null; + } + JsonObject submitted; + try { + submitted = value.getAsJsonObject(); + } catch (RuntimeException e) { + throw new IllegalArgumentException("Invalid SQL error context", e); + } + JsonObject sanitized = new JsonObject(); + sanitized.addProperty("kind", boundedString(submitted, "kind", 32, false)); + sanitized.addProperty("title", boundedString(submitted, "title", 256, false)); + sanitized.addProperty("message", boundedString(submitted, "message", 8 * 1024, true)); + sanitized.addProperty("sql", boundedString(submitted, "sql", MAX_SQL_BYTES, false)); + sanitized.addProperty("sqlState", boundedString(submitted, "sqlState", 128, false)); + try { + if (submitted.has("vendorCode") && !submitted.get("vendorCode").isJsonNull()) { + sanitized.addProperty("vendorCode", submitted.get("vendorCode").getAsInt()); + } + if (submitted.has("timestamp") && !submitted.get("timestamp").isJsonNull()) { + sanitized.addProperty("timestamp", submitted.get("timestamp").getAsLong()); + } + } catch (RuntimeException e) { + throw new IllegalArgumentException("Invalid SQL error context", e); + } + return sanitized; + } + + private static int nonNegativeInt(JsonObject object, String name) { + if (!object.has(name)) { + return 0; + } + int value = object.get(name).getAsInt(); + if (value < 0) { + throw new IllegalArgumentException("Invalid SQL editor selection"); + } + return value; + } + + private static List boundedStringList(JsonObject object, String name, int maximum) { + JsonElement value = object.get(name); + if (value == null || value.isJsonNull()) { + return List.of(); + } + LinkedHashMap values = new LinkedHashMap<>(); + try { + for (JsonElement element : value.getAsJsonArray()) { + String text = element.getAsString().trim(); + if (text.isEmpty() || text.length() > MAX_IDENTIFIER_BYTES) { + throw new IllegalArgumentException("Invalid " + name); + } + values.putIfAbsent(text.toLowerCase(Locale.ROOT), text); + if (values.size() > maximum) { + throw new IllegalArgumentException("Too many " + name); + } + } + } catch (IllegalArgumentException e) { + throw e; + } catch (RuntimeException e) { + throw new IllegalArgumentException("Invalid " + name, e); + } + return List.copyOf(values.values()); + } + + private static String unqualifiedTableName(String value) { + List parts = qualifiedIdentifierParts(value); + return parts.get(parts.size() - 1); + } + + private static List qualifiedIdentifierParts(String value) { + String input = StringUtils.trimToEmpty(value); + if (input.isBlank()) { + throw new IllegalArgumentException("Invalid table name"); + } + List parts = new ArrayList<>(); + StringBuilder part = new StringBuilder(); + char quote = 0; + for (int index = 0; index < input.length(); index++) { + char current = input.charAt(index); + if (quote == 0 && (current == '"' || current == '`' || current == '[')) { + quote = current == '[' ? ']' : current; + part.append(current); + continue; + } + if (quote != 0 && current == quote) { + if (index + 1 < input.length() && input.charAt(index + 1) == quote) { + part.append(current).append(current); + index++; + continue; + } + quote = 0; + part.append(current); + continue; + } + if (quote == 0 && current == '.') { + parts.add(normalizeIdentifierPart(part.toString())); + part.setLength(0); + continue; + } + part.append(current); + } + if (quote != 0) { + throw new IllegalArgumentException("Invalid table name"); + } + parts.add(normalizeIdentifierPart(part.toString())); + if (parts.size() > 3) { + throw new IllegalArgumentException("Invalid table name"); + } + return List.copyOf(parts); + } + + private static String normalizeIdentifierPart(String value) { + String result = StringUtils.trimToEmpty(value); + if (result.length() >= 2 && ((result.startsWith("\"") && result.endsWith("\"")) + || (result.startsWith("`") && result.endsWith("`")) + || (result.startsWith("[") && result.endsWith("]")))) { + result = result.substring(1, result.length() - 1); + } + if (result.isBlank() || result.length() > MAX_IDENTIFIER_BYTES) { + throw new IllegalArgumentException("Invalid table name"); + } + return result; + } + + private static void validateTableQualifier(List parts, String database, String schema) { + if (parts.size() >= 2) { + String qualifier = parts.get(parts.size() - 2); + if (!qualifier.equalsIgnoreCase(StringUtils.defaultString(schema)) + && !qualifier.equalsIgnoreCase(StringUtils.defaultString(database))) { + throw new IllegalArgumentException("Table inspection must stay in the active schema"); + } + } + if (parts.size() == 3 + && !parts.get(0).equalsIgnoreCase(StringUtils.defaultString(database))) { + throw new IllegalArgumentException("Table inspection must stay in the active database"); + } + } + + private static String boundedString(JsonObject object, String name, int maximum, boolean required) { + JsonElement value = object.get(name); + String text = value == null || value.isJsonNull() ? "" : value.getAsString(); + if ((required && text.isBlank()) || text.length() > maximum) { + throw new IllegalArgumentException("Invalid " + name); + } + return text; + } + + private static void addNullableString(JsonObject object, String name, String value) { + object.addProperty(name, StringUtils.defaultString(value)); + } + + public record AgentRequestContext( + String dialect, + String database, + String schema, + String table, + String nodeKey, + String consoleId, + String currentContext, + String sanitizedJson + ) { + } + + record MetadataApprovalScope( + String database, + String schema, + String nodeKey, + List tables, + String query, + boolean discovery + ) { + boolean covers(MetadataApprovalScope requested) { + if (requested == null + || !database.equalsIgnoreCase(requested.database) + || !schema.equalsIgnoreCase(requested.schema) + || !nodeKey.equals(requested.nodeKey)) { + return false; + } + if (discovery) { + return requested.discovery || (requested.query.isBlank() + && !requested.tables.isEmpty() + && requested.tables.size() <= MAX_INSPECT_TABLES); + } + if (!query.equalsIgnoreCase(requested.query)) { + return false; + } + Set allowedTables = tables.stream() + .map(value -> value.toLowerCase(Locale.ROOT)) + .collect(java.util.stream.Collectors.toSet()); + return requested.tables.stream() + .map(value -> value.toLowerCase(Locale.ROOT)) + .allMatch(allowedTables::contains); + } + } + + private record MetadataScope(String catalog, String schema) { + } +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/config/LocaleConfig.java b/backend/web/src/main/java/org/jumpserver/chen/web/config/LocaleConfig.java index 44f7dd3b..70d157b1 100644 --- a/backend/web/src/main/java/org/jumpserver/chen/web/config/LocaleConfig.java +++ b/backend/web/src/main/java/org/jumpserver/chen/web/config/LocaleConfig.java @@ -1,27 +1,14 @@ package org.jumpserver.chen.web.config; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.MessageSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.support.AbstractMessageSource; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; import org.springframework.web.servlet.i18n.SessionLocaleResolver; -import java.io.IOException; -import java.text.MessageFormat; -import java.util.HashMap; import java.util.Locale; -import java.util.Map; @Configuration public class LocaleConfig { @@ -44,101 +31,4 @@ public void addInterceptors(InterceptorRegistry registry) { } }; } - - @Bean - public MessageSource messageSource(@Value("${i18n.endpoint}") String endpoint) { - return new JsonMessageSource(endpoint); - } } - -@Slf4j -@Component -class JsonMessageSource extends AbstractMessageSource { - - private final Map> messages = new HashMap<>(); - private final ObjectMapper objectMapper = new ObjectMapper(); - - private final RestTemplate restTemplate = new RestTemplate(); - private final String endpoint; - - - public JsonMessageSource(@Value("${i18n.endpoint}") String endpoint) { - // 首先从环境变量中读取 core 的地址 - - String coreHost = System.getenv("CORE_HOST"); - if (StringUtils.isNotBlank(coreHost)) { - this.endpoint = coreHost; - } else { - this.endpoint = endpoint; - } - - var languages = new String[]{ - "en", "ja", "zh", "zh_hant" - }; - for (var lang : languages) { - loadMessages(lang); - } - } - - private void loadMessages(String lang) { - try { - - var url = String.format("%s/api/v1/settings/i18n/chen/?lang=%s&flat=1", this.endpoint, lang); - - String jsonData = this.restTemplate.getForObject(url, String.class); - Map jsonMap = objectMapper.readValue(jsonData, new TypeReference<>() { - }); - Map flattenedMap = new HashMap<>(); - flattenJson("", jsonMap, flattenedMap); - - log.info(String.format("load locale : %s", lang)); - messages.put(lang, flattenedMap); - } catch (IOException e) { - throw new RuntimeException("Failed to load messages from " + lang, e); - } - } - - private String extractLocaleFromFilename(String filename) { - String basename = filename.substring(filename.lastIndexOf('/') + 1); - int localeStart = basename.indexOf('_') + 1; - int localeEnd = basename.lastIndexOf('.'); - return basename.substring(localeStart, localeEnd); - } - - private void flattenJson(String prefix, Map map, Map resultMap) { - for (Map.Entry entry : map.entrySet()) { - String newPrefix = prefix.isEmpty() ? entry.getKey() : prefix + "." + entry.getKey(); - if (entry.getValue() instanceof Map) { - flattenJson(newPrefix, (Map) entry.getValue(), resultMap); - } else { - resultMap.put(newPrefix, entry.getValue().toString()); - } - } - } - - private String getLangTag(Locale locale) { - if (locale.equals(Locale.US)) { - return "en"; - } else if (locale.equals(Locale.CHINA)) { - return "zh"; - } else if (locale.equals(Locale.JAPAN)) { - return "ja"; - } else if (locale.equals(Locale.TAIWAN) || locale.equals(Locale.TRADITIONAL_CHINESE)) { - return "zh_hant"; - } - return "en"; - } - - @Override - protected MessageFormat resolveCode(String code, Locale locale) { - String languageTag = getLangTag(locale); - Map localeMessages = messages.get(languageTag); - if (localeMessages != null) { - String message = localeMessages.get(code); - if (StringUtils.isNotBlank(message)) { - return new MessageFormat(message, locale); - } - } - return null; - } -} \ No newline at end of file diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/config/WebSocketConfig.java b/backend/web/src/main/java/org/jumpserver/chen/web/config/WebSocketConfig.java index d2bdc441..4b05c8fc 100644 --- a/backend/web/src/main/java/org/jumpserver/chen/web/config/WebSocketConfig.java +++ b/backend/web/src/main/java/org/jumpserver/chen/web/config/WebSocketConfig.java @@ -1,6 +1,7 @@ package org.jumpserver.chen.web.config; import lombok.extern.slf4j.Slf4j; +import org.jumpserver.chen.web.ai.AgentWebSocketHandler; import org.jumpserver.chen.framework.session.SessionManager; import org.jumpserver.chen.framework.ws.ConsoleWebSocketHandler; import org.jumpserver.chen.framework.ws.DBConsoleWebsocketHandler; @@ -38,9 +39,9 @@ public ServletServerContainerFactoryBean createWebSocketContainer() { @Bean - public WebSocketHandlerMapping chenWebSocketHandlerMapping() { + public WebSocketHandlerMapping chenWebSocketHandlerMapping(AgentWebSocketHandler agentWebSocketHandler) { var handlers = new LinkedHashMap(); - handlers.put("/ws/session", createRequestHandler(new SessionWebSocketHandler())); + handlers.put("/ws/session", createRequestHandler(new SessionWebSocketHandler(agentWebSocketHandler))); handlers.put("/ws/console", createRequestHandler(new ConsoleWebSocketHandler())); handlers.put("/ws/db-console", createRequestHandler(new DBConsoleWebsocketHandler())); diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/controller/AuthController.java b/backend/web/src/main/java/org/jumpserver/chen/web/controller/AuthController.java index 34ebd361..37c49118 100644 --- a/backend/web/src/main/java/org/jumpserver/chen/web/controller/AuthController.java +++ b/backend/web/src/main/java/org/jumpserver/chen/web/controller/AuthController.java @@ -69,7 +69,10 @@ private Locale getLanguage(HttpServletRequest request) { return switch (langTag.toLowerCase()) { case "ja" -> Locale.JAPAN; - case "tw", "zh-hant" -> Locale.TAIWAN; + case "ko" -> Locale.KOREAN; + case "pt", "pt-br", "pt_br" -> Locale.of("pt", "BR"); + case "es", "ru", "vi" -> Locale.of(langTag.toLowerCase()); + case "tw", "zh-hant", "zh-tw" -> Locale.TAIWAN; case "zh", "zh-cn", "zh-hans" -> Locale.CHINA; default -> Locale.US; }; diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/controller/ConsoleController.java b/backend/web/src/main/java/org/jumpserver/chen/web/controller/ConsoleController.java index d02a7368..36625948 100644 --- a/backend/web/src/main/java/org/jumpserver/chen/web/controller/ConsoleController.java +++ b/backend/web/src/main/java/org/jumpserver/chen/web/controller/ConsoleController.java @@ -29,13 +29,13 @@ public ResponseEntity exportData(@PathVariable String fileKey) { // export 文件名只能是单个文件名,先拒绝跨平台的路径分隔符和遍历片段。 if (fileKey == null || fileKey.isBlank() || fileKey.contains("/") || fileKey.contains("\\") || fileKey.contains("..")) { - throw new ChenException("Invalid export file"); + throw new ChenException(MessageUtils.getOrDefault("InvalidExportFile", "Invalid export file")); } var basePath = SessionManager.getCurrentSession().getTempPath().toAbsolutePath().normalize(); var filePath = basePath.resolve(fileKey).normalize(); // normalize 后仍须落在会话目录内,且禁止末级符号链接指向目录外文件。 if (!filePath.startsWith(basePath) || !Files.isRegularFile(filePath, LinkOption.NOFOLLOW_LINKS)) { - throw new ChenException("Invalid export file"); + throw new ChenException(MessageUtils.getOrDefault("InvalidExportFile", "Invalid export file")); } Resource resource = new FileSystemResource(filePath.toFile()); var resp = ResponseEntity diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/controller/IndexController.java b/backend/web/src/main/java/org/jumpserver/chen/web/controller/IndexController.java deleted file mode 100644 index ac61ee12..00000000 --- a/backend/web/src/main/java/org/jumpserver/chen/web/controller/IndexController.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.jumpserver.chen.web.controller; - -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; - -@Controller -public class IndexController { - @GetMapping("/connect") - public String index() { - return "/index.html"; - } - -} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/controller/ResourceController.java b/backend/web/src/main/java/org/jumpserver/chen/web/controller/ResourceController.java index 1a5bc4f1..3f02e868 100644 --- a/backend/web/src/main/java/org/jumpserver/chen/web/controller/ResourceController.java +++ b/backend/web/src/main/java/org/jumpserver/chen/web/controller/ResourceController.java @@ -5,9 +5,20 @@ import org.jumpserver.chen.framework.datasource.entity.action.EventEmitter; import org.jumpserver.chen.framework.datasource.entity.form.FormData; import org.jumpserver.chen.framework.datasource.entity.resource.TreeNode; -import org.jumpserver.chen.framework.session.SessionManager; +import org.jumpserver.chen.web.entity.RelationMetadataPage; import org.jumpserver.chen.web.entity.GetHintsRequest; +import org.jumpserver.chen.web.entity.MetadataColumnsRequest; +import org.jumpserver.chen.web.entity.MetadataColumnsResponse; +import org.jumpserver.chen.web.entity.MetadataRelationsRequest; +import org.jumpserver.chen.web.entity.SchemaOverviewMetadata; +import org.jumpserver.chen.web.entity.SchemaOverviewRequest; +import org.jumpserver.chen.web.entity.TableMetadata; +import org.jumpserver.chen.web.entity.TableMetadataRequest; +import org.jumpserver.chen.web.service.HintsService; import org.jumpserver.chen.web.service.ResourceService; +import org.jumpserver.chen.web.service.SchemaOverviewService; +import org.jumpserver.chen.web.service.TableMetadataService; +import org.jumpserver.chen.web.service.SqlMetadataService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -23,6 +34,18 @@ public class ResourceController { @Autowired private ResourceService resourceService; + @Autowired + private SqlMetadataService sqlMetadataService; + + @Autowired + private SchemaOverviewService schemaOverviewService; + + @Autowired + private TableMetadataService tableMetadataService; + + @Autowired + private HintsService hintsService; + @PostMapping("/children") public List getChild(@RequestBody(required = false) TreeNode node, @@ -52,13 +75,30 @@ public EventEmitter submitResourceFormData(@RequestBody FormData form) throws SQ } @PostMapping("/hints") - public Map> getHints(@RequestBody GetHintsRequest request) throws SQLException { - return SessionManager - .getCurrentSession() - .getDatasource() - .getResourceBrowser() - .getSQLHintsHandler() - .getHints(request.getNodeKey(), request.getContext()); + public Map> getHints(@RequestBody GetHintsRequest request) { + return this.hintsService.getHints(request.getNodeKey(), request.getContext()); + } + + @PostMapping("/metadata/relations") + public RelationMetadataPage getMetadataRelations( + @RequestBody MetadataRelationsRequest request + ) { + return this.sqlMetadataService.listRelations(request); + } + + @PostMapping("/metadata/columns") + public MetadataColumnsResponse getMetadataColumns(@RequestBody MetadataColumnsRequest request) { + return this.sqlMetadataService.listColumns(request); + } + + @PostMapping("/metadata/schema-overview") + public SchemaOverviewMetadata getSchemaOverview(@RequestBody SchemaOverviewRequest request) { + return this.schemaOverviewService.getSchemaOverview(request); + } + + @PostMapping("/metadata/table") + public TableMetadata getTableMetadata(@RequestBody TableMetadataRequest request) { + return this.tableMetadataService.getTableMetadata(request); } } diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/entity/MetadataColumnsRequest.java b/backend/web/src/main/java/org/jumpserver/chen/web/entity/MetadataColumnsRequest.java new file mode 100644 index 00000000..87cbedd4 --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/entity/MetadataColumnsRequest.java @@ -0,0 +1,12 @@ +package org.jumpserver.chen.web.entity; + +import lombok.Data; + +import java.util.List; + +@Data +public class MetadataColumnsRequest { + private String nodeKey; + private String context; + private List relations; +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/entity/MetadataColumnsResponse.java b/backend/web/src/main/java/org/jumpserver/chen/web/entity/MetadataColumnsResponse.java new file mode 100644 index 00000000..df9dd986 --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/entity/MetadataColumnsResponse.java @@ -0,0 +1,6 @@ +package org.jumpserver.chen.web.entity; + +import java.util.List; + +public record MetadataColumnsResponse(List items) { +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/entity/MetadataRelationsRequest.java b/backend/web/src/main/java/org/jumpserver/chen/web/entity/MetadataRelationsRequest.java new file mode 100644 index 00000000..f56e9c30 --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/entity/MetadataRelationsRequest.java @@ -0,0 +1,11 @@ +package org.jumpserver.chen.web.entity; + +import lombok.Data; + +@Data +public class MetadataRelationsRequest { + private String nodeKey; + private String context; + private String prefix; + private Integer limit; +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/entity/QualifiedRelation.java b/backend/web/src/main/java/org/jumpserver/chen/web/entity/QualifiedRelation.java new file mode 100644 index 00000000..bd940aba --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/entity/QualifiedRelation.java @@ -0,0 +1,4 @@ +package org.jumpserver.chen.web.entity; + +public record QualifiedRelation(String catalog, String schema, String name, String kind) { +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/entity/RelationColumnsMetadata.java b/backend/web/src/main/java/org/jumpserver/chen/web/entity/RelationColumnsMetadata.java new file mode 100644 index 00000000..2b2ffd4d --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/entity/RelationColumnsMetadata.java @@ -0,0 +1,6 @@ +package org.jumpserver.chen.web.entity; + +import java.util.List; + +public record RelationColumnsMetadata(QualifiedRelation relation, List columns) { +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/entity/RelationMetadataPage.java b/backend/web/src/main/java/org/jumpserver/chen/web/entity/RelationMetadataPage.java new file mode 100644 index 00000000..092ed8a3 --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/entity/RelationMetadataPage.java @@ -0,0 +1,6 @@ +package org.jumpserver.chen.web.entity; + +import java.util.List; + +public record RelationMetadataPage(List items, boolean truncated) { +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/entity/SchemaOverviewMetadata.java b/backend/web/src/main/java/org/jumpserver/chen/web/entity/SchemaOverviewMetadata.java new file mode 100644 index 00000000..2b7418a4 --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/entity/SchemaOverviewMetadata.java @@ -0,0 +1,98 @@ +package org.jumpserver.chen.web.entity; + +import java.util.List; +import java.util.Set; + +public record SchemaOverviewMetadata( + String catalog, + String schema, + Capabilities capabilities, + Set loadedSections, + List tables, + List views, + List statistics, + List indexes, + List diagram, + String ddl +) { + public record Capabilities( + boolean tableRows, + boolean tableSize, + boolean tableEngine, + boolean tableCharacterSet, + boolean tableCollation, + boolean tableComment, + boolean viewComment, + boolean statistics, + boolean indexes, + boolean ddl, + boolean diagram, + boolean diagramRelationships + ) { + } + + public record TableMetadata( + String name, + String schema, + Long estimatedRows, + Long totalSizeBytes, + String engine, + String characterSet, + String collation, + String comment + ) { + } + + public record ViewMetadata( + String name, + String schema, + String type, + String comment + ) { + } + + public record StatisticMetadata( + String schema, + String table, + Long estimatedRows, + Long totalSizeBytes + ) { + } + + public record IndexMetadata( + String name, + String schema, + String table, + List columns, + Boolean unique, + String method, + String definition + ) { + } + + public record DiagramTableMetadata( + String schema, + String name, + List columns, + List primaryKey, + List foreignKeys + ) { + } + + public record DiagramColumnMetadata( + String name, + int ordinal, + String nativeType, + boolean nullable + ) { + } + + public record DiagramForeignKeyMetadata( + String name, + List columns, + String referencedSchema, + String referencedTable, + List referencedColumns + ) { + } +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/entity/SchemaOverviewRequest.java b/backend/web/src/main/java/org/jumpserver/chen/web/entity/SchemaOverviewRequest.java new file mode 100644 index 00000000..e0ecb6ba --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/entity/SchemaOverviewRequest.java @@ -0,0 +1,13 @@ +package org.jumpserver.chen.web.entity; + +import java.util.Set; + +public record SchemaOverviewRequest(String nodeKey, Set sections, boolean force) { + public SchemaOverviewRequest(String nodeKey) { + this(nodeKey, null, false); + } + + public SchemaOverviewRequest(String nodeKey, Set sections) { + this(nodeKey, sections, false); + } +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/entity/SqlColumnMetadata.java b/backend/web/src/main/java/org/jumpserver/chen/web/entity/SqlColumnMetadata.java new file mode 100644 index 00000000..c0d946d2 --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/entity/SqlColumnMetadata.java @@ -0,0 +1,4 @@ +package org.jumpserver.chen.web.entity; + +public record SqlColumnMetadata(String name, String dataType, boolean nullable) { +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/entity/TableMetadata.java b/backend/web/src/main/java/org/jumpserver/chen/web/entity/TableMetadata.java new file mode 100644 index 00000000..e30e2b4c --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/entity/TableMetadata.java @@ -0,0 +1,85 @@ +package org.jumpserver.chen.web.entity; + +import java.util.List; +import java.util.Set; + +public record TableMetadata( + String catalog, + String schema, + String name, + String kind, + Capabilities capabilities, + Set loadedSections, + List columns, + PrimaryKey primaryKey, + List foreignKeys, + List indexes, + List constraints, + String ddl +) { + public record Capabilities( + boolean columns, + boolean primaryKey, + boolean foreignKeys, + boolean indexes, + boolean constraints, + boolean ddl + ) { + } + + public record Column( + String name, + int ordinal, + String nativeType, + int jdbcType, + Integer size, + Integer scale, + boolean nullable, + String defaultValue, + String comment + ) { + } + + public record PrimaryKey(String name, List columns) { + } + + public record ForeignKey( + String name, + List columns, + String referencedCatalog, + String referencedSchema, + String referencedTable, + List referencedColumns + ) { + } + + public record Index( + String name, + boolean unique, + String method, + List parts, + String definition + ) { + } + + public record IndexPart( + int ordinal, + String columnName, + String expression, + String sortOrder, + boolean included + ) { + } + + public record Constraint( + String name, + String type, + List columns, + String referencedCatalog, + String referencedSchema, + String referencedTable, + List referencedColumns, + String definition + ) { + } +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/entity/TableMetadataRequest.java b/backend/web/src/main/java/org/jumpserver/chen/web/entity/TableMetadataRequest.java new file mode 100644 index 00000000..4b88a590 --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/entity/TableMetadataRequest.java @@ -0,0 +1,13 @@ +package org.jumpserver.chen.web.entity; + +import java.util.Set; + +public record TableMetadataRequest(String nodeKey, Set sections, boolean force) { + public TableMetadataRequest(String nodeKey) { + this(nodeKey, null, false); + } + + public TableMetadataRequest(String nodeKey, Set sections) { + this(nodeKey, sections, false); + } +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/interceptor/SessionInterceptor.java b/backend/web/src/main/java/org/jumpserver/chen/web/interceptor/SessionInterceptor.java index 92defe44..53839218 100644 --- a/backend/web/src/main/java/org/jumpserver/chen/web/interceptor/SessionInterceptor.java +++ b/backend/web/src/main/java/org/jumpserver/chen/web/interceptor/SessionInterceptor.java @@ -16,8 +16,8 @@ public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Objec } //1. 从header 中获取 token String token = req.getHeader("token"); - //2. 判断 token 是否认证 - if (token == null || token.isEmpty() || SessionManager.getSession(token) == null || !SessionManager.getSession(token).isActive()) { + //2. /api/profile 等请求发生在 websocket 建立前,此时 session 已创建但尚未 active。 + if (token == null || token.isEmpty() || SessionManager.getSession(token) == null) { //2.1 认证失败,返回错误信息 resp.setStatus(401); resp.getWriter().write("Unauthorized"); diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/service/HintsService.java b/backend/web/src/main/java/org/jumpserver/chen/web/service/HintsService.java new file mode 100644 index 00000000..c937e3a6 --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/service/HintsService.java @@ -0,0 +1,65 @@ +package org.jumpserver.chen.web.service; + +import org.apache.commons.lang3.StringUtils; +import org.jumpserver.chen.framework.datasource.ResourceBrowser; +import org.jumpserver.chen.framework.datasource.entity.resource.ResourceNodeSnapshot; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; +import org.jumpserver.chen.framework.datasource.metadata.RelationMetadata; +import org.jumpserver.chen.framework.i18n.MessageUtils; +import org.jumpserver.chen.framework.session.SessionManager; +import org.jumpserver.chen.web.exception.ChenException; +import org.springframework.stereotype.Service; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Service +public class HintsService { + private static final Set QUERY_NODE_TYPES = Set.of("datasource", "database", "schema", "table"); + private static final Set HINT_KINDS = Set.of(RelationKind.TABLE, RelationKind.VIEW); + + public Map> getHints(String nodeKey, String context) { + var session = SessionManager.getCurrentSession(); + if (!session.enableAutoComplete()) { + return Map.of(); + } + + var datasource = session.getDatasource(); + var browser = datasource.getResourceBrowser(); + var node = this.resolveNode(browser, nodeKey); + try { + var scope = browser.resolveScope(node, context); + var catalog = datasource.getMetadataCatalog(); + var relations = catalog.listRelations(scope, HINT_KINDS); + var columnsByName = new LinkedHashMap>(); + for (var column : catalog.listColumns(relations.stream().map(RelationMetadata::ref).toList())) { + columnsByName.computeIfAbsent(column.owner().name(), ignored -> new ArrayList<>()).add(column.name()); + } + + var suggestions = new LinkedHashMap>(); + suggestions.put(StringUtils.defaultString(scope.schema()), + relations.stream().map(relation -> relation.ref().name()).toList()); + for (var relation : relations) { + suggestions.put(relation.ref().name(), columnsByName.getOrDefault(relation.ref().name(), List.of())); + } + return suggestions; + } catch (SQLException | IllegalArgumentException e) { + throw new ChenException(MessageUtils.getOrDefault("FailedToLoadSqlHints", "Failed to load SQL hints"), e); + } + } + + private ResourceNodeSnapshot resolveNode(ResourceBrowser browser, String nodeKey) { + if (StringUtils.isBlank(nodeKey)) { + throw new ChenException(MessageUtils.getOrDefault("InvalidMetadataContext", "Invalid metadata context")); + } + var node = browser.getIndexedNode(nodeKey); + if (node == null || !QUERY_NODE_TYPES.contains(node.type())) { + throw new ChenException(MessageUtils.getOrDefault("InvalidMetadataContext", "Invalid metadata context")); + } + return node; + } +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/service/ResourceService.java b/backend/web/src/main/java/org/jumpserver/chen/web/service/ResourceService.java index 74a82a6b..d9e4b0c4 100644 --- a/backend/web/src/main/java/org/jumpserver/chen/web/service/ResourceService.java +++ b/backend/web/src/main/java/org/jumpserver/chen/web/service/ResourceService.java @@ -4,7 +4,10 @@ import org.jumpserver.chen.framework.datasource.entity.resource.TreeNode; import org.jumpserver.chen.framework.datasource.entity.action.Action; import org.jumpserver.chen.framework.datasource.entity.form.FormData; +import org.jumpserver.chen.framework.i18n.MessageUtils; import org.jumpserver.chen.framework.session.SessionManager; +import org.jumpserver.chen.framework.datasource.metadata.ObjectRef; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; import org.jumpserver.chen.framework.utils.TreeUtils; import org.jumpserver.chen.web.exception.ChenException; import org.springframework.stereotype.Service; @@ -20,9 +23,38 @@ public class ResourceService { public List getChildren(TreeNode node, boolean force) { try { var ds = SessionManager.getCurrentSession().getDatasource(); - return ds.getChildren(node, !force); + if (!force || node == null) { + return ds.getChildren(node, !force); + } + var browser = ds.getResourceBrowser(); + var resolvedNode = TreeUtils.getNode(browser.getTree(), node.getKey()); + if (resolvedNode == null || !Objects.equals(resolvedNode.getType(), node.getType())) { + throw new ChenException(MessageUtils.getOrDefault("InvalidResourceNode", "Invalid resource node")); + } + this.invalidateMetadata(ds, resolvedNode); + return ds.getChildren(resolvedNode, false); } catch (SQLException e) { - throw new ChenException(String.format("获取 %s子节点失败", node.getLabel()), e); + throw new ChenException(MessageUtils.getOrDefault( + "GetChildrenFailed", "Failed to load children of %s", node.getLabel()), e); + } + } + + private void invalidateMetadata(org.jumpserver.chen.framework.datasource.Datasource datasource, TreeNode node) + throws SQLException { + var browser = datasource.getResourceBrowser(); + var catalog = datasource.getMetadataCatalog(); + var snapshot = browser.getIndexedNode(node.getKey()); + switch (node.getType()) { + case "datasource" -> catalog.invalidateAll(); + case "database" -> catalog.invalidateCatalog(snapshot.database()); + case "schema", "folder" -> catalog.invalidate(browser.resolveScope(snapshot, null)); + case "table", "view" -> { + var scope = browser.resolveScope(snapshot, null); + var kind = "view".equals(node.getType()) ? RelationKind.VIEW : RelationKind.TABLE; + catalog.invalidate(new ObjectRef(scope.catalog(), scope.schema(), snapshot.table(), kind)); + } + default -> { + } } } @@ -40,7 +72,8 @@ public EventEmitter doAction(TreeNode node, String action) { if (e instanceof ChenException) { throw (ChenException) e; } - throw new ChenException(String.format("执行节点动作 %s 失败", node.getLabel()), e); + throw new ChenException(MessageUtils.getOrDefault( + "ExecuteNodeActionFailed", "Failed to execute action on %s", node.getLabel()), e); } } @@ -48,19 +81,19 @@ private TreeNode resolveActionNode(org.jumpserver.chen.framework.datasource.Data TreeNode requestedNode, String action) throws SQLException { if (requestedNode == null || requestedNode.getKey() == null || requestedNode.getKey().isBlank() || action == null || action.isBlank()) { - throw new ChenException("Invalid resource action"); + throw new ChenException(MessageUtils.getOrDefault("InvalidResourceAction", "Invalid resource action")); } var root = datasource.getResourceBrowser().getTree(); var resolvedNode = root == null ? null : TreeUtils.getNode(root, requestedNode.getKey()); if (resolvedNode == null || !Objects.equals(resolvedNode.getType(), requestedNode.getType())) { - throw new ChenException("Invalid resource node"); + throw new ChenException(MessageUtils.getOrDefault("InvalidResourceNode", "Invalid resource node")); } var exposed = datasource.getActions(resolvedNode).stream() .anyMatch(candidate -> Objects.equals(candidate.getKey(), action)); if (!exposed) { - throw new ChenException("Invalid resource action"); + throw new ChenException(MessageUtils.getOrDefault("InvalidResourceAction", "Invalid resource action")); } return resolvedNode; } diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/service/SchemaOverviewService.java b/backend/web/src/main/java/org/jumpserver/chen/web/service/SchemaOverviewService.java new file mode 100644 index 00000000..138cdde9 --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/service/SchemaOverviewService.java @@ -0,0 +1,236 @@ +package org.jumpserver.chen.web.service; + +import org.apache.commons.lang3.StringUtils; +import org.jumpserver.chen.framework.datasource.ResourceBrowser; +import org.jumpserver.chen.framework.datasource.entity.resource.ResourceNodeSnapshot; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCapabilities; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; +import org.jumpserver.chen.framework.datasource.metadata.MetadataQueryAuditContext; +import org.jumpserver.chen.framework.datasource.metadata.ObjectStatistics; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; +import org.jumpserver.chen.framework.datasource.metadata.RelationScope; +import org.jumpserver.chen.framework.i18n.MessageUtils; +import org.jumpserver.chen.framework.session.SessionManager; +import org.jumpserver.chen.web.entity.SchemaOverviewMetadata; +import org.jumpserver.chen.web.entity.SchemaOverviewRequest; +import org.jumpserver.chen.web.exception.ChenException; +import org.springframework.stereotype.Service; + +import java.sql.SQLException; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +public class SchemaOverviewService { + private static final Set TABLE_KINDS = Set.of(RelationKind.TABLE); + private static final Set VIEW_KINDS = Set.of(RelationKind.VIEW, RelationKind.MATERIALIZED_VIEW); + private static final Set DEFAULT_SECTIONS = Set.of("tables", "views"); + private static final Set ALLOWED_SECTIONS = Set.of( + "tables", "views", "statistics", "indexes", "ddl", "diagram" + ); + + public SchemaOverviewMetadata getSchemaOverview(SchemaOverviewRequest request) { + var session = SessionManager.getCurrentSession(); + var datasource = session.getDatasource(); + var browser = datasource.getResourceBrowser(); + var node = this.resolveSchemaNode(browser, request == null ? null : request.nodeKey()); + try { + var scope = new RelationScope(node.database(), node.schema()); + var sections = normalizeSections(request == null ? null : request.sections()); + try (var ignored = MetadataQueryAuditContext.open()) { + return this.load( + datasource.getMetadataCatalog(), scope, sections, + request != null && request.force() + ); + } + } catch (SQLException | IllegalArgumentException e) { + throw new ChenException(MessageUtils.getOrDefault("FailedToLoadSchemaOverview", "Failed to load schema overview metadata"), e); + } + } + + SchemaOverviewMetadata load( + MetadataCatalog catalog, RelationScope scope, Set sections, boolean force + ) throws SQLException { + if (force) { + catalog.invalidate(scope); + } + return this.load(catalog, scope, sections); + } + + SchemaOverviewMetadata load(MetadataCatalog catalog, RelationScope scope, Set sections) + throws SQLException { + var statistics = sections.contains("statistics") ? catalog.listStatistics(scope) : List.of(); + var statsByName = new HashMap(); + for (var statistic : statistics) { + statsByName.put(statistic.ref().name(), statistic); + } + return new SchemaOverviewMetadata( + scope.catalog(), + scope.schema(), + this.toCapabilities(catalog.capabilities()), + sections, + sections.contains("tables") ? this.loadTables(catalog, scope, statsByName) : List.of(), + sections.contains("views") ? this.loadViews(catalog, scope) : List.of(), + statistics.stream().map(statistic -> new SchemaOverviewMetadata.StatisticMetadata( + statistic.ref().schema(), statistic.ref().name(), + statistic.estimatedRows(), statistic.totalSizeBytes() + )).toList(), + sections.contains("indexes") ? this.loadIndexes(catalog, scope) : List.of(), + sections.contains("diagram") ? this.loadDiagram(catalog, scope) : List.of(), + sections.contains("ddl") ? catalog.getSchemaDefinition(scope) : null + ); + } + + private List loadDiagram( + MetadataCatalog catalog, + RelationScope scope + ) throws SQLException { + var refs = catalog.listRelations(scope, TABLE_KINDS).stream().map(relation -> relation.ref()).toList(); + var columnsByOwner = catalog.listColumns(refs).stream().collect(Collectors.groupingBy( + column -> column.owner(), LinkedHashMap::new, Collectors.toList() + )); + var primaryKeysByOwner = catalog.listPrimaryKeys(refs).stream().collect(Collectors.toMap( + primaryKey -> primaryKey.owner(), primaryKey -> primaryKey, + (first, ignored) -> first, LinkedHashMap::new + )); + var foreignKeysByOwner = catalog.listForeignKeys(refs).stream().collect(Collectors.groupingBy( + foreignKey -> foreignKey.owner(), LinkedHashMap::new, Collectors.toList() + )); + + return refs.stream().map(ref -> { + var primaryKey = primaryKeysByOwner.get(ref); + return new SchemaOverviewMetadata.DiagramTableMetadata( + ref.schema(), ref.name(), + columnsByOwner.getOrDefault(ref, List.of()).stream().map(column -> + new SchemaOverviewMetadata.DiagramColumnMetadata( + column.name(), column.ordinal(), column.nativeType(), column.nullable() + ) + ).toList(), + primaryKey == null ? List.of() : primaryKey.columns(), + foreignKeysByOwner.getOrDefault(ref, List.of()).stream().map(foreignKey -> { + var referenced = foreignKey.referenced(); + return new SchemaOverviewMetadata.DiagramForeignKeyMetadata( + foreignKey.name(), foreignKey.columns(), + referenced == null ? null : referenced.schema(), + referenced == null ? null : referenced.name(), + foreignKey.referencedColumns() + ); + }).toList() + ); + }).toList(); + } + + private List loadTables( + MetadataCatalog catalog, + RelationScope scope, + Map statsByName + ) + throws SQLException { + var relations = catalog.listRelations(scope, TABLE_KINDS); + return relations.stream().map(relation -> { + var stat = statsByName.get(relation.ref().name()); + return new SchemaOverviewMetadata.TableMetadata( + relation.ref().name(), + relation.ref().schema(), + stat == null ? null : stat.estimatedRows(), + stat == null ? null : stat.totalSizeBytes(), + relation.engine(), + relation.characterSet(), + relation.collation(), + relation.comment() + ); + }).toList(); + } + + private List loadViews(MetadataCatalog catalog, RelationScope scope) + throws SQLException { + return catalog.listRelations(scope, VIEW_KINDS).stream() + .map(relation -> new SchemaOverviewMetadata.ViewMetadata( + relation.ref().name(), + relation.ref().schema(), + this.viewType(relation.ref().kind()), + relation.comment() + )) + .toList(); + } + + private List loadIndexes(MetadataCatalog catalog, RelationScope scope) + throws SQLException { + return catalog.listIndexes(scope).stream() + .map(this::toIndexMetadata) + .toList(); + } + + SchemaOverviewMetadata.IndexMetadata toIndexMetadata( + org.jumpserver.chen.framework.datasource.metadata.IndexMetadata index + ) { + return new SchemaOverviewMetadata.IndexMetadata( + index.name(), + index.owner().schema(), + index.owner().name(), + index.parts().stream() + .map(part -> part.columnName() != null ? part.columnName() : part.expression()) + .filter(Objects::nonNull) + .toList(), + index.unique(), + index.method(), + index.definition() + ); + } + + private SchemaOverviewMetadata.Capabilities toCapabilities(MetadataCapabilities capabilities) { + return new SchemaOverviewMetadata.Capabilities( + capabilities.tableRows(), + capabilities.tableSize(), + capabilities.tableEngine(), + capabilities.tableCharacterSet(), + capabilities.tableCollation(), + capabilities.tableComment(), + capabilities.viewComment(), + capabilities.statistics(), + capabilities.indexes(), + capabilities.definitions(), + capabilities.relations() && capabilities.columns(), + capabilities.foreignKeys() + ); + } + + static Set normalizeSections(Set requested) { + var source = requested == null || requested.isEmpty() ? DEFAULT_SECTIONS : requested; + var normalized = new LinkedHashSet(); + for (var section : source) { + if (section == null || section.isBlank()) { + continue; + } + var value = section.trim().toLowerCase(Locale.ROOT).replace('_', '-'); + if (!ALLOWED_SECTIONS.contains(value)) { + throw new IllegalArgumentException(MessageUtils.getOrDefault( + "UnknownSchemaMetadataSection", "Unknown schema metadata section: %s", section)); + } + normalized.add(value); + } + return Set.copyOf(normalized); + } + + private String viewType(RelationKind kind) { + return kind == RelationKind.MATERIALIZED_VIEW ? "MATERIALIZED VIEW" : "VIEW"; + } + + private ResourceNodeSnapshot resolveSchemaNode(ResourceBrowser browser, String nodeKey) { + if (StringUtils.isBlank(nodeKey)) { + throw new ChenException(MessageUtils.getOrDefault("InvalidSchemaOverviewContext", "Invalid schema overview context")); + } + var node = browser.getIndexedNode(nodeKey); + if (node == null || !"schema".equals(node.type()) || StringUtils.isBlank(node.schema())) { + throw new ChenException(MessageUtils.getOrDefault("InvalidSchemaOverviewContext", "Invalid schema overview context")); + } + return node; + } +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/service/SqlMetadataService.java b/backend/web/src/main/java/org/jumpserver/chen/web/service/SqlMetadataService.java new file mode 100644 index 00000000..69acbadf --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/service/SqlMetadataService.java @@ -0,0 +1,240 @@ +package org.jumpserver.chen.web.service; + +import org.apache.commons.lang3.StringUtils; +import org.jumpserver.chen.framework.datasource.ResourceBrowser; +import org.jumpserver.chen.framework.datasource.entity.resource.ResourceNodeSnapshot; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; +import org.jumpserver.chen.framework.datasource.metadata.ObjectRef; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; +import org.jumpserver.chen.framework.datasource.metadata.RelationMetadata; +import org.jumpserver.chen.framework.datasource.metadata.RelationScope; +import org.jumpserver.chen.framework.datasource.metadata.SchemaMetadata; +import org.jumpserver.chen.framework.i18n.MessageUtils; +import org.jumpserver.chen.framework.session.SessionManager; +import org.jumpserver.chen.web.entity.MetadataColumnsRequest; +import org.jumpserver.chen.web.entity.MetadataColumnsResponse; +import org.jumpserver.chen.web.entity.MetadataRelationsRequest; +import org.jumpserver.chen.web.entity.QualifiedRelation; +import org.jumpserver.chen.web.entity.RelationColumnsMetadata; +import org.jumpserver.chen.web.entity.RelationMetadataPage; +import org.jumpserver.chen.web.entity.SqlColumnMetadata; +import org.jumpserver.chen.web.exception.ChenException; +import org.springframework.stereotype.Service; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +@Service +public class SqlMetadataService { + private static final Set QUERY_NODE_TYPES = Set.of("datasource", "database", "schema", "table"); + private static final Set RELATION_KIND_STRINGS = Set.of("table", "view"); + private static final Set COMPLETION_KINDS = Set.of(RelationKind.TABLE, RelationKind.VIEW); + private static final int DEFAULT_RELATION_LIMIT = 100; + private static final int MAX_RELATION_LIMIT = 200; + private static final int MAX_COLUMN_RELATIONS = 20; + + public RelationMetadataPage listRelations(MetadataRelationsRequest request) { + var session = SessionManager.getCurrentSession(); + if (!session.enableAutoComplete()) { + return new RelationMetadataPage(List.of(), false); + } + + var datasource = session.getDatasource(); + var browser = datasource.getResourceBrowser(); + var node = this.resolveNode(browser, request == null ? null : request.getNodeKey()); + try { + var scope = browser.resolveScope(node, request == null ? null : request.getContext()); + var catalog = datasource.getMetadataCatalog(); + var relations = catalog.listRelations(scope, COMPLETION_KINDS); + var normalizedPrefix = StringUtils.defaultString(request.getPrefix()).trim().toLowerCase(Locale.ROOT); + var filtered = relations.stream() + .filter(relation -> normalizedPrefix.isEmpty() + || relation.ref().name().toLowerCase(Locale.ROOT).startsWith(normalizedPrefix) + || (relation.ref().schema() + "." + relation.ref().name()) + .toLowerCase(Locale.ROOT).startsWith(normalizedPrefix)) + .sorted(Comparator.comparing((RelationMetadata relation) -> relation.ref().schema(), String.CASE_INSENSITIVE_ORDER) + .thenComparing(relation -> relation.ref().name(), String.CASE_INSENSITIVE_ORDER) + .thenComparing(relation -> relation.ref().kind().code())) + .toList(); + var boundedLimit = request.getLimit() == null + ? DEFAULT_RELATION_LIMIT + : Math.max(1, Math.min(request.getLimit(), MAX_RELATION_LIMIT)); + var truncated = filtered.size() > boundedLimit; + var items = filtered.stream().limit(boundedLimit) + .map(relation -> new QualifiedRelation( + relation.ref().catalog(), relation.ref().schema(), relation.ref().name(), relation.ref().kind().code())) + .toList(); + return new RelationMetadataPage(items, truncated); + } catch (SQLException | IllegalArgumentException e) { + throw new ChenException(MessageUtils.getOrDefault("FailedToLoadSqlRelationMetadata", "Failed to load SQL relation metadata"), e); + } + } + + public MetadataColumnsResponse listColumns(MetadataColumnsRequest request) { + var session = SessionManager.getCurrentSession(); + if (!session.enableAutoComplete()) { + return new MetadataColumnsResponse(List.of()); + } + + var datasource = session.getDatasource(); + var browser = datasource.getResourceBrowser(); + var node = this.resolveNode(browser, request == null ? null : request.getNodeKey()); + try { + var scope = browser.resolveScope(node, request == null ? null : request.getContext()); + var catalog = datasource.getMetadataCatalog(); + var refs = this.resolveRelations(catalog, scope, request.getRelations()); + var columns = catalog.listColumns(refs); + var columnsByRef = new LinkedHashMap>(); + for (var column : columns) { + columnsByRef.computeIfAbsent(column.owner(), ignored -> new ArrayList<>()) + .add(new SqlColumnMetadata(column.name(), column.nativeType(), column.nullable())); + } + var items = refs.stream() + .map(ref -> new RelationColumnsMetadata( + new QualifiedRelation(ref.catalog(), ref.schema(), ref.name(), ref.kind().code()), + columnsByRef.getOrDefault(ref, List.of()))) + .toList(); + return new MetadataColumnsResponse(items); + } catch (SQLException | IllegalArgumentException e) { + throw new ChenException(MessageUtils.getOrDefault("FailedToLoadSqlColumnMetadata", "Failed to load SQL column metadata"), e); + } + } + + private List resolveRelations( + MetadataCatalog catalog, RelationScope scope, List requestedRelations + ) throws SQLException { + if (requestedRelations == null || requestedRelations.isEmpty()) { + return List.of(); + } + if (requestedRelations.size() > MAX_COLUMN_RELATIONS) { + throw new IllegalArgumentException(MessageUtils.getOrDefault("TooManyRelationsInMetadataRequest", "Too many relations in one metadata request")); + } + + var availableSchemas = catalog.listSchemas(scope.catalog()).stream().map(SchemaMetadata::name).toList(); + var relationsBySchema = new LinkedHashMap>(); + var canonicalRequests = new LinkedHashMap(); + for (var requested : requestedRelations) { + this.validateRequestedRelation(requested, scope.catalog()); + var requestedSchema = StringUtils.defaultIfBlank(requested.schema(), scope.schema()); + var canonicalSchema = this.resolveCanonicalIdentifier( + availableSchemas, requestedSchema, + MessageUtils.getOrDefault("UnknownRelationSchema", "Unknown relation schema")); + + var availableRelations = relationsBySchema.get(canonicalSchema); + if (availableRelations == null) { + availableRelations = this.loadRelationsByKey(catalog, scope.catalog(), canonicalSchema); + relationsBySchema.put(canonicalSchema, availableRelations); + } + + var kind = RelationKind.fromCode(requested.kind()); + var key = new RelationKey(canonicalSchema, requested.name(), kind); + var canonical = availableRelations.get(key); + if (canonical == null) { + var canonicalName = this.resolveCanonicalIdentifier( + availableRelations.values().stream().filter(ref -> ref.kind() == kind).map(ObjectRef::name).toList(), + requested.name(), + MessageUtils.getOrDefault("UnknownRelation", "Unknown relation") + ); + canonical = availableRelations.get(new RelationKey(canonicalSchema, canonicalName, kind)); + } + canonicalRequests.putIfAbsent(canonical, canonical); + } + return List.copyOf(canonicalRequests.keySet()); + } + + private Map loadRelationsByKey(MetadataCatalog catalog, String catalogName, String schema) + throws SQLException { + var result = new LinkedHashMap(); + for (var relation : catalog.listRelations(new RelationScope(catalogName, schema), COMPLETION_KINDS)) { + var ref = relation.ref(); + result.put(new RelationKey(schema, ref.name(), ref.kind()), ref); + } + return result; + } + + private void validateRequestedRelation(QualifiedRelation relation, String catalog) { + if (relation == null || StringUtils.isBlank(relation.name()) || !RELATION_KIND_STRINGS.contains(relation.kind())) { + throw new IllegalArgumentException(MessageUtils.getOrDefault("InvalidRelationMetadataRequest", "Invalid relation metadata request")); + } + if (StringUtils.isNotBlank(relation.catalog()) && StringUtils.isNotBlank(catalog) + && !relation.catalog().equals(catalog)) { + throw new IllegalArgumentException(MessageUtils.getOrDefault("RelationCatalogMismatch", "Relation catalog does not match the active context")); + } + } + + private String resolveCanonicalIdentifier(Collection candidates, String requested, String unknownMessage) { + if (StringUtils.isBlank(requested)) { + throw new IllegalArgumentException(unknownMessage); + } + var exactMatches = candidates.stream().filter(requested::equals).distinct().toList(); + if (exactMatches.size() == 1) { + return exactMatches.get(0); + } + var caseRule = this.identifierCaseRule(); + if (caseRule == IdentifierCaseRule.LOWER || caseRule == IdentifierCaseRule.UPPER) { + var normalizedRequested = caseRule.normalize(requested); + var normalizedMatches = candidates.stream() + .filter(candidate -> candidate.equals(caseRule.normalize(candidate))) + .filter(candidate -> candidate.equals(normalizedRequested)) + .distinct() + .toList(); + if (normalizedMatches.size() == 1) { + return normalizedMatches.get(0); + } + throw new IllegalArgumentException(unknownMessage); + } + if (caseRule == IdentifierCaseRule.INSENSITIVE) { + var insensitiveMatches = candidates.stream() + .filter(candidate -> candidate.equalsIgnoreCase(requested)) + .distinct() + .toList(); + if (insensitiveMatches.size() == 1) { + return insensitiveMatches.get(0); + } + } + throw new IllegalArgumentException(unknownMessage); + } + + private IdentifierCaseRule identifierCaseRule() { + var connectInfo = SessionManager.getCurrentSession().getDatasource().getConnectInfo(); + var dbType = connectInfo == null ? "" : StringUtils.defaultString(connectInfo.getDbType()); + return switch (dbType.toLowerCase(Locale.ROOT)) { + case "postgresql" -> IdentifierCaseRule.LOWER; + case "oracle", "db2", "dm", "dameng" -> IdentifierCaseRule.UPPER; + case "mysql", "mariadb", "sqlserver" -> IdentifierCaseRule.INSENSITIVE; + default -> IdentifierCaseRule.EXACT; + }; + } + + private ResourceNodeSnapshot resolveNode(ResourceBrowser browser, String nodeKey) { + if (StringUtils.isBlank(nodeKey)) { + throw new ChenException(MessageUtils.getOrDefault("InvalidMetadataContext", "Invalid metadata context")); + } + var node = browser.getIndexedNode(nodeKey); + if (node == null || !QUERY_NODE_TYPES.contains(node.type())) { + throw new ChenException(MessageUtils.getOrDefault("InvalidMetadataContext", "Invalid metadata context")); + } + return node; + } + + private record RelationKey(String schema, String name, RelationKind kind) { + } + + private enum IdentifierCaseRule { + LOWER, + UPPER, + INSENSITIVE, + EXACT; + + private String normalize(String identifier) { + return this == UPPER ? identifier.toUpperCase(Locale.ROOT) : identifier.toLowerCase(Locale.ROOT); + } + } +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/service/TableMetadataService.java b/backend/web/src/main/java/org/jumpserver/chen/web/service/TableMetadataService.java new file mode 100644 index 00000000..dd0c668a --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/service/TableMetadataService.java @@ -0,0 +1,193 @@ +package org.jumpserver.chen.web.service; + +import org.apache.commons.lang3.StringUtils; +import org.jumpserver.chen.framework.datasource.ResourceBrowser; +import org.jumpserver.chen.framework.datasource.entity.resource.ResourceNodeSnapshot; +import org.jumpserver.chen.framework.datasource.metadata.ConstraintMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ForeignKeyMetadata; +import org.jumpserver.chen.framework.datasource.metadata.IndexMetadata; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCapabilities; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; +import org.jumpserver.chen.framework.datasource.metadata.MetadataQueryAuditContext; +import org.jumpserver.chen.framework.datasource.metadata.ObjectRef; +import org.jumpserver.chen.framework.datasource.metadata.PrimaryKeyMetadata; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; +import org.jumpserver.chen.framework.i18n.MessageUtils; +import org.jumpserver.chen.framework.session.SessionManager; +import org.jumpserver.chen.web.entity.TableMetadata; +import org.jumpserver.chen.web.entity.TableMetadataRequest; +import org.jumpserver.chen.web.exception.ChenException; +import org.springframework.stereotype.Service; + +import java.sql.SQLException; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +@Service +public class TableMetadataService { + private static final Set DEFAULT_SECTIONS = Set.of("columns", "primaryKey"); + private static final Set AUDITED_PROPERTY_SECTIONS = Set.of( + "foreignKeys", "indexes", "constraints", "ddl" + ); + + public TableMetadata getTableMetadata(TableMetadataRequest request) { + var session = SessionManager.getCurrentSession(); + var datasource = session.getDatasource(); + var node = resolveRelationNode(datasource.getResourceBrowser(), request == null ? null : request.nodeKey()); + var ref = new ObjectRef( + node.database(), node.schema(), node.table(), relationKindForNode(node) + ); + try { + var sections = normalizeSections(request == null ? null : request.sections()); + var force = request != null && request.force(); + if (!shouldAuditPropertyQuery(sections, force)) { + return load(datasource.getMetadataCatalog(), ref, sections, force); + } + try (var ignored = MetadataQueryAuditContext.open()) { + return load(datasource.getMetadataCatalog(), ref, sections, force); + } + } catch (SQLException | IllegalArgumentException e) { + throw new ChenException(MessageUtils.getOrDefault("FailedToLoadTableMetadata", "Failed to load table metadata"), e); + } + } + + static boolean shouldAuditPropertyQuery(Set sections, boolean force) { + return !force && sections.stream().anyMatch(AUDITED_PROPERTY_SECTIONS::contains); + } + + TableMetadata load(MetadataCatalog catalog, ObjectRef ref, Set sections, boolean force) + throws SQLException { + if (force) { + catalog.invalidate(ref); + } + return load(catalog, ref, sections); + } + + TableMetadata load(MetadataCatalog catalog, ObjectRef ref, Set sections) throws SQLException { + var columns = sections.contains("columns") + ? catalog.listColumns(List.of(ref)).stream().map(column -> new TableMetadata.Column( + column.name(), column.ordinal(), column.nativeType(), column.jdbcType(), + column.size(), column.scale(), column.nullable(), column.defaultValue(), column.comment() + )).toList() + : List.of(); + + TableMetadata.PrimaryKey primaryKey = null; + if (sections.contains("primaryKey")) { + primaryKey = catalog.listPrimaryKeys(List.of(ref)).stream().findFirst() + .map(this::toPrimaryKey).orElse(null); + } + + var foreignKeys = sections.contains("foreignKeys") + ? catalog.listForeignKeys(List.of(ref)).stream().map(this::toForeignKey).toList() + : List.of(); + var indexes = sections.contains("indexes") + ? catalog.listIndexes(List.of(ref)).stream().map(this::toIndex).toList() + : List.of(); + var constraints = sections.contains("constraints") + ? catalog.listConstraints(List.of(ref)).stream().map(this::toConstraint).toList() + : List.of(); + var ddl = sections.contains("ddl") ? catalog.getTableDefinition(ref) : null; + + return new TableMetadata( + ref.catalog(), ref.schema(), ref.name(), ref.kind().code(), + toCapabilities(catalog.capabilities()), sections, + columns, primaryKey, foreignKeys, indexes, constraints, ddl + ); + } + + private TableMetadata.PrimaryKey toPrimaryKey(PrimaryKeyMetadata primaryKey) { + return new TableMetadata.PrimaryKey(primaryKey.name(), primaryKey.columns()); + } + + private TableMetadata.ForeignKey toForeignKey(ForeignKeyMetadata foreignKey) { + var referenced = foreignKey.referenced(); + return new TableMetadata.ForeignKey( + foreignKey.name(), foreignKey.columns(), + referenced == null ? null : referenced.catalog(), + referenced == null ? null : referenced.schema(), + referenced == null ? null : referenced.name(), + foreignKey.referencedColumns() + ); + } + + private TableMetadata.Index toIndex(IndexMetadata index) { + return new TableMetadata.Index( + index.name(), index.unique(), index.method(), + index.parts().stream().map(part -> new TableMetadata.IndexPart( + part.ordinal(), part.columnName(), part.expression(), part.sortOrder(), part.included() + )).toList(), + index.definition() + ); + } + + private TableMetadata.Constraint toConstraint(ConstraintMetadata constraint) { + var referenced = constraint.referenced(); + return new TableMetadata.Constraint( + constraint.name(), constraint.type().code(), constraint.columns(), + referenced == null ? null : referenced.catalog(), + referenced == null ? null : referenced.schema(), + referenced == null ? null : referenced.name(), + constraint.referencedColumns(), constraint.definition() + ); + } + + private TableMetadata.Capabilities toCapabilities(MetadataCapabilities capabilities) { + return new TableMetadata.Capabilities( + capabilities.columns(), capabilities.primaryKeys(), capabilities.foreignKeys(), + capabilities.indexes(), capabilities.constraints(), capabilities.tableDefinitions() + ); + } + + static Set normalizeSections(Set requested) { + var source = requested == null || requested.isEmpty() ? DEFAULT_SECTIONS : requested; + var normalized = new LinkedHashSet(); + for (var section : source) { + if (section == null || section.isBlank()) { + continue; + } + var compact = section.trim().toLowerCase(Locale.ROOT).replace("_", "").replace("-", ""); + normalized.add(switch (compact) { + case "columns" -> "columns"; + case "primarykey", "pk" -> "primaryKey"; + case "foreignkeys", "foreignkey", "fk" -> "foreignKeys"; + case "indexes", "indices" -> "indexes"; + case "constraints" -> "constraints"; + case "ddl" -> "ddl"; + default -> throw new IllegalArgumentException(MessageUtils.getOrDefault( + "UnknownTableMetadataSection", "Unknown table metadata section: %s", section)); + }); + } + return Set.copyOf(normalized); + } + + static RelationKind relationKindForNodeType(String type) { + if ("view".equals(type)) { + return RelationKind.VIEW; + } + if ("table".equals(type)) { + return RelationKind.TABLE; + } + throw new ChenException(MessageUtils.getOrDefault("InvalidTableMetadataContext", "Invalid table metadata context")); + } + + static RelationKind relationKindForNode(ResourceNodeSnapshot node) { + if (node.relationKind() != null) { + return node.relationKind(); + } + return relationKindForNodeType(node.type()); + } + + ResourceNodeSnapshot resolveRelationNode(ResourceBrowser browser, String nodeKey) { + if (StringUtils.isBlank(nodeKey)) { + throw new ChenException(MessageUtils.getOrDefault("InvalidTableMetadataContext", "Invalid table metadata context")); + } + var node = browser.getIndexedNode(nodeKey); + if (node == null || StringUtils.isBlank(node.table())) { + throw new ChenException(MessageUtils.getOrDefault("InvalidTableMetadataContext", "Invalid table metadata context")); + } + relationKindForNode(node); + return node; + } +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/service/impl/JmsSessionService.java b/backend/web/src/main/java/org/jumpserver/chen/web/service/impl/JmsSessionService.java index 306f1552..77a2e894 100644 --- a/backend/web/src/main/java/org/jumpserver/chen/web/service/impl/JmsSessionService.java +++ b/backend/web/src/main/java/org/jumpserver/chen/web/service/impl/JmsSessionService.java @@ -132,6 +132,14 @@ private Datasource createDatasource(ServiceOuterClass.TokenResponse tokenResp) { var platformSettings = tokenResp.getData().getPlatform().getProtocols(0).getSettingsMap(); applyPlatformSettings(dbConnectInfo, platformSettings); + var connectOptions = tokenResp.getData().getConnectOptions().getSettingsMap(); + var useSysDBA = "oracle".equals(dbConnectInfo.getDbType()) && Boolean.parseBoolean( + connectOptions.getOrDefault("use_sysdba", "false") + ); + if (useSysDBA) { + dbConnectInfo.getOptions().put("internal_logon", "sysdba"); + } + var asset = tokenResp.getData().getAsset(); if (asset.getSpecific().getUseSsl()) { @@ -146,10 +154,6 @@ private Datasource createDatasource(ServiceOuterClass.TokenResponse tokenResp) { } static void applyPlatformSettings(DBConnectInfo dbConnectInfo, Map platformSettings) { - if (platformSettings.containsKey("sysdba") && platformSettings.get("sysdba").equals("true")) { - dbConnectInfo.getOptions().put("internal_logon", "sysdba"); - } - if (platformSettings.containsKey("version")) { dbConnectInfo.getOptions().put("version", platformSettings.get("version")); } diff --git a/backend/web/src/main/resources/i18n/chen.properties b/backend/web/src/main/resources/i18n/chen.properties new file mode 100644 index 00000000..b335a457 --- /dev/null +++ b/backend/web/src/main/resources/i18n/chen.properties @@ -0,0 +1,165 @@ +ACLRejectError=\ Command rejected by ACL +AffectedRows=Affected rows +AlreadyFirstPageError=It's already the first page +AlreadyLastPageError=It's already the last page +BatchSqlReview=Batch SQL:\n%s\n\nReview-triggering SQL:\n%s +Cancel=Cancel +ChangeContextError=Error while changing context +CommandReview=Command review +CommandReviewMessage=The command you entered requires verification before it can be executed. Would you like to initiate a review request? +CommandReviewRejectBy=Command review request has been rejected by %s +CommandReviewTimeoutError=Command review request has timed out +CommandWarningDialogMessage=The command you executed is risky and an alert notification will be sent to the administrator. Do you want to continue? +Confirm=Confirm +ConfirmDelete=Confirm delete +Connected=Connected +ConnectError=Error while fetching data +ConnectSuccess=Connected successfully +Content=Content +Copy=Copy +CopyFailed=Copy failed +CopyNotAllowed=You are not allowed to copy, please contact the administrator to open it! +CopySucceeded=Copy succeeded +CreateCommandTicketFailed=Failed to create command ticket: %s +Current=Current +DatabaseExplorer=Database Explorer +DatabaseProperties=Datasource properties +Delete=Delete +DeleteSuccess=Deleted successfully +DownloadNotAllowed=You are not allowed to download, please contact the administrator to open it! +DriverClass=Driver class +DriverVersion=Driver version +ErrorMessage=Error message +ErrorPrefix=Error: %s +ExecuteError=Error while executing +ExecuteNodeActionFailed=Failed to execute action on %s +ExecuteSuccess=Executed successfully +ExecutionCanceled=Execution Canceled +ExportALL=Export all data +ExportAll=Export all +ExportCurrent=Export current page +ExportData=Export data +ExportDataNamed=Export data: %s +FailedToLoadSchemaOverview=Failed to load schema overview metadata +FailedToLoadSqlColumnMetadata=Failed to load SQL column metadata +FailedToLoadSqlHints=Failed to load SQL hints +FailedToLoadSqlRelationMetadata=Failed to load SQL relation metadata +FailedToLoadTableMetadata=Failed to load table metadata +FetchError=Error while fetching data +Format=Format +FormatHotKey=Format (Ctrl + L) +GetChildrenFailed=Failed to load children of %s +InitializeDatasource=Initialize datasource +InitializeDatasourceFailed=Initialize datasource failed +initializingDatasourceFailedMessage=Connection failed, please check if the database connection configuration is correct +InitializingDatasourceMessage=Initializing data source, please wait... +Insert=Insert +InsertStatement=Insert statement +InvalidExportFile=Invalid export file +InvalidMetadataContext=Invalid metadata context +InvalidRelationMetadataRequest=Invalid relation metadata request +InvalidResourceAction=Invalid resource action +InvalidResourceNode=Invalid resource node +InvalidSchemaOverviewContext=Invalid schema overview context +InvalidTableMetadataContext=Invalid table metadata context +JDBCURL=JDBC URL +LogOutput=Log Output +Name=Name +NewQuery=New query +NoPermissionError=\ You do not have permission to perform this action +NumRow={num} rows +Open=Open +OverMaxIdleTimeError=Since this sessioon has been idle for more than %d minutes, it has been closed +OverMaxSessionTimeError=Since this session has been active for more than %d hours, it has been closed +ParseError=Error while parsing +PasteNotAllowed=You are not allowed to paste, please contact the administrator to open it! +PermissionAlreadyExpired=Permission already expired +PermissionExpiredDialogMessage=Permission has expired, and the session will expire in ten minutes. Please contact the administrator promptly for renewal +PermissionExpiredDialogTitle=Permission expired +PermissionsExpiredOn=Permissions associated with this session expired on %s +Plan.Assignment=Statements with assignment cannot be estimated +Plan.CannotEstimateFailedTransaction=Cannot estimate a plan in a failed transaction +Plan.CheckTxAndRetry=Check the transaction state and retry +Plan.ClassicExplainEmpty=Classic EXPLAIN produced no rows +Plan.ConnectionUnusable=Connection became unusable during execution plan +Plan.DynamicSql=Dynamic SQL cannot be estimated +Plan.EmptySql=SQL statement is empty +Plan.EstimateFailed=Failed to estimate execution plan +Plan.ExplainWrapper=Wrapped EXPLAIN statements cannot be estimated +Plan.FailedToCreateSavepoint=Failed to create SAVEPOINT +Plan.FinishTxAndRetry=Finish the existing transaction and retry +Plan.FunctionNotMariaDBBuiltin=Function %s is not a known MariaDB builtin +Plan.FunctionNotMysqlBuiltin=Function %s is not a known MySQL builtin +Plan.FunctionNotPostgresBuiltin=Function %s is not a known PostgreSQL builtin +Plan.FunctionNotSafeAtPlanTime=Function %s is not safe to estimate at planning time +Plan.FunctionNotSafeToEstimate=Function %s is not safe to estimate +Plan.FunctionOutsideMariaDBCatalogs=Function %s is outside known MariaDB catalogs +Plan.FunctionOutsideMysqlCatalogs=Function %s is outside known MySQL catalogs +Plan.FunctionOutsidePgCatalog=Function %s is outside pg_catalog and cannot be proven side-effect free +Plan.LockingRead=Locking reads cannot be estimated +Plan.ManualCommitIdleCannotStart=A manual-commit idle connection cannot start a plan transaction +Plan.ManualCommitIdleNotUsed=Manual-commit idle connections are not used for EXPLAIN +Plan.MariaDBJsonNormalizeFailed=MariaDB plan JSON could not be normalized +Plan.MariaDBTransactionAborted=The current MariaDB transaction is already aborted +Plan.MultiStatement=Multiple SQL statements cannot be estimated +Plan.MysqlJsonNormalizeFailed=MySQL plan JSON could not be normalized +Plan.MysqlTransactionAborted=The current MySQL transaction is already aborted +Plan.NestedWrite=Statements with nested writes cannot be estimated +Plan.NotSingleSelect=Only a single SELECT statement can be estimated +Plan.ParseFailed=Chen cannot parse this SQL for an estimated plan +Plan.PostgresTransactionAborted=The current PostgreSQL transaction is already aborted +Plan.PostgresTxProbeFailed=PostgreSQL transaction state probe failed +Plan.PostgresTxProbeFailedDetail=PostgreSQL transaction state probe failed: %s +Plan.PostgresTxUnknown=PostgreSQL transaction state is unknown +Plan.RawPlanExceededDisplayLimit=Raw plan exceeded the display limit +Plan.ReconnectWithoutSavepointProbe=Reconnect and retry without relying on SAVEPOINT probing +Plan.RemoveUdfOrExternalRowset=Remove user-defined functions or external rowset access and retry +Plan.RemoveUnsafeFunctions=Remove user-defined or planning-time-unsafe functions and retry +Plan.RequestCancelled=Execution plan request cancelled +Plan.RollbackThenRetry=Rollback or finish the existing transaction, then retry +Plan.RunExplainAutoCommitOrOpenTx=Run EXPLAIN while auto-commit idle or inside an already-open transaction +Plan.SavepointCreateFailed=Failed to create a request-scoped SAVEPOINT +Plan.SavepointRestoreFailed=SAVEPOINT restore failed after EXPLAIN +Plan.SelectInto=SELECT INTO cannot be estimated +Plan.SingleSelect=Single SELECT +Plan.SqlExceedsSizeLimit=SQL exceeds the execution plan size limit +Plan.StatementCannotBeEstimated=This statement cannot be estimated +Plan.UnboundParameter=Statements with unbound parameters cannot be estimated +Plan.UnsupportedDatabase=Execution plan is not implemented for this datasource +Properties=Properties +QueryOkRows=Query OK, %d rows discovered +QueryUnsupported=This SQL is not supported by Query because Chen cannot parse it with Druid. Use Console to draft and run it. +Refresh=Refresh +RelationCatalogMismatch=Relation catalog does not match the active context +RowsExported=%d rows exported +Run=Run +RunHotKey=Run (Ctrl + Enter) +RunSelected=Run selected +Save=Save +SaveSQL=Save SQL +SaveSucceed=Save succeed +Scope=Scope +SelectSQL=Select SQL +SessionClosedBy=This session has been closed by %s. +SessionFinished=Session finished +SessionLockedError=This session has been paused and cannot continue executing commands +SessionLockedMessage=This session is locked by %s, please wait for the session to be unlocked. +SessionUnlockedMessage=This session has been unlocked by %s. +ShowProperties=Show properties +StopHotKey=Stop (Ctrl + D) +Submit=Submit +TooManyRelationsInMetadataRequest=Too many relations in one metadata request +Total=Total +Type=Type +Unknown=unknown +UnknownRelation=Unknown relation +UnknownRelationSchema=Unknown relation schema +UnknownSchemaMetadataSection=Unknown schema metadata section: %s +UnknownTableMetadataSection=Unknown table metadata section: %s +UpdateStatement=Update statement +User=User +UserCancelCommandReviewError=The user has cancelled the command review. +Version=Version +ViewData=View data +WaitCommandReviewMessage=The review request has been initiated, please wait for the review results +Warning=Warning diff --git a/backend/web/src/main/resources/i18n/chen_en.properties b/backend/web/src/main/resources/i18n/chen_en.properties new file mode 100644 index 00000000..b335a457 --- /dev/null +++ b/backend/web/src/main/resources/i18n/chen_en.properties @@ -0,0 +1,165 @@ +ACLRejectError=\ Command rejected by ACL +AffectedRows=Affected rows +AlreadyFirstPageError=It's already the first page +AlreadyLastPageError=It's already the last page +BatchSqlReview=Batch SQL:\n%s\n\nReview-triggering SQL:\n%s +Cancel=Cancel +ChangeContextError=Error while changing context +CommandReview=Command review +CommandReviewMessage=The command you entered requires verification before it can be executed. Would you like to initiate a review request? +CommandReviewRejectBy=Command review request has been rejected by %s +CommandReviewTimeoutError=Command review request has timed out +CommandWarningDialogMessage=The command you executed is risky and an alert notification will be sent to the administrator. Do you want to continue? +Confirm=Confirm +ConfirmDelete=Confirm delete +Connected=Connected +ConnectError=Error while fetching data +ConnectSuccess=Connected successfully +Content=Content +Copy=Copy +CopyFailed=Copy failed +CopyNotAllowed=You are not allowed to copy, please contact the administrator to open it! +CopySucceeded=Copy succeeded +CreateCommandTicketFailed=Failed to create command ticket: %s +Current=Current +DatabaseExplorer=Database Explorer +DatabaseProperties=Datasource properties +Delete=Delete +DeleteSuccess=Deleted successfully +DownloadNotAllowed=You are not allowed to download, please contact the administrator to open it! +DriverClass=Driver class +DriverVersion=Driver version +ErrorMessage=Error message +ErrorPrefix=Error: %s +ExecuteError=Error while executing +ExecuteNodeActionFailed=Failed to execute action on %s +ExecuteSuccess=Executed successfully +ExecutionCanceled=Execution Canceled +ExportALL=Export all data +ExportAll=Export all +ExportCurrent=Export current page +ExportData=Export data +ExportDataNamed=Export data: %s +FailedToLoadSchemaOverview=Failed to load schema overview metadata +FailedToLoadSqlColumnMetadata=Failed to load SQL column metadata +FailedToLoadSqlHints=Failed to load SQL hints +FailedToLoadSqlRelationMetadata=Failed to load SQL relation metadata +FailedToLoadTableMetadata=Failed to load table metadata +FetchError=Error while fetching data +Format=Format +FormatHotKey=Format (Ctrl + L) +GetChildrenFailed=Failed to load children of %s +InitializeDatasource=Initialize datasource +InitializeDatasourceFailed=Initialize datasource failed +initializingDatasourceFailedMessage=Connection failed, please check if the database connection configuration is correct +InitializingDatasourceMessage=Initializing data source, please wait... +Insert=Insert +InsertStatement=Insert statement +InvalidExportFile=Invalid export file +InvalidMetadataContext=Invalid metadata context +InvalidRelationMetadataRequest=Invalid relation metadata request +InvalidResourceAction=Invalid resource action +InvalidResourceNode=Invalid resource node +InvalidSchemaOverviewContext=Invalid schema overview context +InvalidTableMetadataContext=Invalid table metadata context +JDBCURL=JDBC URL +LogOutput=Log Output +Name=Name +NewQuery=New query +NoPermissionError=\ You do not have permission to perform this action +NumRow={num} rows +Open=Open +OverMaxIdleTimeError=Since this sessioon has been idle for more than %d minutes, it has been closed +OverMaxSessionTimeError=Since this session has been active for more than %d hours, it has been closed +ParseError=Error while parsing +PasteNotAllowed=You are not allowed to paste, please contact the administrator to open it! +PermissionAlreadyExpired=Permission already expired +PermissionExpiredDialogMessage=Permission has expired, and the session will expire in ten minutes. Please contact the administrator promptly for renewal +PermissionExpiredDialogTitle=Permission expired +PermissionsExpiredOn=Permissions associated with this session expired on %s +Plan.Assignment=Statements with assignment cannot be estimated +Plan.CannotEstimateFailedTransaction=Cannot estimate a plan in a failed transaction +Plan.CheckTxAndRetry=Check the transaction state and retry +Plan.ClassicExplainEmpty=Classic EXPLAIN produced no rows +Plan.ConnectionUnusable=Connection became unusable during execution plan +Plan.DynamicSql=Dynamic SQL cannot be estimated +Plan.EmptySql=SQL statement is empty +Plan.EstimateFailed=Failed to estimate execution plan +Plan.ExplainWrapper=Wrapped EXPLAIN statements cannot be estimated +Plan.FailedToCreateSavepoint=Failed to create SAVEPOINT +Plan.FinishTxAndRetry=Finish the existing transaction and retry +Plan.FunctionNotMariaDBBuiltin=Function %s is not a known MariaDB builtin +Plan.FunctionNotMysqlBuiltin=Function %s is not a known MySQL builtin +Plan.FunctionNotPostgresBuiltin=Function %s is not a known PostgreSQL builtin +Plan.FunctionNotSafeAtPlanTime=Function %s is not safe to estimate at planning time +Plan.FunctionNotSafeToEstimate=Function %s is not safe to estimate +Plan.FunctionOutsideMariaDBCatalogs=Function %s is outside known MariaDB catalogs +Plan.FunctionOutsideMysqlCatalogs=Function %s is outside known MySQL catalogs +Plan.FunctionOutsidePgCatalog=Function %s is outside pg_catalog and cannot be proven side-effect free +Plan.LockingRead=Locking reads cannot be estimated +Plan.ManualCommitIdleCannotStart=A manual-commit idle connection cannot start a plan transaction +Plan.ManualCommitIdleNotUsed=Manual-commit idle connections are not used for EXPLAIN +Plan.MariaDBJsonNormalizeFailed=MariaDB plan JSON could not be normalized +Plan.MariaDBTransactionAborted=The current MariaDB transaction is already aborted +Plan.MultiStatement=Multiple SQL statements cannot be estimated +Plan.MysqlJsonNormalizeFailed=MySQL plan JSON could not be normalized +Plan.MysqlTransactionAborted=The current MySQL transaction is already aborted +Plan.NestedWrite=Statements with nested writes cannot be estimated +Plan.NotSingleSelect=Only a single SELECT statement can be estimated +Plan.ParseFailed=Chen cannot parse this SQL for an estimated plan +Plan.PostgresTransactionAborted=The current PostgreSQL transaction is already aborted +Plan.PostgresTxProbeFailed=PostgreSQL transaction state probe failed +Plan.PostgresTxProbeFailedDetail=PostgreSQL transaction state probe failed: %s +Plan.PostgresTxUnknown=PostgreSQL transaction state is unknown +Plan.RawPlanExceededDisplayLimit=Raw plan exceeded the display limit +Plan.ReconnectWithoutSavepointProbe=Reconnect and retry without relying on SAVEPOINT probing +Plan.RemoveUdfOrExternalRowset=Remove user-defined functions or external rowset access and retry +Plan.RemoveUnsafeFunctions=Remove user-defined or planning-time-unsafe functions and retry +Plan.RequestCancelled=Execution plan request cancelled +Plan.RollbackThenRetry=Rollback or finish the existing transaction, then retry +Plan.RunExplainAutoCommitOrOpenTx=Run EXPLAIN while auto-commit idle or inside an already-open transaction +Plan.SavepointCreateFailed=Failed to create a request-scoped SAVEPOINT +Plan.SavepointRestoreFailed=SAVEPOINT restore failed after EXPLAIN +Plan.SelectInto=SELECT INTO cannot be estimated +Plan.SingleSelect=Single SELECT +Plan.SqlExceedsSizeLimit=SQL exceeds the execution plan size limit +Plan.StatementCannotBeEstimated=This statement cannot be estimated +Plan.UnboundParameter=Statements with unbound parameters cannot be estimated +Plan.UnsupportedDatabase=Execution plan is not implemented for this datasource +Properties=Properties +QueryOkRows=Query OK, %d rows discovered +QueryUnsupported=This SQL is not supported by Query because Chen cannot parse it with Druid. Use Console to draft and run it. +Refresh=Refresh +RelationCatalogMismatch=Relation catalog does not match the active context +RowsExported=%d rows exported +Run=Run +RunHotKey=Run (Ctrl + Enter) +RunSelected=Run selected +Save=Save +SaveSQL=Save SQL +SaveSucceed=Save succeed +Scope=Scope +SelectSQL=Select SQL +SessionClosedBy=This session has been closed by %s. +SessionFinished=Session finished +SessionLockedError=This session has been paused and cannot continue executing commands +SessionLockedMessage=This session is locked by %s, please wait for the session to be unlocked. +SessionUnlockedMessage=This session has been unlocked by %s. +ShowProperties=Show properties +StopHotKey=Stop (Ctrl + D) +Submit=Submit +TooManyRelationsInMetadataRequest=Too many relations in one metadata request +Total=Total +Type=Type +Unknown=unknown +UnknownRelation=Unknown relation +UnknownRelationSchema=Unknown relation schema +UnknownSchemaMetadataSection=Unknown schema metadata section: %s +UnknownTableMetadataSection=Unknown table metadata section: %s +UpdateStatement=Update statement +User=User +UserCancelCommandReviewError=The user has cancelled the command review. +Version=Version +ViewData=View data +WaitCommandReviewMessage=The review request has been initiated, please wait for the review results +Warning=Warning diff --git a/backend/web/src/main/resources/i18n/chen_es.properties b/backend/web/src/main/resources/i18n/chen_es.properties new file mode 100644 index 00000000..20ec1e5a --- /dev/null +++ b/backend/web/src/main/resources/i18n/chen_es.properties @@ -0,0 +1,165 @@ +ACLRejectError=Este comando está prohibido de ser ejecutado. +AffectedRows=Número de filas afectadas +AlreadyFirstPageError=Ya estás en la primera página +AlreadyLastPageError=Ya es la última página +BatchSqlReview=SQL por lotes:\n%s\n\nSQL que activa la revisión:\n%s +Cancel=Cancelar +ChangeContextError=Cambio de contexto fallido +CommandReview=Revisión de comandos +CommandReviewMessage=El comando que ingresaste necesita ser revisado antes de ser ejecutado, ¿deseas iniciar una solicitud de revisión? +CommandReviewRejectBy=Revisión de comandos rechazada por %s +CommandReviewTimeoutError=Tiempo de espera para la revisión del comando +CommandWarningDialogMessage=El comando que ha ejecutado presenta riesgos, se enviará una notificación de advertencia al administrador. ¿Desea continuar? +Confirm=Confirmar +ConfirmDelete=Confirmar eliminación +Connected=Conectado +ConnectError=Conexión fallida +ConnectSuccess=Conexión exitosa. +Content=Contenido +Copy=Copiar. +CopyFailed=Copia fallida +CopyNotAllowed=No se permite copiar, ¡por favor contacte al administrador para habilitar los permisos! +CopySucceeded=Copia exitosa +CreateCommandTicketFailed=No se pudo crear el ticket de comando: %s +Current=Actual +DatabaseExplorer=Navegador de base de datos. +DatabaseProperties=Atributos de la fuente de datos +Delete=Eliminar +DeleteSuccess=Eliminado con éxito +DownloadNotAllowed=No se permite la descarga, ¡por favor contacta al administrador para habilitar los permisos! +DriverClass=Clase de controlador. +DriverVersion=Versión del controlador +ErrorMessage=Mensaje de error +ErrorPrefix=Error: %s +ExecuteError=Ejecución fallida +ExecuteNodeActionFailed=No se pudo ejecutar la acción en %s +ExecuteSuccess=Ejecución exitosa +ExecutionCanceled=Ejecución cancelada. +ExportALL=Exportar todos los datos +ExportAll=Exportar todo. +ExportCurrent=Exportar la página actual +ExportData=Exportar datos. +ExportDataNamed=Exportar datos: %s +FailedToLoadSchemaOverview=No se pudieron cargar los metadatos del resumen del esquema +FailedToLoadSqlColumnMetadata=No se pudieron cargar los metadatos de columnas SQL +FailedToLoadSqlHints=No se pudieron cargar las sugerencias SQL +FailedToLoadSqlRelationMetadata=No se pudieron cargar los metadatos de relaciones SQL +FailedToLoadTableMetadata=No se pudieron cargar los metadatos de la tabla +FetchError=Falló la obtención de datos +Format=Formato. +FormatHotKey=Formatear (Ctrl + L). +GetChildrenFailed=No se pudieron cargar los nodos secundarios de %s +InitializeDatasource=Inicializando fuente de datos +InitializeDatasourceFailed=Fallo en la inicialización de la fuente de datos +initializingDatasourceFailedMessage=Conexión fallida, por favor verifica si la configuración de conexión a la base de datos es correcta. +InitializingDatasourceMessage=Iniciando la fuente de datos, por favor espere... +Insert=Insertar +InsertStatement=Instrucción de inserción +InvalidExportFile=Archivo de exportación no válido +InvalidMetadataContext=Contexto de metadatos no válido +InvalidRelationMetadataRequest=Solicitud de metadatos de relación no válida +InvalidResourceAction=Acción de recurso no válida +InvalidResourceNode=Nodo de recurso no válido +InvalidSchemaOverviewContext=Contexto de resumen de esquema no válido +InvalidTableMetadataContext=Contexto de metadatos de tabla no válido +JDBCURL=URL JDBC. +LogOutput=Registro de salida +Name=Nombre +NewQuery=Nueva consulta +NoPermissionError=No tienes permiso para realizar esta acción +NumRow={num} filas +Open=Abrir +OverMaxIdleTimeError=Debido a que esta sesión ha estado inactiva durante más de %d minutos, ha sido cerrada. +OverMaxSessionTimeError=Debido a que esta sesión ha estado activa durante más de %d horas, ha sido cerrada. +ParseError=Análisis fallido +PasteNotAllowed=No se permite pegar, ¡por favor contacta al administrador para habilitar los permisos! +PermissionAlreadyExpired=Autorización expirada +PermissionExpiredDialogMessage=La autorización ha expirado, la sesión expirará en diez minutos, por favor contacta al administrador para la renovación a tiempo +PermissionExpiredDialogTitle=La autorización ha expirado. +PermissionsExpiredOn=Los permisos asociados a esta sesión han expirado el %s +Plan.Assignment=Statements with assignment cannot be estimated +Plan.CannotEstimateFailedTransaction=Cannot estimate a plan in a failed transaction +Plan.CheckTxAndRetry=Check the transaction state and retry +Plan.ClassicExplainEmpty=Classic EXPLAIN produced no rows +Plan.ConnectionUnusable=Connection became unusable during execution plan +Plan.DynamicSql=Dynamic SQL cannot be estimated +Plan.EmptySql=SQL statement is empty +Plan.EstimateFailed=Failed to estimate execution plan +Plan.ExplainWrapper=Wrapped EXPLAIN statements cannot be estimated +Plan.FailedToCreateSavepoint=Failed to create SAVEPOINT +Plan.FinishTxAndRetry=Finish the existing transaction and retry +Plan.FunctionNotMariaDBBuiltin=Function %s is not a known MariaDB builtin +Plan.FunctionNotMysqlBuiltin=Function %s is not a known MySQL builtin +Plan.FunctionNotPostgresBuiltin=Function %s is not a known PostgreSQL builtin +Plan.FunctionNotSafeAtPlanTime=Function %s is not safe to estimate at planning time +Plan.FunctionNotSafeToEstimate=Function %s is not safe to estimate +Plan.FunctionOutsideMariaDBCatalogs=Function %s is outside known MariaDB catalogs +Plan.FunctionOutsideMysqlCatalogs=Function %s is outside known MySQL catalogs +Plan.FunctionOutsidePgCatalog=Function %s is outside pg_catalog and cannot be proven side-effect free +Plan.LockingRead=Locking reads cannot be estimated +Plan.ManualCommitIdleCannotStart=A manual-commit idle connection cannot start a plan transaction +Plan.ManualCommitIdleNotUsed=Manual-commit idle connections are not used for EXPLAIN +Plan.MariaDBJsonNormalizeFailed=MariaDB plan JSON could not be normalized +Plan.MariaDBTransactionAborted=The current MariaDB transaction is already aborted +Plan.MultiStatement=Multiple SQL statements cannot be estimated +Plan.MysqlJsonNormalizeFailed=MySQL plan JSON could not be normalized +Plan.MysqlTransactionAborted=The current MySQL transaction is already aborted +Plan.NestedWrite=Statements with nested writes cannot be estimated +Plan.NotSingleSelect=Only a single SELECT statement can be estimated +Plan.ParseFailed=Chen cannot parse this SQL for an estimated plan +Plan.PostgresTransactionAborted=The current PostgreSQL transaction is already aborted +Plan.PostgresTxProbeFailed=PostgreSQL transaction state probe failed +Plan.PostgresTxProbeFailedDetail=PostgreSQL transaction state probe failed: %s +Plan.PostgresTxUnknown=PostgreSQL transaction state is unknown +Plan.RawPlanExceededDisplayLimit=Raw plan exceeded the display limit +Plan.ReconnectWithoutSavepointProbe=Reconnect and retry without relying on SAVEPOINT probing +Plan.RemoveUdfOrExternalRowset=Remove user-defined functions or external rowset access and retry +Plan.RemoveUnsafeFunctions=Remove user-defined or planning-time-unsafe functions and retry +Plan.RequestCancelled=Execution plan request cancelled +Plan.RollbackThenRetry=Rollback or finish the existing transaction, then retry +Plan.RunExplainAutoCommitOrOpenTx=Run EXPLAIN while auto-commit idle or inside an already-open transaction +Plan.SavepointCreateFailed=Failed to create a request-scoped SAVEPOINT +Plan.SavepointRestoreFailed=SAVEPOINT restore failed after EXPLAIN +Plan.SelectInto=SELECT INTO cannot be estimated +Plan.SingleSelect=Single SELECT +Plan.SqlExceedsSizeLimit=SQL exceeds the execution plan size limit +Plan.StatementCannotBeEstimated=This statement cannot be estimated +Plan.UnboundParameter=Statements with unbound parameters cannot be estimated +Plan.UnsupportedDatabase=Execution plan is not implemented for this datasource +Properties=Atributos +QueryOkRows=Consulta correcta, %d filas encontradas +QueryUnsupported=Query no admite este SQL porque Chen no puede analizarlo con Druid. Use Console para redactarlo y ejecutarlo. +Refresh=Actualizar +RelationCatalogMismatch=El catálogo de la relación no coincide con el contexto activo +RowsExported=%d filas exportadas +Run=Ejecutar +RunHotKey=Ejecutar (Ctrl + Enter). +RunSelected=Ejecutar seleccionado. +Save=Guardar. +SaveSQL=Guardar SQL. +SaveSucceed=Guardado exitoso. +Scope=Rango +SelectSQL=Seleccionar SQL +SessionClosedBy=La sesión ha sido cerrada por %s +SessionFinished=La sesión ha terminado. +SessionLockedError=La sesión actual ha sido bloqueada y no se pueden seguir ejecutando comandos. +SessionLockedMessage=Esta sesión ha sido bloqueada por %s y no se pueden seguir ejecutando comandos. +SessionUnlockedMessage=Esta sesión ha sido desbloqueada por %s, puedes continuar con la ejecución del comando +ShowProperties=Atributos +StopHotKey=Detener (Ctrl + C). +Submit=Enviar +TooManyRelationsInMetadataRequest=Demasiadas relaciones en una solicitud de metadatos +Total=Total. +Type=Tipo. +Unknown=desconocido +UnknownRelation=Relación desconocida +UnknownRelationSchema=Esquema de relación desconocido +UnknownSchemaMetadataSection=Sección de metadatos de esquema desconocida: %s +UnknownTableMetadataSection=Sección de metadatos de tabla desconocida: %s +UpdateStatement=Declaración de actualización +User=Usuario. +UserCancelCommandReviewError=El usuario canceló la revisión del comando +Version=Versión +ViewData=Ver datos +WaitCommandReviewMessage=Se ha iniciado la solicitud de revisión, por favor espere el resultado. +Warning=Advertencia diff --git a/backend/web/src/main/resources/i18n/chen_ja.properties b/backend/web/src/main/resources/i18n/chen_ja.properties new file mode 100644 index 00000000..953f3903 --- /dev/null +++ b/backend/web/src/main/resources/i18n/chen_ja.properties @@ -0,0 +1,165 @@ +ACLRejectError=このコマンドの実行は禁止されています +AffectedRows=影響を受けた行数 +AlreadyFirstPageError=既に最初のページです +AlreadyLastPageError=既に最後のページです +BatchSqlReview=バッチ SQL:\n%s\n\nレビュー対象の SQL:\n%s +Cancel=キャンセル +ChangeContextError=コンテキストの切り替えに失敗しました +CommandReview=コマンドレビュー +CommandReviewMessage=入力されたコマンドはレビュー後に実行されます。レビューリクエストを送信しますか? +CommandReviewRejectBy=コマンドレビューが%sに拒否されました +CommandReviewTimeoutError=コマンドレビューがタイムアウトしました +CommandWarningDialogMessage=あなたが実行したコマンドにはリスクがあり、警告通知が管理者に送信されます。続行しますか? +Confirm=確認 +ConfirmDelete=削除の確認 +Connected=接続済み +ConnectError=接続に失敗しました +ConnectSuccess=接続に成功しました +Content=内容 +Copy=コピー +CopyFailed=コピーしっぱい +CopyNotAllowed=コピーは許可されていません。管理者に連絡して権限を開いてください! +CopySucceeded=コピーせいこう +CreateCommandTicketFailed=コマンド確認チケットの作成に失敗しました: %s +Current=現在 +DatabaseExplorer=データベースエクスプローラー +DatabaseProperties=データソースプロパティ +Delete=削除 +DeleteSuccess=削除に成功しました +DownloadNotAllowed=ダウンロードは許可されていません。管理者に連絡して権限を開いてください! +DriverClass=ドライバークラス +DriverVersion=ドライバーバージョン +ErrorMessage=エラーメッセージ +ErrorPrefix=エラー: %s +ExecuteError=実行に失敗しました +ExecuteNodeActionFailed=%s のアクション実行に失敗しました +ExecuteSuccess=実行に成功しました +ExecutionCanceled=実行がキャンセルされました +ExportALL=すべてのデータをエクスポート +ExportAll=すべてエクスポート +ExportCurrent=現在のページをエクスポート +ExportData=データをエクスポート +ExportDataNamed=データをエクスポート: %s +FailedToLoadSchemaOverview=スキーマ概要メタデータの読み込みに失敗しました +FailedToLoadSqlColumnMetadata=SQL 列メタデータの読み込みに失敗しました +FailedToLoadSqlHints=SQL ヒントの読み込みに失敗しました +FailedToLoadSqlRelationMetadata=SQL リレーションメタデータの読み込みに失敗しました +FailedToLoadTableMetadata=テーブルメタデータの読み込みに失敗しました +FetchError=データの取得に失敗しました +Format=フォーマット +FormatHotKey=フォーマット (Ctrl + L) +GetChildrenFailed=%s の子ノードの読み込みに失敗しました +InitializeDatasource=データソースを初期化 +InitializeDatasourceFailed=データソースの初期化に失敗しました +initializingDatasourceFailedMessage=接続に失敗しました。データベース接続設定が正しいか確認してください +InitializingDatasourceMessage=データソースを初期化中、しばらくお待ちください... +Insert=挿入 +InsertStatement=インサート文 +InvalidExportFile=無効なエクスポートファイルです +InvalidMetadataContext=メタデータコンテキストが無効です +InvalidRelationMetadataRequest=リレーションメタデータリクエストが無効です +InvalidResourceAction=リソースアクションが無効です +InvalidResourceNode=リソースノードが無効です +InvalidSchemaOverviewContext=スキーマ概要コンテキストが無効です +InvalidTableMetadataContext=テーブルメタデータコンテキストが無効です +JDBCURL=JDBC URL +LogOutput=ログ出力 +Name=名前 +NewQuery=新規クエリ +NoPermissionError=この操作を実行する権限がありません +NumRow={num} 行 +Open=開く +OverMaxIdleTimeError=このセッションのアイドル時間が%d分を超えたため、閉じられました +OverMaxSessionTimeError=このセッションの時間が%d時間を超えたため、閉じられました +ParseError=解析に失敗しました +PasteNotAllowed=貼り付けは許可されていません。管理者に連絡して権限を開いてください! +PermissionAlreadyExpired=権限が期限切れ +PermissionExpiredDialogMessage=権限が期限切れで、セッションは10分後に期限切れになります。管理者に連絡し、期限を延長してください。 +PermissionExpiredDialogTitle=権限が期限切れ +PermissionsExpiredOn=このセッションに関連する権限は%sに期限切れです +Plan.Assignment=Statements with assignment cannot be estimated +Plan.CannotEstimateFailedTransaction=Cannot estimate a plan in a failed transaction +Plan.CheckTxAndRetry=Check the transaction state and retry +Plan.ClassicExplainEmpty=Classic EXPLAIN produced no rows +Plan.ConnectionUnusable=Connection became unusable during execution plan +Plan.DynamicSql=Dynamic SQL cannot be estimated +Plan.EmptySql=SQL statement is empty +Plan.EstimateFailed=Failed to estimate execution plan +Plan.ExplainWrapper=Wrapped EXPLAIN statements cannot be estimated +Plan.FailedToCreateSavepoint=Failed to create SAVEPOINT +Plan.FinishTxAndRetry=Finish the existing transaction and retry +Plan.FunctionNotMariaDBBuiltin=Function %s is not a known MariaDB builtin +Plan.FunctionNotMysqlBuiltin=Function %s is not a known MySQL builtin +Plan.FunctionNotPostgresBuiltin=Function %s is not a known PostgreSQL builtin +Plan.FunctionNotSafeAtPlanTime=Function %s is not safe to estimate at planning time +Plan.FunctionNotSafeToEstimate=Function %s is not safe to estimate +Plan.FunctionOutsideMariaDBCatalogs=Function %s is outside known MariaDB catalogs +Plan.FunctionOutsideMysqlCatalogs=Function %s is outside known MySQL catalogs +Plan.FunctionOutsidePgCatalog=Function %s is outside pg_catalog and cannot be proven side-effect free +Plan.LockingRead=Locking reads cannot be estimated +Plan.ManualCommitIdleCannotStart=A manual-commit idle connection cannot start a plan transaction +Plan.ManualCommitIdleNotUsed=Manual-commit idle connections are not used for EXPLAIN +Plan.MariaDBJsonNormalizeFailed=MariaDB plan JSON could not be normalized +Plan.MariaDBTransactionAborted=The current MariaDB transaction is already aborted +Plan.MultiStatement=Multiple SQL statements cannot be estimated +Plan.MysqlJsonNormalizeFailed=MySQL plan JSON could not be normalized +Plan.MysqlTransactionAborted=The current MySQL transaction is already aborted +Plan.NestedWrite=Statements with nested writes cannot be estimated +Plan.NotSingleSelect=Only a single SELECT statement can be estimated +Plan.ParseFailed=Chen cannot parse this SQL for an estimated plan +Plan.PostgresTransactionAborted=The current PostgreSQL transaction is already aborted +Plan.PostgresTxProbeFailed=PostgreSQL transaction state probe failed +Plan.PostgresTxProbeFailedDetail=PostgreSQL transaction state probe failed: %s +Plan.PostgresTxUnknown=PostgreSQL transaction state is unknown +Plan.RawPlanExceededDisplayLimit=Raw plan exceeded the display limit +Plan.ReconnectWithoutSavepointProbe=Reconnect and retry without relying on SAVEPOINT probing +Plan.RemoveUdfOrExternalRowset=Remove user-defined functions or external rowset access and retry +Plan.RemoveUnsafeFunctions=Remove user-defined or planning-time-unsafe functions and retry +Plan.RequestCancelled=Execution plan request cancelled +Plan.RollbackThenRetry=Rollback or finish the existing transaction, then retry +Plan.RunExplainAutoCommitOrOpenTx=Run EXPLAIN while auto-commit idle or inside an already-open transaction +Plan.SavepointCreateFailed=Failed to create a request-scoped SAVEPOINT +Plan.SavepointRestoreFailed=SAVEPOINT restore failed after EXPLAIN +Plan.SelectInto=SELECT INTO cannot be estimated +Plan.SingleSelect=Single SELECT +Plan.SqlExceedsSizeLimit=SQL exceeds the execution plan size limit +Plan.StatementCannotBeEstimated=This statement cannot be estimated +Plan.UnboundParameter=Statements with unbound parameters cannot be estimated +Plan.UnsupportedDatabase=Execution plan is not implemented for this datasource +Properties=プロパティ +QueryOkRows=クエリ成功、%d 行を取得しました +QueryUnsupported=Chen が Druid で解析できないため、Query ではこの SQL を実行できません。Console で作成して実行してください。 +Refresh=リフレッシュ +RelationCatalogMismatch=リレーションカタログが現在のコンテキストと一致しません +RowsExported=%d 行をエクスポートしました +Run=実行 +RunHotKey=実行 (Ctrl + Enter) +RunSelected=選択を実行 +Save=保存 +SaveSQL=SQLを保存 +SaveSucceed=保存に成功しました +Scope=範囲 +SelectSQL=SQLを選択 +SessionClosedBy=セッションが%sによって閉じられました +SessionFinished=セッションが終了しました +SessionLockedError=現在のセッションはロックされており、コマンドを続行できません +SessionLockedMessage=このセッションは%sにロックされており、コマンドを続行できません +SessionUnlockedMessage=このセッションは%sにアンロックされ、コマンドを続行できます +ShowProperties=プロパティ +StopHotKey=停止 (Ctrl + C) +Submit=提出 +TooManyRelationsInMetadataRequest=1 回のメタデータリクエストに含まれるリレーションが多すぎます +Total=合計 +Type=タイプ +Unknown=不明 +UnknownRelation=不明なリレーションです +UnknownRelationSchema=不明なリレーションスキーマです +UnknownSchemaMetadataSection=不明なスキーマメタデータセクションです: %s +UnknownTableMetadataSection=不明なテーブルメタデータセクションです: %s +UpdateStatement=アップデート文 +User=ユーザー +UserCancelCommandReviewError=ユーザーがコマンドレビューをキャンセルしました +Version=バージョン +ViewData=データを見る +WaitCommandReviewMessage=レビューリクエストが送信されました。レビュー結果をお待ちください +Warning=警告 diff --git a/backend/web/src/main/resources/i18n/chen_ko.properties b/backend/web/src/main/resources/i18n/chen_ko.properties new file mode 100644 index 00000000..a0f164be --- /dev/null +++ b/backend/web/src/main/resources/i18n/chen_ko.properties @@ -0,0 +1,165 @@ +ACLRejectError=이 명령은 실행할 수 없습니다 +AffectedRows=영향을 받은 행 수 +AlreadyFirstPageError=이미 첫 페이지입니다. +AlreadyLastPageError=이미 마지막 페이지입니다. +BatchSqlReview=배치 SQL:\n%s\n\n검토를 트리거한 SQL:\n%s +Cancel=취소 +ChangeContextError=콘텍스트 전환 실패 +CommandReview=명령어 검토 +CommandReviewMessage=귀하가 입력한 명령은 검토 후 실행해야 합니다. 검토 요청을 하시겠습니까? +CommandReviewRejectBy=명령 검토가 %s에 의해 거부되었습니다. +CommandReviewTimeoutError=명령 재검토 시간 초과 +CommandWarningDialogMessage=귀하가 실행한 명령은 위험이 있습니다. 경고 알림이 관리자에게 전송됩니다. 계속하시겠습니까? +Confirm=확인 +ConfirmDelete=삭제 확인 +Connected=연결됨 +ConnectError=연결 실패 +ConnectSuccess=연결 성공 +Content=내용 +Copy=복사 +CopyFailed=복사 실패 +CopyNotAllowed=복사가 허용되지 않습니다. 관리자에게 권한을 요청해 주십시오! +CopySucceeded=복사 성공 +CreateCommandTicketFailed=명령 검토 티켓을 만들지 못했습니다: %s +Current=현재 +DatabaseExplorer=데이터베이스 탐색기 +DatabaseProperties=데이터 원본 속성 +Delete=삭제 +DeleteSuccess=삭제 성공 +DownloadNotAllowed=다운로드가 허용되지 않습니다. 관리자에게 권한을 활성화해 달라고 연락하십시오! +DriverClass=드라이버 클래스 +DriverVersion=드라이버 버전 +ErrorMessage=오류 메시지 +ErrorPrefix=오류: %s +ExecuteError=실행 실패 +ExecuteNodeActionFailed=%s에 대한 동작을 실행하지 못했습니다 +ExecuteSuccess=실행 성공 +ExecutionCanceled=실행이 취소되었습니다. +ExportALL=모든 데이터 내보내기 +ExportAll=모두 내보내기 +ExportCurrent=현재 페이지 내보내기 +ExportData=데이터 내보내기 +ExportDataNamed=데이터 내보내기: %s +FailedToLoadSchemaOverview=스키마 개요 메타데이터를 불러오지 못했습니다 +FailedToLoadSqlColumnMetadata=SQL 컬럼 메타데이터를 불러오지 못했습니다 +FailedToLoadSqlHints=SQL 힌트를 불러오지 못했습니다 +FailedToLoadSqlRelationMetadata=SQL 릴레이션 메타데이터를 불러오지 못했습니다 +FailedToLoadTableMetadata=테이블 메타데이터를 불러오지 못했습니다 +FetchError=데이터를 가져오는 데 실패했습니다 +Format=형식 +FormatHotKey=형식화 (Ctrl + L) +GetChildrenFailed=%s의 하위 노드를 불러오지 못했습니다 +InitializeDatasource=데이터 소스 초기화 +InitializeDatasourceFailed=데이터 소스 초기화 실패 +initializingDatasourceFailedMessage=연결 실패, 데이터베이스 연결 구성이 올바른지 확인해 주세요 +InitializingDatasourceMessage=데이터 소스를 초기화하는 중입니다. 잠시만 기다려 주십시오... +Insert=삽입 +InsertStatement=삽입 문 +InvalidExportFile=잘못된 내보내기 파일입니다 +InvalidMetadataContext=잘못된 메타데이터 컨텍스트입니다 +InvalidRelationMetadataRequest=잘못된 릴레이션 메타데이터 요청입니다 +InvalidResourceAction=잘못된 리소스 동작입니다 +InvalidResourceNode=잘못된 리소스 노드입니다 +InvalidSchemaOverviewContext=잘못된 스키마 개요 컨텍스트입니다 +InvalidTableMetadataContext=잘못된 테이블 메타데이터 컨텍스트입니다 +JDBCURL=JDBC URL +LogOutput=로그 출력 +Name=이름 +NewQuery=새 쿼리 만들기 +NoPermissionError=이 작업을 수행할 권한이 없습니다. +NumRow={num} 행 +Open=열기 +OverMaxIdleTimeError=이 세션의 유휴 시간이 %d 분을 초과하여 종료되었습니다 +OverMaxSessionTimeError=이 세션의 시간이 %d 시간을 초과하여 종료되었습니다. +ParseError=분석 실패 +PasteNotAllowed=붙여넣기가 허용되지 않습니다. 관리자에게 문의하여 권한을 요청하십시오! +PermissionAlreadyExpired=권한이 만료되었습니다. +PermissionExpiredDialogMessage=권한이 만료되었습니다. 세션은 10분 후 만료될 예정입니다. 관리자가 연장할 수 있도록 신속하게 연락해 주시기 바랍니다. +PermissionExpiredDialogTitle=권한이 만료되었습니다. +PermissionsExpiredOn=이 세션에 연결된 권한이 %s에 만료되었습니다 +Plan.Assignment=Statements with assignment cannot be estimated +Plan.CannotEstimateFailedTransaction=Cannot estimate a plan in a failed transaction +Plan.CheckTxAndRetry=Check the transaction state and retry +Plan.ClassicExplainEmpty=Classic EXPLAIN produced no rows +Plan.ConnectionUnusable=Connection became unusable during execution plan +Plan.DynamicSql=Dynamic SQL cannot be estimated +Plan.EmptySql=SQL statement is empty +Plan.EstimateFailed=Failed to estimate execution plan +Plan.ExplainWrapper=Wrapped EXPLAIN statements cannot be estimated +Plan.FailedToCreateSavepoint=Failed to create SAVEPOINT +Plan.FinishTxAndRetry=Finish the existing transaction and retry +Plan.FunctionNotMariaDBBuiltin=Function %s is not a known MariaDB builtin +Plan.FunctionNotMysqlBuiltin=Function %s is not a known MySQL builtin +Plan.FunctionNotPostgresBuiltin=Function %s is not a known PostgreSQL builtin +Plan.FunctionNotSafeAtPlanTime=Function %s is not safe to estimate at planning time +Plan.FunctionNotSafeToEstimate=Function %s is not safe to estimate +Plan.FunctionOutsideMariaDBCatalogs=Function %s is outside known MariaDB catalogs +Plan.FunctionOutsideMysqlCatalogs=Function %s is outside known MySQL catalogs +Plan.FunctionOutsidePgCatalog=Function %s is outside pg_catalog and cannot be proven side-effect free +Plan.LockingRead=Locking reads cannot be estimated +Plan.ManualCommitIdleCannotStart=A manual-commit idle connection cannot start a plan transaction +Plan.ManualCommitIdleNotUsed=Manual-commit idle connections are not used for EXPLAIN +Plan.MariaDBJsonNormalizeFailed=MariaDB plan JSON could not be normalized +Plan.MariaDBTransactionAborted=The current MariaDB transaction is already aborted +Plan.MultiStatement=Multiple SQL statements cannot be estimated +Plan.MysqlJsonNormalizeFailed=MySQL plan JSON could not be normalized +Plan.MysqlTransactionAborted=The current MySQL transaction is already aborted +Plan.NestedWrite=Statements with nested writes cannot be estimated +Plan.NotSingleSelect=Only a single SELECT statement can be estimated +Plan.ParseFailed=Chen cannot parse this SQL for an estimated plan +Plan.PostgresTransactionAborted=The current PostgreSQL transaction is already aborted +Plan.PostgresTxProbeFailed=PostgreSQL transaction state probe failed +Plan.PostgresTxProbeFailedDetail=PostgreSQL transaction state probe failed: %s +Plan.PostgresTxUnknown=PostgreSQL transaction state is unknown +Plan.RawPlanExceededDisplayLimit=Raw plan exceeded the display limit +Plan.ReconnectWithoutSavepointProbe=Reconnect and retry without relying on SAVEPOINT probing +Plan.RemoveUdfOrExternalRowset=Remove user-defined functions or external rowset access and retry +Plan.RemoveUnsafeFunctions=Remove user-defined or planning-time-unsafe functions and retry +Plan.RequestCancelled=Execution plan request cancelled +Plan.RollbackThenRetry=Rollback or finish the existing transaction, then retry +Plan.RunExplainAutoCommitOrOpenTx=Run EXPLAIN while auto-commit idle or inside an already-open transaction +Plan.SavepointCreateFailed=Failed to create a request-scoped SAVEPOINT +Plan.SavepointRestoreFailed=SAVEPOINT restore failed after EXPLAIN +Plan.SelectInto=SELECT INTO cannot be estimated +Plan.SingleSelect=Single SELECT +Plan.SqlExceedsSizeLimit=SQL exceeds the execution plan size limit +Plan.StatementCannotBeEstimated=This statement cannot be estimated +Plan.UnboundParameter=Statements with unbound parameters cannot be estimated +Plan.UnsupportedDatabase=Execution plan is not implemented for this datasource +Properties=속성 +QueryOkRows=쿼리 성공, %d개 행을 찾았습니다 +QueryUnsupported=Chen이 Druid로 이 SQL을 구문 분석할 수 없어 Query에서 지원하지 않습니다. Console에서 작성하고 실행하세요. +Refresh=갱신 +RelationCatalogMismatch=릴레이션 카탈로그가 현재 컨텍스트와 일치하지 않습니다 +RowsExported=%d개 행을 내보냈습니다 +Run=실행 +RunHotKey=실행하기 (Ctrl + Enter) +RunSelected=선택 실행 +Save=저장 +SaveSQL=SQL 저장 +SaveSucceed=저장 성공 +Scope=범위 +SelectSQL=SQL 선택 +SessionClosedBy=세션이 %s에 의해 종료되었습니다 +SessionFinished=세션이 종료되었습니다 +SessionLockedError=현재 세션이 잠겨 있어 명령을 계속 실행할 수 없습니다 +SessionLockedMessage=현재 세션이 %s에 의해 잠겨 있어 명령어를 계속 실행할 수 없습니다 +SessionUnlockedMessage=이 세션은 %s에 의해 해제되었으며, 명령을 계속 실행할 수 있습니다 +ShowProperties=속성 +StopHotKey=중지 (Ctrl + C) +Submit=제출 +TooManyRelationsInMetadataRequest=하나의 메타데이터 요청에 릴레이션이 너무 많습니다 +Total=총계 +Type=형식 +Unknown=알 수 없음 +UnknownRelation=알 수 없는 릴레이션입니다 +UnknownRelationSchema=알 수 없는 릴레이션 스키마입니다 +UnknownSchemaMetadataSection=알 수 없는 스키마 메타데이터 섹션: %s +UnknownTableMetadataSection=알 수 없는 테이블 메타데이터 섹션: %s +UpdateStatement=업데이트 명령어 +User=사용자 +UserCancelCommandReviewError=사용자가 명령 승인을 취소했습니다 +Version=버전 +ViewData=데이터 보기 +WaitCommandReviewMessage=승인 요청이 발송되었습니다, 승인 결과를 기다려 주세요 +Warning=경고 diff --git a/backend/web/src/main/resources/i18n/chen_pt_BR.properties b/backend/web/src/main/resources/i18n/chen_pt_BR.properties new file mode 100644 index 00000000..bc1fe99d --- /dev/null +++ b/backend/web/src/main/resources/i18n/chen_pt_BR.properties @@ -0,0 +1,165 @@ +ACLRejectError=Este comando não é permitido ser executado +AffectedRows=Linhas afetadas +AlreadyFirstPageError=Já é a primeira página +AlreadyLastPageError=Já é a última página +BatchSqlReview=SQL em lote:\n%s\n\nSQL que acionou a revisão:\n%s +Cancel=Cancelar +ChangeContextError=Falha ao alternar o contexto +CommandReview=Revisão de Comando +CommandReviewMessage=O comando que você digitou precisa ser revisado antes de executar, gostaria de iniciar a solicitação de revisão? +CommandReviewRejectBy=O comando de revisão foi recusado por %s +CommandReviewTimeoutError=Tempo limite para revisão de comando +CommandWarningDialogMessage=O comando que você está tentando executar tem riscos, um aviso será enviado para o administrador. Deseja continuar? +Confirm=Confirmar +ConfirmDelete=Confirmar exclusão +Connected=Conectado +ConnectError=Falha na conexão +ConnectSuccess=Conectado com sucesso +Content=Conteúdo +Copy=cópia +CopyFailed=Cópia falhada +CopyNotAllowed=Cópia não permitida, por favor contate o administrador para liberar a permissão! +CopySucceeded=Cópia bem-sucedida +CreateCommandTicketFailed=Falha ao criar o ticket de comando: %s +Current=Atual +DatabaseExplorer=Navegador de Banco de Dados +DatabaseProperties=Propriedades da fonte de dados +Delete=Excluir +DeleteSuccess=Excluído com sucesso +DownloadNotAllowed=O download não é permitido, por favor, entre em contato com o administrador para habilitar as permissões! +DriverClass=Classe do driver +DriverVersion=Versão do driver +ErrorMessage=Mensagem de erro +ErrorPrefix=Erro: %s +ExecuteError=Falha na execução +ExecuteNodeActionFailed=Falha ao executar a ação em %s +ExecuteSuccess=Execução bem-sucedida +ExecutionCanceled=Execução cancelada +ExportALL=Exportar todos os dados +ExportAll=Exportar tudo +ExportCurrent=Exportar página atual +ExportData=Exportar dados +ExportDataNamed=Exportar dados: %s +FailedToLoadSchemaOverview=Falha ao carregar metadados da visão geral do schema +FailedToLoadSqlColumnMetadata=Falha ao carregar metadados de colunas SQL +FailedToLoadSqlHints=Falha ao carregar as dicas SQL +FailedToLoadSqlRelationMetadata=Falha ao carregar metadados de relações SQL +FailedToLoadTableMetadata=Falha ao carregar metadados da tabela +FetchError=Falha ao obter os dados +Format=Formato +FormatHotKey=Formatar (Ctrl + L) +GetChildrenFailed=Falha ao carregar os filhos de %s +InitializeDatasource=Inicializar fonte de dados +InitializeDatasourceFailed=Falha ao inicializar a fonte de dados +initializingDatasourceFailedMessage=Falha na conexão, verifique se a configuração de conexão do banco de dados está correta +InitializingDatasourceMessage=Inicializando a fonte de dados, por favor aguarde... +Insert=Inserir +InsertStatement=instrução de inserção +InvalidExportFile=Arquivo de exportação inválido +InvalidMetadataContext=Contexto de metadados inválido +InvalidRelationMetadataRequest=Solicitação de metadados de relação inválida +InvalidResourceAction=Ação de recurso inválida +InvalidResourceNode=Nó de recurso inválido +InvalidSchemaOverviewContext=Contexto da visão geral do schema inválido +InvalidTableMetadataContext=Contexto de metadados da tabela inválido +JDBCURL=URL JDBC +LogOutput=Log de saída +Name=Nome +NewQuery=Nova consulta +NoPermissionError=Não tem permissão para executar esta Action +NumRow={num} linhas +Open=Aberto +OverMaxIdleTimeError=Esta sessão foi encerrada pois o tempo ocioso foi maior que %d minutos +OverMaxSessionTimeError=Esta sessão foi encerrada por ter excedido %d horas +ParseError=Falha ao processar +PasteNotAllowed=Colagem não permitida, por favor contate o administrador para liberar a permissão! +PermissionAlreadyExpired=Autorização expirada +PermissionExpiredDialogMessage=A autorização expirou, a sessão expira em dez minutos, por favor entre em contato com o administrador para renovação +PermissionExpiredDialogTitle=A autorização expirou +PermissionsExpiredOn=As permissões associadas a esta sessão expiraram em %s +Plan.Assignment=Statements with assignment cannot be estimated +Plan.CannotEstimateFailedTransaction=Cannot estimate a plan in a failed transaction +Plan.CheckTxAndRetry=Check the transaction state and retry +Plan.ClassicExplainEmpty=Classic EXPLAIN produced no rows +Plan.ConnectionUnusable=Connection became unusable during execution plan +Plan.DynamicSql=Dynamic SQL cannot be estimated +Plan.EmptySql=SQL statement is empty +Plan.EstimateFailed=Failed to estimate execution plan +Plan.ExplainWrapper=Wrapped EXPLAIN statements cannot be estimated +Plan.FailedToCreateSavepoint=Failed to create SAVEPOINT +Plan.FinishTxAndRetry=Finish the existing transaction and retry +Plan.FunctionNotMariaDBBuiltin=Function %s is not a known MariaDB builtin +Plan.FunctionNotMysqlBuiltin=Function %s is not a known MySQL builtin +Plan.FunctionNotPostgresBuiltin=Function %s is not a known PostgreSQL builtin +Plan.FunctionNotSafeAtPlanTime=Function %s is not safe to estimate at planning time +Plan.FunctionNotSafeToEstimate=Function %s is not safe to estimate +Plan.FunctionOutsideMariaDBCatalogs=Function %s is outside known MariaDB catalogs +Plan.FunctionOutsideMysqlCatalogs=Function %s is outside known MySQL catalogs +Plan.FunctionOutsidePgCatalog=Function %s is outside pg_catalog and cannot be proven side-effect free +Plan.LockingRead=Locking reads cannot be estimated +Plan.ManualCommitIdleCannotStart=A manual-commit idle connection cannot start a plan transaction +Plan.ManualCommitIdleNotUsed=Manual-commit idle connections are not used for EXPLAIN +Plan.MariaDBJsonNormalizeFailed=MariaDB plan JSON could not be normalized +Plan.MariaDBTransactionAborted=The current MariaDB transaction is already aborted +Plan.MultiStatement=Multiple SQL statements cannot be estimated +Plan.MysqlJsonNormalizeFailed=MySQL plan JSON could not be normalized +Plan.MysqlTransactionAborted=The current MySQL transaction is already aborted +Plan.NestedWrite=Statements with nested writes cannot be estimated +Plan.NotSingleSelect=Only a single SELECT statement can be estimated +Plan.ParseFailed=Chen cannot parse this SQL for an estimated plan +Plan.PostgresTransactionAborted=The current PostgreSQL transaction is already aborted +Plan.PostgresTxProbeFailed=PostgreSQL transaction state probe failed +Plan.PostgresTxProbeFailedDetail=PostgreSQL transaction state probe failed: %s +Plan.PostgresTxUnknown=PostgreSQL transaction state is unknown +Plan.RawPlanExceededDisplayLimit=Raw plan exceeded the display limit +Plan.ReconnectWithoutSavepointProbe=Reconnect and retry without relying on SAVEPOINT probing +Plan.RemoveUdfOrExternalRowset=Remove user-defined functions or external rowset access and retry +Plan.RemoveUnsafeFunctions=Remove user-defined or planning-time-unsafe functions and retry +Plan.RequestCancelled=Execution plan request cancelled +Plan.RollbackThenRetry=Rollback or finish the existing transaction, then retry +Plan.RunExplainAutoCommitOrOpenTx=Run EXPLAIN while auto-commit idle or inside an already-open transaction +Plan.SavepointCreateFailed=Failed to create a request-scoped SAVEPOINT +Plan.SavepointRestoreFailed=SAVEPOINT restore failed after EXPLAIN +Plan.SelectInto=SELECT INTO cannot be estimated +Plan.SingleSelect=Single SELECT +Plan.SqlExceedsSizeLimit=SQL exceeds the execution plan size limit +Plan.StatementCannotBeEstimated=This statement cannot be estimated +Plan.UnboundParameter=Statements with unbound parameters cannot be estimated +Plan.UnsupportedDatabase=Execution plan is not implemented for this datasource +Properties=Propriedades +QueryOkRows=Consulta OK, %d linhas encontradas +QueryUnsupported=O Query não oferece suporte a este SQL porque o Chen não consegue analisá-lo com o Druid. Use o Console para redigir e executar. +Refresh=Atualizar +RelationCatalogMismatch=O catálogo da relação não corresponde ao contexto ativo +RowsExported=%d linhas exportadas +Run=Executar +RunHotKey=Executar (Ctrl + Enter) +RunSelected=Executar selecionado +Save=Salvar +SaveSQL=Salve o SQL +SaveSucceed=Salvo com sucesso +Scope=\ Alcance +SelectSQL=Selecionar SQL +SessionClosedBy=A sessão foi fechada por %s +SessionFinished=A sessão terminou +SessionLockedError=A sessão atual está bloqueada, não é possível continuar com o comando +SessionLockedMessage=Esta sessão foi bloqueada por %s, não é possível continuar com a ação +SessionUnlockedMessage=Esta sessão foi desbloqueada por %s, comandos agora podem ser executados +ShowProperties=Propriedades +StopHotKey=Parar (Ctrl + C) +Submit=Submeter +TooManyRelationsInMetadataRequest=Há relações demais em uma solicitação de metadados +Total=Total +Type=Escrever +Unknown=desconhecido +UnknownRelation=Relação desconhecida +UnknownRelationSchema=Schema de relação desconhecido +UnknownSchemaMetadataSection=Seção de metadados do schema desconhecida: %s +UnknownTableMetadataSection=Seção de metadados da tabela desconhecida: %s +UpdateStatement=instrução de atualização +User=Usuário +UserCancelCommandReviewError=O usuário cancelou a revisão do comando +Version=Versão +ViewData=Ver dados +WaitCommandReviewMessage=Solicitação de revisão enviada, aguarde os resultados da revisão +Warning=Aviso diff --git a/backend/web/src/main/resources/i18n/chen_ru.properties b/backend/web/src/main/resources/i18n/chen_ru.properties new file mode 100644 index 00000000..691ec69d --- /dev/null +++ b/backend/web/src/main/resources/i18n/chen_ru.properties @@ -0,0 +1,165 @@ +ACLRejectError=Выполнение команды запрещено правилом +AffectedRows=Затронутые строки +AlreadyFirstPageError=Это первая страница +AlreadyLastPageError=Это последняя страница +BatchSqlReview=Пакетный SQL:\n%s\n\nSQL, вызвавший проверку:\n%s +Cancel=Отменить +ChangeContextError=Ошибка смены контекста +CommandReview=Проверка команды +CommandReviewMessage=Введенная вами команда требует проверки перед выполнением, хотите отправить запрос на проверку? +CommandReviewRejectBy=Команда проверки отклонена %s +CommandReviewTimeoutError=Тайм-аут проверки команды +CommandWarningDialogMessage=Команда, которую вы хотите выполнить, содержит риски, уведомление будет отправлено администратору. Продолжить? +Confirm=Подтвердить +ConfirmDelete=Подтвердить удаление +Connected=Подключено +ConnectError=Ошибка подключения +ConnectSuccess=Успешно подключено +Content=Содержимое +Copy=Копировать +CopyFailed=Копирование не удалось +CopyNotAllowed=Копирование запрещено, пожалуйста, свяжитесь с администратором для получения прав! +CopySucceeded=Успешное копирование +CreateCommandTicketFailed=Не удалось создать заявку на проверку команды: %s +Current=Текущий +DatabaseExplorer=Проводник баз данных +DatabaseProperties=Свойства источника данных +Delete=Удалить +DeleteSuccess=Удалено успешно +DownloadNotAllowed=Загрузка не разрешена, пожалуйста, свяжитесь с администратором для получения доступа! +DriverClass=Класс драйвера +DriverVersion=Версия драйвера +ErrorMessage=Сообщение об ошибке +ErrorPrefix=Ошибка: %s +ExecuteError=Выполнение не удалось +ExecuteNodeActionFailed=Не удалось выполнить действие для %s +ExecuteSuccess=Выполнено успешно +ExecutionCanceled=Выполнение отменено +ExportALL=Экспортировать все данные +ExportAll=Экспортировать все +ExportCurrent=Экспорт текущей страницы +ExportData=Экспорт данных +ExportDataNamed=Экспорт данных: %s +FailedToLoadSchemaOverview=Не удалось загрузить метаданные обзора схемы +FailedToLoadSqlColumnMetadata=Не удалось загрузить метаданные столбцов SQL +FailedToLoadSqlHints=Не удалось загрузить подсказки SQL +FailedToLoadSqlRelationMetadata=Не удалось загрузить метаданные отношений SQL +FailedToLoadTableMetadata=Не удалось загрузить метаданные таблицы +FetchError=Не удалось получить данные +Format=Формат +FormatHotKey=Форматировать (Ctrl + L) +GetChildrenFailed=Не удалось загрузить дочерние узлы %s +InitializeDatasource=Инициализация источника данных +InitializeDatasourceFailed=Не удалось инициализировать источник данных +initializingDatasourceFailedMessage=Ошибка подключения, пожалуйста, проверьте настройки подключения к базе данных +InitializingDatasourceMessage=Инициализация источника данных, пожалуйста, подождите... +Insert=Вставить +InsertStatement=Инструкция INSERT +InvalidExportFile=Недопустимый файл экспорта +InvalidMetadataContext=Недопустимый контекст метаданных +InvalidRelationMetadataRequest=Недопустимый запрос метаданных отношения +InvalidResourceAction=Недопустимое действие ресурса +InvalidResourceNode=Недопустимый узел ресурса +InvalidSchemaOverviewContext=Недопустимый контекст обзора схемы +InvalidTableMetadataContext=Недопустимый контекст метаданных таблицы +JDBCURL=JDBC URL +LogOutput=Журнал вывода +Name=Название +NewQuery=Новый запрос +NoPermissionError=Нет прав на выполнение этого действия +NumRow={num} строк +Open=Открыть +OverMaxIdleTimeError=Сессия была закрыта, так как время простоя превысило %d минут +OverMaxSessionTimeError=Сессия была закрыта, так как продолжительность сессии превысила %d часов +ParseError=Ошибка анализа +PasteNotAllowed=Вставка не разрешена, пожалуйста, свяжитесь с администратором для получения прав! +PermissionAlreadyExpired=Срок действия разрешения истек +PermissionExpiredDialogMessage=Разрешение истекло, сессия истечет через 10 минут, пожалуйста, свяжитесь с администратором для продления +PermissionExpiredDialogTitle=Разрешение истекло +PermissionsExpiredOn=Разрешение для этой сессии истекло %s +Plan.Assignment=Statements with assignment cannot be estimated +Plan.CannotEstimateFailedTransaction=Cannot estimate a plan in a failed transaction +Plan.CheckTxAndRetry=Check the transaction state and retry +Plan.ClassicExplainEmpty=Classic EXPLAIN produced no rows +Plan.ConnectionUnusable=Connection became unusable during execution plan +Plan.DynamicSql=Dynamic SQL cannot be estimated +Plan.EmptySql=SQL statement is empty +Plan.EstimateFailed=Failed to estimate execution plan +Plan.ExplainWrapper=Wrapped EXPLAIN statements cannot be estimated +Plan.FailedToCreateSavepoint=Failed to create SAVEPOINT +Plan.FinishTxAndRetry=Finish the existing transaction and retry +Plan.FunctionNotMariaDBBuiltin=Function %s is not a known MariaDB builtin +Plan.FunctionNotMysqlBuiltin=Function %s is not a known MySQL builtin +Plan.FunctionNotPostgresBuiltin=Function %s is not a known PostgreSQL builtin +Plan.FunctionNotSafeAtPlanTime=Function %s is not safe to estimate at planning time +Plan.FunctionNotSafeToEstimate=Function %s is not safe to estimate +Plan.FunctionOutsideMariaDBCatalogs=Function %s is outside known MariaDB catalogs +Plan.FunctionOutsideMysqlCatalogs=Function %s is outside known MySQL catalogs +Plan.FunctionOutsidePgCatalog=Function %s is outside pg_catalog and cannot be proven side-effect free +Plan.LockingRead=Locking reads cannot be estimated +Plan.ManualCommitIdleCannotStart=A manual-commit idle connection cannot start a plan transaction +Plan.ManualCommitIdleNotUsed=Manual-commit idle connections are not used for EXPLAIN +Plan.MariaDBJsonNormalizeFailed=MariaDB plan JSON could not be normalized +Plan.MariaDBTransactionAborted=The current MariaDB transaction is already aborted +Plan.MultiStatement=Multiple SQL statements cannot be estimated +Plan.MysqlJsonNormalizeFailed=MySQL plan JSON could not be normalized +Plan.MysqlTransactionAborted=The current MySQL transaction is already aborted +Plan.NestedWrite=Statements with nested writes cannot be estimated +Plan.NotSingleSelect=Only a single SELECT statement can be estimated +Plan.ParseFailed=Chen cannot parse this SQL for an estimated plan +Plan.PostgresTransactionAborted=The current PostgreSQL transaction is already aborted +Plan.PostgresTxProbeFailed=PostgreSQL transaction state probe failed +Plan.PostgresTxProbeFailedDetail=PostgreSQL transaction state probe failed: %s +Plan.PostgresTxUnknown=PostgreSQL transaction state is unknown +Plan.RawPlanExceededDisplayLimit=Raw plan exceeded the display limit +Plan.ReconnectWithoutSavepointProbe=Reconnect and retry without relying on SAVEPOINT probing +Plan.RemoveUdfOrExternalRowset=Remove user-defined functions or external rowset access and retry +Plan.RemoveUnsafeFunctions=Remove user-defined or planning-time-unsafe functions and retry +Plan.RequestCancelled=Execution plan request cancelled +Plan.RollbackThenRetry=Rollback or finish the existing transaction, then retry +Plan.RunExplainAutoCommitOrOpenTx=Run EXPLAIN while auto-commit idle or inside an already-open transaction +Plan.SavepointCreateFailed=Failed to create a request-scoped SAVEPOINT +Plan.SavepointRestoreFailed=SAVEPOINT restore failed after EXPLAIN +Plan.SelectInto=SELECT INTO cannot be estimated +Plan.SingleSelect=Single SELECT +Plan.SqlExceedsSizeLimit=SQL exceeds the execution plan size limit +Plan.StatementCannotBeEstimated=This statement cannot be estimated +Plan.UnboundParameter=Statements with unbound parameters cannot be estimated +Plan.UnsupportedDatabase=Execution plan is not implemented for this datasource +Properties=Свойства +QueryOkRows=Запрос выполнен, найдено строк: %d +QueryUnsupported=Query не поддерживает этот SQL, потому что Chen не может разобрать его через Druid. Используйте Console, чтобы составить и выполнить его. +Refresh=Обновить +RelationCatalogMismatch=Каталог отношения не совпадает с текущим контекстом +RowsExported=Экспортировано строк: %d +Run=Запуск +RunHotKey=Запуск (Ctrl + Enter) +RunSelected=Запустить выбранные +Save=Сохранить +SaveSQL=Сохранить SQL +SaveSucceed=Успешно сохранено +Scope=Область +SelectSQL=Выбрать SQL +SessionClosedBy=Сессия была закрыта %s +SessionFinished=Сессия завершена +SessionLockedError=Текущая сессия заблокирована, невозможно продолжить выполнение команды +SessionLockedMessage=\ Эта сессия была заблокирована %s, невозможно продолжить выполнение команды +SessionUnlockedMessage=Эта сессия была разблокирована %s, можно продолжить выполнение команды +ShowProperties=Свойства +StopHotKey=Остановить (Ctrl + C) +Submit=Отправить +TooManyRelationsInMetadataRequest=Слишком много отношений в одном запросе метаданных +Total=Всего +Type=Тип +Unknown=неизвестно +UnknownRelation=Неизвестное отношение +UnknownRelationSchema=Неизвестная схема отношения +UnknownSchemaMetadataSection=Неизвестный раздел метаданных схемы: %s +UnknownTableMetadataSection=Неизвестный раздел метаданных таблицы: %s +UpdateStatement=Обновить запрос +User=Пользователь +UserCancelCommandReviewError=Пользователь отменил проверку команды +Version=Версия +ViewData=Просмотр данных +WaitCommandReviewMessage=Запрос на проверку отправлен, пожалуйста, ожидайте результатов проверки +Warning=Предупреждение diff --git a/backend/web/src/main/resources/i18n/chen_vi.properties b/backend/web/src/main/resources/i18n/chen_vi.properties new file mode 100644 index 00000000..a16adbd5 --- /dev/null +++ b/backend/web/src/main/resources/i18n/chen_vi.properties @@ -0,0 +1,165 @@ +ACLRejectError=Lệnh này không được phép thực hiện +AffectedRows=Trình duyệt cơ sở dữ liệu +AlreadyFirstPageError=Đã là trang đầu tiên +AlreadyLastPageError=Đã đến trang cuối cùng +BatchSqlReview=SQL hàng loạt:\n%s\n\nSQL kích hoạt duyệt:\n%s +Cancel=Hủy bỏ +ChangeContextError=Chuyển đổi ngữ cảnh thất bại +CommandReview=Xác nhận lệnh +CommandReviewMessage=Lệnh bạn nhập cần được xác nhận trước khi thực thi, có muốn gửi yêu cầu xác nhận không? +CommandReviewRejectBy=Lệnh xác nhận bị %s từ chối +CommandReviewTimeoutError=Thời gian xác nhận lệnh đã hết +CommandWarningDialogMessage=Lệnh bạn thực hiện có nguy cơ, thông báo cảnh báo sẽ được gửi đến quản lý. Bạn có muốn tiếp tục không? +Confirm=Xác nhận +ConfirmDelete=Xác nhận xóa +Connected=Đã kết nối +ConnectError=Kết nối thất bại +ConnectSuccess=Kết nối thành công +Content=Nội dung +Copy=Sao chép +CopyFailed=Sao chép thất bại +CopyNotAllowed=Không được phép sao chép, hãy liên hệ với quản trị viên để mở quyền! +CopySucceeded=Sao chép thành công +CreateCommandTicketFailed=Tạo phiếu duyệt lệnh thất bại: %s +Current=Hiện tại +DatabaseExplorer=Người dùng +DatabaseProperties=Thuộc tính nguồn dữ liệu +Delete=Xóa +DeleteSuccess=Xóa thành công +DownloadNotAllowed=Không cho phép tải về, vui lòng liên hệ với quản lý để mở quyền! +DriverClass=Điều khiển +DriverVersion=Phiên bản lái +ErrorMessage=Thông báo lỗi +ErrorPrefix=Lỗi: %s +ExecuteError=Thực hiện thành công +ExecuteNodeActionFailed=Thực thi thao tác trên %s thất bại +ExecuteSuccess=Thực hiện thành công +ExecutionCanceled=Thực hiện đã bị hủy +ExportALL=Xuất tất cả dữ liệu +ExportAll=Xuất toàn bộ +ExportCurrent=Xuất trang hiện tại +ExportData=Xuất dữ liệu +ExportDataNamed=Xuất dữ liệu: %s +FailedToLoadSchemaOverview=Tải metadata tổng quan schema thất bại +FailedToLoadSqlColumnMetadata=Tải metadata cột SQL thất bại +FailedToLoadSqlHints=Tải gợi ý SQL thất bại +FailedToLoadSqlRelationMetadata=Tải metadata quan hệ SQL thất bại +FailedToLoadTableMetadata=Tải metadata bảng thất bại +FetchError=Lấy dữ liệu thất bại +Format=Định dạng +FormatHotKey=Định dạng (Ctrl + L) +GetChildrenFailed=Tải nút con của %s thất bại +InitializeDatasource=Khởi tạo nguồn dữ liệu +InitializeDatasourceFailed=Khởi tạo nguồn dữ liệu thất bại +initializingDatasourceFailedMessage=Kết nối thất bại, vui lòng kiểm tra cấu hình kết nối cơ sở dữ liệu có chính xác hay không. +InitializingDatasourceMessage=Đang khởi tạo nguồn dữ liệu, xin vui lòng chờ... +Insert=Chèn +InsertStatement=Câu lệnh chèn +InvalidExportFile=Tệp xuất không hợp lệ +InvalidMetadataContext=Ngữ cảnh metadata không hợp lệ +InvalidRelationMetadataRequest=Yêu cầu metadata quan hệ không hợp lệ +InvalidResourceAction=Thao tác tài nguyên không hợp lệ +InvalidResourceNode=Nút tài nguyên không hợp lệ +InvalidSchemaOverviewContext=Ngữ cảnh tổng quan schema không hợp lệ +InvalidTableMetadataContext=Ngữ cảnh metadata bảng không hợp lệ +JDBCURL=JDBC URL +LogOutput=Xuất nhật ký +Name=Tên +NewQuery=Tạo mới truy vấn +NoPermissionError=Không có quyền thực hiện thao tác này +NumRow={num} dòng +Open=Mở +OverMaxIdleTimeError=Do phiên này quá thời gian rỗi vượt quá %d phút, đã bị đóng +OverMaxSessionTimeError=Phiên này đã bị đóng vì kéo dài hơn %d giờ. +ParseError=Phân tích thất bại +PasteNotAllowed=Không cho phép dán, vui lòng liên hệ với quản trị viên để mở quyền! +PermissionAlreadyExpired=Quyền đã hết hạn +PermissionExpiredDialogMessage=Quyền đã hết hạn; phiên sẽ hết hạn sau mười phút. Vui lòng liên hệ với quản trị viên để gia hạn. +PermissionExpiredDialogTitle=Số dòng bị ảnh hưởng +PermissionsExpiredOn=Quyền liên kết với phiên này đã hết hạn vào %s +Plan.Assignment=Statements with assignment cannot be estimated +Plan.CannotEstimateFailedTransaction=Cannot estimate a plan in a failed transaction +Plan.CheckTxAndRetry=Check the transaction state and retry +Plan.ClassicExplainEmpty=Classic EXPLAIN produced no rows +Plan.ConnectionUnusable=Connection became unusable during execution plan +Plan.DynamicSql=Dynamic SQL cannot be estimated +Plan.EmptySql=SQL statement is empty +Plan.EstimateFailed=Failed to estimate execution plan +Plan.ExplainWrapper=Wrapped EXPLAIN statements cannot be estimated +Plan.FailedToCreateSavepoint=Failed to create SAVEPOINT +Plan.FinishTxAndRetry=Finish the existing transaction and retry +Plan.FunctionNotMariaDBBuiltin=Function %s is not a known MariaDB builtin +Plan.FunctionNotMysqlBuiltin=Function %s is not a known MySQL builtin +Plan.FunctionNotPostgresBuiltin=Function %s is not a known PostgreSQL builtin +Plan.FunctionNotSafeAtPlanTime=Function %s is not safe to estimate at planning time +Plan.FunctionNotSafeToEstimate=Function %s is not safe to estimate +Plan.FunctionOutsideMariaDBCatalogs=Function %s is outside known MariaDB catalogs +Plan.FunctionOutsideMysqlCatalogs=Function %s is outside known MySQL catalogs +Plan.FunctionOutsidePgCatalog=Function %s is outside pg_catalog and cannot be proven side-effect free +Plan.LockingRead=Locking reads cannot be estimated +Plan.ManualCommitIdleCannotStart=A manual-commit idle connection cannot start a plan transaction +Plan.ManualCommitIdleNotUsed=Manual-commit idle connections are not used for EXPLAIN +Plan.MariaDBJsonNormalizeFailed=MariaDB plan JSON could not be normalized +Plan.MariaDBTransactionAborted=The current MariaDB transaction is already aborted +Plan.MultiStatement=Multiple SQL statements cannot be estimated +Plan.MysqlJsonNormalizeFailed=MySQL plan JSON could not be normalized +Plan.MysqlTransactionAborted=The current MySQL transaction is already aborted +Plan.NestedWrite=Statements with nested writes cannot be estimated +Plan.NotSingleSelect=Only a single SELECT statement can be estimated +Plan.ParseFailed=Chen cannot parse this SQL for an estimated plan +Plan.PostgresTransactionAborted=The current PostgreSQL transaction is already aborted +Plan.PostgresTxProbeFailed=PostgreSQL transaction state probe failed +Plan.PostgresTxProbeFailedDetail=PostgreSQL transaction state probe failed: %s +Plan.PostgresTxUnknown=PostgreSQL transaction state is unknown +Plan.RawPlanExceededDisplayLimit=Raw plan exceeded the display limit +Plan.ReconnectWithoutSavepointProbe=Reconnect and retry without relying on SAVEPOINT probing +Plan.RemoveUdfOrExternalRowset=Remove user-defined functions or external rowset access and retry +Plan.RemoveUnsafeFunctions=Remove user-defined or planning-time-unsafe functions and retry +Plan.RequestCancelled=Execution plan request cancelled +Plan.RollbackThenRetry=Rollback or finish the existing transaction, then retry +Plan.RunExplainAutoCommitOrOpenTx=Run EXPLAIN while auto-commit idle or inside an already-open transaction +Plan.SavepointCreateFailed=Failed to create a request-scoped SAVEPOINT +Plan.SavepointRestoreFailed=SAVEPOINT restore failed after EXPLAIN +Plan.SelectInto=SELECT INTO cannot be estimated +Plan.SingleSelect=Single SELECT +Plan.SqlExceedsSizeLimit=SQL exceeds the execution plan size limit +Plan.StatementCannotBeEstimated=This statement cannot be estimated +Plan.UnboundParameter=Statements with unbound parameters cannot be estimated +Plan.UnsupportedDatabase=Execution plan is not implemented for this datasource +Properties=Thuộc tính +QueryOkRows=Truy vấn thành công, tìm thấy %d hàng +QueryUnsupported=Query không hỗ trợ SQL này vì Chen không phân tích được bằng Druid. Hãy dùng Console để soạn và chạy. +Refresh=Làm mới +RelationCatalogMismatch=Catalog quan hệ không khớp ngữ cảnh hiện tại +RowsExported=Đã xuất %d hàng +Run=Chạy +RunHotKey=Quyền đã hết hạn +RunSelected=Chạy đã chọn +Save=Lưu +SaveSQL=Lưu SQL +SaveSucceed=Lưu thành công +Scope=Phạm vi +SelectSQL=Chọn SQL +SessionClosedBy=Phiên đã bị %s đóng +SessionFinished=Phiên làm việc đã kết thúc +SessionLockedError=Phiên hiện tại đã bị khóa, không thể tiếp tục thực hiện lệnh +SessionLockedMessage=Phiên này đã bị %s khóa, không thể tiếp tục thực hiện lệnh +SessionUnlockedMessage=Phiên này đã được %s mở khóa, có thể tiếp tục thực hiện lệnh +ShowProperties=Thuộc tính +StopHotKey=Dừng (Ctrl + D) +Submit=Gửi +TooManyRelationsInMetadataRequest=Quá nhiều quan hệ trong một yêu cầu metadata +Total=Tổng cộng +Type=Loại +Unknown=không xác định +UnknownRelation=Quan hệ không xác định +UnknownRelationSchema=Schema quan hệ không xác định +UnknownSchemaMetadataSection=Phần metadata schema không xác định: %s +UnknownTableMetadataSection=Phần metadata bảng không xác định: %s +UpdateStatement=Câu lệnh cập nhật +User=Thực hiện thất bại +UserCancelCommandReviewError=Người dùng hủy lệnh duyệt lại +Version=Phiên bản +ViewData=Xem dữ liệu +WaitCommandReviewMessage=Yêu cầu xem xét đã được gửi đi, xin vui lòng chờ kết quả xem xét. +Warning=Cảnh báo diff --git a/backend/web/src/main/resources/i18n/chen_zh.properties b/backend/web/src/main/resources/i18n/chen_zh.properties new file mode 100644 index 00000000..a2ce172d --- /dev/null +++ b/backend/web/src/main/resources/i18n/chen_zh.properties @@ -0,0 +1,165 @@ +ACLRejectError=\ 此命令禁止被执行 +AffectedRows=受影响行数 +AlreadyFirstPageError=已经是第一页 +AlreadyLastPageError=已经是最后一页 +BatchSqlReview=批量 SQL:\n%s\n\n触发复核的 SQL:\n%s +Cancel=取消 +ChangeContextError=切换上下文失败 +CommandReview=命令复核 +CommandReviewMessage=您输入的命令需要复核后才可以执行, 是否发起复核请求? +CommandReviewRejectBy=命令复核被 %s 拒绝 +CommandReviewTimeoutError=命令复核超时 +CommandWarningDialogMessage=您执行的命令存在风险,告警通知将发送给管理员。是否继续? +Confirm=确认 +ConfirmDelete=确认删除 +Connected=已连接 +ConnectError=连接失败 +ConnectSuccess=连接成功 +Content=内容 +Copy=复制 +CopyFailed=复制失败 +CopyNotAllowed=不允许复制,请联系管理员开启权限! +CopySucceeded=复制成功 +CreateCommandTicketFailed=创建命令复核工单失败: %s +Current=当前 +DatabaseExplorer=数据库浏览器 +DatabaseProperties=数据源属性 +Delete=删除 +DeleteSuccess=删除成功 +DownloadNotAllowed=不允许下载,请联系管理员开启权限! +DriverClass=驱动类 +DriverVersion=驱动版本 +ErrorMessage=错误消息 +ErrorPrefix=错误: %s +ExecuteError=执行失败 +ExecuteNodeActionFailed=执行节点动作 %s 失败 +ExecuteSuccess=执行成功 +ExecutionCanceled=执行已取消 +ExportALL=导出所有数据 +ExportAll=导出全部 +ExportCurrent=导出当前页面 +ExportData=导出数据 +ExportDataNamed=导出数据: %s +FailedToLoadSchemaOverview=加载 schema 概览元数据失败 +FailedToLoadSqlColumnMetadata=加载 SQL 列元数据失败 +FailedToLoadSqlHints=加载 SQL 提示失败 +FailedToLoadSqlRelationMetadata=加载 SQL 关系元数据失败 +FailedToLoadTableMetadata=加载表元数据失败 +FetchError=获取数据失败 +Format=格式 +FormatHotKey=格式化 (Ctrl + L) +GetChildrenFailed=获取 %s 的子节点失败 +InitializeDatasource=初始化数据源 +InitializeDatasourceFailed=初始化数据源失败 +initializingDatasourceFailedMessage=连接失败,请检查数据库连接配置是否正确 +InitializingDatasourceMessage=正在初始化数据源,请稍候... +Insert=插入 +InsertStatement=插入语句 +InvalidExportFile=无效的导出文件 +InvalidMetadataContext=无效的元数据上下文 +InvalidRelationMetadataRequest=无效的关系元数据请求 +InvalidResourceAction=无效的资源动作 +InvalidResourceNode=无效的资源节点 +InvalidSchemaOverviewContext=无效的 schema 概览上下文 +InvalidTableMetadataContext=无效的表元数据上下文 +JDBCURL=JDBC URL +LogOutput=日志输出 +Name=名称 +NewQuery=新建查询 +NoPermissionError=没有执行此操作的权限 +NumRow={num} 行 +Open=打开 +OverMaxIdleTimeError=由于此会话空闲时间大于 %d 分钟,已经被关闭 +OverMaxSessionTimeError=由于此会话时间大于 %d 小时,已经被关闭 +ParseError=解析失败 +PasteNotAllowed=不允许粘贴,请联系管理员开启权限! +PermissionAlreadyExpired=授权已过期 +PermissionExpiredDialogMessage=授权已过期,会话将在十分钟后过期,请及时联系管理员续期 +PermissionExpiredDialogTitle=授权已过期 +PermissionsExpiredOn=此会话关联的权限已于 %s 过期 +Plan.Assignment=包含赋值的语句无法估算执行计划 +Plan.CannotEstimateFailedTransaction=无法在失败的事务中估算执行计划 +Plan.CheckTxAndRetry=请检查事务状态后重试 +Plan.ClassicExplainEmpty=经典 EXPLAIN 没有返回结果 +Plan.ConnectionUnusable=获取执行计划期间连接已不可用 +Plan.DynamicSql=动态 SQL 无法估算执行计划 +Plan.EmptySql=SQL 语句为空 +Plan.EstimateFailed=估算执行计划失败 +Plan.ExplainWrapper=已包装的 EXPLAIN 语句无法估算执行计划 +Plan.FailedToCreateSavepoint=创建 SAVEPOINT 失败 +Plan.FinishTxAndRetry=请结束当前事务后重试 +Plan.FunctionNotMariaDBBuiltin=函数 %s 不是已知的 MariaDB 内置函数 +Plan.FunctionNotMysqlBuiltin=函数 %s 不是已知的 MySQL 内置函数 +Plan.FunctionNotPostgresBuiltin=函数 %s 不是已知的 PostgreSQL 内置函数 +Plan.FunctionNotSafeAtPlanTime=函数 %s 不适合在规划阶段估算 +Plan.FunctionNotSafeToEstimate=函数 %s 不适合估算执行计划 +Plan.FunctionOutsideMariaDBCatalogs=函数 %s 不在已知的 MariaDB 系统库中 +Plan.FunctionOutsideMysqlCatalogs=函数 %s 不在已知的 MySQL 系统库中 +Plan.FunctionOutsidePgCatalog=函数 %s 不在 pg_catalog 中,无法证明无副作用 +Plan.LockingRead=加锁读取无法估算执行计划 +Plan.ManualCommitIdleCannotStart=手动提交的空闲连接不能启动执行计划事务 +Plan.ManualCommitIdleNotUsed=手动提交的空闲连接不会用于 EXPLAIN +Plan.MariaDBJsonNormalizeFailed=无法规范化 MariaDB 执行计划 JSON +Plan.MariaDBTransactionAborted=当前 MariaDB 事务已中止 +Plan.MultiStatement=无法估算多条 SQL 语句的执行计划 +Plan.MysqlJsonNormalizeFailed=无法规范化 MySQL 执行计划 JSON +Plan.MysqlTransactionAborted=当前 MySQL 事务已中止 +Plan.NestedWrite=包含嵌套写操作的语句无法估算执行计划 +Plan.NotSingleSelect=仅支持估算单条 SELECT 语句 +Plan.ParseFailed=Chen 无法解析此 SQL,因此不能估算执行计划 +Plan.PostgresTransactionAborted=当前 PostgreSQL 事务已中止 +Plan.PostgresTxProbeFailed=PostgreSQL 事务状态探测失败 +Plan.PostgresTxProbeFailedDetail=PostgreSQL 事务状态探测失败: %s +Plan.PostgresTxUnknown=PostgreSQL 事务状态未知 +Plan.RawPlanExceededDisplayLimit=原始执行计划超出显示限制 +Plan.ReconnectWithoutSavepointProbe=请重新连接并重试,不要依赖 SAVEPOINT 探测 +Plan.RemoveUdfOrExternalRowset=请移除用户自定义函数或外部行集访问后重试 +Plan.RemoveUnsafeFunctions=请移除用户自定义或不适合在规划阶段使用的函数后重试 +Plan.RequestCancelled=执行计划请求已取消 +Plan.RollbackThenRetry=请回滚或结束当前事务后重试 +Plan.RunExplainAutoCommitOrOpenTx=请在自动提交空闲连接或已打开的事务中运行 EXPLAIN +Plan.SavepointCreateFailed=创建请求级 SAVEPOINT 失败 +Plan.SavepointRestoreFailed=EXPLAIN 之后恢复 SAVEPOINT 失败 +Plan.SelectInto=SELECT INTO 无法估算执行计划 +Plan.SingleSelect=单条 SELECT +Plan.SqlExceedsSizeLimit=SQL 超出执行计划大小限制 +Plan.StatementCannotBeEstimated=此语句无法估算执行计划 +Plan.UnboundParameter=包含未绑定参数的语句无法估算执行计划 +Plan.UnsupportedDatabase=当前数据源尚未实现执行计划 +Properties=属性 +QueryOkRows=查询成功,发现 %d 行 +QueryUnsupported=Query 不支持此 SQL,因为 Chen 无法用 Druid 解析。请使用 Console 编写并执行。 +Refresh=刷新 +RelationCatalogMismatch=关系 catalog 与当前上下文不匹配 +RowsExported=已导出 %d 行 +Run=运行 +RunHotKey=运行 (Ctrl + Enter) +RunSelected=运行选中 +Save=保存 +SaveSQL=保存 SQL +SaveSucceed=保存成功 +Scope=范围 +SelectSQL=选择 SQL +SessionClosedBy=会话被 %s 关闭 +SessionFinished=会话已结束 +SessionLockedError=当前会话已被锁定,无法继续执行命令 +SessionLockedMessage=此会话已被 %s 锁定,无法继续执行命令 +SessionUnlockedMessage=此会话已被 %s 解锁,可以继续执行命令 +ShowProperties=属性 +StopHotKey=停止 (Ctrl + D) +Submit=提交 +TooManyRelationsInMetadataRequest=单次元数据请求中的关系过多 +Total=总计 +Type=类型 +Unknown=未知 +UnknownRelation=未知的关系 +UnknownRelationSchema=未知的关系 schema +UnknownSchemaMetadataSection=未知的 schema 元数据分区: %s +UnknownTableMetadataSection=未知的表元数据分区: %s +UpdateStatement=更新语句 +User=用户 +UserCancelCommandReviewError=用户取消命令复核 +Version=版本 +ViewData=查看数据 +WaitCommandReviewMessage=复核请求已发起, 请等待复核结果 +Warning=警告 diff --git a/backend/web/src/main/resources/i18n/chen_zh_HANT.properties b/backend/web/src/main/resources/i18n/chen_zh_HANT.properties deleted file mode 100644 index c47ac342..00000000 --- a/backend/web/src/main/resources/i18n/chen_zh_HANT.properties +++ /dev/null @@ -1,49 +0,0 @@ -action.refresh=刷新 -action.new=新建 -action.new_query=新建查詢 -action.view_data=查看數據 -action.show_properties=屬性 -action.label.name=名稱 -action.label.type=類型 -action.label.version=版本 -action.label.user=用戶 -action.label.jdbc_url=JDBC 連結 -action.label.driver_class=驅動類 -action.label.driver_version=驅動版本 -title.query=查詢 -title.properties=屬性 -title.datasource_properties=數據源屬性 -state.connected=已連接 -msg.error.fetch_data=獲取數據失敗 -msg.error.connect_error=連接失敗 -msg.error.change_context_error=切換上下文錯誤 -msg.error.parse_error=解析錯誤 -msg.error.execute_error=執行錯誤 -msg.error.already_first_page=已經是第一頁 -msg.error.already_last_page=已經是最後一頁 -msg.error.user_cancel_command_review=用戶取消命令覆核 -msg.error.command_review_timeout=命令覆核超時 -msg.error.command_review_reject=命令覆核被 %s 拒絕 -msg.success.execute_success=執行成功 -msg.info.affected_rows=受影響行數 -msg.error.perms_expired=此會話關聯的權限已於 %s 過期 -msg.error.over_max_idle_time=由於此會話空閒時間大於 %d 分鐘,已經被關閉 -msg.error.over_max_session_time=由於此會話時間大於 %d 小時,已經被關閉 -msg.error.session_closed_by=當前會話被管理員 %s 關閉 -msg.error.session_locked=當前會話已被鎖定,無法繼續執行命令 -msg.error.no_permission = 沒有執行此操作的權限 -msg.error.acl_reject= 此命令禁止被執行 -msg.dialog.session_locked=此會話已被 %s 鎖定,無法繼續執行命令 -msg.dialog.session_unlocked=此會話已被 %s 解鎖,可以繼續執行命令 -msg.dialog.title.init_datasource=初始化數據源 -msg.dialog.message.init_datasource=正在初始化數據源,請稍候... -msg.dialog.title.init_datasource_failed=初始化數據源失敗 -msg.dialog.message.init_datasource_failed=連接失敗,請檢查資料庫連接配置是否正確 -msg.dialog.title.error_message=錯誤消息 -msg.success.connect_success=連接成功 -msg.dialog.title.session_finished=會話已結束 -msg.dialog.title.command_review=命令覆核 -msg.dialog.message.command_review=您輸入的命令需要覆核後才可以執行, 是否發起覆核請求? -msg.dialog.message.wait_command_review=覆核請求已發起, 請等待覆核結果 -btn.label.submit=提交 -btn.label.cancel=取消 \ No newline at end of file diff --git a/backend/web/src/main/resources/i18n/chen_zh_TW.properties b/backend/web/src/main/resources/i18n/chen_zh_TW.properties new file mode 100644 index 00000000..2c5e95f5 --- /dev/null +++ b/backend/web/src/main/resources/i18n/chen_zh_TW.properties @@ -0,0 +1,165 @@ +ACLRejectError=此命令禁止被執行 +AffectedRows=受影響行數 +AlreadyFirstPageError=已經是第一頁 +AlreadyLastPageError=已經是最後一頁 +BatchSqlReview=批次 SQL:\n%s\n\n觸發覆核的 SQL:\n%s +Cancel=取消 +ChangeContextError=切換上下文失敗 +CommandReview=命令覆核 +CommandReviewMessage=您輸入的命令需要覆核後才可以執行,是否發起覆核請求? +CommandReviewRejectBy=命令覆核被 %s 拒絕 +CommandReviewTimeoutError=命令覆核超時 +CommandWarningDialogMessage=您進行的動作存在風險,警告通知將會寄給管理員。你確定要繼續嗎? +Confirm=確認 +ConfirmDelete=確認刪除 +Connected=已連接 +ConnectError=連接失敗 +ConnectSuccess=連接成功 +Content=內容 +Copy=複製 +CopyFailed=複製失敗 +CopyNotAllowed=不允許複製,請聯絡管理員開啟權限! +CopySucceeded=複製成功 +CreateCommandTicketFailed=建立命令覆核工單失敗: %s +Current=目前 +DatabaseExplorer=資料庫瀏覽器 +DatabaseProperties=資料源屬性 +Delete=刪除 +DeleteSuccess=刪除成功 +DownloadNotAllowed=不允許下載,請聯繫管理員開啟權限! +DriverClass=驅動類 +DriverVersion=驅動版本 +ErrorMessage=錯誤消息 +ErrorPrefix=錯誤: %s +ExecuteError=執行失敗 +ExecuteNodeActionFailed=執行節點動作 %s 失敗 +ExecuteSuccess=執行成功 +ExecutionCanceled=動作已取消 +ExportALL=匯出所有資料 +ExportAll=匯出全部 +ExportCurrent=匯出當前頁面 +ExportData=匯出資料 +ExportDataNamed=匯出資料: %s +FailedToLoadSchemaOverview=載入 schema 總覽中繼資料失敗 +FailedToLoadSqlColumnMetadata=載入 SQL 欄位中繼資料失敗 +FailedToLoadSqlHints=載入 SQL 提示失敗 +FailedToLoadSqlRelationMetadata=載入 SQL 關聯中繼資料失敗 +FailedToLoadTableMetadata=載入資料表中繼資料失敗 +FetchError=獲取資料失敗 +Format=格式 +FormatHotKey=格式化 (Ctrl + L) +GetChildrenFailed=取得 %s 的子節點失敗 +InitializeDatasource=初始化資料源 +InitializeDatasourceFailed=初始化資料源失敗 +initializingDatasourceFailedMessage=連接失敗,請檢查資料庫連接配置是否正確 +InitializingDatasourceMessage=正在初始化資料源,請稍候... +Insert=插入 +InsertStatement=插入語句 +InvalidExportFile=無效的匯出檔案 +InvalidMetadataContext=無效的中繼資料內容 +InvalidRelationMetadataRequest=無效的關聯中繼資料請求 +InvalidResourceAction=無效的資源動作 +InvalidResourceNode=無效的資源節點 +InvalidSchemaOverviewContext=無效的 schema 總覽內容 +InvalidTableMetadataContext=無效的資料表中繼資料內容 +JDBCURL=JDBC URL +LogOutput=日誌輸出 +Name=名稱 +NewQuery=新建查詢 +NoPermissionError=沒有執行此操作的權限 +NumRow={num} 行 +Open=開啟 +OverMaxIdleTimeError=由於此會話閒置時間大於 %d 分鐘,已經被關閉 +OverMaxSessionTimeError=由於此會話時間大於 %d 小時,已經被關閉 +ParseError=解析失敗 +PasteNotAllowed=不允許貼上,請聯絡管理員開啟權限! +PermissionAlreadyExpired=授權已過期 +PermissionExpiredDialogMessage=授權已過期,對話將在十分鐘後過期,請及時聯繫管理員續期。 +PermissionExpiredDialogTitle=授權已過期 +PermissionsExpiredOn=此會話關聯的權限已於 %s 過期 +Plan.Assignment=包含賦值的語句無法估算執行計畫 +Plan.CannotEstimateFailedTransaction=無法在失敗的事務中估算執行計畫 +Plan.CheckTxAndRetry=請檢查事務狀態后重試 +Plan.ClassicExplainEmpty=經典 EXPLAIN 沒有傳回結果 +Plan.ConnectionUnusable=获取執行計畫期间連線已不可用 +Plan.DynamicSql=動態 SQL 無法估算執行計畫 +Plan.EmptySql=SQL 語句为空 +Plan.EstimateFailed=估算執行計畫失敗 +Plan.ExplainWrapper=已包裝的 EXPLAIN 語句無法估算執行計畫 +Plan.FailedToCreateSavepoint=建立 SAVEPOINT 失敗 +Plan.FinishTxAndRetry=請結束目前事務后重試 +Plan.FunctionNotMariaDBBuiltin=函式 %s 不是已知的 MariaDB 內建函式 +Plan.FunctionNotMysqlBuiltin=函式 %s 不是已知的 MySQL 內建函式 +Plan.FunctionNotPostgresBuiltin=函式 %s 不是已知的 PostgreSQL 內建函式 +Plan.FunctionNotSafeAtPlanTime=函式 %s 不適合在規劃階段估算 +Plan.FunctionNotSafeToEstimate=函式 %s 不適合估算執行計畫 +Plan.FunctionOutsideMariaDBCatalogs=函式 %s 不在已知的 MariaDB 系統庫中 +Plan.FunctionOutsideMysqlCatalogs=函式 %s 不在已知的 MySQL 系統庫中 +Plan.FunctionOutsidePgCatalog=函式 %s 不在 pg_catalog 中,無法证明無副作用 +Plan.LockingRead=加鎖讀取無法估算執行計畫 +Plan.ManualCommitIdleCannotStart=手動提交的閒置連線不能启动執行計畫事務 +Plan.ManualCommitIdleNotUsed=手動提交的閒置連線不会用于 EXPLAIN +Plan.MariaDBJsonNormalizeFailed=無法規範化 MariaDB 執行計畫 JSON +Plan.MariaDBTransactionAborted=目前 MariaDB 事務已中止 +Plan.MultiStatement=無法估算多条 SQL 語句的執行計畫 +Plan.MysqlJsonNormalizeFailed=無法規範化 MySQL 執行計畫 JSON +Plan.MysqlTransactionAborted=目前 MySQL 事務已中止 +Plan.NestedWrite=包含巢狀寫入操作的語句無法估算執行計畫 +Plan.NotSingleSelect=仅支援估算單條 SELECT 語句 +Plan.ParseFailed=Chen 無法解析此 SQL,因此不能估算執行計畫 +Plan.PostgresTransactionAborted=目前 PostgreSQL 事務已中止 +Plan.PostgresTxProbeFailed=PostgreSQL 事務狀態探測失敗 +Plan.PostgresTxProbeFailedDetail=PostgreSQL 事務狀態探測失敗: %s +Plan.PostgresTxUnknown=PostgreSQL 事務狀態未知 +Plan.RawPlanExceededDisplayLimit=原始執行計畫超出顯示限制 +Plan.ReconnectWithoutSavepointProbe=請重新連線并重試,不要依賴 SAVEPOINT 探測 +Plan.RemoveUdfOrExternalRowset=請移除使用者自訂函式或外部行集访问后重試 +Plan.RemoveUnsafeFunctions=請移除使用者自訂或不適合在規劃階段使用的函式后重試 +Plan.RequestCancelled=執行計畫請求已取消 +Plan.RollbackThenRetry=請回滾或結束目前事務后重試 +Plan.RunExplainAutoCommitOrOpenTx=請在自動提交閒置連線或已開啟的事務中运行 EXPLAIN +Plan.SavepointCreateFailed=建立請求级 SAVEPOINT 失敗 +Plan.SavepointRestoreFailed=EXPLAIN 之後恢復 SAVEPOINT 失敗 +Plan.SelectInto=SELECT INTO 無法估算執行計畫 +Plan.SingleSelect=單條 SELECT +Plan.SqlExceedsSizeLimit=SQL 超出執行計畫大小限制 +Plan.StatementCannotBeEstimated=此語句無法估算執行計畫 +Plan.UnboundParameter=包含未繫結參數的語句無法估算執行計畫 +Plan.UnsupportedDatabase=目前資料來源尚未實作執行計畫 +Properties=屬性 +QueryOkRows=查詢成功,發現 %d 行 +QueryUnsupported=Query 不支援此 SQL,因為 Chen 無法用 Druid 解析。請使用 Console 編寫並執行。 +Refresh=刷新 +RelationCatalogMismatch=關聯 catalog 與目前內容不符 +RowsExported=已匯出 %d 行 +Run=執行 +RunHotKey=執行 (Ctrl + Enter) +RunSelected=執行選擇 +Save=保存 +SaveSQL=儲存 SQL +SaveSucceed=儲存成功 +Scope=範圍 +SelectSQL=選擇 SQL +SessionClosedBy=會話被 %s 關閉 +SessionFinished=會話已結束 +SessionLockedError=當前會話已被鎖定,無法繼續執行命令 +SessionLockedMessage=此會話已被 %s 鎖定,無法繼續執行命令 +SessionUnlockedMessage=此會話已被 %s 解鎖,可以繼續執行命令 +ShowProperties=屬性 +StopHotKey=停止 (Ctrl + C) +Submit=提交 +TooManyRelationsInMetadataRequest=單次中繼資料請求中的關聯過多 +Total=總計 +Type=類型 +Unknown=未知 +UnknownRelation=未知的關聯 +UnknownRelationSchema=未知的關聯 schema +UnknownSchemaMetadataSection=未知的 schema 中繼資料區段: %s +UnknownTableMetadataSection=未知的資料表中繼資料區段: %s +UpdateStatement=更新語句 +User=用戶 +UserCancelCommandReviewError=用戶取消命令覆核 +Version=版本 +ViewData=查看資料 +WaitCommandReviewMessage=覆核請求已發起,請等待覆核結果 +Warning=警告。 diff --git a/backend/web/src/test/java/org/jumpserver/chen/web/ai/AgentWebSocketHandlerTest.java b/backend/web/src/test/java/org/jumpserver/chen/web/ai/AgentWebSocketHandlerTest.java new file mode 100644 index 00000000..8f372b6a --- /dev/null +++ b/backend/web/src/test/java/org/jumpserver/chen/web/ai/AgentWebSocketHandlerTest.java @@ -0,0 +1,200 @@ +package org.jumpserver.chen.web.ai; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.junit.jupiter.api.Test; +import org.jumpserver.chen.framework.session.impl.JMSSession; +import org.jumpserver.wisp.Common; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; + +import java.sql.SQLException; +import java.sql.SQLTimeoutException; +import java.time.Duration; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +class AgentWebSocketHandlerTest { + @Test + void marksSqlProposalAsFinalResult() { + Map proposal = AgentWebSocketHandler.toolDefinitions().stream() + .filter(tool -> "propose_sql".equals(tool.get("name"))) + .findFirst() + .orElseThrow(); + + assertEquals(true, ((Map) proposal.get("_meta")).get("com.jumpserver/finalResult")); + } + + @Test + void deadlineSuppressesLateDriverResult() throws Exception { + assertSingleOutcomeAfterInterruption(true); + } + + @Test + void cancellationSuppressesLateDriverResult() throws Exception { + assertSingleOutcomeAfterInterruption(false); + } + + private void assertSingleOutcomeAfterInterruption(boolean timeout) throws Exception { + var fixture = new Fixture(timeout ? Duration.ofSeconds(1) : Duration.ofSeconds(60)); + var started = new CountDownLatch(1); + var release = new CountDownLatch(1); + when(fixture.service.execute(eq(fixture.session), isNull(), anyString(), anyString())).thenAnswer(call -> { + started.countDown(); + // Simulate JDBC returning successfully after ignoring cancellation. + boolean released = false; + while (!released) { + try { + released = release.await(3, TimeUnit.SECONDS); + if (!released) throw new SQLException("Test driver was not released"); + } catch (InterruptedException ignored) { + } + } + return "{}"; + }); + try { + fixture.submit(); + assertTrue(started.await(3, TimeUnit.SECONDS)); + if (!timeout) { + fixture.handler.handleToolCancel(fixture.socket, fixture.session, packet(""" + {"jsonrpc":"2.0","method":"notifications/cancelled","params":{"requestId":"request-1"}} + """)); + } + assertOutcome(fixture.responses.poll(3, TimeUnit.SECONDS), timeout ? "timeout" : "cancelled"); + if (!timeout) assertEquals("mcp.cancel_result", fixture.responses.poll(3, TimeUnit.SECONDS).get("type").getAsString()); + } finally { + release.countDown(); + fixture.close(); + } + assertTrue(fixture.responses.isEmpty(), "Late JDBC completion must not produce a second response"); + } + + @Test + void deadlineAlsoExpiresQueuedRequests() throws Exception { + var fixture = new Fixture(Duration.ofSeconds(1)); + var started = new CountDownLatch(2); + var release = new CountDownLatch(1); + var executor = (ThreadPoolExecutor) ReflectionTestUtils.getField(fixture.handler, "toolExecutor"); + assertNotNull(executor); + try { + for (int i = 0; i < 2; i++) { + executor.execute(() -> { + started.countDown(); + try { + release.await(3, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + } + assertTrue(started.await(3, TimeUnit.SECONDS)); + fixture.submit(); + assertOutcome(fixture.responses.poll(3, TimeUnit.SECONDS), "timeout"); + verifyNoInteractions(fixture.service); + assertTrue(executor.getQueue().isEmpty()); + } finally { + release.countDown(); + fixture.close(); + } + } + + @Test + void wrappedSqlTimeoutIsAnExpectedToolOutcome() throws Exception { + var fixture = new Fixture(Duration.ofSeconds(60)); + when(fixture.service.execute(eq(fixture.session), isNull(), anyString(), anyString())) + .thenThrow(new SQLException("Driver failed", new SQLTimeoutException("Query timed out"))); + try { + fixture.submit(); + assertOutcome(fixture.responses.poll(3, TimeUnit.SECONDS), "timeout"); + } finally { + fixture.close(); + } + } + + @Test + void ordinaryDatabaseFailureRemainsAnError() throws Exception { + var fixture = new Fixture(Duration.ofSeconds(60)); + when(fixture.service.execute(eq(fixture.session), isNull(), anyString(), anyString())) + .thenThrow(new SQLException("Connection failed")); + try { + fixture.submit(); + var response = fixture.responses.poll(3, TimeUnit.SECONDS); + assertNotNull(response); + assertEquals(-32603, response.getAsJsonObject("data").getAsJsonObject("error").get("code").getAsInt()); + } finally { + fixture.close(); + } + } + + private static void assertOutcome(JsonObject response, String status) { + assertNotNull(response); + var data = response.getAsJsonObject("data"); + assertFalse(data.has("error")); + var result = data.getAsJsonObject("result"); + assertTrue(result.get("isError").getAsBoolean()); + var metadata = result.getAsJsonObject("_meta").getAsJsonObject("com.jumpserver/agent"); + assertEquals(status, metadata.get("status").getAsString()); + assertEquals("tool_" + status, metadata.get("code").getAsString()); + } + + private static JsonObject packet(String data) { + var packet = JsonParser.parseString("{\"version\":1,\"resource_session_id\":\"session-1\"}").getAsJsonObject(); + packet.add("data", JsonParser.parseString(data)); + return packet; + } + + private static class Fixture { + final SqlAgentToolService service = mock(SqlAgentToolService.class); + final JMSSession session = mock(JMSSession.class); + final WebSocketSession socket = mock(WebSocketSession.class); + final BlockingQueue responses = new LinkedBlockingQueue<>(); + final AgentWebSocketHandler handler; + + Fixture(Duration timeout) throws Exception { + handler = new AgentWebSocketHandler(service, timeout); + when(session.getJmsSession()).thenReturn(Common.Session.newBuilder().setId("session-1").build()); + when(socket.getId()).thenReturn("socket-1"); + when(socket.isOpen()).thenReturn(true); + doAnswer(call -> { + responses.add(JsonParser.parseString(((TextMessage) call.getArgument(0)).getPayload()).getAsJsonObject()); + return null; + }).when(socket).sendMessage(any(TextMessage.class)); + } + + void submit() { + handler.handleToolRequest(socket, "token-1", session, packet(""" + {"jsonrpc":"2.0","id":"request-1","method":"tools/call","params":{ + "name":"inspect_schema","arguments":{},"_meta":{ + "com.jumpserver/agent":{"resource_session_id":"session-1","revision":1,"tool_call_id":"tool-1"}, + "com.jumpserver/sqlContext":{},"com.jumpserver/sqlOperation":"generate" + } + }} + """)); + } + + void close() throws InterruptedException { + handler.shutdown(); + var executor = (ThreadPoolExecutor) ReflectionTestUtils.getField(handler, "toolExecutor"); + assertNotNull(executor); + assertTrue(executor.awaitTermination(3, TimeUnit.SECONDS)); + } + } +} diff --git a/backend/web/src/test/java/org/jumpserver/chen/web/ai/SqlAgentToolServiceTest.java b/backend/web/src/test/java/org/jumpserver/chen/web/ai/SqlAgentToolServiceTest.java new file mode 100644 index 00000000..558773c9 --- /dev/null +++ b/backend/web/src/test/java/org/jumpserver/chen/web/ai/SqlAgentToolServiceTest.java @@ -0,0 +1,452 @@ +package org.jumpserver.chen.web.ai; + +import com.alibaba.druid.DbType; +import com.google.gson.JsonParser; +import org.junit.jupiter.api.Test; +import org.jumpserver.chen.framework.console.Console; +import org.jumpserver.chen.framework.console.context.ConsoleContext; +import org.jumpserver.chen.framework.datasource.ConnectionManager; +import org.jumpserver.chen.framework.datasource.Datasource; +import org.jumpserver.chen.framework.datasource.ResourceBrowser; +import org.jumpserver.chen.framework.datasource.entity.DBConnectInfo; +import org.jumpserver.chen.framework.session.Session; + +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class SqlAgentToolServiceTest { + @Test + void resolvesContextFromActiveConsoleWhenResourceIndexWasRefreshed() { + String nodeKey = "datasource:root,schema:public,folder:tables,table:settings_setting"; + var session = mock(Session.class); + var datasource = mock(Datasource.class); + var connectionManager = mock(ConnectionManager.class); + var resourceBrowser = mock(ResourceBrowser.class); + var console = mock(Console.class); + var connectInfo = new DBConnectInfo(); + connectInfo.setDbType("postgresql"); + + when(session.isActive()).thenReturn(true); + when(session.getDatasource()).thenReturn(datasource); + when(session.getConsoles()).thenReturn(Map.of("console-1", console)); + when(datasource.getConnectInfo()).thenReturn(connectInfo); + when(datasource.getConnectionManager()).thenReturn(connectionManager); + when(datasource.getDruidDbType()).thenReturn(DbType.postgresql); + when(datasource.getResourceBrowser()).thenReturn(resourceBrowser); + when(connectionManager.getContextKey()).thenReturn("schema"); + when(connectionManager.getDatabaseContextKey()).thenReturn("database"); + when(resourceBrowser.getIndexedNode(nodeKey)).thenReturn(null); + when(console.getNodeKey()).thenReturn(nodeKey); + when(console.getContext()).thenReturn(new ConsoleContext( + nodeKey, "table", "jumpserver", "jumpserver.public", "settings_setting" + )); + + var service = new SqlAgentToolService(); + var resolved = service.resolveRequestContext(session, """ + { + "nodeKey": "%s", + "consoleId": "console-1", + "workspaceTabId": "query-1", + "workspaceTabKind": "query", + "tabId": "query-1", + "proposalTarget": "selection", + "documentSql": "SELECT 1;\\nSELECT id FROM settings_setting", + "selectionFrom": 10, + "selectionTo": 41 + } + """.formatted(nodeKey), "repair"); + + assertEquals(nodeKey, resolved.nodeKey()); + assertEquals("jumpserver", resolved.database()); + assertEquals("public", resolved.schema()); + assertEquals("settings_setting", resolved.table()); + var sanitized = JsonParser.parseString(resolved.sanitizedJson()).getAsJsonObject(); + assertEquals("public", sanitized.get("schema").getAsString()); + assertEquals("jumpserver.public", sanitized.get("displaySchema").getAsString()); + assertEquals("table", sanitized.get("nodeType").getAsString()); + assertEquals("settings_setting", sanitized.get("table").getAsString()); + var connection = sanitized.getAsJsonObject("connectionContext"); + assertEquals("jumpserver.public", connection.get("displaySchema").getAsString()); + assertEquals("public", connection.get("resolvedSchema").getAsString()); + assertEquals("\"", connection.get("identifierQuote").getAsString()); + assertFalse(connection.get("businessRowAccess").getAsBoolean()); + assertEquals("", sanitized.get("documentSql").getAsString()); + assertEquals("SELECT id FROM settings_setting", sanitized.get("selectedSql").getAsString()); + assertEquals("settings_setting", + sanitized.getAsJsonArray("referencedTables").get(0).getAsString()); + assertTrue(sanitized.has("currentSqlAnalysis")); + + when(console.getContext()).thenReturn(new ConsoleContext( + nodeKey, "table", "jumpserver", "jumpserver.private", "settings_setting" + )); + assertThrows(IllegalStateException.class, () -> service.execute( + session, resolved, "validate_sql", "{\"sql\":\"SELECT 1\"}" + )); + } + + @Test + void normalizesCatalogQualifiedSchemaForJdbcMetadata() { + assertEquals("public", SqlAgentToolService.normalizeMetadataSchema( + "jumpserver", "jumpserver.public" + )); + assertEquals("public", SqlAgentToolService.normalizeMetadataSchema( + "jumpserver", "public" + )); + assertEquals("Public", SqlAgentToolService.normalizeMetadataSchema( + "JumpServer", "jumpserver.Public" + )); + assertEquals("", SqlAgentToolService.normalizeMetadataSchema("jumpserver", null)); + } + + @Test + void validatesReadOnlySqlForEveryChenDialect() { + List dialects = List.of( + DbType.mysql, + DbType.mariadb, + DbType.postgresql, + DbType.sqlserver, + DbType.oracle, + DbType.clickhouse, + DbType.dm, + DbType.db2 + ); + + for (DbType dialect : dialects) { + Map result = SqlAgentToolService.validateSQL(dialect, "SELECT id FROM users"); + assertEquals(true, result.get("valid"), dialect.name()); + assertEquals(1, result.get("statementCount"), dialect.name()); + assertEquals(1, result.get("riskLevel"), dialect.name()); + } + } + + @Test + void reportsSyntaxErrorsWithoutExecutingSql() { + Map result = SqlAgentToolService.validateSQL(DbType.postgresql, "SELECT * FRM users"); + + assertEquals(false, result.get("valid")); + assertEquals(0, result.get("statementCount")); + assertFalse(((List) result.get("errors")).isEmpty()); + } + + @Test + void preparesValidatedDraftWithoutExecutingSql() throws Exception { + String nodeKey = "datasource:root,schema:public"; + var session = mock(Session.class); + var datasource = mock(Datasource.class); + var connectionManager = mock(ConnectionManager.class); + var resourceBrowser = mock(ResourceBrowser.class); + var connectInfo = new DBConnectInfo(); + connectInfo.setDbType("postgresql"); + + when(session.isActive()).thenReturn(true); + when(session.getDatasource()).thenReturn(datasource); + when(session.getConsoles()).thenReturn(Map.of()); + when(datasource.getConnectInfo()).thenReturn(connectInfo); + when(datasource.getConnectionManager()).thenReturn(connectionManager); + when(datasource.getDruidDbType()).thenReturn(DbType.postgresql); + when(datasource.getResourceBrowser()).thenReturn(resourceBrowser); + when(resourceBrowser.getIndexedNode(nodeKey)).thenReturn( + new org.jumpserver.chen.framework.datasource.entity.resource.ResourceNodeSnapshot( + nodeKey, "schema", "jumpserver", "public", "", null + ) + ); + + var service = new SqlAgentToolService(); + var context = service.resolveRequestContext(session, """ + { + "nodeKey":"%s", + "paneId":"pane-1", + "workspaceTabKind":"database", + "proposalTarget":"new_query", + "revision":0, + "selectionFrom":0, + "selectionTo":0 + } + """.formatted(nodeKey), "generate"); + var result = JsonParser.parseString(service.execute( + session, + context, + "propose_sql", + "{\"sql\":\"SELECT id FROM users\",\"explanation\":\"List users\"}" + )).getAsJsonObject(); + + assertEquals("proposal", result.get("kind").getAsString()); + assertTrue(result.getAsJsonObject("analysis").get("valid").getAsBoolean()); + assertEquals("new_query", result.getAsJsonObject("proposal") + .getAsJsonObject("base").get("target").getAsString()); + } + + @Test + void usesExplicitProposalTargetInsteadOfInferringItFromEditorContext() throws Exception { + String nodeKey = "datasource:root,schema:public"; + var session = mock(Session.class); + var datasource = mock(Datasource.class); + var connectionManager = mock(ConnectionManager.class); + var console = mock(Console.class); + var connectInfo = new DBConnectInfo(); + connectInfo.setDbType("postgresql"); + + when(session.isActive()).thenReturn(true); + when(session.getDatasource()).thenReturn(datasource); + when(session.getConsoles()).thenReturn(Map.of("console-1", console)); + when(datasource.getConnectInfo()).thenReturn(connectInfo); + when(datasource.getConnectionManager()).thenReturn(connectionManager); + when(datasource.getDruidDbType()).thenReturn(DbType.postgresql); + when(connectionManager.getContextKey()).thenReturn("schema"); + when(connectionManager.getDatabaseContextKey()).thenReturn("database"); + when(console.getNodeKey()).thenReturn(nodeKey); + when(console.getContext()).thenReturn(new ConsoleContext( + nodeKey, "schema", "jumpserver", "public", "" + )); + + var service = new SqlAgentToolService(); + for (var expected : List.of( + List.of("console", "new_query"), + List.of("query", "document"), + List.of("console", "document") + )) { + var context = service.resolveRequestContext(session, """ + { + "nodeKey":"%s", + "consoleId":"console-1", + "paneId":"pane-1", + "tabId":"tab-1", + "workspaceTabId":"tab-1", + "workspaceTabKind":"%s", + "proposalTarget":"%s", + "documentSql":"SELECT 1", + "selectionFrom":0, + "selectionTo":0 + } + """.formatted(nodeKey, expected.get(0), expected.get(1)), "generate"); + var result = JsonParser.parseString(service.execute( + session, + context, + "propose_sql", + "{\"sql\":\"SELECT 2\",\"explanation\":\"Next query\"}" + )).getAsJsonObject(); + + assertEquals(expected.get(1), result.getAsJsonObject("proposal") + .getAsJsonObject("base").get("target").getAsString()); + assertEquals("tab-1", result.getAsJsonObject("proposal") + .getAsJsonObject("base").get("tabId").getAsString()); + } + } + + @Test + void identifiesMultiStatementWriteRisk() { + Map result = SqlAgentToolService.validateSQL( + DbType.mysql, + "SELECT id FROM users; DELETE FROM users WHERE id = 1" + ); + + assertEquals(true, result.get("valid")); + assertEquals(2, result.get("statementCount")); + assertEquals("MULTI", result.get("statementType")); + assertTrue(((Number) result.get("riskLevel")).intValue() >= 3); + } + + @Test + void errorContextDropsUnknownBusinessData() { + var sanitized = SqlAgentToolService.sanitizeLastError(JsonParser.parseString(""" + { + "kind": "execute", + "message": "column does not exist", + "sql": "SELECT secret FROM users", + "rows": [{"secret": "must-not-leave-chen"}] + } + """)); + + assertEquals("execute", sanitized.get("kind").getAsString()); + assertEquals("column does not exist", sanitized.get("message").getAsString()); + assertFalse(sanitized.has("rows")); + } + + @Test + void metadataApprovalStaysInsideActiveSchema() { + var service = new SqlAgentToolService(); + var context = agentContext("jumpserver", "public", "node-public"); + + var scope = service.resolveMetadataApprovalScope(context, """ + {"schema":"public","tables":["public.users","\\\"public\\\".\\\"roles\\\""]} + """); + + assertEquals("jumpserver", scope.database()); + assertEquals("public", scope.schema()); + assertEquals(List.of("users", "roles"), scope.tables()); + assertThrows(IllegalArgumentException.class, () -> service.resolveMetadataApprovalScope(context, """ + {"schema":"information_schema","tables":["tables"]} + """)); + assertThrows(IllegalArgumentException.class, () -> service.resolveMetadataApprovalScope(context, """ + {"tables":["private.users"]} + """)); + assertThrows(IllegalArgumentException.class, () -> service.resolveMetadataApprovalScope( + agentContext("jumpserver", "information_schema", "node-system"), + "{\"tables\":[\"tables\"]}" + )); + } + + @Test + void reportsTheProtectedActiveSchemaPrecisely() { + var service = new SqlAgentToolService(); + + var error = assertThrows(IllegalArgumentException.class, () -> service.resolveMetadataApprovalScope( + agentContext("jumpserver", "pg_catalog", "node-system"), + "{\"query\":\"*\"}" + )); + + assertEquals( + "The active schema 'pg_catalog' is a protected system schema; " + + "table metadata is unavailable in this scope", + error.getMessage() + ); + } + + @Test + void sessionMetadataApprovalOnlyCoversSameOrSmallerScope() { + var service = new SqlAgentToolService(); + var context = agentContext("jumpserver", "public", "node-public"); + var grant = service.resolveMetadataApprovalScope(context, """ + {"tables":["users","roles"]} + """); + + assertTrue(grant.covers(service.resolveMetadataApprovalScope(context, """ + {"tables":["users"]} + """))); + assertFalse(grant.covers(service.resolveMetadataApprovalScope(context, """ + {"tables":["users","accounts"]} + """))); + assertFalse(grant.covers(service.resolveMetadataApprovalScope( + agentContext("jumpserver", "public", "another-node"), + "{\"tables\":[\"users\"]}" + ))); + } + + @Test + void metadataSearchApprovalRequiresTheSameSearch() { + var service = new SqlAgentToolService(); + var context = agentContext("jumpserver", "public", "node-public"); + var grant = service.resolveMetadataApprovalScope(context, "{\"query\":\"user\"}"); + + assertTrue(grant.covers(service.resolveMetadataApprovalScope(context, "{\"query\":\"USER\"}"))); + assertFalse(grant.covers(service.resolveMetadataApprovalScope(context, "{\"query\":\"account\"}"))); + assertFalse(grant.covers(service.resolveMetadataApprovalScope(context, "{\"tables\":[\"users\"]}"))); + } + + @Test + void tableDiscoveryApprovalCoversOnlyBoundedFollowUpInspectionInTheSameContext() { + var service = new SqlAgentToolService(); + var context = agentContext("jumpserver", "public", "node-public"); + var grant = service.resolveMetadataApprovalScope(context, "{\"query\":\"*\"}"); + + assertTrue(grant.discovery()); + assertTrue(grant.covers(service.resolveMetadataApprovalScope(context, """ + {"tables":["users","orders"]} + """))); + assertFalse(grant.covers(service.resolveMetadataApprovalScope(context, "{\"query\":\"user\"}"))); + assertFalse(grant.covers(service.resolveMetadataApprovalScope( + agentContext("jumpserver", "private", "node-private"), + "{\"tables\":[\"users\"]}" + ))); + assertThrows(IllegalArgumentException.class, () -> service.resolveMetadataApprovalScope( + context, "{\"query\":\"*\",\"tables\":[\"users\"]}" + )); + } + + @Test + void rejectsSystemCatalogReferencesWithoutBlockingValidTableAliases() { + String nodeKey = "datasource:root,schema:public"; + var session = mock(Session.class); + var datasource = mock(Datasource.class); + var connectionManager = mock(ConnectionManager.class); + var resourceBrowser = mock(ResourceBrowser.class); + var console = mock(Console.class); + var connectInfo = new DBConnectInfo(); + connectInfo.setDbType("postgresql"); + + when(session.isActive()).thenReturn(true); + when(session.getDatasource()).thenReturn(datasource); + when(session.getConsoles()).thenReturn(Map.of("console-1", console)); + when(datasource.getConnectInfo()).thenReturn(connectInfo); + when(datasource.getConnectionManager()).thenReturn(connectionManager); + when(datasource.getDruidDbType()).thenReturn(DbType.postgresql); + when(datasource.getResourceBrowser()).thenReturn(resourceBrowser); + when(connectionManager.getContextKey()).thenReturn("schema"); + when(connectionManager.getDatabaseContextKey()).thenReturn("database"); + when(console.getNodeKey()).thenReturn(nodeKey); + when(console.getContext()).thenReturn(new ConsoleContext( + nodeKey, "schema", "jumpserver", "public", "" + )); + + var service = new SqlAgentToolService(); + var aliasContext = service.resolveRequestContext( + session, + """ + { + "nodeKey":"%s", + "consoleId":"console-1", + "workspaceTabKind":"query", + "tabId":"tab-1", + "proposalTarget":"document", + "documentSql":"SELECT sys.id FROM users AS sys", + "selectionFrom":0, + "selectionTo":0 + } + """.formatted(nodeKey), + "explain" + ); + var aliasAnalysis = JsonParser.parseString(aliasContext.sanitizedJson()) + .getAsJsonObject().getAsJsonObject("currentSqlAnalysis"); + assertEquals("users", aliasAnalysis.getAsJsonArray("tables").get(0).getAsString()); + + assertThrows(IllegalArgumentException.class, () -> service.resolveRequestContext( + session, + """ + { + "nodeKey":"%s", + "consoleId":"console-1", + "workspaceTabKind":"query", + "tabId":"tab-1", + "proposalTarget":"document", + "documentSql":"SELECT * FROM information_schema.tables", + "selectionFrom":0, + "selectionTo":0 + } + """.formatted(nodeKey), + "explain" + )); + assertThrows(IllegalArgumentException.class, () -> service.resolveRequestContext( + session, + """ + { + "nodeKey":"%s", + "consoleId":"console-1", + "workspaceTabKind":"query", + "tabId":"tab-1", + "proposalTarget":"document", + "documentSql":"SELECT * FRM information_schema.tables", + "selectionFrom":0, + "selectionTo":0 + } + """.formatted(nodeKey), + "repair" + )); + } + + private static SqlAgentToolService.AgentRequestContext agentContext( + String database, + String schema, + String nodeKey + ) { + return new SqlAgentToolService.AgentRequestContext( + "postgresql", database, schema, "", nodeKey, "", "", "{}" + ); + } +} diff --git a/backend/wisp/src/main/java/org/jumpserver/wisp/Common.java b/backend/wisp/src/main/java/org/jumpserver/wisp/Common.java index f23c9739..7430fcb0 100644 --- a/backend/wisp/src/main/java/org/jumpserver/wisp/Common.java +++ b/backend/wisp/src/main/java/org/jumpserver/wisp/Common.java @@ -441,7 +441,7 @@ public java.lang.String getId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); id_ = s; @@ -457,7 +457,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -480,7 +480,7 @@ public java.lang.String getName() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); name_ = s; @@ -496,7 +496,7 @@ public java.lang.String getName() { getNameBytes() { java.lang.Object ref = name_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); name_ = b; @@ -519,7 +519,7 @@ public java.lang.String getUsername() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); username_ = s; @@ -535,7 +535,7 @@ public java.lang.String getUsername() { getUsernameBytes() { java.lang.Object ref = username_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); username_ = b; @@ -558,7 +558,7 @@ public java.lang.String getRole() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); role_ = s; @@ -574,7 +574,7 @@ public java.lang.String getRole() { getRoleBytes() { java.lang.Object ref = role_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); role_ = b; @@ -1045,7 +1045,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -1117,7 +1117,7 @@ public java.lang.String getName() { getNameBytes() { java.lang.Object ref = name_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); name_ = b; @@ -1189,7 +1189,7 @@ public java.lang.String getUsername() { getUsernameBytes() { java.lang.Object ref = username_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); username_ = b; @@ -1261,7 +1261,7 @@ public java.lang.String getRole() { getRoleBytes() { java.lang.Object ref = role_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); role_ = b; @@ -1545,7 +1545,7 @@ public java.lang.String getId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); id_ = s; @@ -1561,7 +1561,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -1584,7 +1584,7 @@ public java.lang.String getName() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); name_ = s; @@ -1600,7 +1600,7 @@ public java.lang.String getName() { getNameBytes() { java.lang.Object ref = name_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); name_ = b; @@ -1623,7 +1623,7 @@ public java.lang.String getUsername() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); username_ = s; @@ -1639,7 +1639,7 @@ public java.lang.String getUsername() { getUsernameBytes() { java.lang.Object ref = username_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); username_ = b; @@ -1662,7 +1662,7 @@ public java.lang.String getSecret() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); secret_ = s; @@ -1678,7 +1678,7 @@ public java.lang.String getSecret() { getSecretBytes() { java.lang.Object ref = secret_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); secret_ = b; @@ -2150,7 +2150,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -2222,7 +2222,7 @@ public java.lang.String getName() { getNameBytes() { java.lang.Object ref = name_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); name_ = b; @@ -2294,7 +2294,7 @@ public java.lang.String getUsername() { getUsernameBytes() { java.lang.Object ref = username_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); username_ = b; @@ -2366,7 +2366,7 @@ public java.lang.String getSecret() { getSecretBytes() { java.lang.Object ref = secret_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); secret_ = b; @@ -2521,7 +2521,7 @@ public org.jumpserver.wisp.Common.LabelValueOrBuilder getSecretTypeOrBuilder() { * .message.LabelValue secretType = 6; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.Common.LabelValue, org.jumpserver.wisp.Common.LabelValue.Builder, org.jumpserver.wisp.Common.LabelValueOrBuilder> + org.jumpserver.wisp.Common.LabelValue, org.jumpserver.wisp.Common.LabelValue.Builder, org.jumpserver.wisp.Common.LabelValueOrBuilder> internalGetSecretTypeFieldBuilder() { if (secretTypeBuilder_ == null) { secretTypeBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -2665,7 +2665,7 @@ public java.lang.String getLabel() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); label_ = s; @@ -2681,7 +2681,7 @@ public java.lang.String getLabel() { getLabelBytes() { java.lang.Object ref = label_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); label_ = b; @@ -2704,7 +2704,7 @@ public java.lang.String getValue() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); value_ = s; @@ -2720,7 +2720,7 @@ public java.lang.String getValue() { getValueBytes() { java.lang.Object ref = value_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); value_ = b; @@ -3073,7 +3073,7 @@ public java.lang.String getLabel() { getLabelBytes() { java.lang.Object ref = label_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); label_ = b; @@ -3145,7 +3145,7 @@ public java.lang.String getValue() { getValueBytes() { java.lang.Object ref = value_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); value_ = b; @@ -3310,7 +3310,7 @@ public interface AssetOrBuilder extends /** * repeated .message.Protocol protocols = 6; */ - java.util.List + java.util.List getProtocolsList(); /** * repeated .message.Protocol protocols = 6; @@ -3323,7 +3323,7 @@ public interface AssetOrBuilder extends /** * repeated .message.Protocol protocols = 6; */ - java.util.List + java.util.List getProtocolsOrBuilderList(); /** * repeated .message.Protocol protocols = 6; @@ -3598,7 +3598,7 @@ public java.lang.String getDbName() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); dbName_ = s; @@ -3614,7 +3614,7 @@ public java.lang.String getDbName() { getDbNameBytes() { java.lang.Object ref = dbName_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); dbName_ = b; @@ -3648,7 +3648,7 @@ public java.lang.String getCaCert() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); caCert_ = s; @@ -3664,7 +3664,7 @@ public java.lang.String getCaCert() { getCaCertBytes() { java.lang.Object ref = caCert_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); caCert_ = b; @@ -3687,7 +3687,7 @@ public java.lang.String getClientCert() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); clientCert_ = s; @@ -3703,7 +3703,7 @@ public java.lang.String getClientCert() { getClientCertBytes() { java.lang.Object ref = clientCert_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); clientCert_ = b; @@ -3726,7 +3726,7 @@ public java.lang.String getClientKey() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); clientKey_ = s; @@ -3742,7 +3742,7 @@ public java.lang.String getClientKey() { getClientKeyBytes() { java.lang.Object ref = clientKey_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); clientKey_ = b; @@ -3776,7 +3776,7 @@ public java.lang.String getAutoFill() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); autoFill_ = s; @@ -3792,7 +3792,7 @@ public java.lang.String getAutoFill() { getAutoFillBytes() { java.lang.Object ref = autoFill_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); autoFill_ = b; @@ -3815,7 +3815,7 @@ public java.lang.String getUsernameSelector() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); usernameSelector_ = s; @@ -3831,7 +3831,7 @@ public java.lang.String getUsernameSelector() { getUsernameSelectorBytes() { java.lang.Object ref = usernameSelector_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); usernameSelector_ = b; @@ -3854,7 +3854,7 @@ public java.lang.String getPasswordSelector() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); passwordSelector_ = s; @@ -3870,7 +3870,7 @@ public java.lang.String getPasswordSelector() { getPasswordSelectorBytes() { java.lang.Object ref = passwordSelector_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); passwordSelector_ = b; @@ -3893,7 +3893,7 @@ public java.lang.String getSubmitSelector() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); submitSelector_ = s; @@ -3909,7 +3909,7 @@ public java.lang.String getSubmitSelector() { getSubmitSelectorBytes() { java.lang.Object ref = submitSelector_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); submitSelector_ = b; @@ -3932,7 +3932,7 @@ public java.lang.String getScript() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); script_ = s; @@ -3948,7 +3948,7 @@ public java.lang.String getScript() { getScriptBytes() { java.lang.Object ref = script_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); script_ = b; @@ -3971,7 +3971,7 @@ public java.lang.String getHttpProxy() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); httpProxy_ = s; @@ -3987,7 +3987,7 @@ public java.lang.String getHttpProxy() { getHttpProxyBytes() { java.lang.Object ref = httpProxy_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); httpProxy_ = b; @@ -4010,7 +4010,7 @@ public java.lang.String getPgSslMode() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); pgSslMode_ = s; @@ -4026,7 +4026,7 @@ public java.lang.String getPgSslMode() { getPgSslModeBytes() { java.lang.Object ref = pgSslMode_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); pgSslMode_ = b; @@ -4643,7 +4643,7 @@ public java.lang.String getDbName() { getDbNameBytes() { java.lang.Object ref = dbName_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); dbName_ = b; @@ -4747,7 +4747,7 @@ public java.lang.String getCaCert() { getCaCertBytes() { java.lang.Object ref = caCert_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); caCert_ = b; @@ -4819,7 +4819,7 @@ public java.lang.String getClientCert() { getClientCertBytes() { java.lang.Object ref = clientCert_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); clientCert_ = b; @@ -4891,7 +4891,7 @@ public java.lang.String getClientKey() { getClientKeyBytes() { java.lang.Object ref = clientKey_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); clientKey_ = b; @@ -4995,7 +4995,7 @@ public java.lang.String getAutoFill() { getAutoFillBytes() { java.lang.Object ref = autoFill_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); autoFill_ = b; @@ -5067,7 +5067,7 @@ public java.lang.String getUsernameSelector() { getUsernameSelectorBytes() { java.lang.Object ref = usernameSelector_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); usernameSelector_ = b; @@ -5139,7 +5139,7 @@ public java.lang.String getPasswordSelector() { getPasswordSelectorBytes() { java.lang.Object ref = passwordSelector_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); passwordSelector_ = b; @@ -5211,7 +5211,7 @@ public java.lang.String getSubmitSelector() { getSubmitSelectorBytes() { java.lang.Object ref = submitSelector_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); submitSelector_ = b; @@ -5283,7 +5283,7 @@ public java.lang.String getScript() { getScriptBytes() { java.lang.Object ref = script_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); script_ = b; @@ -5355,7 +5355,7 @@ public java.lang.String getHttpProxy() { getHttpProxyBytes() { java.lang.Object ref = httpProxy_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); httpProxy_ = b; @@ -5427,7 +5427,7 @@ public java.lang.String getPgSslMode() { getPgSslModeBytes() { java.lang.Object ref = pgSslMode_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); pgSslMode_ = b; @@ -5539,7 +5539,7 @@ public java.lang.String getId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); id_ = s; @@ -5555,7 +5555,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -5578,7 +5578,7 @@ public java.lang.String getName() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); name_ = s; @@ -5594,7 +5594,7 @@ public java.lang.String getName() { getNameBytes() { java.lang.Object ref = name_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); name_ = b; @@ -5617,7 +5617,7 @@ public java.lang.String getAddress() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); address_ = s; @@ -5633,7 +5633,7 @@ public java.lang.String getAddress() { getAddressBytes() { java.lang.Object ref = address_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); address_ = b; @@ -5656,7 +5656,7 @@ public java.lang.String getOrgId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); orgId_ = s; @@ -5672,7 +5672,7 @@ public java.lang.String getOrgId() { getOrgIdBytes() { java.lang.Object ref = orgId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); orgId_ = b; @@ -5695,7 +5695,7 @@ public java.lang.String getOrgName() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); orgName_ = s; @@ -5711,7 +5711,7 @@ public java.lang.String getOrgName() { getOrgNameBytes() { java.lang.Object ref = orgName_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); orgName_ = b; @@ -5735,7 +5735,7 @@ public java.util.List getProtocolsList() { * repeated .message.Protocol protocols = 6; */ @java.lang.Override - public java.util.List + public java.util.List getProtocolsOrBuilderList() { return protocols_; } @@ -6197,7 +6197,7 @@ public Builder mergeFrom(org.jumpserver.wisp.Common.Asset other) { protocolsBuilder_ = null; protocols_ = other.protocols_; bitField0_ = (bitField0_ & ~0x00000020); - protocolsBuilder_ = + protocolsBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? internalGetProtocolsFieldBuilder() : null; } else { @@ -6321,7 +6321,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -6393,7 +6393,7 @@ public java.lang.String getName() { getNameBytes() { java.lang.Object ref = name_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); name_ = b; @@ -6465,7 +6465,7 @@ public java.lang.String getAddress() { getAddressBytes() { java.lang.Object ref = address_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); address_ = b; @@ -6537,7 +6537,7 @@ public java.lang.String getOrgId() { getOrgIdBytes() { java.lang.Object ref = orgId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); orgId_ = b; @@ -6609,7 +6609,7 @@ public java.lang.String getOrgName() { getOrgNameBytes() { java.lang.Object ref = orgName_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); orgName_ = b; @@ -6851,7 +6851,7 @@ public org.jumpserver.wisp.Common.ProtocolOrBuilder getProtocolsOrBuilder( /** * repeated .message.Protocol protocols = 6; */ - public java.util.List + public java.util.List getProtocolsOrBuilderList() { if (protocolsBuilder_ != null) { return protocolsBuilder_.getMessageOrBuilderList(); @@ -6877,12 +6877,12 @@ public org.jumpserver.wisp.Common.Protocol.Builder addProtocolsBuilder( /** * repeated .message.Protocol protocols = 6; */ - public java.util.List + public java.util.List getProtocolsBuilderList() { return internalGetProtocolsFieldBuilder().getBuilderList(); } private com.google.protobuf.RepeatedFieldBuilder< - org.jumpserver.wisp.Common.Protocol, org.jumpserver.wisp.Common.Protocol.Builder, org.jumpserver.wisp.Common.ProtocolOrBuilder> + org.jumpserver.wisp.Common.Protocol, org.jumpserver.wisp.Common.Protocol.Builder, org.jumpserver.wisp.Common.ProtocolOrBuilder> internalGetProtocolsFieldBuilder() { if (protocolsBuilder_ == null) { protocolsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< @@ -7004,7 +7004,7 @@ public org.jumpserver.wisp.Common.Asset.SpecificOrBuilder getSpecificOrBuilder() * .message.Asset.Specific specific = 7; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.Common.Asset.Specific, org.jumpserver.wisp.Common.Asset.Specific.Builder, org.jumpserver.wisp.Common.Asset.SpecificOrBuilder> + org.jumpserver.wisp.Common.Asset.Specific, org.jumpserver.wisp.Common.Asset.Specific.Builder, org.jumpserver.wisp.Common.Asset.SpecificOrBuilder> internalGetSpecificFieldBuilder() { if (specificBuilder_ == null) { specificBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -7147,7 +7147,7 @@ public java.lang.String getName() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); name_ = s; @@ -7163,7 +7163,7 @@ public java.lang.String getName() { getNameBytes() { java.lang.Object ref = name_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); name_ = b; @@ -7560,7 +7560,7 @@ public java.lang.String getName() { getNameBytes() { java.lang.Object ref = name_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); name_ = b; @@ -7873,7 +7873,7 @@ public java.lang.String getId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); id_ = s; @@ -7889,7 +7889,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -7912,7 +7912,7 @@ public java.lang.String getName() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); name_ = s; @@ -7928,7 +7928,7 @@ public java.lang.String getName() { getNameBytes() { java.lang.Object ref = name_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); name_ = b; @@ -7951,7 +7951,7 @@ public java.lang.String getIp() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); ip_ = s; @@ -7967,7 +7967,7 @@ public java.lang.String getIp() { getIpBytes() { java.lang.Object ref = ip_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); ip_ = b; @@ -8001,7 +8001,7 @@ public java.lang.String getProtocol() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); protocol_ = s; @@ -8017,7 +8017,7 @@ public java.lang.String getProtocol() { getProtocolBytes() { java.lang.Object ref = protocol_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); protocol_ = b; @@ -8040,7 +8040,7 @@ public java.lang.String getUsername() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); username_ = s; @@ -8056,7 +8056,7 @@ public java.lang.String getUsername() { getUsernameBytes() { java.lang.Object ref = username_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); username_ = b; @@ -8079,7 +8079,7 @@ public java.lang.String getPassword() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); password_ = s; @@ -8095,7 +8095,7 @@ public java.lang.String getPassword() { getPasswordBytes() { java.lang.Object ref = password_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); password_ = b; @@ -8118,7 +8118,7 @@ public java.lang.String getPrivateKey() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); privateKey_ = s; @@ -8134,7 +8134,7 @@ public java.lang.String getPrivateKey() { getPrivateKeyBytes() { java.lang.Object ref = privateKey_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); privateKey_ = b; @@ -8630,7 +8630,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -8702,7 +8702,7 @@ public java.lang.String getName() { getNameBytes() { java.lang.Object ref = name_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); name_ = b; @@ -8774,7 +8774,7 @@ public java.lang.String getIp() { getIpBytes() { java.lang.Object ref = ip_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); ip_ = b; @@ -8878,7 +8878,7 @@ public java.lang.String getProtocol() { getProtocolBytes() { java.lang.Object ref = protocol_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); protocol_ = b; @@ -8950,7 +8950,7 @@ public java.lang.String getUsername() { getUsernameBytes() { java.lang.Object ref = username_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); username_ = b; @@ -9022,7 +9022,7 @@ public java.lang.String getPassword() { getPasswordBytes() { java.lang.Object ref = password_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); password_ = b; @@ -9094,7 +9094,7 @@ public java.lang.String getPrivateKey() { getPrivateKeyBytes() { java.lang.Object ref = privateKey_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); privateKey_ = b; @@ -9973,7 +9973,7 @@ public interface CommandACLOrBuilder extends /** * repeated .message.CommandGroup command_groups = 7; */ - java.util.List + java.util.List getCommandGroupsList(); /** * repeated .message.CommandGroup command_groups = 7; @@ -9986,7 +9986,7 @@ public interface CommandACLOrBuilder extends /** * repeated .message.CommandGroup command_groups = 7; */ - java.util.List + java.util.List getCommandGroupsOrBuilderList(); /** * repeated .message.CommandGroup command_groups = 7; @@ -10201,7 +10201,7 @@ public java.lang.String getId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); id_ = s; @@ -10217,7 +10217,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -10240,7 +10240,7 @@ public java.lang.String getName() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); name_ = s; @@ -10256,7 +10256,7 @@ public java.lang.String getName() { getNameBytes() { java.lang.Object ref = name_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); name_ = b; @@ -10320,7 +10320,7 @@ public java.util.List getCommandGroupsL * repeated .message.CommandGroup command_groups = 7; */ @java.lang.Override - public java.util.List + public java.util.List getCommandGroupsOrBuilderList() { return commandGroups_; } @@ -10717,7 +10717,7 @@ public Builder mergeFrom(org.jumpserver.wisp.Common.CommandACL other) { commandGroupsBuilder_ = null; commandGroups_ = other.commandGroups_; bitField0_ = (bitField0_ & ~0x00000020); - commandGroupsBuilder_ = + commandGroupsBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? internalGetCommandGroupsFieldBuilder() : null; } else { @@ -10831,7 +10831,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -10903,7 +10903,7 @@ public java.lang.String getName() { getNameBytes() { java.lang.Object ref = name_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); name_ = b; @@ -11260,7 +11260,7 @@ public org.jumpserver.wisp.Common.CommandGroupOrBuilder getCommandGroupsOrBuilde /** * repeated .message.CommandGroup command_groups = 7; */ - public java.util.List + public java.util.List getCommandGroupsOrBuilderList() { if (commandGroupsBuilder_ != null) { return commandGroupsBuilder_.getMessageOrBuilderList(); @@ -11286,12 +11286,12 @@ public org.jumpserver.wisp.Common.CommandGroup.Builder addCommandGroupsBuilder( /** * repeated .message.CommandGroup command_groups = 7; */ - public java.util.List + public java.util.List getCommandGroupsBuilderList() { return internalGetCommandGroupsFieldBuilder().getBuilderList(); } private com.google.protobuf.RepeatedFieldBuilder< - org.jumpserver.wisp.Common.CommandGroup, org.jumpserver.wisp.Common.CommandGroup.Builder, org.jumpserver.wisp.Common.CommandGroupOrBuilder> + org.jumpserver.wisp.Common.CommandGroup, org.jumpserver.wisp.Common.CommandGroup.Builder, org.jumpserver.wisp.Common.CommandGroupOrBuilder> internalGetCommandGroupsFieldBuilder() { if (commandGroupsBuilder_ == null) { commandGroupsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< @@ -11487,7 +11487,7 @@ public java.lang.String getId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); id_ = s; @@ -11503,7 +11503,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -11526,7 +11526,7 @@ public java.lang.String getName() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); name_ = s; @@ -11542,7 +11542,7 @@ public java.lang.String getName() { getNameBytes() { java.lang.Object ref = name_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); name_ = b; @@ -11587,7 +11587,7 @@ public java.lang.String getMaskingMethod() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); maskingMethod_ = s; @@ -11603,7 +11603,7 @@ public java.lang.String getMaskingMethod() { getMaskingMethodBytes() { java.lang.Object ref = maskingMethod_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); maskingMethod_ = b; @@ -11626,7 +11626,7 @@ public java.lang.String getMaskPattern() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); maskPattern_ = s; @@ -11642,7 +11642,7 @@ public java.lang.String getMaskPattern() { getMaskPatternBytes() { java.lang.Object ref = maskPattern_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); maskPattern_ = b; @@ -11665,7 +11665,7 @@ public java.lang.String getFieldsPattern() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); fieldsPattern_ = s; @@ -11681,7 +11681,7 @@ public java.lang.String getFieldsPattern() { getFieldsPatternBytes() { java.lang.Object ref = fieldsPattern_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); fieldsPattern_ = b; @@ -12153,7 +12153,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -12225,7 +12225,7 @@ public java.lang.String getName() { getNameBytes() { java.lang.Object ref = name_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); name_ = b; @@ -12361,7 +12361,7 @@ public java.lang.String getMaskingMethod() { getMaskingMethodBytes() { java.lang.Object ref = maskingMethod_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); maskingMethod_ = b; @@ -12433,7 +12433,7 @@ public java.lang.String getMaskPattern() { getMaskPatternBytes() { java.lang.Object ref = maskPattern_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); maskPattern_ = b; @@ -12505,7 +12505,7 @@ public java.lang.String getFieldsPattern() { getFieldsPatternBytes() { java.lang.Object ref = fieldsPattern_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); fieldsPattern_ = b; @@ -12728,7 +12728,7 @@ public java.lang.String getId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); id_ = s; @@ -12744,7 +12744,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -12767,7 +12767,7 @@ public java.lang.String getName() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); name_ = s; @@ -12783,7 +12783,7 @@ public java.lang.String getName() { getNameBytes() { java.lang.Object ref = name_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); name_ = b; @@ -12806,7 +12806,7 @@ public java.lang.String getContent() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); content_ = s; @@ -12822,7 +12822,7 @@ public java.lang.String getContent() { getContentBytes() { java.lang.Object ref = content_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); content_ = b; @@ -12845,7 +12845,7 @@ public java.lang.String getType() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); type_ = s; @@ -12861,7 +12861,7 @@ public java.lang.String getType() { getTypeBytes() { java.lang.Object ref = type_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); type_ = b; @@ -12884,7 +12884,7 @@ public java.lang.String getPattern() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); pattern_ = s; @@ -12900,7 +12900,7 @@ public java.lang.String getPattern() { getPatternBytes() { java.lang.Object ref = pattern_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); pattern_ = b; @@ -13360,7 +13360,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -13432,7 +13432,7 @@ public java.lang.String getName() { getNameBytes() { java.lang.Object ref = name_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); name_ = b; @@ -13504,7 +13504,7 @@ public java.lang.String getContent() { getContentBytes() { java.lang.Object ref = content_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); content_ = b; @@ -13576,7 +13576,7 @@ public java.lang.String getType() { getTypeBytes() { java.lang.Object ref = type_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); type_ = b; @@ -13648,7 +13648,7 @@ public java.lang.String getPattern() { getPatternBytes() { java.lang.Object ref = pattern_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); pattern_ = b; @@ -14563,7 +14563,7 @@ public java.lang.String getId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); id_ = s; @@ -14579,7 +14579,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -14602,7 +14602,7 @@ public java.lang.String getUser() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); user_ = s; @@ -14618,7 +14618,7 @@ public java.lang.String getUser() { getUserBytes() { java.lang.Object ref = user_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); user_ = b; @@ -14641,7 +14641,7 @@ public java.lang.String getAsset() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); asset_ = s; @@ -14657,7 +14657,7 @@ public java.lang.String getAsset() { getAssetBytes() { java.lang.Object ref = asset_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); asset_ = b; @@ -14680,7 +14680,7 @@ public java.lang.String getAccount() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); account_ = s; @@ -14696,7 +14696,7 @@ public java.lang.String getAccount() { getAccountBytes() { java.lang.Object ref = account_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); account_ = b; @@ -14737,7 +14737,7 @@ public java.lang.String getRemoteAddr() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); remoteAddr_ = s; @@ -14753,7 +14753,7 @@ public java.lang.String getRemoteAddr() { getRemoteAddrBytes() { java.lang.Object ref = remoteAddr_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); remoteAddr_ = b; @@ -14776,7 +14776,7 @@ public java.lang.String getProtocol() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); protocol_ = s; @@ -14792,7 +14792,7 @@ public java.lang.String getProtocol() { getProtocolBytes() { java.lang.Object ref = protocol_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); protocol_ = b; @@ -14826,7 +14826,7 @@ public java.lang.String getOrgId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); orgId_ = s; @@ -14842,7 +14842,7 @@ public java.lang.String getOrgId() { getOrgIdBytes() { java.lang.Object ref = orgId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); orgId_ = b; @@ -14865,7 +14865,7 @@ public java.lang.String getUserId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); userId_ = s; @@ -14881,7 +14881,7 @@ public java.lang.String getUserId() { getUserIdBytes() { java.lang.Object ref = userId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); userId_ = b; @@ -14904,7 +14904,7 @@ public java.lang.String getAssetId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); assetId_ = s; @@ -14920,7 +14920,7 @@ public java.lang.String getAssetId() { getAssetIdBytes() { java.lang.Object ref = assetId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); assetId_ = b; @@ -14943,7 +14943,7 @@ public java.lang.String getAccountId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); accountId_ = s; @@ -14959,7 +14959,7 @@ public java.lang.String getAccountId() { getAccountIdBytes() { java.lang.Object ref = accountId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); accountId_ = b; @@ -14982,7 +14982,7 @@ public java.lang.String getTokenId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); tokenId_ = s; @@ -14998,7 +14998,7 @@ public java.lang.String getTokenId() { getTokenIdBytes() { java.lang.Object ref = tokenId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); tokenId_ = b; @@ -15613,7 +15613,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -15685,7 +15685,7 @@ public java.lang.String getUser() { getUserBytes() { java.lang.Object ref = user_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); user_ = b; @@ -15757,7 +15757,7 @@ public java.lang.String getAsset() { getAssetBytes() { java.lang.Object ref = asset_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); asset_ = b; @@ -15829,7 +15829,7 @@ public java.lang.String getAccount() { getAccountBytes() { java.lang.Object ref = account_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); account_ = b; @@ -15952,7 +15952,7 @@ public java.lang.String getRemoteAddr() { getRemoteAddrBytes() { java.lang.Object ref = remoteAddr_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); remoteAddr_ = b; @@ -16024,7 +16024,7 @@ public java.lang.String getProtocol() { getProtocolBytes() { java.lang.Object ref = protocol_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); protocol_ = b; @@ -16128,7 +16128,7 @@ public java.lang.String getOrgId() { getOrgIdBytes() { java.lang.Object ref = orgId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); orgId_ = b; @@ -16200,7 +16200,7 @@ public java.lang.String getUserId() { getUserIdBytes() { java.lang.Object ref = userId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); userId_ = b; @@ -16272,7 +16272,7 @@ public java.lang.String getAssetId() { getAssetIdBytes() { java.lang.Object ref = assetId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); assetId_ = b; @@ -16344,7 +16344,7 @@ public java.lang.String getAccountId() { getAccountIdBytes() { java.lang.Object ref = accountId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); accountId_ = b; @@ -16416,7 +16416,7 @@ public java.lang.String getTokenId() { getTokenIdBytes() { java.lang.Object ref = tokenId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); tokenId_ = b; @@ -16600,7 +16600,7 @@ public java.lang.String getCode() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); code_ = s; @@ -16616,7 +16616,7 @@ public java.lang.String getCode() { getCodeBytes() { java.lang.Object ref = code_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); code_ = b; @@ -16639,7 +16639,7 @@ public java.lang.String getDetail() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); detail_ = s; @@ -16655,7 +16655,7 @@ public java.lang.String getDetail() { getDetailBytes() { java.lang.Object ref = detail_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); detail_ = b; @@ -17043,7 +17043,7 @@ public java.lang.String getCode() { getCodeBytes() { java.lang.Object ref = code_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); code_ = b; @@ -17115,7 +17115,7 @@ public java.lang.String getDetail() { getDetailBytes() { java.lang.Object ref = detail_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); detail_ = b; @@ -17379,7 +17379,7 @@ public java.lang.String getId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); id_ = s; @@ -17395,7 +17395,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -17436,7 +17436,7 @@ public java.lang.String getSessionId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); sessionId_ = s; @@ -17452,7 +17452,7 @@ public java.lang.String getSessionId() { getSessionIdBytes() { java.lang.Object ref = sessionId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); sessionId_ = b; @@ -17475,7 +17475,7 @@ public java.lang.String getTerminatedBy() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); terminatedBy_ = s; @@ -17491,7 +17491,7 @@ public java.lang.String getTerminatedBy() { getTerminatedByBytes() { java.lang.Object ref = terminatedBy_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); terminatedBy_ = b; @@ -17514,7 +17514,7 @@ public java.lang.String getCreatedBy() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); createdBy_ = s; @@ -17530,7 +17530,7 @@ public java.lang.String getCreatedBy() { getCreatedByBytes() { java.lang.Object ref = createdBy_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); createdBy_ = b; @@ -18024,7 +18024,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -18147,7 +18147,7 @@ public java.lang.String getSessionId() { getSessionIdBytes() { java.lang.Object ref = sessionId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); sessionId_ = b; @@ -18219,7 +18219,7 @@ public java.lang.String getTerminatedBy() { getTerminatedByBytes() { java.lang.Object ref = terminatedBy_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); terminatedBy_ = b; @@ -18291,7 +18291,7 @@ public java.lang.String getCreatedBy() { getCreatedByBytes() { java.lang.Object ref = createdBy_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); createdBy_ = b; @@ -18446,7 +18446,7 @@ public org.jumpserver.wisp.Common.TokenStatusOrBuilder getTokenStatusOrBuilder() * .message.TokenStatus token_status = 6; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.Common.TokenStatus, org.jumpserver.wisp.Common.TokenStatus.Builder, org.jumpserver.wisp.Common.TokenStatusOrBuilder> + org.jumpserver.wisp.Common.TokenStatus, org.jumpserver.wisp.Common.TokenStatus.Builder, org.jumpserver.wisp.Common.TokenStatusOrBuilder> internalGetTokenStatusFieldBuilder() { if (tokenStatusBuilder_ == null) { tokenStatusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -18510,230 +18510,51 @@ public org.jumpserver.wisp.Common.TerminalTask getDefaultInstanceForType() { } - public interface TokenAuthInfoOrBuilder extends - // @@protoc_insertion_point(interface_extends:message.TokenAuthInfo) + public interface ConnectOptionsOrBuilder extends + // @@protoc_insertion_point(interface_extends:message.ConnectOptions) com.google.protobuf.MessageOrBuilder { /** - * string key_id = 1; - * @return The keyId. - */ - java.lang.String getKeyId(); - /** - * string key_id = 1; - * @return The bytes for keyId. - */ - com.google.protobuf.ByteString - getKeyIdBytes(); - - /** - * string secrete_id = 2; - * @return The secreteId. - */ - java.lang.String getSecreteId(); - /** - * string secrete_id = 2; - * @return The bytes for secreteId. - */ - com.google.protobuf.ByteString - getSecreteIdBytes(); - - /** - * .message.Asset asset = 3; - * @return Whether the asset field is set. - */ - boolean hasAsset(); - /** - * .message.Asset asset = 3; - * @return The asset. - */ - org.jumpserver.wisp.Common.Asset getAsset(); - /** - * .message.Asset asset = 3; - */ - org.jumpserver.wisp.Common.AssetOrBuilder getAssetOrBuilder(); - - /** - * .message.User user = 4; - * @return Whether the user field is set. - */ - boolean hasUser(); - /** - * .message.User user = 4; - * @return The user. - */ - org.jumpserver.wisp.Common.User getUser(); - /** - * .message.User user = 4; - */ - org.jumpserver.wisp.Common.UserOrBuilder getUserOrBuilder(); - - /** - * .message.Account account = 5; - * @return Whether the account field is set. - */ - boolean hasAccount(); - /** - * .message.Account account = 5; - * @return The account. - */ - org.jumpserver.wisp.Common.Account getAccount(); - /** - * .message.Account account = 5; - */ - org.jumpserver.wisp.Common.AccountOrBuilder getAccountOrBuilder(); - - /** - * .message.Permission permission = 6; - * @return Whether the permission field is set. - */ - boolean hasPermission(); - /** - * .message.Permission permission = 6; - * @return The permission. - */ - org.jumpserver.wisp.Common.Permission getPermission(); - /** - * .message.Permission permission = 6; - */ - org.jumpserver.wisp.Common.PermissionOrBuilder getPermissionOrBuilder(); - - /** - * .message.ExpireInfo expire_info = 7; - * @return Whether the expireInfo field is set. - */ - boolean hasExpireInfo(); - /** - * .message.ExpireInfo expire_info = 7; - * @return The expireInfo. - */ - org.jumpserver.wisp.Common.ExpireInfo getExpireInfo(); - /** - * .message.ExpireInfo expire_info = 7; - */ - org.jumpserver.wisp.Common.ExpireInfoOrBuilder getExpireInfoOrBuilder(); - - /** - * repeated .message.CommandACL filter_rules = 8; - */ - java.util.List - getFilterRulesList(); - /** - * repeated .message.CommandACL filter_rules = 8; - */ - org.jumpserver.wisp.Common.CommandACL getFilterRules(int index); - /** - * repeated .message.CommandACL filter_rules = 8; - */ - int getFilterRulesCount(); - /** - * repeated .message.CommandACL filter_rules = 8; - */ - java.util.List - getFilterRulesOrBuilderList(); - /** - * repeated .message.CommandACL filter_rules = 8; - */ - org.jumpserver.wisp.Common.CommandACLOrBuilder getFilterRulesOrBuilder( - int index); - - /** - * repeated .message.Gateway gateways = 9; - */ - java.util.List - getGatewaysList(); - /** - * repeated .message.Gateway gateways = 9; - */ - org.jumpserver.wisp.Common.Gateway getGateways(int index); - /** - * repeated .message.Gateway gateways = 9; - */ - int getGatewaysCount(); - /** - * repeated .message.Gateway gateways = 9; - */ - java.util.List - getGatewaysOrBuilderList(); - /** - * repeated .message.Gateway gateways = 9; - */ - org.jumpserver.wisp.Common.GatewayOrBuilder getGatewaysOrBuilder( - int index); - - /** - * .message.ComponentSetting setting = 10; - * @return Whether the setting field is set. - */ - boolean hasSetting(); - /** - * .message.ComponentSetting setting = 10; - * @return The setting. - */ - org.jumpserver.wisp.Common.ComponentSetting getSetting(); - /** - * .message.ComponentSetting setting = 10; - */ - org.jumpserver.wisp.Common.ComponentSettingOrBuilder getSettingOrBuilder(); - - /** - * .message.Platform platform = 11; - * @return Whether the platform field is set. - */ - boolean hasPlatform(); - /** - * .message.Platform platform = 11; - * @return The platform. - */ - org.jumpserver.wisp.Common.Platform getPlatform(); - /** - * .message.Platform platform = 11; - */ - org.jumpserver.wisp.Common.PlatformOrBuilder getPlatformOrBuilder(); - - /** - * string FaceMonitorToken = 12; - * @return The faceMonitorToken. - */ - java.lang.String getFaceMonitorToken(); - /** - * string FaceMonitorToken = 12; - * @return The bytes for faceMonitorToken. + * map<string, string> settings = 1; */ - com.google.protobuf.ByteString - getFaceMonitorTokenBytes(); - + int getSettingsCount(); /** - * repeated .message.DataMaskingRule data_masking_rules = 13; + * map<string, string> settings = 1; */ - java.util.List - getDataMaskingRulesList(); + boolean containsSettings( + java.lang.String key); /** - * repeated .message.DataMaskingRule data_masking_rules = 13; + * Use {@link #getSettingsMap()} instead. */ - org.jumpserver.wisp.Common.DataMaskingRule getDataMaskingRules(int index); + @java.lang.Deprecated + java.util.Map + getSettings(); /** - * repeated .message.DataMaskingRule data_masking_rules = 13; + * map<string, string> settings = 1; */ - int getDataMaskingRulesCount(); + java.util.Map + getSettingsMap(); /** - * repeated .message.DataMaskingRule data_masking_rules = 13; + * map<string, string> settings = 1; */ - java.util.List - getDataMaskingRulesOrBuilderList(); + /* nullable */ +java.lang.String getSettingsOrDefault( + java.lang.String key, + /* nullable */ +java.lang.String defaultValue); /** - * repeated .message.DataMaskingRule data_masking_rules = 13; + * map<string, string> settings = 1; */ - org.jumpserver.wisp.Common.DataMaskingRuleOrBuilder getDataMaskingRulesOrBuilder( - int index); + java.lang.String getSettingsOrThrow( + java.lang.String key); } /** - * Protobuf type {@code message.TokenAuthInfo} + * Protobuf type {@code message.ConnectOptions} */ - public static final class TokenAuthInfo extends + public static final class ConnectOptions extends com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:message.TokenAuthInfo) - TokenAuthInfoOrBuilder { + // @@protoc_insertion_point(message_implements:message.ConnectOptions) + ConnectOptionsOrBuilder { private static final long serialVersionUID = 0L; static { com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( @@ -18742,39 +18563,908 @@ public static final class TokenAuthInfo extends /* minor= */ 32, /* patch= */ 1, /* suffix= */ "", - TokenAuthInfo.class.getName()); + ConnectOptions.class.getName()); } - // Use TokenAuthInfo.newBuilder() to construct. - private TokenAuthInfo(com.google.protobuf.GeneratedMessage.Builder builder) { + // Use ConnectOptions.newBuilder() to construct. + private ConnectOptions(com.google.protobuf.GeneratedMessage.Builder builder) { super(builder); } - private TokenAuthInfo() { - keyId_ = ""; - secreteId_ = ""; - filterRules_ = java.util.Collections.emptyList(); - gateways_ = java.util.Collections.emptyList(); - faceMonitorToken_ = ""; - dataMaskingRules_ = java.util.Collections.emptyList(); + private ConnectOptions() { } public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return org.jumpserver.wisp.Common.internal_static_message_TokenAuthInfo_descriptor; + return org.jumpserver.wisp.Common.internal_static_message_ConnectOptions_descriptor; } + @SuppressWarnings({"rawtypes"}) + @java.lang.Override + protected com.google.protobuf.MapFieldReflectionAccessor internalGetMapFieldReflection( + int number) { + switch (number) { + case 1: + return internalGetSettings(); + default: + throw new RuntimeException( + "Invalid map field number: " + number); + } + } @java.lang.Override protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return org.jumpserver.wisp.Common.internal_static_message_TokenAuthInfo_fieldAccessorTable + return org.jumpserver.wisp.Common.internal_static_message_ConnectOptions_fieldAccessorTable .ensureFieldAccessorsInitialized( - org.jumpserver.wisp.Common.TokenAuthInfo.class, org.jumpserver.wisp.Common.TokenAuthInfo.Builder.class); + org.jumpserver.wisp.Common.ConnectOptions.class, org.jumpserver.wisp.Common.ConnectOptions.Builder.class); } - private int bitField0_; - public static final int KEY_ID_FIELD_NUMBER = 1; - @SuppressWarnings("serial") - private volatile java.lang.Object keyId_ = ""; - /** + public static final int SETTINGS_FIELD_NUMBER = 1; + private static final class SettingsDefaultEntryHolder { + static final com.google.protobuf.MapEntry< + java.lang.String, java.lang.String> defaultEntry = + com.google.protobuf.MapEntry + .newDefaultInstance( + org.jumpserver.wisp.Common.internal_static_message_ConnectOptions_SettingsEntry_descriptor, + com.google.protobuf.WireFormat.FieldType.STRING, + "", + com.google.protobuf.WireFormat.FieldType.STRING, + ""); + } + @SuppressWarnings("serial") + private com.google.protobuf.MapField< + java.lang.String, java.lang.String> settings_; + private com.google.protobuf.MapField + internalGetSettings() { + if (settings_ == null) { + return com.google.protobuf.MapField.emptyMapField( + SettingsDefaultEntryHolder.defaultEntry); + } + return settings_; + } + public int getSettingsCount() { + return internalGetSettings().getMap().size(); + } + /** + * map<string, string> settings = 1; + */ + @java.lang.Override + public boolean containsSettings( + java.lang.String key) { + if (key == null) { throw new NullPointerException("map key"); } + return internalGetSettings().getMap().containsKey(key); + } + /** + * Use {@link #getSettingsMap()} instead. + */ + @java.lang.Override + @java.lang.Deprecated + public java.util.Map getSettings() { + return getSettingsMap(); + } + /** + * map<string, string> settings = 1; + */ + @java.lang.Override + public java.util.Map getSettingsMap() { + return internalGetSettings().getMap(); + } + /** + * map<string, string> settings = 1; + */ + @java.lang.Override + public /* nullable */ +java.lang.String getSettingsOrDefault( + java.lang.String key, + /* nullable */ +java.lang.String defaultValue) { + if (key == null) { throw new NullPointerException("map key"); } + java.util.Map map = + internalGetSettings().getMap(); + return map.containsKey(key) ? map.get(key) : defaultValue; + } + /** + * map<string, string> settings = 1; + */ + @java.lang.Override + public java.lang.String getSettingsOrThrow( + java.lang.String key) { + if (key == null) { throw new NullPointerException("map key"); } + java.util.Map map = + internalGetSettings().getMap(); + if (!map.containsKey(key)) { + throw new java.lang.IllegalArgumentException(); + } + return map.get(key); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + com.google.protobuf.GeneratedMessage + .serializeStringMapTo( + output, + internalGetSettings(), + SettingsDefaultEntryHolder.defaultEntry, + 1); + getUnknownFields().writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + for (java.util.Map.Entry entry + : internalGetSettings().getMap().entrySet()) { + com.google.protobuf.MapEntry + settings__ = SettingsDefaultEntryHolder.defaultEntry.newBuilderForType() + .setKey(entry.getKey()) + .setValue(entry.getValue()) + .build(); + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(1, settings__); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof org.jumpserver.wisp.Common.ConnectOptions)) { + return super.equals(obj); + } + org.jumpserver.wisp.Common.ConnectOptions other = (org.jumpserver.wisp.Common.ConnectOptions) obj; + + if (!internalGetSettings().equals( + other.internalGetSettings())) return false; + if (!getUnknownFields().equals(other.getUnknownFields())) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (!internalGetSettings().getMap().isEmpty()) { + hash = (37 * hash) + SETTINGS_FIELD_NUMBER; + hash = (53 * hash) + internalGetSettings().hashCode(); + } + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static org.jumpserver.wisp.Common.ConnectOptions parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.Common.ConnectOptions parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.Common.ConnectOptions parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.Common.ConnectOptions parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.Common.ConnectOptions parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.Common.ConnectOptions parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.Common.ConnectOptions parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.jumpserver.wisp.Common.ConnectOptions parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + public static org.jumpserver.wisp.Common.ConnectOptions parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input); + } + + public static org.jumpserver.wisp.Common.ConnectOptions parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static org.jumpserver.wisp.Common.ConnectOptions parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.jumpserver.wisp.Common.ConnectOptions parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(org.jumpserver.wisp.Common.ConnectOptions prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code message.ConnectOptions} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder implements + // @@protoc_insertion_point(builder_implements:message.ConnectOptions) + org.jumpserver.wisp.Common.ConnectOptionsOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.jumpserver.wisp.Common.internal_static_message_ConnectOptions_descriptor; + } + + @SuppressWarnings({"rawtypes"}) + protected com.google.protobuf.MapFieldReflectionAccessor internalGetMapFieldReflection( + int number) { + switch (number) { + case 1: + return internalGetSettings(); + default: + throw new RuntimeException( + "Invalid map field number: " + number); + } + } + @SuppressWarnings({"rawtypes"}) + protected com.google.protobuf.MapFieldReflectionAccessor internalGetMutableMapFieldReflection( + int number) { + switch (number) { + case 1: + return internalGetMutableSettings(); + default: + throw new RuntimeException( + "Invalid map field number: " + number); + } + } + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.jumpserver.wisp.Common.internal_static_message_ConnectOptions_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.jumpserver.wisp.Common.ConnectOptions.class, org.jumpserver.wisp.Common.ConnectOptions.Builder.class); + } + + // Construct using org.jumpserver.wisp.Common.ConnectOptions.newBuilder() + private Builder() { + + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + + } + @java.lang.Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + internalGetMutableSettings().clear(); + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.jumpserver.wisp.Common.internal_static_message_ConnectOptions_descriptor; + } + + @java.lang.Override + public org.jumpserver.wisp.Common.ConnectOptions getDefaultInstanceForType() { + return org.jumpserver.wisp.Common.ConnectOptions.getDefaultInstance(); + } + + @java.lang.Override + public org.jumpserver.wisp.Common.ConnectOptions build() { + org.jumpserver.wisp.Common.ConnectOptions result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public org.jumpserver.wisp.Common.ConnectOptions buildPartial() { + org.jumpserver.wisp.Common.ConnectOptions result = new org.jumpserver.wisp.Common.ConnectOptions(this); + if (bitField0_ != 0) { buildPartial0(result); } + onBuilt(); + return result; + } + + private void buildPartial0(org.jumpserver.wisp.Common.ConnectOptions result) { + int from_bitField0_ = bitField0_; + if (((from_bitField0_ & 0x00000001) != 0)) { + result.settings_ = internalGetSettings(); + result.settings_.makeImmutable(); + } + } + + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.jumpserver.wisp.Common.ConnectOptions) { + return mergeFrom((org.jumpserver.wisp.Common.ConnectOptions)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.jumpserver.wisp.Common.ConnectOptions other) { + if (other == org.jumpserver.wisp.Common.ConnectOptions.getDefaultInstance()) return this; + internalGetMutableSettings().mergeFrom( + other.internalGetSettings()); + bitField0_ |= 0x00000001; + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + com.google.protobuf.MapEntry + settings__ = input.readMessage( + SettingsDefaultEntryHolder.defaultEntry.getParserForType(), extensionRegistry); + internalGetMutableSettings().getMutableMap().put( + settings__.getKey(), settings__.getValue()); + bitField0_ |= 0x00000001; + break; + } // case 10 + default: { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + private int bitField0_; + + private com.google.protobuf.MapField< + java.lang.String, java.lang.String> settings_; + private com.google.protobuf.MapField + internalGetSettings() { + if (settings_ == null) { + return com.google.protobuf.MapField.emptyMapField( + SettingsDefaultEntryHolder.defaultEntry); + } + return settings_; + } + private com.google.protobuf.MapField + internalGetMutableSettings() { + if (settings_ == null) { + settings_ = com.google.protobuf.MapField.newMapField( + SettingsDefaultEntryHolder.defaultEntry); + } + if (!settings_.isMutable()) { + settings_ = settings_.copy(); + } + bitField0_ |= 0x00000001; + onChanged(); + return settings_; + } + public int getSettingsCount() { + return internalGetSettings().getMap().size(); + } + /** + * map<string, string> settings = 1; + */ + @java.lang.Override + public boolean containsSettings( + java.lang.String key) { + if (key == null) { throw new NullPointerException("map key"); } + return internalGetSettings().getMap().containsKey(key); + } + /** + * Use {@link #getSettingsMap()} instead. + */ + @java.lang.Override + @java.lang.Deprecated + public java.util.Map getSettings() { + return getSettingsMap(); + } + /** + * map<string, string> settings = 1; + */ + @java.lang.Override + public java.util.Map getSettingsMap() { + return internalGetSettings().getMap(); + } + /** + * map<string, string> settings = 1; + */ + @java.lang.Override + public /* nullable */ +java.lang.String getSettingsOrDefault( + java.lang.String key, + /* nullable */ +java.lang.String defaultValue) { + if (key == null) { throw new NullPointerException("map key"); } + java.util.Map map = + internalGetSettings().getMap(); + return map.containsKey(key) ? map.get(key) : defaultValue; + } + /** + * map<string, string> settings = 1; + */ + @java.lang.Override + public java.lang.String getSettingsOrThrow( + java.lang.String key) { + if (key == null) { throw new NullPointerException("map key"); } + java.util.Map map = + internalGetSettings().getMap(); + if (!map.containsKey(key)) { + throw new java.lang.IllegalArgumentException(); + } + return map.get(key); + } + public Builder clearSettings() { + bitField0_ = (bitField0_ & ~0x00000001); + internalGetMutableSettings().getMutableMap() + .clear(); + return this; + } + /** + * map<string, string> settings = 1; + */ + public Builder removeSettings( + java.lang.String key) { + if (key == null) { throw new NullPointerException("map key"); } + internalGetMutableSettings().getMutableMap() + .remove(key); + return this; + } + /** + * Use alternate mutation accessors instead. + */ + @java.lang.Deprecated + public java.util.Map + getMutableSettings() { + bitField0_ |= 0x00000001; + return internalGetMutableSettings().getMutableMap(); + } + /** + * map<string, string> settings = 1; + */ + public Builder putSettings( + java.lang.String key, + java.lang.String value) { + if (key == null) { throw new NullPointerException("map key"); } + if (value == null) { throw new NullPointerException("map value"); } + internalGetMutableSettings().getMutableMap() + .put(key, value); + bitField0_ |= 0x00000001; + return this; + } + /** + * map<string, string> settings = 1; + */ + public Builder putAllSettings( + java.util.Map values) { + internalGetMutableSettings().getMutableMap() + .putAll(values); + bitField0_ |= 0x00000001; + return this; + } + + // @@protoc_insertion_point(builder_scope:message.ConnectOptions) + } + + // @@protoc_insertion_point(class_scope:message.ConnectOptions) + private static final org.jumpserver.wisp.Common.ConnectOptions DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new org.jumpserver.wisp.Common.ConnectOptions(); + } + + public static org.jumpserver.wisp.Common.ConnectOptions getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public ConnectOptions parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public org.jumpserver.wisp.Common.ConnectOptions getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface TokenAuthInfoOrBuilder extends + // @@protoc_insertion_point(interface_extends:message.TokenAuthInfo) + com.google.protobuf.MessageOrBuilder { + + /** + * string key_id = 1; + * @return The keyId. + */ + java.lang.String getKeyId(); + /** + * string key_id = 1; + * @return The bytes for keyId. + */ + com.google.protobuf.ByteString + getKeyIdBytes(); + + /** + * string secrete_id = 2; + * @return The secreteId. + */ + java.lang.String getSecreteId(); + /** + * string secrete_id = 2; + * @return The bytes for secreteId. + */ + com.google.protobuf.ByteString + getSecreteIdBytes(); + + /** + * .message.Asset asset = 3; + * @return Whether the asset field is set. + */ + boolean hasAsset(); + /** + * .message.Asset asset = 3; + * @return The asset. + */ + org.jumpserver.wisp.Common.Asset getAsset(); + /** + * .message.Asset asset = 3; + */ + org.jumpserver.wisp.Common.AssetOrBuilder getAssetOrBuilder(); + + /** + * .message.User user = 4; + * @return Whether the user field is set. + */ + boolean hasUser(); + /** + * .message.User user = 4; + * @return The user. + */ + org.jumpserver.wisp.Common.User getUser(); + /** + * .message.User user = 4; + */ + org.jumpserver.wisp.Common.UserOrBuilder getUserOrBuilder(); + + /** + * .message.Account account = 5; + * @return Whether the account field is set. + */ + boolean hasAccount(); + /** + * .message.Account account = 5; + * @return The account. + */ + org.jumpserver.wisp.Common.Account getAccount(); + /** + * .message.Account account = 5; + */ + org.jumpserver.wisp.Common.AccountOrBuilder getAccountOrBuilder(); + + /** + * .message.Permission permission = 6; + * @return Whether the permission field is set. + */ + boolean hasPermission(); + /** + * .message.Permission permission = 6; + * @return The permission. + */ + org.jumpserver.wisp.Common.Permission getPermission(); + /** + * .message.Permission permission = 6; + */ + org.jumpserver.wisp.Common.PermissionOrBuilder getPermissionOrBuilder(); + + /** + * .message.ExpireInfo expire_info = 7; + * @return Whether the expireInfo field is set. + */ + boolean hasExpireInfo(); + /** + * .message.ExpireInfo expire_info = 7; + * @return The expireInfo. + */ + org.jumpserver.wisp.Common.ExpireInfo getExpireInfo(); + /** + * .message.ExpireInfo expire_info = 7; + */ + org.jumpserver.wisp.Common.ExpireInfoOrBuilder getExpireInfoOrBuilder(); + + /** + * repeated .message.CommandACL filter_rules = 8; + */ + java.util.List + getFilterRulesList(); + /** + * repeated .message.CommandACL filter_rules = 8; + */ + org.jumpserver.wisp.Common.CommandACL getFilterRules(int index); + /** + * repeated .message.CommandACL filter_rules = 8; + */ + int getFilterRulesCount(); + /** + * repeated .message.CommandACL filter_rules = 8; + */ + java.util.List + getFilterRulesOrBuilderList(); + /** + * repeated .message.CommandACL filter_rules = 8; + */ + org.jumpserver.wisp.Common.CommandACLOrBuilder getFilterRulesOrBuilder( + int index); + + /** + * repeated .message.Gateway gateways = 9; + */ + java.util.List + getGatewaysList(); + /** + * repeated .message.Gateway gateways = 9; + */ + org.jumpserver.wisp.Common.Gateway getGateways(int index); + /** + * repeated .message.Gateway gateways = 9; + */ + int getGatewaysCount(); + /** + * repeated .message.Gateway gateways = 9; + */ + java.util.List + getGatewaysOrBuilderList(); + /** + * repeated .message.Gateway gateways = 9; + */ + org.jumpserver.wisp.Common.GatewayOrBuilder getGatewaysOrBuilder( + int index); + + /** + * .message.ComponentSetting setting = 10; + * @return Whether the setting field is set. + */ + boolean hasSetting(); + /** + * .message.ComponentSetting setting = 10; + * @return The setting. + */ + org.jumpserver.wisp.Common.ComponentSetting getSetting(); + /** + * .message.ComponentSetting setting = 10; + */ + org.jumpserver.wisp.Common.ComponentSettingOrBuilder getSettingOrBuilder(); + + /** + * .message.Platform platform = 11; + * @return Whether the platform field is set. + */ + boolean hasPlatform(); + /** + * .message.Platform platform = 11; + * @return The platform. + */ + org.jumpserver.wisp.Common.Platform getPlatform(); + /** + * .message.Platform platform = 11; + */ + org.jumpserver.wisp.Common.PlatformOrBuilder getPlatformOrBuilder(); + + /** + * string FaceMonitorToken = 12; + * @return The faceMonitorToken. + */ + java.lang.String getFaceMonitorToken(); + /** + * string FaceMonitorToken = 12; + * @return The bytes for faceMonitorToken. + */ + com.google.protobuf.ByteString + getFaceMonitorTokenBytes(); + + /** + * repeated .message.DataMaskingRule data_masking_rules = 13; + */ + java.util.List + getDataMaskingRulesList(); + /** + * repeated .message.DataMaskingRule data_masking_rules = 13; + */ + org.jumpserver.wisp.Common.DataMaskingRule getDataMaskingRules(int index); + /** + * repeated .message.DataMaskingRule data_masking_rules = 13; + */ + int getDataMaskingRulesCount(); + /** + * repeated .message.DataMaskingRule data_masking_rules = 13; + */ + java.util.List + getDataMaskingRulesOrBuilderList(); + /** + * repeated .message.DataMaskingRule data_masking_rules = 13; + */ + org.jumpserver.wisp.Common.DataMaskingRuleOrBuilder getDataMaskingRulesOrBuilder( + int index); + + /** + * .message.ConnectOptions connect_options = 14; + * @return Whether the connectOptions field is set. + */ + boolean hasConnectOptions(); + /** + * .message.ConnectOptions connect_options = 14; + * @return The connectOptions. + */ + org.jumpserver.wisp.Common.ConnectOptions getConnectOptions(); + /** + * .message.ConnectOptions connect_options = 14; + */ + org.jumpserver.wisp.Common.ConnectOptionsOrBuilder getConnectOptionsOrBuilder(); + } + /** + * Protobuf type {@code message.TokenAuthInfo} + */ + public static final class TokenAuthInfo extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:message.TokenAuthInfo) + TokenAuthInfoOrBuilder { + private static final long serialVersionUID = 0L; + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 32, + /* patch= */ 1, + /* suffix= */ "", + TokenAuthInfo.class.getName()); + } + // Use TokenAuthInfo.newBuilder() to construct. + private TokenAuthInfo(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + } + private TokenAuthInfo() { + keyId_ = ""; + secreteId_ = ""; + filterRules_ = java.util.Collections.emptyList(); + gateways_ = java.util.Collections.emptyList(); + faceMonitorToken_ = ""; + dataMaskingRules_ = java.util.Collections.emptyList(); + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.jumpserver.wisp.Common.internal_static_message_TokenAuthInfo_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.jumpserver.wisp.Common.internal_static_message_TokenAuthInfo_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.jumpserver.wisp.Common.TokenAuthInfo.class, org.jumpserver.wisp.Common.TokenAuthInfo.Builder.class); + } + + private int bitField0_; + public static final int KEY_ID_FIELD_NUMBER = 1; + @SuppressWarnings("serial") + private volatile java.lang.Object keyId_ = ""; + /** * string key_id = 1; * @return The keyId. */ @@ -18784,7 +19474,7 @@ public java.lang.String getKeyId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); keyId_ = s; @@ -18800,7 +19490,7 @@ public java.lang.String getKeyId() { getKeyIdBytes() { java.lang.Object ref = keyId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); keyId_ = b; @@ -18823,7 +19513,7 @@ public java.lang.String getSecreteId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); secreteId_ = s; @@ -18839,7 +19529,7 @@ public java.lang.String getSecreteId() { getSecreteIdBytes() { java.lang.Object ref = secreteId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); secreteId_ = b; @@ -18993,7 +19683,7 @@ public java.util.List getFilterRulesList( * repeated .message.CommandACL filter_rules = 8; */ @java.lang.Override - public java.util.List + public java.util.List getFilterRulesOrBuilderList() { return filterRules_; } @@ -19034,7 +19724,7 @@ public java.util.List getGatewaysList() { * repeated .message.Gateway gateways = 9; */ @java.lang.Override - public java.util.List + public java.util.List getGatewaysOrBuilderList() { return gateways_; } @@ -19126,7 +19816,7 @@ public java.lang.String getFaceMonitorToken() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); faceMonitorToken_ = s; @@ -19142,7 +19832,7 @@ public java.lang.String getFaceMonitorToken() { getFaceMonitorTokenBytes() { java.lang.Object ref = faceMonitorToken_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); faceMonitorToken_ = b; @@ -19166,7 +19856,7 @@ public java.util.List getDataMasking * repeated .message.DataMaskingRule data_masking_rules = 13; */ @java.lang.Override - public java.util.List + public java.util.List getDataMaskingRulesOrBuilderList() { return dataMaskingRules_; } @@ -19193,6 +19883,32 @@ public org.jumpserver.wisp.Common.DataMaskingRuleOrBuilder getDataMaskingRulesOr return dataMaskingRules_.get(index); } + public static final int CONNECT_OPTIONS_FIELD_NUMBER = 14; + private org.jumpserver.wisp.Common.ConnectOptions connectOptions_; + /** + * .message.ConnectOptions connect_options = 14; + * @return Whether the connectOptions field is set. + */ + @java.lang.Override + public boolean hasConnectOptions() { + return ((bitField0_ & 0x00000080) != 0); + } + /** + * .message.ConnectOptions connect_options = 14; + * @return The connectOptions. + */ + @java.lang.Override + public org.jumpserver.wisp.Common.ConnectOptions getConnectOptions() { + return connectOptions_ == null ? org.jumpserver.wisp.Common.ConnectOptions.getDefaultInstance() : connectOptions_; + } + /** + * .message.ConnectOptions connect_options = 14; + */ + @java.lang.Override + public org.jumpserver.wisp.Common.ConnectOptionsOrBuilder getConnectOptionsOrBuilder() { + return connectOptions_ == null ? org.jumpserver.wisp.Common.ConnectOptions.getDefaultInstance() : connectOptions_; + } + private byte memoizedIsInitialized = -1; @java.lang.Override public final boolean isInitialized() { @@ -19246,6 +19962,9 @@ public void writeTo(com.google.protobuf.CodedOutputStream output) for (int i = 0; i < dataMaskingRules_.size(); i++) { output.writeMessage(13, dataMaskingRules_.get(i)); } + if (((bitField0_ & 0x00000080) != 0)) { + output.writeMessage(14, getConnectOptions()); + } getUnknownFields().writeTo(output); } @@ -19304,6 +20023,10 @@ public int getSerializedSize() { size += com.google.protobuf.CodedOutputStream .computeMessageSize(13, dataMaskingRules_.get(i)); } + if (((bitField0_ & 0x00000080) != 0)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(14, getConnectOptions()); + } size += getUnknownFields().getSerializedSize(); memoizedSize = size; return size; @@ -19366,6 +20089,11 @@ public boolean equals(final java.lang.Object obj) { .equals(other.getFaceMonitorToken())) return false; if (!getDataMaskingRulesList() .equals(other.getDataMaskingRulesList())) return false; + if (hasConnectOptions() != other.hasConnectOptions()) return false; + if (hasConnectOptions()) { + if (!getConnectOptions() + .equals(other.getConnectOptions())) return false; + } if (!getUnknownFields().equals(other.getUnknownFields())) return false; return true; } @@ -19423,6 +20151,10 @@ public int hashCode() { hash = (37 * hash) + DATA_MASKING_RULES_FIELD_NUMBER; hash = (53 * hash) + getDataMaskingRulesList().hashCode(); } + if (hasConnectOptions()) { + hash = (37 * hash) + CONNECT_OPTIONS_FIELD_NUMBER; + hash = (53 * hash) + getConnectOptions().hashCode(); + } hash = (29 * hash) + getUnknownFields().hashCode(); memoizedHashCode = hash; return hash; @@ -19563,6 +20295,7 @@ private void maybeForceBuilderInitialization() { internalGetSettingFieldBuilder(); internalGetPlatformFieldBuilder(); internalGetDataMaskingRulesFieldBuilder(); + internalGetConnectOptionsFieldBuilder(); } } @java.lang.Override @@ -19628,6 +20361,11 @@ public Builder clear() { dataMaskingRulesBuilder_.clear(); } bitField0_ = (bitField0_ & ~0x00001000); + connectOptions_ = null; + if (connectOptionsBuilder_ != null) { + connectOptionsBuilder_.dispose(); + connectOptionsBuilder_ = null; + } return this; } @@ -19744,6 +20482,12 @@ private void buildPartial0(org.jumpserver.wisp.Common.TokenAuthInfo result) { if (((from_bitField0_ & 0x00000800) != 0)) { result.faceMonitorToken_ = faceMonitorToken_; } + if (((from_bitField0_ & 0x00002000) != 0)) { + result.connectOptions_ = connectOptionsBuilder_ == null + ? connectOptions_ + : connectOptionsBuilder_.build(); + to_bitField0_ |= 0x00000080; + } result.bitField0_ |= to_bitField0_; } @@ -19802,7 +20546,7 @@ public Builder mergeFrom(org.jumpserver.wisp.Common.TokenAuthInfo other) { filterRulesBuilder_ = null; filterRules_ = other.filterRules_; bitField0_ = (bitField0_ & ~0x00000080); - filterRulesBuilder_ = + filterRulesBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? internalGetFilterRulesFieldBuilder() : null; } else { @@ -19828,7 +20572,7 @@ public Builder mergeFrom(org.jumpserver.wisp.Common.TokenAuthInfo other) { gatewaysBuilder_ = null; gateways_ = other.gateways_; bitField0_ = (bitField0_ & ~0x00000100); - gatewaysBuilder_ = + gatewaysBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? internalGetGatewaysFieldBuilder() : null; } else { @@ -19865,7 +20609,7 @@ public Builder mergeFrom(org.jumpserver.wisp.Common.TokenAuthInfo other) { dataMaskingRulesBuilder_ = null; dataMaskingRules_ = other.dataMaskingRules_; bitField0_ = (bitField0_ & ~0x00001000); - dataMaskingRulesBuilder_ = + dataMaskingRulesBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? internalGetDataMaskingRulesFieldBuilder() : null; } else { @@ -19873,6 +20617,9 @@ public Builder mergeFrom(org.jumpserver.wisp.Common.TokenAuthInfo other) { } } } + if (other.hasConnectOptions()) { + mergeConnectOptions(other.getConnectOptions()); + } this.mergeUnknownFields(other.getUnknownFields()); onChanged(); return this; @@ -20002,6 +20749,13 @@ public Builder mergeFrom( } break; } // case 106 + case 114: { + input.readMessage( + internalGetConnectOptionsFieldBuilder().getBuilder(), + extensionRegistry); + bitField0_ |= 0x00002000; + break; + } // case 114 default: { if (!super.parseUnknownField(input, extensionRegistry, tag)) { done = true; // was an endgroup tag @@ -20044,7 +20798,7 @@ public java.lang.String getKeyId() { getKeyIdBytes() { java.lang.Object ref = keyId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); keyId_ = b; @@ -20116,7 +20870,7 @@ public java.lang.String getSecreteId() { getSecreteIdBytes() { java.lang.Object ref = secreteId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); secreteId_ = b; @@ -20271,7 +21025,7 @@ public org.jumpserver.wisp.Common.AssetOrBuilder getAssetOrBuilder() { * .message.Asset asset = 3; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.Common.Asset, org.jumpserver.wisp.Common.Asset.Builder, org.jumpserver.wisp.Common.AssetOrBuilder> + org.jumpserver.wisp.Common.Asset, org.jumpserver.wisp.Common.Asset.Builder, org.jumpserver.wisp.Common.AssetOrBuilder> internalGetAssetFieldBuilder() { if (assetBuilder_ == null) { assetBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -20392,7 +21146,7 @@ public org.jumpserver.wisp.Common.UserOrBuilder getUserOrBuilder() { * .message.User user = 4; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.Common.User, org.jumpserver.wisp.Common.User.Builder, org.jumpserver.wisp.Common.UserOrBuilder> + org.jumpserver.wisp.Common.User, org.jumpserver.wisp.Common.User.Builder, org.jumpserver.wisp.Common.UserOrBuilder> internalGetUserFieldBuilder() { if (userBuilder_ == null) { userBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -20513,7 +21267,7 @@ public org.jumpserver.wisp.Common.AccountOrBuilder getAccountOrBuilder() { * .message.Account account = 5; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.Common.Account, org.jumpserver.wisp.Common.Account.Builder, org.jumpserver.wisp.Common.AccountOrBuilder> + org.jumpserver.wisp.Common.Account, org.jumpserver.wisp.Common.Account.Builder, org.jumpserver.wisp.Common.AccountOrBuilder> internalGetAccountFieldBuilder() { if (accountBuilder_ == null) { accountBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -20634,7 +21388,7 @@ public org.jumpserver.wisp.Common.PermissionOrBuilder getPermissionOrBuilder() { * .message.Permission permission = 6; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.Common.Permission, org.jumpserver.wisp.Common.Permission.Builder, org.jumpserver.wisp.Common.PermissionOrBuilder> + org.jumpserver.wisp.Common.Permission, org.jumpserver.wisp.Common.Permission.Builder, org.jumpserver.wisp.Common.PermissionOrBuilder> internalGetPermissionFieldBuilder() { if (permissionBuilder_ == null) { permissionBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -20755,7 +21509,7 @@ public org.jumpserver.wisp.Common.ExpireInfoOrBuilder getExpireInfoOrBuilder() { * .message.ExpireInfo expire_info = 7; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.Common.ExpireInfo, org.jumpserver.wisp.Common.ExpireInfo.Builder, org.jumpserver.wisp.Common.ExpireInfoOrBuilder> + org.jumpserver.wisp.Common.ExpireInfo, org.jumpserver.wisp.Common.ExpireInfo.Builder, org.jumpserver.wisp.Common.ExpireInfoOrBuilder> internalGetExpireInfoFieldBuilder() { if (expireInfoBuilder_ == null) { expireInfoBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -20963,7 +21717,7 @@ public org.jumpserver.wisp.Common.CommandACLOrBuilder getFilterRulesOrBuilder( /** * repeated .message.CommandACL filter_rules = 8; */ - public java.util.List + public java.util.List getFilterRulesOrBuilderList() { if (filterRulesBuilder_ != null) { return filterRulesBuilder_.getMessageOrBuilderList(); @@ -20989,12 +21743,12 @@ public org.jumpserver.wisp.Common.CommandACL.Builder addFilterRulesBuilder( /** * repeated .message.CommandACL filter_rules = 8; */ - public java.util.List + public java.util.List getFilterRulesBuilderList() { return internalGetFilterRulesFieldBuilder().getBuilderList(); } private com.google.protobuf.RepeatedFieldBuilder< - org.jumpserver.wisp.Common.CommandACL, org.jumpserver.wisp.Common.CommandACL.Builder, org.jumpserver.wisp.Common.CommandACLOrBuilder> + org.jumpserver.wisp.Common.CommandACL, org.jumpserver.wisp.Common.CommandACL.Builder, org.jumpserver.wisp.Common.CommandACLOrBuilder> internalGetFilterRulesFieldBuilder() { if (filterRulesBuilder_ == null) { filterRulesBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< @@ -21203,7 +21957,7 @@ public org.jumpserver.wisp.Common.GatewayOrBuilder getGatewaysOrBuilder( /** * repeated .message.Gateway gateways = 9; */ - public java.util.List + public java.util.List getGatewaysOrBuilderList() { if (gatewaysBuilder_ != null) { return gatewaysBuilder_.getMessageOrBuilderList(); @@ -21229,12 +21983,12 @@ public org.jumpserver.wisp.Common.Gateway.Builder addGatewaysBuilder( /** * repeated .message.Gateway gateways = 9; */ - public java.util.List + public java.util.List getGatewaysBuilderList() { return internalGetGatewaysFieldBuilder().getBuilderList(); } private com.google.protobuf.RepeatedFieldBuilder< - org.jumpserver.wisp.Common.Gateway, org.jumpserver.wisp.Common.Gateway.Builder, org.jumpserver.wisp.Common.GatewayOrBuilder> + org.jumpserver.wisp.Common.Gateway, org.jumpserver.wisp.Common.Gateway.Builder, org.jumpserver.wisp.Common.GatewayOrBuilder> internalGetGatewaysFieldBuilder() { if (gatewaysBuilder_ == null) { gatewaysBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< @@ -21356,7 +22110,7 @@ public org.jumpserver.wisp.Common.ComponentSettingOrBuilder getSettingOrBuilder( * .message.ComponentSetting setting = 10; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.Common.ComponentSetting, org.jumpserver.wisp.Common.ComponentSetting.Builder, org.jumpserver.wisp.Common.ComponentSettingOrBuilder> + org.jumpserver.wisp.Common.ComponentSetting, org.jumpserver.wisp.Common.ComponentSetting.Builder, org.jumpserver.wisp.Common.ComponentSettingOrBuilder> internalGetSettingFieldBuilder() { if (settingBuilder_ == null) { settingBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -21477,7 +22231,7 @@ public org.jumpserver.wisp.Common.PlatformOrBuilder getPlatformOrBuilder() { * .message.Platform platform = 11; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.Common.Platform, org.jumpserver.wisp.Common.Platform.Builder, org.jumpserver.wisp.Common.PlatformOrBuilder> + org.jumpserver.wisp.Common.Platform, org.jumpserver.wisp.Common.Platform.Builder, org.jumpserver.wisp.Common.PlatformOrBuilder> internalGetPlatformFieldBuilder() { if (platformBuilder_ == null) { platformBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -21515,7 +22269,7 @@ public java.lang.String getFaceMonitorToken() { getFaceMonitorTokenBytes() { java.lang.Object ref = faceMonitorToken_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); faceMonitorToken_ = b; @@ -21757,7 +22511,7 @@ public org.jumpserver.wisp.Common.DataMaskingRuleOrBuilder getDataMaskingRulesOr /** * repeated .message.DataMaskingRule data_masking_rules = 13; */ - public java.util.List + public java.util.List getDataMaskingRulesOrBuilderList() { if (dataMaskingRulesBuilder_ != null) { return dataMaskingRulesBuilder_.getMessageOrBuilderList(); @@ -21783,12 +22537,12 @@ public org.jumpserver.wisp.Common.DataMaskingRule.Builder addDataMaskingRulesBui /** * repeated .message.DataMaskingRule data_masking_rules = 13; */ - public java.util.List + public java.util.List getDataMaskingRulesBuilderList() { return internalGetDataMaskingRulesFieldBuilder().getBuilderList(); } private com.google.protobuf.RepeatedFieldBuilder< - org.jumpserver.wisp.Common.DataMaskingRule, org.jumpserver.wisp.Common.DataMaskingRule.Builder, org.jumpserver.wisp.Common.DataMaskingRuleOrBuilder> + org.jumpserver.wisp.Common.DataMaskingRule, org.jumpserver.wisp.Common.DataMaskingRule.Builder, org.jumpserver.wisp.Common.DataMaskingRuleOrBuilder> internalGetDataMaskingRulesFieldBuilder() { if (dataMaskingRulesBuilder_ == null) { dataMaskingRulesBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< @@ -21802,6 +22556,127 @@ public org.jumpserver.wisp.Common.DataMaskingRule.Builder addDataMaskingRulesBui return dataMaskingRulesBuilder_; } + private org.jumpserver.wisp.Common.ConnectOptions connectOptions_; + private com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.Common.ConnectOptions, org.jumpserver.wisp.Common.ConnectOptions.Builder, org.jumpserver.wisp.Common.ConnectOptionsOrBuilder> connectOptionsBuilder_; + /** + * .message.ConnectOptions connect_options = 14; + * @return Whether the connectOptions field is set. + */ + public boolean hasConnectOptions() { + return ((bitField0_ & 0x00002000) != 0); + } + /** + * .message.ConnectOptions connect_options = 14; + * @return The connectOptions. + */ + public org.jumpserver.wisp.Common.ConnectOptions getConnectOptions() { + if (connectOptionsBuilder_ == null) { + return connectOptions_ == null ? org.jumpserver.wisp.Common.ConnectOptions.getDefaultInstance() : connectOptions_; + } else { + return connectOptionsBuilder_.getMessage(); + } + } + /** + * .message.ConnectOptions connect_options = 14; + */ + public Builder setConnectOptions(org.jumpserver.wisp.Common.ConnectOptions value) { + if (connectOptionsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + connectOptions_ = value; + } else { + connectOptionsBuilder_.setMessage(value); + } + bitField0_ |= 0x00002000; + onChanged(); + return this; + } + /** + * .message.ConnectOptions connect_options = 14; + */ + public Builder setConnectOptions( + org.jumpserver.wisp.Common.ConnectOptions.Builder builderForValue) { + if (connectOptionsBuilder_ == null) { + connectOptions_ = builderForValue.build(); + } else { + connectOptionsBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00002000; + onChanged(); + return this; + } + /** + * .message.ConnectOptions connect_options = 14; + */ + public Builder mergeConnectOptions(org.jumpserver.wisp.Common.ConnectOptions value) { + if (connectOptionsBuilder_ == null) { + if (((bitField0_ & 0x00002000) != 0) && + connectOptions_ != null && + connectOptions_ != org.jumpserver.wisp.Common.ConnectOptions.getDefaultInstance()) { + getConnectOptionsBuilder().mergeFrom(value); + } else { + connectOptions_ = value; + } + } else { + connectOptionsBuilder_.mergeFrom(value); + } + if (connectOptions_ != null) { + bitField0_ |= 0x00002000; + onChanged(); + } + return this; + } + /** + * .message.ConnectOptions connect_options = 14; + */ + public Builder clearConnectOptions() { + bitField0_ = (bitField0_ & ~0x00002000); + connectOptions_ = null; + if (connectOptionsBuilder_ != null) { + connectOptionsBuilder_.dispose(); + connectOptionsBuilder_ = null; + } + onChanged(); + return this; + } + /** + * .message.ConnectOptions connect_options = 14; + */ + public org.jumpserver.wisp.Common.ConnectOptions.Builder getConnectOptionsBuilder() { + bitField0_ |= 0x00002000; + onChanged(); + return internalGetConnectOptionsFieldBuilder().getBuilder(); + } + /** + * .message.ConnectOptions connect_options = 14; + */ + public org.jumpserver.wisp.Common.ConnectOptionsOrBuilder getConnectOptionsOrBuilder() { + if (connectOptionsBuilder_ != null) { + return connectOptionsBuilder_.getMessageOrBuilder(); + } else { + return connectOptions_ == null ? + org.jumpserver.wisp.Common.ConnectOptions.getDefaultInstance() : connectOptions_; + } + } + /** + * .message.ConnectOptions connect_options = 14; + */ + private com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.Common.ConnectOptions, org.jumpserver.wisp.Common.ConnectOptions.Builder, org.jumpserver.wisp.Common.ConnectOptionsOrBuilder> + internalGetConnectOptionsFieldBuilder() { + if (connectOptionsBuilder_ == null) { + connectOptionsBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.Common.ConnectOptions, org.jumpserver.wisp.Common.ConnectOptions.Builder, org.jumpserver.wisp.Common.ConnectOptionsOrBuilder>( + getConnectOptions(), + getParentForChildren(), + isClean()); + connectOptions_ = null; + } + return connectOptionsBuilder_; + } + // @@protoc_insertion_point(builder_scope:message.TokenAuthInfo) } @@ -21914,7 +22789,7 @@ public interface PlatformOrBuilder extends /** * repeated .message.PlatformProtocol protocols = 6; */ - java.util.List + java.util.List getProtocolsList(); /** * repeated .message.PlatformProtocol protocols = 6; @@ -21927,7 +22802,7 @@ public interface PlatformOrBuilder extends /** * repeated .message.PlatformProtocol protocols = 6; */ - java.util.List + java.util.List getProtocolsOrBuilderList(); /** * repeated .message.PlatformProtocol protocols = 6; @@ -22001,7 +22876,7 @@ public java.lang.String getName() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); name_ = s; @@ -22017,7 +22892,7 @@ public java.lang.String getName() { getNameBytes() { java.lang.Object ref = name_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); name_ = b; @@ -22040,7 +22915,7 @@ public java.lang.String getCategory() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); category_ = s; @@ -22056,7 +22931,7 @@ public java.lang.String getCategory() { getCategoryBytes() { java.lang.Object ref = category_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); category_ = b; @@ -22079,7 +22954,7 @@ public java.lang.String getCharset() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); charset_ = s; @@ -22095,7 +22970,7 @@ public java.lang.String getCharset() { getCharsetBytes() { java.lang.Object ref = charset_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); charset_ = b; @@ -22118,7 +22993,7 @@ public java.lang.String getType() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); type_ = s; @@ -22134,7 +23009,7 @@ public java.lang.String getType() { getTypeBytes() { java.lang.Object ref = type_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); type_ = b; @@ -22158,7 +23033,7 @@ public java.util.List getProtocolsL * repeated .message.PlatformProtocol protocols = 6; */ @java.lang.Override - public java.util.List + public java.util.List getProtocolsOrBuilderList() { return protocols_; } @@ -22557,7 +23432,7 @@ public Builder mergeFrom(org.jumpserver.wisp.Common.Platform other) { protocolsBuilder_ = null; protocols_ = other.protocols_; bitField0_ = (bitField0_ & ~0x00000020); - protocolsBuilder_ = + protocolsBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? internalGetProtocolsFieldBuilder() : null; } else { @@ -22703,7 +23578,7 @@ public java.lang.String getName() { getNameBytes() { java.lang.Object ref = name_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); name_ = b; @@ -22775,7 +23650,7 @@ public java.lang.String getCategory() { getCategoryBytes() { java.lang.Object ref = category_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); category_ = b; @@ -22847,7 +23722,7 @@ public java.lang.String getCharset() { getCharsetBytes() { java.lang.Object ref = charset_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); charset_ = b; @@ -22919,7 +23794,7 @@ public java.lang.String getType() { getTypeBytes() { java.lang.Object ref = type_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); type_ = b; @@ -23161,7 +24036,7 @@ public org.jumpserver.wisp.Common.PlatformProtocolOrBuilder getProtocolsOrBuilde /** * repeated .message.PlatformProtocol protocols = 6; */ - public java.util.List + public java.util.List getProtocolsOrBuilderList() { if (protocolsBuilder_ != null) { return protocolsBuilder_.getMessageOrBuilderList(); @@ -23187,12 +24062,12 @@ public org.jumpserver.wisp.Common.PlatformProtocol.Builder addProtocolsBuilder( /** * repeated .message.PlatformProtocol protocols = 6; */ - public java.util.List + public java.util.List getProtocolsBuilderList() { return internalGetProtocolsFieldBuilder().getBuilderList(); } private com.google.protobuf.RepeatedFieldBuilder< - org.jumpserver.wisp.Common.PlatformProtocol, org.jumpserver.wisp.Common.PlatformProtocol.Builder, org.jumpserver.wisp.Common.PlatformProtocolOrBuilder> + org.jumpserver.wisp.Common.PlatformProtocol, org.jumpserver.wisp.Common.PlatformProtocol.Builder, org.jumpserver.wisp.Common.PlatformProtocolOrBuilder> internalGetProtocolsFieldBuilder() { if (protocolsBuilder_ == null) { protocolsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< @@ -23393,7 +24268,7 @@ public java.lang.String getName() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); name_ = s; @@ -23409,7 +24284,7 @@ public java.lang.String getName() { getNameBytes() { java.lang.Object ref = name_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); name_ = b; @@ -23436,7 +24311,7 @@ private static final class SettingsDefaultEntryHolder { java.lang.String, java.lang.String> defaultEntry = com.google.protobuf.MapEntry .newDefaultInstance( - org.jumpserver.wisp.Common.internal_static_message_PlatformProtocol_SettingsEntry_descriptor, + org.jumpserver.wisp.Common.internal_static_message_PlatformProtocol_SettingsEntry_descriptor, com.google.protobuf.WireFormat.FieldType.STRING, "", com.google.protobuf.WireFormat.FieldType.STRING, @@ -23967,7 +24842,7 @@ public java.lang.String getName() { getNameBytes() { java.lang.Object ref = name_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); name_ = b; @@ -24816,7 +25691,7 @@ public java.lang.String getId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); id_ = s; @@ -24832,7 +25707,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -24855,7 +25730,7 @@ public java.lang.String getHost() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); host_ = s; @@ -24871,7 +25746,7 @@ public java.lang.String getHost() { getHostBytes() { java.lang.Object ref = host_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); host_ = b; @@ -25258,7 +26133,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -25330,7 +26205,7 @@ public java.lang.String getHost() { getHostBytes() { java.lang.Object ref = host_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); host_ = b; @@ -25477,52 +26352,52 @@ public interface PublicSettingOrBuilder extends boolean getValidLicense(); /** - * string gpt_base_url = 3; - * @return The gptBaseUrl. + * string chat_ai_base_url = 3; + * @return The chatAiBaseUrl. */ - java.lang.String getGptBaseUrl(); + java.lang.String getChatAiBaseUrl(); /** - * string gpt_base_url = 3; - * @return The bytes for gptBaseUrl. + * string chat_ai_base_url = 3; + * @return The bytes for chatAiBaseUrl. */ com.google.protobuf.ByteString - getGptBaseUrlBytes(); + getChatAiBaseUrlBytes(); /** - * string gpt_api_key = 4; - * @return The gptApiKey. + * string chat_ai_api_key = 4; + * @return The chatAiApiKey. */ - java.lang.String getGptApiKey(); + java.lang.String getChatAiApiKey(); /** - * string gpt_api_key = 4; - * @return The bytes for gptApiKey. + * string chat_ai_api_key = 4; + * @return The bytes for chatAiApiKey. */ com.google.protobuf.ByteString - getGptApiKeyBytes(); + getChatAiApiKeyBytes(); /** - * string gpt_proxy = 5; - * @return The gptProxy. + * string chat_ai_proxy = 5; + * @return The chatAiProxy. */ - java.lang.String getGptProxy(); + java.lang.String getChatAiProxy(); /** - * string gpt_proxy = 5; - * @return The bytes for gptProxy. + * string chat_ai_proxy = 5; + * @return The bytes for chatAiProxy. */ com.google.protobuf.ByteString - getGptProxyBytes(); + getChatAiProxyBytes(); /** - * string gpt_model = 6; - * @return The gptModel. + * string chat_ai_model = 6; + * @return The chatAiModel. */ - java.lang.String getGptModel(); + java.lang.String getChatAiModel(); /** - * string gpt_model = 6; - * @return The bytes for gptModel. + * string chat_ai_model = 6; + * @return The bytes for chatAiModel. */ com.google.protobuf.ByteString - getGptModelBytes(); + getChatAiModelBytes(); /** * string license_content = 7; @@ -25558,10 +26433,10 @@ private PublicSetting(com.google.protobuf.GeneratedMessage.Builder builder) { super(builder); } private PublicSetting() { - gptBaseUrl_ = ""; - gptApiKey_ = ""; - gptProxy_ = ""; - gptModel_ = ""; + chatAiBaseUrl_ = ""; + chatAiApiKey_ = ""; + chatAiProxy_ = ""; + chatAiModel_ = ""; licenseContent_ = ""; } @@ -25600,156 +26475,156 @@ public boolean getValidLicense() { return validLicense_; } - public static final int GPT_BASE_URL_FIELD_NUMBER = 3; + public static final int CHAT_AI_BASE_URL_FIELD_NUMBER = 3; @SuppressWarnings("serial") - private volatile java.lang.Object gptBaseUrl_ = ""; + private volatile java.lang.Object chatAiBaseUrl_ = ""; /** - * string gpt_base_url = 3; - * @return The gptBaseUrl. + * string chat_ai_base_url = 3; + * @return The chatAiBaseUrl. */ @java.lang.Override - public java.lang.String getGptBaseUrl() { - java.lang.Object ref = gptBaseUrl_; + public java.lang.String getChatAiBaseUrl() { + java.lang.Object ref = chatAiBaseUrl_; if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - gptBaseUrl_ = s; + chatAiBaseUrl_ = s; return s; } } /** - * string gpt_base_url = 3; - * @return The bytes for gptBaseUrl. + * string chat_ai_base_url = 3; + * @return The bytes for chatAiBaseUrl. */ @java.lang.Override public com.google.protobuf.ByteString - getGptBaseUrlBytes() { - java.lang.Object ref = gptBaseUrl_; + getChatAiBaseUrlBytes() { + java.lang.Object ref = chatAiBaseUrl_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); - gptBaseUrl_ = b; + chatAiBaseUrl_ = b; return b; } else { return (com.google.protobuf.ByteString) ref; } } - public static final int GPT_API_KEY_FIELD_NUMBER = 4; + public static final int CHAT_AI_API_KEY_FIELD_NUMBER = 4; @SuppressWarnings("serial") - private volatile java.lang.Object gptApiKey_ = ""; + private volatile java.lang.Object chatAiApiKey_ = ""; /** - * string gpt_api_key = 4; - * @return The gptApiKey. + * string chat_ai_api_key = 4; + * @return The chatAiApiKey. */ @java.lang.Override - public java.lang.String getGptApiKey() { - java.lang.Object ref = gptApiKey_; + public java.lang.String getChatAiApiKey() { + java.lang.Object ref = chatAiApiKey_; if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - gptApiKey_ = s; + chatAiApiKey_ = s; return s; } } /** - * string gpt_api_key = 4; - * @return The bytes for gptApiKey. + * string chat_ai_api_key = 4; + * @return The bytes for chatAiApiKey. */ @java.lang.Override public com.google.protobuf.ByteString - getGptApiKeyBytes() { - java.lang.Object ref = gptApiKey_; + getChatAiApiKeyBytes() { + java.lang.Object ref = chatAiApiKey_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); - gptApiKey_ = b; + chatAiApiKey_ = b; return b; } else { return (com.google.protobuf.ByteString) ref; } } - public static final int GPT_PROXY_FIELD_NUMBER = 5; + public static final int CHAT_AI_PROXY_FIELD_NUMBER = 5; @SuppressWarnings("serial") - private volatile java.lang.Object gptProxy_ = ""; + private volatile java.lang.Object chatAiProxy_ = ""; /** - * string gpt_proxy = 5; - * @return The gptProxy. + * string chat_ai_proxy = 5; + * @return The chatAiProxy. */ @java.lang.Override - public java.lang.String getGptProxy() { - java.lang.Object ref = gptProxy_; + public java.lang.String getChatAiProxy() { + java.lang.Object ref = chatAiProxy_; if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - gptProxy_ = s; + chatAiProxy_ = s; return s; } } /** - * string gpt_proxy = 5; - * @return The bytes for gptProxy. + * string chat_ai_proxy = 5; + * @return The bytes for chatAiProxy. */ @java.lang.Override public com.google.protobuf.ByteString - getGptProxyBytes() { - java.lang.Object ref = gptProxy_; + getChatAiProxyBytes() { + java.lang.Object ref = chatAiProxy_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); - gptProxy_ = b; + chatAiProxy_ = b; return b; } else { return (com.google.protobuf.ByteString) ref; } } - public static final int GPT_MODEL_FIELD_NUMBER = 6; + public static final int CHAT_AI_MODEL_FIELD_NUMBER = 6; @SuppressWarnings("serial") - private volatile java.lang.Object gptModel_ = ""; + private volatile java.lang.Object chatAiModel_ = ""; /** - * string gpt_model = 6; - * @return The gptModel. + * string chat_ai_model = 6; + * @return The chatAiModel. */ @java.lang.Override - public java.lang.String getGptModel() { - java.lang.Object ref = gptModel_; + public java.lang.String getChatAiModel() { + java.lang.Object ref = chatAiModel_; if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - gptModel_ = s; + chatAiModel_ = s; return s; } } /** - * string gpt_model = 6; - * @return The bytes for gptModel. + * string chat_ai_model = 6; + * @return The bytes for chatAiModel. */ @java.lang.Override public com.google.protobuf.ByteString - getGptModelBytes() { - java.lang.Object ref = gptModel_; + getChatAiModelBytes() { + java.lang.Object ref = chatAiModel_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); - gptModel_ = b; + chatAiModel_ = b; return b; } else { return (com.google.protobuf.ByteString) ref; @@ -25769,7 +26644,7 @@ public java.lang.String getLicenseContent() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); licenseContent_ = s; @@ -25785,7 +26660,7 @@ public java.lang.String getLicenseContent() { getLicenseContentBytes() { java.lang.Object ref = licenseContent_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); licenseContent_ = b; @@ -25815,17 +26690,17 @@ public void writeTo(com.google.protobuf.CodedOutputStream output) if (validLicense_ != false) { output.writeBool(2, validLicense_); } - if (!com.google.protobuf.GeneratedMessage.isStringEmpty(gptBaseUrl_)) { - com.google.protobuf.GeneratedMessage.writeString(output, 3, gptBaseUrl_); + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(chatAiBaseUrl_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 3, chatAiBaseUrl_); } - if (!com.google.protobuf.GeneratedMessage.isStringEmpty(gptApiKey_)) { - com.google.protobuf.GeneratedMessage.writeString(output, 4, gptApiKey_); + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(chatAiApiKey_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 4, chatAiApiKey_); } - if (!com.google.protobuf.GeneratedMessage.isStringEmpty(gptProxy_)) { - com.google.protobuf.GeneratedMessage.writeString(output, 5, gptProxy_); + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(chatAiProxy_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 5, chatAiProxy_); } - if (!com.google.protobuf.GeneratedMessage.isStringEmpty(gptModel_)) { - com.google.protobuf.GeneratedMessage.writeString(output, 6, gptModel_); + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(chatAiModel_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 6, chatAiModel_); } if (!com.google.protobuf.GeneratedMessage.isStringEmpty(licenseContent_)) { com.google.protobuf.GeneratedMessage.writeString(output, 7, licenseContent_); @@ -25847,17 +26722,17 @@ public int getSerializedSize() { size += com.google.protobuf.CodedOutputStream .computeBoolSize(2, validLicense_); } - if (!com.google.protobuf.GeneratedMessage.isStringEmpty(gptBaseUrl_)) { - size += com.google.protobuf.GeneratedMessage.computeStringSize(3, gptBaseUrl_); + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(chatAiBaseUrl_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(3, chatAiBaseUrl_); } - if (!com.google.protobuf.GeneratedMessage.isStringEmpty(gptApiKey_)) { - size += com.google.protobuf.GeneratedMessage.computeStringSize(4, gptApiKey_); + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(chatAiApiKey_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(4, chatAiApiKey_); } - if (!com.google.protobuf.GeneratedMessage.isStringEmpty(gptProxy_)) { - size += com.google.protobuf.GeneratedMessage.computeStringSize(5, gptProxy_); + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(chatAiProxy_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(5, chatAiProxy_); } - if (!com.google.protobuf.GeneratedMessage.isStringEmpty(gptModel_)) { - size += com.google.protobuf.GeneratedMessage.computeStringSize(6, gptModel_); + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(chatAiModel_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(6, chatAiModel_); } if (!com.google.protobuf.GeneratedMessage.isStringEmpty(licenseContent_)) { size += com.google.protobuf.GeneratedMessage.computeStringSize(7, licenseContent_); @@ -25881,14 +26756,14 @@ public boolean equals(final java.lang.Object obj) { != other.getXpackEnabled()) return false; if (getValidLicense() != other.getValidLicense()) return false; - if (!getGptBaseUrl() - .equals(other.getGptBaseUrl())) return false; - if (!getGptApiKey() - .equals(other.getGptApiKey())) return false; - if (!getGptProxy() - .equals(other.getGptProxy())) return false; - if (!getGptModel() - .equals(other.getGptModel())) return false; + if (!getChatAiBaseUrl() + .equals(other.getChatAiBaseUrl())) return false; + if (!getChatAiApiKey() + .equals(other.getChatAiApiKey())) return false; + if (!getChatAiProxy() + .equals(other.getChatAiProxy())) return false; + if (!getChatAiModel() + .equals(other.getChatAiModel())) return false; if (!getLicenseContent() .equals(other.getLicenseContent())) return false; if (!getUnknownFields().equals(other.getUnknownFields())) return false; @@ -25908,14 +26783,14 @@ public int hashCode() { hash = (37 * hash) + VALID_LICENSE_FIELD_NUMBER; hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( getValidLicense()); - hash = (37 * hash) + GPT_BASE_URL_FIELD_NUMBER; - hash = (53 * hash) + getGptBaseUrl().hashCode(); - hash = (37 * hash) + GPT_API_KEY_FIELD_NUMBER; - hash = (53 * hash) + getGptApiKey().hashCode(); - hash = (37 * hash) + GPT_PROXY_FIELD_NUMBER; - hash = (53 * hash) + getGptProxy().hashCode(); - hash = (37 * hash) + GPT_MODEL_FIELD_NUMBER; - hash = (53 * hash) + getGptModel().hashCode(); + hash = (37 * hash) + CHAT_AI_BASE_URL_FIELD_NUMBER; + hash = (53 * hash) + getChatAiBaseUrl().hashCode(); + hash = (37 * hash) + CHAT_AI_API_KEY_FIELD_NUMBER; + hash = (53 * hash) + getChatAiApiKey().hashCode(); + hash = (37 * hash) + CHAT_AI_PROXY_FIELD_NUMBER; + hash = (53 * hash) + getChatAiProxy().hashCode(); + hash = (37 * hash) + CHAT_AI_MODEL_FIELD_NUMBER; + hash = (53 * hash) + getChatAiModel().hashCode(); hash = (37 * hash) + LICENSE_CONTENT_FIELD_NUMBER; hash = (53 * hash) + getLicenseContent().hashCode(); hash = (29 * hash) + getUnknownFields().hashCode(); @@ -26051,10 +26926,10 @@ public Builder clear() { bitField0_ = 0; xpackEnabled_ = false; validLicense_ = false; - gptBaseUrl_ = ""; - gptApiKey_ = ""; - gptProxy_ = ""; - gptModel_ = ""; + chatAiBaseUrl_ = ""; + chatAiApiKey_ = ""; + chatAiProxy_ = ""; + chatAiModel_ = ""; licenseContent_ = ""; return this; } @@ -26096,16 +26971,16 @@ private void buildPartial0(org.jumpserver.wisp.Common.PublicSetting result) { result.validLicense_ = validLicense_; } if (((from_bitField0_ & 0x00000004) != 0)) { - result.gptBaseUrl_ = gptBaseUrl_; + result.chatAiBaseUrl_ = chatAiBaseUrl_; } if (((from_bitField0_ & 0x00000008) != 0)) { - result.gptApiKey_ = gptApiKey_; + result.chatAiApiKey_ = chatAiApiKey_; } if (((from_bitField0_ & 0x00000010) != 0)) { - result.gptProxy_ = gptProxy_; + result.chatAiProxy_ = chatAiProxy_; } if (((from_bitField0_ & 0x00000020) != 0)) { - result.gptModel_ = gptModel_; + result.chatAiModel_ = chatAiModel_; } if (((from_bitField0_ & 0x00000040) != 0)) { result.licenseContent_ = licenseContent_; @@ -26130,23 +27005,23 @@ public Builder mergeFrom(org.jumpserver.wisp.Common.PublicSetting other) { if (other.getValidLicense() != false) { setValidLicense(other.getValidLicense()); } - if (!other.getGptBaseUrl().isEmpty()) { - gptBaseUrl_ = other.gptBaseUrl_; + if (!other.getChatAiBaseUrl().isEmpty()) { + chatAiBaseUrl_ = other.chatAiBaseUrl_; bitField0_ |= 0x00000004; onChanged(); } - if (!other.getGptApiKey().isEmpty()) { - gptApiKey_ = other.gptApiKey_; + if (!other.getChatAiApiKey().isEmpty()) { + chatAiApiKey_ = other.chatAiApiKey_; bitField0_ |= 0x00000008; onChanged(); } - if (!other.getGptProxy().isEmpty()) { - gptProxy_ = other.gptProxy_; + if (!other.getChatAiProxy().isEmpty()) { + chatAiProxy_ = other.chatAiProxy_; bitField0_ |= 0x00000010; onChanged(); } - if (!other.getGptModel().isEmpty()) { - gptModel_ = other.gptModel_; + if (!other.getChatAiModel().isEmpty()) { + chatAiModel_ = other.chatAiModel_; bitField0_ |= 0x00000020; onChanged(); } @@ -26192,22 +27067,22 @@ public Builder mergeFrom( break; } // case 16 case 26: { - gptBaseUrl_ = input.readStringRequireUtf8(); + chatAiBaseUrl_ = input.readStringRequireUtf8(); bitField0_ |= 0x00000004; break; } // case 26 case 34: { - gptApiKey_ = input.readStringRequireUtf8(); + chatAiApiKey_ = input.readStringRequireUtf8(); bitField0_ |= 0x00000008; break; } // case 34 case 42: { - gptProxy_ = input.readStringRequireUtf8(); + chatAiProxy_ = input.readStringRequireUtf8(); bitField0_ |= 0x00000010; break; } // case 42 case 50: { - gptModel_ = input.readStringRequireUtf8(); + chatAiModel_ = input.readStringRequireUtf8(); bitField0_ |= 0x00000020; break; } // case 50 @@ -26297,289 +27172,289 @@ public Builder clearValidLicense() { return this; } - private java.lang.Object gptBaseUrl_ = ""; + private java.lang.Object chatAiBaseUrl_ = ""; /** - * string gpt_base_url = 3; - * @return The gptBaseUrl. + * string chat_ai_base_url = 3; + * @return The chatAiBaseUrl. */ - public java.lang.String getGptBaseUrl() { - java.lang.Object ref = gptBaseUrl_; + public java.lang.String getChatAiBaseUrl() { + java.lang.Object ref = chatAiBaseUrl_; if (!(ref instanceof java.lang.String)) { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - gptBaseUrl_ = s; + chatAiBaseUrl_ = s; return s; } else { return (java.lang.String) ref; } } /** - * string gpt_base_url = 3; - * @return The bytes for gptBaseUrl. + * string chat_ai_base_url = 3; + * @return The bytes for chatAiBaseUrl. */ public com.google.protobuf.ByteString - getGptBaseUrlBytes() { - java.lang.Object ref = gptBaseUrl_; + getChatAiBaseUrlBytes() { + java.lang.Object ref = chatAiBaseUrl_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); - gptBaseUrl_ = b; + chatAiBaseUrl_ = b; return b; } else { return (com.google.protobuf.ByteString) ref; } } /** - * string gpt_base_url = 3; - * @param value The gptBaseUrl to set. + * string chat_ai_base_url = 3; + * @param value The chatAiBaseUrl to set. * @return This builder for chaining. */ - public Builder setGptBaseUrl( + public Builder setChatAiBaseUrl( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - gptBaseUrl_ = value; + chatAiBaseUrl_ = value; bitField0_ |= 0x00000004; onChanged(); return this; } /** - * string gpt_base_url = 3; + * string chat_ai_base_url = 3; * @return This builder for chaining. */ - public Builder clearGptBaseUrl() { - gptBaseUrl_ = getDefaultInstance().getGptBaseUrl(); + public Builder clearChatAiBaseUrl() { + chatAiBaseUrl_ = getDefaultInstance().getChatAiBaseUrl(); bitField0_ = (bitField0_ & ~0x00000004); onChanged(); return this; } /** - * string gpt_base_url = 3; - * @param value The bytes for gptBaseUrl to set. + * string chat_ai_base_url = 3; + * @param value The bytes for chatAiBaseUrl to set. * @return This builder for chaining. */ - public Builder setGptBaseUrlBytes( + public Builder setChatAiBaseUrlBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } checkByteStringIsUtf8(value); - gptBaseUrl_ = value; + chatAiBaseUrl_ = value; bitField0_ |= 0x00000004; onChanged(); return this; } - private java.lang.Object gptApiKey_ = ""; + private java.lang.Object chatAiApiKey_ = ""; /** - * string gpt_api_key = 4; - * @return The gptApiKey. + * string chat_ai_api_key = 4; + * @return The chatAiApiKey. */ - public java.lang.String getGptApiKey() { - java.lang.Object ref = gptApiKey_; + public java.lang.String getChatAiApiKey() { + java.lang.Object ref = chatAiApiKey_; if (!(ref instanceof java.lang.String)) { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - gptApiKey_ = s; + chatAiApiKey_ = s; return s; } else { return (java.lang.String) ref; } } /** - * string gpt_api_key = 4; - * @return The bytes for gptApiKey. + * string chat_ai_api_key = 4; + * @return The bytes for chatAiApiKey. */ public com.google.protobuf.ByteString - getGptApiKeyBytes() { - java.lang.Object ref = gptApiKey_; + getChatAiApiKeyBytes() { + java.lang.Object ref = chatAiApiKey_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); - gptApiKey_ = b; + chatAiApiKey_ = b; return b; } else { return (com.google.protobuf.ByteString) ref; } } /** - * string gpt_api_key = 4; - * @param value The gptApiKey to set. + * string chat_ai_api_key = 4; + * @param value The chatAiApiKey to set. * @return This builder for chaining. */ - public Builder setGptApiKey( + public Builder setChatAiApiKey( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - gptApiKey_ = value; + chatAiApiKey_ = value; bitField0_ |= 0x00000008; onChanged(); return this; } /** - * string gpt_api_key = 4; + * string chat_ai_api_key = 4; * @return This builder for chaining. */ - public Builder clearGptApiKey() { - gptApiKey_ = getDefaultInstance().getGptApiKey(); + public Builder clearChatAiApiKey() { + chatAiApiKey_ = getDefaultInstance().getChatAiApiKey(); bitField0_ = (bitField0_ & ~0x00000008); onChanged(); return this; } /** - * string gpt_api_key = 4; - * @param value The bytes for gptApiKey to set. + * string chat_ai_api_key = 4; + * @param value The bytes for chatAiApiKey to set. * @return This builder for chaining. */ - public Builder setGptApiKeyBytes( + public Builder setChatAiApiKeyBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } checkByteStringIsUtf8(value); - gptApiKey_ = value; + chatAiApiKey_ = value; bitField0_ |= 0x00000008; onChanged(); return this; } - private java.lang.Object gptProxy_ = ""; + private java.lang.Object chatAiProxy_ = ""; /** - * string gpt_proxy = 5; - * @return The gptProxy. + * string chat_ai_proxy = 5; + * @return The chatAiProxy. */ - public java.lang.String getGptProxy() { - java.lang.Object ref = gptProxy_; + public java.lang.String getChatAiProxy() { + java.lang.Object ref = chatAiProxy_; if (!(ref instanceof java.lang.String)) { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - gptProxy_ = s; + chatAiProxy_ = s; return s; } else { return (java.lang.String) ref; } } /** - * string gpt_proxy = 5; - * @return The bytes for gptProxy. + * string chat_ai_proxy = 5; + * @return The bytes for chatAiProxy. */ public com.google.protobuf.ByteString - getGptProxyBytes() { - java.lang.Object ref = gptProxy_; + getChatAiProxyBytes() { + java.lang.Object ref = chatAiProxy_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); - gptProxy_ = b; + chatAiProxy_ = b; return b; } else { return (com.google.protobuf.ByteString) ref; } } /** - * string gpt_proxy = 5; - * @param value The gptProxy to set. + * string chat_ai_proxy = 5; + * @param value The chatAiProxy to set. * @return This builder for chaining. */ - public Builder setGptProxy( + public Builder setChatAiProxy( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - gptProxy_ = value; + chatAiProxy_ = value; bitField0_ |= 0x00000010; onChanged(); return this; } /** - * string gpt_proxy = 5; + * string chat_ai_proxy = 5; * @return This builder for chaining. */ - public Builder clearGptProxy() { - gptProxy_ = getDefaultInstance().getGptProxy(); + public Builder clearChatAiProxy() { + chatAiProxy_ = getDefaultInstance().getChatAiProxy(); bitField0_ = (bitField0_ & ~0x00000010); onChanged(); return this; } /** - * string gpt_proxy = 5; - * @param value The bytes for gptProxy to set. + * string chat_ai_proxy = 5; + * @param value The bytes for chatAiProxy to set. * @return This builder for chaining. */ - public Builder setGptProxyBytes( + public Builder setChatAiProxyBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } checkByteStringIsUtf8(value); - gptProxy_ = value; + chatAiProxy_ = value; bitField0_ |= 0x00000010; onChanged(); return this; } - private java.lang.Object gptModel_ = ""; + private java.lang.Object chatAiModel_ = ""; /** - * string gpt_model = 6; - * @return The gptModel. + * string chat_ai_model = 6; + * @return The chatAiModel. */ - public java.lang.String getGptModel() { - java.lang.Object ref = gptModel_; + public java.lang.String getChatAiModel() { + java.lang.Object ref = chatAiModel_; if (!(ref instanceof java.lang.String)) { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - gptModel_ = s; + chatAiModel_ = s; return s; } else { return (java.lang.String) ref; } } /** - * string gpt_model = 6; - * @return The bytes for gptModel. + * string chat_ai_model = 6; + * @return The bytes for chatAiModel. */ public com.google.protobuf.ByteString - getGptModelBytes() { - java.lang.Object ref = gptModel_; + getChatAiModelBytes() { + java.lang.Object ref = chatAiModel_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); - gptModel_ = b; + chatAiModel_ = b; return b; } else { return (com.google.protobuf.ByteString) ref; } } /** - * string gpt_model = 6; - * @param value The gptModel to set. + * string chat_ai_model = 6; + * @param value The chatAiModel to set. * @return This builder for chaining. */ - public Builder setGptModel( + public Builder setChatAiModel( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - gptModel_ = value; + chatAiModel_ = value; bitField0_ |= 0x00000020; onChanged(); return this; } /** - * string gpt_model = 6; + * string chat_ai_model = 6; * @return This builder for chaining. */ - public Builder clearGptModel() { - gptModel_ = getDefaultInstance().getGptModel(); + public Builder clearChatAiModel() { + chatAiModel_ = getDefaultInstance().getChatAiModel(); bitField0_ = (bitField0_ & ~0x00000020); onChanged(); return this; } /** - * string gpt_model = 6; - * @param value The bytes for gptModel to set. + * string chat_ai_model = 6; + * @param value The bytes for chatAiModel to set. * @return This builder for chaining. */ - public Builder setGptModelBytes( + public Builder setChatAiModelBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } checkByteStringIsUtf8(value); - gptModel_ = value; + chatAiModel_ = value; bitField0_ |= 0x00000020; onChanged(); return this; @@ -26610,7 +27485,7 @@ public java.lang.String getLicenseContent() { getLicenseContentBytes() { java.lang.Object ref = licenseContent_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); licenseContent_ = b; @@ -26788,7 +27663,7 @@ public java.lang.String getName() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); name_ = s; @@ -26804,7 +27679,7 @@ public java.lang.String getName() { getNameBytes() { java.lang.Object ref = name_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); name_ = b; @@ -26827,7 +27702,7 @@ public java.lang.String getValue() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); value_ = s; @@ -26843,7 +27718,7 @@ public java.lang.String getValue() { getValueBytes() { java.lang.Object ref = value_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); value_ = b; @@ -27196,7 +28071,7 @@ public java.lang.String getName() { getNameBytes() { java.lang.Object ref = name_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); name_ = b; @@ -27268,7 +28143,7 @@ public java.lang.String getValue() { getValueBytes() { java.lang.Object ref = value_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); value_ = b; @@ -27692,7 +28567,7 @@ public java.lang.String getReason() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); reason_ = s; @@ -27708,7 +28583,7 @@ public java.lang.String getReason() { getReasonBytes() { java.lang.Object ref = reason_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); reason_ = b; @@ -27731,7 +28606,7 @@ public java.lang.String getUser() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); user_ = s; @@ -27747,7 +28622,7 @@ public java.lang.String getUser() { getUserBytes() { java.lang.Object ref = user_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); user_ = b; @@ -28173,7 +29048,7 @@ public java.lang.String getReason() { getReasonBytes() { java.lang.Object ref = reason_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); reason_ = b; @@ -28245,7 +29120,7 @@ public java.lang.String getUser() { getUserBytes() { java.lang.Object ref = user_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); user_ = b; @@ -28345,122 +29220,132 @@ public org.jumpserver.wisp.Common.LifecycleLogData getDefaultInstanceForType() { private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_User_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_User_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_Account_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_Account_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_LabelValue_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_LabelValue_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_Asset_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_Asset_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_Asset_Specific_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_Asset_Specific_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_Protocol_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_Protocol_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_Gateway_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_Gateway_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_Permission_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_Permission_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_CommandACL_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_CommandACL_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_DataMaskingRule_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_DataMaskingRule_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_CommandGroup_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_CommandGroup_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_ExpireInfo_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_ExpireInfo_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_Session_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_Session_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_TokenStatus_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_TokenStatus_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_TerminalTask_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_TerminalTask_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_message_ConnectOptions_descriptor; + private static final + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_message_ConnectOptions_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_message_ConnectOptions_SettingsEntry_descriptor; + private static final + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_message_ConnectOptions_SettingsEntry_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_TokenAuthInfo_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_TokenAuthInfo_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_Platform_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_Platform_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_PlatformProtocol_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_PlatformProtocol_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_PlatformProtocol_SettingsEntry_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_PlatformProtocol_SettingsEntry_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_ComponentSetting_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_ComponentSetting_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_Forward_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_Forward_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_PublicSetting_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_PublicSetting_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_Cookie_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_Cookie_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_LifecycleLogData_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_LifecycleLogData_fieldAccessorTable; @@ -28526,51 +29411,57 @@ public org.jumpserver.wisp.Common.LifecycleLogData getDefaultInstanceForType() { "essage.TaskAction\022\022\n\nsession_id\030\003 \001(\t\022\025\n" + "\rterminated_by\030\004 \001(\t\022\022\n\ncreated_by\030\005 \001(\t" + "\022*\n\014token_status\030\006 \001(\0132\024.message.TokenSt" + - "atus\"\325\003\n\rTokenAuthInfo\022\016\n\006key_id\030\001 \001(\t\022\022" + - "\n\nsecrete_id\030\002 \001(\t\022\035\n\005asset\030\003 \001(\0132\016.mess" + - "age.Asset\022\033\n\004user\030\004 \001(\0132\r.message.User\022!" + - "\n\007account\030\005 \001(\0132\020.message.Account\022\'\n\nper" + - "mission\030\006 \001(\0132\023.message.Permission\022(\n\013ex" + - "pire_info\030\007 \001(\0132\023.message.ExpireInfo\022)\n\014" + - "filter_rules\030\010 \003(\0132\023.message.CommandACL\022" + - "\"\n\010gateways\030\t \003(\0132\020.message.Gateway\022*\n\007s" + - "etting\030\n \001(\0132\031.message.ComponentSetting\022" + - "#\n\010platform\030\013 \001(\0132\021.message.Platform\022\030\n\020" + - "FaceMonitorToken\030\014 \001(\t\0224\n\022data_masking_r" + - "ules\030\r \003(\0132\030.message.DataMaskingRule\"\203\001\n" + - "\010Platform\022\n\n\002id\030\001 \001(\005\022\014\n\004name\030\002 \001(\t\022\020\n\010c" + - "ategory\030\003 \001(\t\022\017\n\007charset\030\004 \001(\t\022\014\n\004type\030\005" + - " \001(\t\022,\n\tprotocols\030\006 \003(\0132\031.message.Platfo" + - "rmProtocol\"\246\001\n\020PlatformProtocol\022\n\n\002id\030\001 " + - "\001(\005\022\014\n\004name\030\002 \001(\t\022\014\n\004port\030\003 \001(\005\0229\n\010setti" + - "ngs\030\004 \003(\0132\'.message.PlatformProtocol.Set" + - "tingsEntry\032/\n\rSettingsEntry\022\013\n\003key\030\001 \001(\t" + - "\022\r\n\005value\030\002 \001(\t:\0028\001\"C\n\020ComponentSetting\022" + - "\025\n\rmax_idle_time\030\001 \001(\005\022\030\n\020max_session_ti" + - "me\030\002 \001(\005\"1\n\007Forward\022\n\n\002id\030\001 \001(\t\022\014\n\004Host\030" + - "\002 \001(\t\022\014\n\004port\030\003 \001(\005\"\247\001\n\rPublicSetting\022\025\n" + - "\rxpack_enabled\030\001 \001(\010\022\025\n\rvalid_license\030\002 " + - "\001(\010\022\024\n\014gpt_base_url\030\003 \001(\t\022\023\n\013gpt_api_key" + - "\030\004 \001(\t\022\021\n\tgpt_proxy\030\005 \001(\t\022\021\n\tgpt_model\030\006" + - " \001(\t\022\027\n\017license_content\030\007 \001(\t\"%\n\006Cookie\022" + - "\014\n\004name\030\001 \001(\t\022\r\n\005value\030\002 \001(\t\"\250\003\n\020Lifecyc" + - "leLogData\0223\n\005event\030\001 \001(\0162$.message.Lifec" + - "ycleLogData.event_type\022\016\n\006reason\030\002 \001(\t\022\014" + - "\n\004user\030\003 \001(\t\"\300\002\n\nevent_type\022\027\n\023AssetConn" + - "ectSuccess\020\000\022\030\n\024AssetConnectFinished\020\001\022\023" + - "\n\017CreateShareLink\020\002\022\023\n\017UserJoinSession\020\003" + - "\022\024\n\020UserLeaveSession\020\004\022\024\n\020AdminJoinMonit" + - "or\020\005\022\024\n\020AdminExitMonitor\020\006\022\026\n\022ReplayConv" + - "ertStart\020\007\022\030\n\024ReplayConvertSuccess\020\010\022\030\n\024" + - "ReplayConvertFailure\020\t\022\025\n\021ReplayUploadSt" + - "art\020\n\022\027\n\023ReplayUploadSuccess\020\013\022\027\n\023Replay" + - "UploadFailure\020\014*k\n\nTaskAction\022\017\n\013KillSes" + - "sion\020\000\022\017\n\013LockSession\020\001\022\021\n\rUnlockSession" + - "\020\002\022\024\n\020TokenPermExpired\020\003\022\022\n\016TokenPermVal" + - "id\020\004*f\n\tRiskLevel\022\n\n\006Normal\020\000\022\013\n\007Warning" + - "\020\001\022\n\n\006Reject\020\002\022\020\n\014ReviewReject\020\003\022\020\n\014Revi" + - "ewAccept\020\004\022\020\n\014ReviewCancel\020\005B \n\023org.jump" + - "server.wispZ\t/protobufb\006proto3" + "atus\"z\n\016ConnectOptions\0227\n\010settings\030\001 \003(\013" + + "2%.message.ConnectOptions.SettingsEntry\032" + + "/\n\rSettingsEntry\022\013\n\003key\030\001 \001(\t\022\r\n\005value\030\002" + + " \001(\t:\0028\001\"\207\004\n\rTokenAuthInfo\022\016\n\006key_id\030\001 \001" + + "(\t\022\022\n\nsecrete_id\030\002 \001(\t\022\035\n\005asset\030\003 \001(\0132\016." + + "message.Asset\022\033\n\004user\030\004 \001(\0132\r.message.Us" + + "er\022!\n\007account\030\005 \001(\0132\020.message.Account\022\'\n" + + "\npermission\030\006 \001(\0132\023.message.Permission\022(" + + "\n\013expire_info\030\007 \001(\0132\023.message.ExpireInfo" + + "\022)\n\014filter_rules\030\010 \003(\0132\023.message.Command" + + "ACL\022\"\n\010gateways\030\t \003(\0132\020.message.Gateway\022" + + "*\n\007setting\030\n \001(\0132\031.message.ComponentSett" + + "ing\022#\n\010platform\030\013 \001(\0132\021.message.Platform" + + "\022\030\n\020FaceMonitorToken\030\014 \001(\t\0224\n\022data_maski" + + "ng_rules\030\r \003(\0132\030.message.DataMaskingRule" + + "\0220\n\017connect_options\030\016 \001(\0132\027.message.Conn" + + "ectOptions\"\203\001\n\010Platform\022\n\n\002id\030\001 \001(\005\022\014\n\004n" + + "ame\030\002 \001(\t\022\020\n\010category\030\003 \001(\t\022\017\n\007charset\030\004" + + " \001(\t\022\014\n\004type\030\005 \001(\t\022,\n\tprotocols\030\006 \003(\0132\031." + + "message.PlatformProtocol\"\246\001\n\020PlatformPro" + + "tocol\022\n\n\002id\030\001 \001(\005\022\014\n\004name\030\002 \001(\t\022\014\n\004port\030" + + "\003 \001(\005\0229\n\010settings\030\004 \003(\0132\'.message.Platfo" + + "rmProtocol.SettingsEntry\032/\n\rSettingsEntr" + + "y\022\013\n\003key\030\001 \001(\t\022\r\n\005value\030\002 \001(\t:\0028\001\"Z\n\020Com" + + "ponentSetting\022\025\n\rmax_idle_time\030\001 \001(\005\022\030\n\020" + + "max_session_time\030\002 \001(\005J\004\010\003\020\004R\017chat_ai_en" + + "abled\"1\n\007Forward\022\n\n\002id\030\001 \001(\t\022\014\n\004Host\030\002 \001" + + "(\t\022\014\n\004port\030\003 \001(\005\"\267\001\n\rPublicSetting\022\025\n\rxp" + + "ack_enabled\030\001 \001(\010\022\025\n\rvalid_license\030\002 \001(\010" + + "\022\030\n\020chat_ai_base_url\030\003 \001(\t\022\027\n\017chat_ai_ap" + + "i_key\030\004 \001(\t\022\025\n\rchat_ai_proxy\030\005 \001(\t\022\025\n\rch" + + "at_ai_model\030\006 \001(\t\022\027\n\017license_content\030\007 \001" + + "(\t\"%\n\006Cookie\022\014\n\004name\030\001 \001(\t\022\r\n\005value\030\002 \001(" + + "\t\"\250\003\n\020LifecycleLogData\0223\n\005event\030\001 \001(\0162$." + + "message.LifecycleLogData.event_type\022\016\n\006r" + + "eason\030\002 \001(\t\022\014\n\004user\030\003 \001(\t\"\300\002\n\nevent_type" + + "\022\027\n\023AssetConnectSuccess\020\000\022\030\n\024AssetConnec" + + "tFinished\020\001\022\023\n\017CreateShareLink\020\002\022\023\n\017User" + + "JoinSession\020\003\022\024\n\020UserLeaveSession\020\004\022\024\n\020A" + + "dminJoinMonitor\020\005\022\024\n\020AdminExitMonitor\020\006\022" + + "\026\n\022ReplayConvertStart\020\007\022\030\n\024ReplayConvert" + + "Success\020\010\022\030\n\024ReplayConvertFailure\020\t\022\025\n\021R" + + "eplayUploadStart\020\n\022\027\n\023ReplayUploadSucces" + + "s\020\013\022\027\n\023ReplayUploadFailure\020\014*k\n\nTaskActi" + + "on\022\017\n\013KillSession\020\000\022\017\n\013LockSession\020\001\022\021\n\r" + + "UnlockSession\020\002\022\024\n\020TokenPermExpired\020\003\022\022\n" + + "\016TokenPermValid\020\004*f\n\tRiskLevel\022\n\n\006Normal" + + "\020\000\022\013\n\007Warning\020\001\022\n\n\006Reject\020\002\022\020\n\014ReviewRej" + + "ect\020\003\022\020\n\014ReviewAccept\020\004\022\020\n\014ReviewCancel\020" + + "\005B \n\023org.jumpserver.wispZ\t/protobufb\006pro" + + "to3" }; descriptor = com.google.protobuf.Descriptors.FileDescriptor .internalBuildGeneratedFileFrom(descriptorData, @@ -28666,20 +29557,32 @@ public org.jumpserver.wisp.Common.LifecycleLogData getDefaultInstanceForType() { com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_message_TerminalTask_descriptor, new java.lang.String[] { "Id", "Action", "SessionId", "TerminatedBy", "CreatedBy", "TokenStatus", }); - internal_static_message_TokenAuthInfo_descriptor = + internal_static_message_ConnectOptions_descriptor = getDescriptor().getMessageTypes().get(14); + internal_static_message_ConnectOptions_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_message_ConnectOptions_descriptor, + new java.lang.String[] { "Settings", }); + internal_static_message_ConnectOptions_SettingsEntry_descriptor = + internal_static_message_ConnectOptions_descriptor.getNestedTypes().get(0); + internal_static_message_ConnectOptions_SettingsEntry_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_message_ConnectOptions_SettingsEntry_descriptor, + new java.lang.String[] { "Key", "Value", }); + internal_static_message_TokenAuthInfo_descriptor = + getDescriptor().getMessageTypes().get(15); internal_static_message_TokenAuthInfo_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_message_TokenAuthInfo_descriptor, - new java.lang.String[] { "KeyId", "SecreteId", "Asset", "User", "Account", "Permission", "ExpireInfo", "FilterRules", "Gateways", "Setting", "Platform", "FaceMonitorToken", "DataMaskingRules", }); + new java.lang.String[] { "KeyId", "SecreteId", "Asset", "User", "Account", "Permission", "ExpireInfo", "FilterRules", "Gateways", "Setting", "Platform", "FaceMonitorToken", "DataMaskingRules", "ConnectOptions", }); internal_static_message_Platform_descriptor = - getDescriptor().getMessageTypes().get(15); + getDescriptor().getMessageTypes().get(16); internal_static_message_Platform_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_message_Platform_descriptor, new java.lang.String[] { "Id", "Name", "Category", "Charset", "Type", "Protocols", }); internal_static_message_PlatformProtocol_descriptor = - getDescriptor().getMessageTypes().get(16); + getDescriptor().getMessageTypes().get(17); internal_static_message_PlatformProtocol_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_message_PlatformProtocol_descriptor, @@ -28691,31 +29594,31 @@ public org.jumpserver.wisp.Common.LifecycleLogData getDefaultInstanceForType() { internal_static_message_PlatformProtocol_SettingsEntry_descriptor, new java.lang.String[] { "Key", "Value", }); internal_static_message_ComponentSetting_descriptor = - getDescriptor().getMessageTypes().get(17); + getDescriptor().getMessageTypes().get(18); internal_static_message_ComponentSetting_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_message_ComponentSetting_descriptor, new java.lang.String[] { "MaxIdleTime", "MaxSessionTime", }); internal_static_message_Forward_descriptor = - getDescriptor().getMessageTypes().get(18); + getDescriptor().getMessageTypes().get(19); internal_static_message_Forward_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_message_Forward_descriptor, new java.lang.String[] { "Id", "Host", "Port", }); internal_static_message_PublicSetting_descriptor = - getDescriptor().getMessageTypes().get(19); + getDescriptor().getMessageTypes().get(20); internal_static_message_PublicSetting_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_message_PublicSetting_descriptor, - new java.lang.String[] { "XpackEnabled", "ValidLicense", "GptBaseUrl", "GptApiKey", "GptProxy", "GptModel", "LicenseContent", }); + new java.lang.String[] { "XpackEnabled", "ValidLicense", "ChatAiBaseUrl", "ChatAiApiKey", "ChatAiProxy", "ChatAiModel", "LicenseContent", }); internal_static_message_Cookie_descriptor = - getDescriptor().getMessageTypes().get(20); + getDescriptor().getMessageTypes().get(21); internal_static_message_Cookie_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_message_Cookie_descriptor, new java.lang.String[] { "Name", "Value", }); internal_static_message_LifecycleLogData_descriptor = - getDescriptor().getMessageTypes().get(21); + getDescriptor().getMessageTypes().get(22); internal_static_message_LifecycleLogData_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_message_LifecycleLogData_descriptor, diff --git a/backend/wisp/src/main/java/org/jumpserver/wisp/ServiceGrpc.java b/backend/wisp/src/main/java/org/jumpserver/wisp/ServiceGrpc.java index 21d8cc1a..ed664e5b 100644 --- a/backend/wisp/src/main/java/org/jumpserver/wisp/ServiceGrpc.java +++ b/backend/wisp/src/main/java/org/jumpserver/wisp/ServiceGrpc.java @@ -4,15 +4,12 @@ /** */ -@javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.56.1)", - comments = "Source: service.proto") @io.grpc.stub.annotations.GrpcGenerated public final class ServiceGrpc { private ServiceGrpc() {} - public static final String SERVICE_NAME = "message.Service"; + public static final java.lang.String SERVICE_NAME = "message.Service"; // Static method descriptors that strictly reflect the proto. private static volatile io.grpc.MethodDescriptor factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @java.lang.Override + public ServiceBlockingV2Stub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new ServiceBlockingV2Stub(channel, callOptions); + } + }; + return ServiceBlockingV2Stub.newStub(factory, channel); + } + /** * Creates a new blocking-style stub that supports unary and streaming output calls on the service */ @@ -1245,6 +1257,200 @@ public void callAPI(org.jumpserver.wisp.ServiceOuterClass.HTTPRequest request, /** * A stub to allow clients to do synchronous rpc calls to service Service. */ + public static final class ServiceBlockingV2Stub + extends io.grpc.stub.AbstractBlockingStub { + private ServiceBlockingV2Stub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @java.lang.Override + protected ServiceBlockingV2Stub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new ServiceBlockingV2Stub(channel, callOptions); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.TokenResponse getTokenAuthInfo(org.jumpserver.wisp.ServiceOuterClass.TokenRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getGetTokenAuthInfoMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.StatusResponse renewToken(org.jumpserver.wisp.ServiceOuterClass.TokenRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getRenewTokenMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.SessionCreateResponse createSession(org.jumpserver.wisp.ServiceOuterClass.SessionCreateRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getCreateSessionMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.SessionFinishResp finishSession(org.jumpserver.wisp.ServiceOuterClass.SessionFinishRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getFinishSessionMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.ReplayResponse uploadReplayFile(org.jumpserver.wisp.ServiceOuterClass.ReplayRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getUploadReplayFileMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.CommandResponse uploadCommand(org.jumpserver.wisp.ServiceOuterClass.CommandRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getUploadCommandMethod(), getCallOptions(), request); + } + + /** + */ + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918") + public io.grpc.stub.BlockingClientCall + dispatchTask() { + return io.grpc.stub.ClientCalls.blockingBidiStreamingCall( + getChannel(), getDispatchTaskMethod(), getCallOptions()); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.RemainReplayResponse scanRemainReplays(org.jumpserver.wisp.ServiceOuterClass.RemainReplayRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getScanRemainReplaysMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.CommandConfirmResponse createCommandTicket(org.jumpserver.wisp.ServiceOuterClass.CommandConfirmRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getCreateCommandTicketMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.AssetLoginTicketResponse checkOrCreateAssetLoginTicket(org.jumpserver.wisp.ServiceOuterClass.AssetLoginTicketRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getCheckOrCreateAssetLoginTicketMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.TicketStateResponse checkTicketState(org.jumpserver.wisp.ServiceOuterClass.TicketRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getCheckTicketStateMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.StatusResponse cancelTicket(org.jumpserver.wisp.ServiceOuterClass.TicketRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getCancelTicketMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.ForwardResponse createForward(org.jumpserver.wisp.ServiceOuterClass.ForwardRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getCreateForwardMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.StatusResponse deleteForward(org.jumpserver.wisp.ServiceOuterClass.ForwardDeleteRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getDeleteForwardMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.PublicSettingResponse getPublicSetting(org.jumpserver.wisp.ServiceOuterClass.Empty request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getGetPublicSettingMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.ListenPortResponse getListenPorts(org.jumpserver.wisp.ServiceOuterClass.Empty request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getGetListenPortsMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.PortInfoResponse getPortInfo(org.jumpserver.wisp.ServiceOuterClass.PortInfoRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getGetPortInfoMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.StatusResponse handlePortFailure(org.jumpserver.wisp.ServiceOuterClass.PortFailureRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getHandlePortFailureMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.UserResponse checkUserByCookies(org.jumpserver.wisp.ServiceOuterClass.CookiesRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getCheckUserByCookiesMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.StatusResponse recordSessionLifecycleLog(org.jumpserver.wisp.ServiceOuterClass.SessionLifecycleLogRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getRecordSessionLifecycleLogMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.FaceRecognitionCallbackResponse faceRecognitionCallback(org.jumpserver.wisp.ServiceOuterClass.FaceRecognitionCallbackRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getFaceRecognitionCallbackMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.FaceMonitorCallbackResponse faceMonitorCallback(org.jumpserver.wisp.ServiceOuterClass.FaceMonitorCallbackRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getFaceMonitorCallbackMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.JoinFaceMonitorResponse joinFaceMonitor(org.jumpserver.wisp.ServiceOuterClass.JoinFaceMonitorRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getJoinFaceMonitorMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.AccountDetailResponse getAccountChat(org.jumpserver.wisp.ServiceOuterClass.Empty request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getGetAccountChatMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.HTTPResponse callAPI(org.jumpserver.wisp.ServiceOuterClass.HTTPRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getCallAPIMethod(), getCallOptions(), request); + } + } + + /** + * A stub to allow clients to do limited synchronous rpc calls to service Service. + */ public static final class ServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub { private ServiceBlockingStub( @@ -1997,9 +2203,9 @@ private static final class ServiceFileDescriptorSupplier private static final class ServiceMethodDescriptorSupplier extends ServiceBaseDescriptorSupplier implements io.grpc.protobuf.ProtoMethodDescriptorSupplier { - private final String methodName; + private final java.lang.String methodName; - ServiceMethodDescriptorSupplier(String methodName) { + ServiceMethodDescriptorSupplier(java.lang.String methodName) { this.methodName = methodName; } diff --git a/backend/wisp/src/main/java/org/jumpserver/wisp/ServiceOuterClass.java b/backend/wisp/src/main/java/org/jumpserver/wisp/ServiceOuterClass.java index faebbfaf..5665672c 100644 --- a/backend/wisp/src/main/java/org/jumpserver/wisp/ServiceOuterClass.java +++ b/backend/wisp/src/main/java/org/jumpserver/wisp/ServiceOuterClass.java @@ -106,7 +106,7 @@ public java.lang.String getFaceMonitorToken() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); faceMonitorToken_ = s; @@ -122,7 +122,7 @@ public java.lang.String getFaceMonitorToken() { getFaceMonitorTokenBytes() { java.lang.Object ref = faceMonitorToken_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); faceMonitorToken_ = b; @@ -145,7 +145,7 @@ public java.lang.String getSessionId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); sessionId_ = s; @@ -161,7 +161,7 @@ public java.lang.String getSessionId() { getSessionIdBytes() { java.lang.Object ref = sessionId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); sessionId_ = b; @@ -514,7 +514,7 @@ public java.lang.String getFaceMonitorToken() { getFaceMonitorTokenBytes() { java.lang.Object ref = faceMonitorToken_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); faceMonitorToken_ = b; @@ -586,7 +586,7 @@ public java.lang.String getSessionId() { getSessionIdBytes() { java.lang.Object ref = sessionId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); sessionId_ = b; @@ -1190,7 +1190,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -1386,7 +1386,7 @@ public java.lang.String getToken() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); token_ = s; @@ -1402,7 +1402,7 @@ public java.lang.String getToken() { getTokenBytes() { java.lang.Object ref = token_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); token_ = b; @@ -1436,7 +1436,7 @@ public java.lang.String getErrorMessage() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); errorMessage_ = s; @@ -1452,7 +1452,7 @@ public java.lang.String getErrorMessage() { getErrorMessageBytes() { java.lang.Object ref = errorMessage_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); errorMessage_ = b; @@ -1486,7 +1486,7 @@ public java.lang.String getAction() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); action_ = s; @@ -1502,7 +1502,7 @@ public java.lang.String getAction() { getActionBytes() { java.lang.Object ref = action_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); action_ = b; @@ -2003,7 +2003,7 @@ public java.lang.String getToken() { getTokenBytes() { java.lang.Object ref = token_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); token_ = b; @@ -2107,7 +2107,7 @@ public java.lang.String getErrorMessage() { getErrorMessageBytes() { java.lang.Object ref = errorMessage_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); errorMessage_ = b; @@ -2211,7 +2211,7 @@ public java.lang.String getAction() { getActionBytes() { java.lang.Object ref = action_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); action_ = b; @@ -2926,7 +2926,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -3089,7 +3089,7 @@ public java.lang.String getToken() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); token_ = s; @@ -3105,7 +3105,7 @@ public java.lang.String getToken() { getTokenBytes() { java.lang.Object ref = token_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); token_ = b; @@ -3139,7 +3139,7 @@ public java.lang.String getErrorMessage() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); errorMessage_ = s; @@ -3155,7 +3155,7 @@ public java.lang.String getErrorMessage() { getErrorMessageBytes() { java.lang.Object ref = errorMessage_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); errorMessage_ = b; @@ -3178,7 +3178,7 @@ public java.lang.String getFaceCode() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); faceCode_ = s; @@ -3194,7 +3194,7 @@ public java.lang.String getFaceCode() { getFaceCodeBytes() { java.lang.Object ref = faceCode_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); faceCode_ = b; @@ -3595,7 +3595,7 @@ public java.lang.String getToken() { getTokenBytes() { java.lang.Object ref = token_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); token_ = b; @@ -3699,7 +3699,7 @@ public java.lang.String getErrorMessage() { getErrorMessageBytes() { java.lang.Object ref = errorMessage_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); errorMessage_ = b; @@ -3771,7 +3771,7 @@ public java.lang.String getFaceCode() { getFaceCodeBytes() { java.lang.Object ref = faceCode_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); faceCode_ = b; @@ -4375,7 +4375,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -4532,7 +4532,7 @@ public java.lang.String getUserId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); userId_ = s; @@ -4548,7 +4548,7 @@ public java.lang.String getUserId() { getUserIdBytes() { java.lang.Object ref = userId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); userId_ = b; @@ -4571,7 +4571,7 @@ public java.lang.String getAssetId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); assetId_ = s; @@ -4587,7 +4587,7 @@ public java.lang.String getAssetId() { getAssetIdBytes() { java.lang.Object ref = assetId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); assetId_ = b; @@ -4610,7 +4610,7 @@ public java.lang.String getAccountUsername() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); accountUsername_ = s; @@ -4626,7 +4626,7 @@ public java.lang.String getAccountUsername() { getAccountUsernameBytes() { java.lang.Object ref = accountUsername_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); accountUsername_ = b; @@ -5003,7 +5003,7 @@ public java.lang.String getUserId() { getUserIdBytes() { java.lang.Object ref = userId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); userId_ = b; @@ -5075,7 +5075,7 @@ public java.lang.String getAssetId() { getAssetIdBytes() { java.lang.Object ref = assetId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); assetId_ = b; @@ -5147,7 +5147,7 @@ public java.lang.String getAccountUsername() { getAccountUsernameBytes() { java.lang.Object ref = accountUsername_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); accountUsername_ = b; @@ -5412,7 +5412,7 @@ public java.lang.String getTicketId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); ticketId_ = s; @@ -5428,7 +5428,7 @@ public java.lang.String getTicketId() { getTicketIdBytes() { java.lang.Object ref = ticketId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); ticketId_ = b; @@ -5947,7 +5947,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -6068,7 +6068,7 @@ public org.jumpserver.wisp.ServiceOuterClass.TicketInfoOrBuilder getTicketInfoOr * .message.TicketInfo ticket_info = 2; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.TicketInfo, org.jumpserver.wisp.ServiceOuterClass.TicketInfo.Builder, org.jumpserver.wisp.ServiceOuterClass.TicketInfoOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.TicketInfo, org.jumpserver.wisp.ServiceOuterClass.TicketInfo.Builder, org.jumpserver.wisp.ServiceOuterClass.TicketInfoOrBuilder> internalGetTicketInfoFieldBuilder() { if (ticketInfoBuilder_ == null) { ticketInfoBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -6138,7 +6138,7 @@ public java.lang.String getTicketId() { getTicketIdBytes() { java.lang.Object ref = ticketId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); ticketId_ = b; @@ -6320,7 +6320,7 @@ public java.lang.String getErr() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); err_ = s; @@ -6336,7 +6336,7 @@ public java.lang.String getErr() { getErrBytes() { java.lang.Object ref = err_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); err_ = b; @@ -6721,7 +6721,7 @@ public java.lang.String getErr() { getErrBytes() { java.lang.Object ref = err_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); err_ = b; @@ -6886,7 +6886,7 @@ public java.lang.String getToken() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); token_ = s; @@ -6902,7 +6902,7 @@ public java.lang.String getToken() { getTokenBytes() { java.lang.Object ref = token_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); token_ = b; @@ -7231,7 +7231,7 @@ public java.lang.String getToken() { getTokenBytes() { java.lang.Object ref = token_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); token_ = b; @@ -7914,7 +7914,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -8035,7 +8035,7 @@ public org.jumpserver.wisp.Common.TokenAuthInfoOrBuilder getDataOrBuilder() { * .message.TokenAuthInfo data = 2; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.Common.TokenAuthInfo, org.jumpserver.wisp.Common.TokenAuthInfo.Builder, org.jumpserver.wisp.Common.TokenAuthInfoOrBuilder> + org.jumpserver.wisp.Common.TokenAuthInfo, org.jumpserver.wisp.Common.TokenAuthInfo.Builder, org.jumpserver.wisp.Common.TokenAuthInfoOrBuilder> internalGetDataFieldBuilder() { if (dataBuilder_ == null) { dataBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -8605,7 +8605,7 @@ public org.jumpserver.wisp.Common.SessionOrBuilder getDataOrBuilder() { * .message.Session data = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.Common.Session, org.jumpserver.wisp.Common.Session.Builder, org.jumpserver.wisp.Common.SessionOrBuilder> + org.jumpserver.wisp.Common.Session, org.jumpserver.wisp.Common.Session.Builder, org.jumpserver.wisp.Common.SessionOrBuilder> internalGetDataFieldBuilder() { if (dataBuilder_ == null) { dataBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -9254,7 +9254,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -9375,7 +9375,7 @@ public org.jumpserver.wisp.Common.SessionOrBuilder getDataOrBuilder() { * .message.Session data = 2; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.Common.Session, org.jumpserver.wisp.Common.Session.Builder, org.jumpserver.wisp.Common.SessionOrBuilder> + org.jumpserver.wisp.Common.Session, org.jumpserver.wisp.Common.Session.Builder, org.jumpserver.wisp.Common.SessionOrBuilder> internalGetDataFieldBuilder() { if (dataBuilder_ == null) { dataBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -9531,7 +9531,7 @@ public java.lang.String getId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); id_ = s; @@ -9547,7 +9547,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -9592,7 +9592,7 @@ public java.lang.String getErr() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); err_ = s; @@ -9608,7 +9608,7 @@ public java.lang.String getErr() { getErrBytes() { java.lang.Object ref = err_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); err_ = b; @@ -10009,7 +10009,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -10145,7 +10145,7 @@ public java.lang.String getErr() { getErrBytes() { java.lang.Object ref = err_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); err_ = b; @@ -10749,7 +10749,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -10893,7 +10893,7 @@ public java.lang.String getSessionId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); sessionId_ = s; @@ -10909,7 +10909,7 @@ public java.lang.String getSessionId() { getSessionIdBytes() { java.lang.Object ref = sessionId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); sessionId_ = b; @@ -10932,7 +10932,7 @@ public java.lang.String getReplayFilePath() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); replayFilePath_ = s; @@ -10948,7 +10948,7 @@ public java.lang.String getReplayFilePath() { getReplayFilePathBytes() { java.lang.Object ref = replayFilePath_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); replayFilePath_ = b; @@ -11301,7 +11301,7 @@ public java.lang.String getSessionId() { getSessionIdBytes() { java.lang.Object ref = sessionId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); sessionId_ = b; @@ -11373,7 +11373,7 @@ public java.lang.String getReplayFilePath() { getReplayFilePathBytes() { java.lang.Object ref = replayFilePath_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); replayFilePath_ = b; @@ -11977,7 +11977,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -12230,7 +12230,7 @@ public java.lang.String getSid() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); sid_ = s; @@ -12246,7 +12246,7 @@ public java.lang.String getSid() { getSidBytes() { java.lang.Object ref = sid_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); sid_ = b; @@ -12269,7 +12269,7 @@ public java.lang.String getOrgId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); orgId_ = s; @@ -12285,7 +12285,7 @@ public java.lang.String getOrgId() { getOrgIdBytes() { java.lang.Object ref = orgId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); orgId_ = b; @@ -12308,7 +12308,7 @@ public java.lang.String getInput() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); input_ = s; @@ -12324,7 +12324,7 @@ public java.lang.String getInput() { getInputBytes() { java.lang.Object ref = input_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); input_ = b; @@ -12347,7 +12347,7 @@ public java.lang.String getOutput() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); output_ = s; @@ -12363,7 +12363,7 @@ public java.lang.String getOutput() { getOutputBytes() { java.lang.Object ref = output_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); output_ = b; @@ -12386,7 +12386,7 @@ public java.lang.String getUser() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); user_ = s; @@ -12402,7 +12402,7 @@ public java.lang.String getUser() { getUserBytes() { java.lang.Object ref = user_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); user_ = b; @@ -12425,7 +12425,7 @@ public java.lang.String getAsset() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); asset_ = s; @@ -12441,7 +12441,7 @@ public java.lang.String getAsset() { getAssetBytes() { java.lang.Object ref = asset_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); asset_ = b; @@ -12464,7 +12464,7 @@ public java.lang.String getAccount() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); account_ = s; @@ -12480,7 +12480,7 @@ public java.lang.String getAccount() { getAccountBytes() { java.lang.Object ref = account_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); account_ = b; @@ -12532,7 +12532,7 @@ public java.lang.String getCmdAclId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); cmdAclId_ = s; @@ -12548,7 +12548,7 @@ public java.lang.String getCmdAclId() { getCmdAclIdBytes() { java.lang.Object ref = cmdAclId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); cmdAclId_ = b; @@ -12571,7 +12571,7 @@ public java.lang.String getCmdGroupId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); cmdGroupId_ = s; @@ -12587,7 +12587,7 @@ public java.lang.String getCmdGroupId() { getCmdGroupIdBytes() { java.lang.Object ref = cmdGroupId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); cmdGroupId_ = b; @@ -13154,7 +13154,7 @@ public java.lang.String getSid() { getSidBytes() { java.lang.Object ref = sid_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); sid_ = b; @@ -13226,7 +13226,7 @@ public java.lang.String getOrgId() { getOrgIdBytes() { java.lang.Object ref = orgId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); orgId_ = b; @@ -13298,7 +13298,7 @@ public java.lang.String getInput() { getInputBytes() { java.lang.Object ref = input_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); input_ = b; @@ -13370,7 +13370,7 @@ public java.lang.String getOutput() { getOutputBytes() { java.lang.Object ref = output_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); output_ = b; @@ -13442,7 +13442,7 @@ public java.lang.String getUser() { getUserBytes() { java.lang.Object ref = user_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); user_ = b; @@ -13514,7 +13514,7 @@ public java.lang.String getAsset() { getAssetBytes() { java.lang.Object ref = asset_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); asset_ = b; @@ -13586,7 +13586,7 @@ public java.lang.String getAccount() { getAccountBytes() { java.lang.Object ref = account_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); account_ = b; @@ -13741,7 +13741,7 @@ public java.lang.String getCmdAclId() { getCmdAclIdBytes() { java.lang.Object ref = cmdAclId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); cmdAclId_ = b; @@ -13813,7 +13813,7 @@ public java.lang.String getCmdGroupId() { getCmdGroupIdBytes() { java.lang.Object ref = cmdGroupId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); cmdGroupId_ = b; @@ -14417,7 +14417,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -14548,7 +14548,7 @@ public java.lang.String getTaskId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); taskId_ = s; @@ -14564,7 +14564,7 @@ public java.lang.String getTaskId() { getTaskIdBytes() { java.lang.Object ref = taskId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); taskId_ = b; @@ -14893,7 +14893,7 @@ public java.lang.String getTaskId() { getTaskIdBytes() { java.lang.Object ref = taskId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); taskId_ = b; @@ -15497,7 +15497,7 @@ public org.jumpserver.wisp.Common.TerminalTaskOrBuilder getTaskOrBuilder() { * .message.TerminalTask task = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.Common.TerminalTask, org.jumpserver.wisp.Common.TerminalTask.Builder, org.jumpserver.wisp.Common.TerminalTaskOrBuilder> + org.jumpserver.wisp.Common.TerminalTask, org.jumpserver.wisp.Common.TerminalTask.Builder, org.jumpserver.wisp.Common.TerminalTaskOrBuilder> internalGetTaskFieldBuilder() { if (taskBuilder_ == null) { taskBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -15628,7 +15628,7 @@ public java.lang.String getReplayDir() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); replayDir_ = s; @@ -15644,7 +15644,7 @@ public java.lang.String getReplayDir() { getReplayDirBytes() { java.lang.Object ref = replayDir_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); replayDir_ = b; @@ -15973,7 +15973,7 @@ public java.lang.String getReplayDir() { getReplayDirBytes() { java.lang.Object ref = replayDir_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); replayDir_ = b; @@ -16886,7 +16886,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -17789,7 +17789,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -17946,7 +17946,7 @@ public java.lang.String getSessionId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); sessionId_ = s; @@ -17962,7 +17962,7 @@ public java.lang.String getSessionId() { getSessionIdBytes() { java.lang.Object ref = sessionId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); sessionId_ = b; @@ -17985,7 +17985,7 @@ public java.lang.String getCmdAclId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); cmdAclId_ = s; @@ -18001,7 +18001,7 @@ public java.lang.String getCmdAclId() { getCmdAclIdBytes() { java.lang.Object ref = cmdAclId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); cmdAclId_ = b; @@ -18024,7 +18024,7 @@ public java.lang.String getCmd() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); cmd_ = s; @@ -18040,7 +18040,7 @@ public java.lang.String getCmd() { getCmdBytes() { java.lang.Object ref = cmd_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); cmd_ = b; @@ -18417,7 +18417,7 @@ public java.lang.String getSessionId() { getSessionIdBytes() { java.lang.Object ref = sessionId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); sessionId_ = b; @@ -18489,7 +18489,7 @@ public java.lang.String getCmdAclId() { getCmdAclIdBytes() { java.lang.Object ref = cmdAclId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); cmdAclId_ = b; @@ -18561,7 +18561,7 @@ public java.lang.String getCmd() { getCmdBytes() { java.lang.Object ref = cmd_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); cmd_ = b; @@ -18739,7 +18739,7 @@ public java.lang.String getMethod() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); method_ = s; @@ -18755,7 +18755,7 @@ public java.lang.String getMethod() { getMethodBytes() { java.lang.Object ref = method_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); method_ = b; @@ -18778,7 +18778,7 @@ public java.lang.String getUrl() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); url_ = s; @@ -18794,7 +18794,7 @@ public java.lang.String getUrl() { getUrlBytes() { java.lang.Object ref = url_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); url_ = b; @@ -19147,7 +19147,7 @@ public java.lang.String getMethod() { getMethodBytes() { java.lang.Object ref = method_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); method_ = b; @@ -19219,7 +19219,7 @@ public java.lang.String getUrl() { getUrlBytes() { java.lang.Object ref = url_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); url_ = b; @@ -19902,7 +19902,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -20023,7 +20023,7 @@ public org.jumpserver.wisp.ServiceOuterClass.TicketInfoOrBuilder getInfoOrBuilde * .message.TicketInfo info = 2; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.TicketInfo, org.jumpserver.wisp.ServiceOuterClass.TicketInfo.Builder, org.jumpserver.wisp.ServiceOuterClass.TicketInfoOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.TicketInfo, org.jumpserver.wisp.ServiceOuterClass.TicketInfo.Builder, org.jumpserver.wisp.ServiceOuterClass.TicketInfoOrBuilder> internalGetInfoFieldBuilder() { if (infoBuilder_ == null) { infoBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -20264,7 +20264,7 @@ public java.lang.String getTicketDetailUrl() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); ticketDetailUrl_ = s; @@ -20280,7 +20280,7 @@ public java.lang.String getTicketDetailUrl() { getTicketDetailUrlBytes() { java.lang.Object ref = ticketDetailUrl_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); ticketDetailUrl_ = b; @@ -20851,7 +20851,7 @@ public org.jumpserver.wisp.ServiceOuterClass.ReqInfoOrBuilder getCheckReqOrBuild * .message.ReqInfo check_req = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.ReqInfo, org.jumpserver.wisp.ServiceOuterClass.ReqInfo.Builder, org.jumpserver.wisp.ServiceOuterClass.ReqInfoOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.ReqInfo, org.jumpserver.wisp.ServiceOuterClass.ReqInfo.Builder, org.jumpserver.wisp.ServiceOuterClass.ReqInfoOrBuilder> internalGetCheckReqFieldBuilder() { if (checkReqBuilder_ == null) { checkReqBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -20972,7 +20972,7 @@ public org.jumpserver.wisp.ServiceOuterClass.ReqInfoOrBuilder getCancelReqOrBuil * .message.ReqInfo cancel_req = 2; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.ReqInfo, org.jumpserver.wisp.ServiceOuterClass.ReqInfo.Builder, org.jumpserver.wisp.ServiceOuterClass.ReqInfoOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.ReqInfo, org.jumpserver.wisp.ServiceOuterClass.ReqInfo.Builder, org.jumpserver.wisp.ServiceOuterClass.ReqInfoOrBuilder> internalGetCancelReqFieldBuilder() { if (cancelReqBuilder_ == null) { cancelReqBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -21010,7 +21010,7 @@ public java.lang.String getTicketDetailUrl() { getTicketDetailUrlBytes() { java.lang.Object ref = ticketDetailUrl_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); ticketDetailUrl_ = b; @@ -21725,7 +21725,7 @@ public org.jumpserver.wisp.ServiceOuterClass.ReqInfoOrBuilder getReqOrBuilder() * .message.ReqInfo req = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.ReqInfo, org.jumpserver.wisp.ServiceOuterClass.ReqInfo.Builder, org.jumpserver.wisp.ServiceOuterClass.ReqInfoOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.ReqInfo, org.jumpserver.wisp.ServiceOuterClass.ReqInfo.Builder, org.jumpserver.wisp.ServiceOuterClass.ReqInfoOrBuilder> internalGetReqFieldBuilder() { if (reqBuilder_ == null) { reqBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -22374,7 +22374,7 @@ public org.jumpserver.wisp.ServiceOuterClass.TicketStateOrBuilder getDataOrBuild * .message.TicketState Data = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.TicketState, org.jumpserver.wisp.ServiceOuterClass.TicketState.Builder, org.jumpserver.wisp.ServiceOuterClass.TicketStateOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.TicketState, org.jumpserver.wisp.ServiceOuterClass.TicketState.Builder, org.jumpserver.wisp.ServiceOuterClass.TicketStateOrBuilder> internalGetDataFieldBuilder() { if (dataBuilder_ == null) { dataBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -22495,7 +22495,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 2; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -22791,7 +22791,7 @@ public java.lang.String getProcessor() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); processor_ = s; @@ -22807,7 +22807,7 @@ public java.lang.String getProcessor() { getProcessorBytes() { java.lang.Object ref = processor_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); processor_ = b; @@ -23209,7 +23209,7 @@ public java.lang.String getProcessor() { getProcessorBytes() { java.lang.Object ref = processor_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); processor_ = b; @@ -23332,7 +23332,7 @@ public interface ForwardRequestOrBuilder extends /** * repeated .message.Gateway gateways = 3; */ - java.util.List + java.util.List getGatewaysList(); /** * repeated .message.Gateway gateways = 3; @@ -23345,7 +23345,7 @@ public interface ForwardRequestOrBuilder extends /** * repeated .message.Gateway gateways = 3; */ - java.util.List + java.util.List getGatewaysOrBuilderList(); /** * repeated .message.Gateway gateways = 3; @@ -23405,7 +23405,7 @@ public java.lang.String getHost() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); host_ = s; @@ -23421,7 +23421,7 @@ public java.lang.String getHost() { getHostBytes() { java.lang.Object ref = host_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); host_ = b; @@ -23456,7 +23456,7 @@ public java.util.List getGatewaysList() { * repeated .message.Gateway gateways = 3; */ @java.lang.Override - public java.util.List + public java.util.List getGatewaysOrBuilderList() { return gateways_; } @@ -23798,7 +23798,7 @@ public Builder mergeFrom(org.jumpserver.wisp.ServiceOuterClass.ForwardRequest ot gatewaysBuilder_ = null; gateways_ = other.gateways_; bitField0_ = (bitField0_ & ~0x00000004); - gatewaysBuilder_ = + gatewaysBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? internalGetGatewaysFieldBuilder() : null; } else { @@ -23897,7 +23897,7 @@ public java.lang.String getHost() { getHostBytes() { java.lang.Object ref = host_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); host_ = b; @@ -24171,7 +24171,7 @@ public org.jumpserver.wisp.Common.GatewayOrBuilder getGatewaysOrBuilder( /** * repeated .message.Gateway gateways = 3; */ - public java.util.List + public java.util.List getGatewaysOrBuilderList() { if (gatewaysBuilder_ != null) { return gatewaysBuilder_.getMessageOrBuilderList(); @@ -24197,12 +24197,12 @@ public org.jumpserver.wisp.Common.Gateway.Builder addGatewaysBuilder( /** * repeated .message.Gateway gateways = 3; */ - public java.util.List + public java.util.List getGatewaysBuilderList() { return internalGetGatewaysFieldBuilder().getBuilderList(); } private com.google.protobuf.RepeatedFieldBuilder< - org.jumpserver.wisp.Common.Gateway, org.jumpserver.wisp.Common.Gateway.Builder, org.jumpserver.wisp.Common.GatewayOrBuilder> + org.jumpserver.wisp.Common.Gateway, org.jumpserver.wisp.Common.Gateway.Builder, org.jumpserver.wisp.Common.GatewayOrBuilder> internalGetGatewaysFieldBuilder() { if (gatewaysBuilder_ == null) { gatewaysBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< @@ -24334,7 +24334,7 @@ public java.lang.String getId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); id_ = s; @@ -24350,7 +24350,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -24679,7 +24679,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -24905,7 +24905,7 @@ public java.lang.String getId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); id_ = s; @@ -24921,7 +24921,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -24944,7 +24944,7 @@ public java.lang.String getHost() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); host_ = s; @@ -24960,7 +24960,7 @@ public java.lang.String getHost() { getHostBytes() { java.lang.Object ref = host_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); host_ = b; @@ -25475,7 +25475,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -25513,7 +25513,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -25585,7 +25585,7 @@ public java.lang.String getHost() { getHostBytes() { java.lang.Object ref = host_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); host_ = b; @@ -26300,7 +26300,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -26421,7 +26421,7 @@ public org.jumpserver.wisp.Common.PublicSettingOrBuilder getDataOrBuilder() { * .message.PublicSetting data = 2; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.Common.PublicSetting, org.jumpserver.wisp.Common.PublicSetting.Builder, org.jumpserver.wisp.Common.PublicSettingOrBuilder> + org.jumpserver.wisp.Common.PublicSetting, org.jumpserver.wisp.Common.PublicSetting.Builder, org.jumpserver.wisp.Common.PublicSettingOrBuilder> internalGetDataFieldBuilder() { if (dataBuilder_ == null) { dataBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -27454,7 +27454,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -28621,7 +28621,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -28742,7 +28742,7 @@ public org.jumpserver.wisp.ServiceOuterClass.PortInfoOrBuilder getDataOrBuilder( * .message.PortInfo data = 2; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.PortInfo, org.jumpserver.wisp.ServiceOuterClass.PortInfo.Builder, org.jumpserver.wisp.ServiceOuterClass.PortInfoOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.PortInfo, org.jumpserver.wisp.ServiceOuterClass.PortInfo.Builder, org.jumpserver.wisp.ServiceOuterClass.PortInfoOrBuilder> internalGetDataFieldBuilder() { if (dataBuilder_ == null) { dataBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -28828,7 +28828,7 @@ public interface PortInfoOrBuilder extends /** * repeated .message.Gateway gateways = 2; */ - java.util.List + java.util.List getGatewaysList(); /** * repeated .message.Gateway gateways = 2; @@ -28841,7 +28841,7 @@ public interface PortInfoOrBuilder extends /** * repeated .message.Gateway gateways = 2; */ - java.util.List + java.util.List getGatewaysOrBuilderList(); /** * repeated .message.Gateway gateways = 2; @@ -28928,7 +28928,7 @@ public java.util.List getGatewaysList() { * repeated .message.Gateway gateways = 2; */ @java.lang.Override - public java.util.List + public java.util.List getGatewaysOrBuilderList() { return gateways_; } @@ -29272,7 +29272,7 @@ public Builder mergeFrom(org.jumpserver.wisp.ServiceOuterClass.PortInfo other) { gatewaysBuilder_ = null; gateways_ = other.gateways_; bitField0_ = (bitField0_ & ~0x00000002); - gatewaysBuilder_ = + gatewaysBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? internalGetGatewaysFieldBuilder() : null; } else { @@ -29451,7 +29451,7 @@ public org.jumpserver.wisp.Common.AssetOrBuilder getAssetOrBuilder() { * .message.Asset asset = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.Common.Asset, org.jumpserver.wisp.Common.Asset.Builder, org.jumpserver.wisp.Common.AssetOrBuilder> + org.jumpserver.wisp.Common.Asset, org.jumpserver.wisp.Common.Asset.Builder, org.jumpserver.wisp.Common.AssetOrBuilder> internalGetAssetFieldBuilder() { if (assetBuilder_ == null) { assetBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -29659,7 +29659,7 @@ public org.jumpserver.wisp.Common.GatewayOrBuilder getGatewaysOrBuilder( /** * repeated .message.Gateway gateways = 2; */ - public java.util.List + public java.util.List getGatewaysOrBuilderList() { if (gatewaysBuilder_ != null) { return gatewaysBuilder_.getMessageOrBuilderList(); @@ -29685,12 +29685,12 @@ public org.jumpserver.wisp.Common.Gateway.Builder addGatewaysBuilder( /** * repeated .message.Gateway gateways = 2; */ - public java.util.List + public java.util.List getGatewaysBuilderList() { return internalGetGatewaysFieldBuilder().getBuilderList(); } private com.google.protobuf.RepeatedFieldBuilder< - org.jumpserver.wisp.Common.Gateway, org.jumpserver.wisp.Common.Gateway.Builder, org.jumpserver.wisp.Common.GatewayOrBuilder> + org.jumpserver.wisp.Common.Gateway, org.jumpserver.wisp.Common.Gateway.Builder, org.jumpserver.wisp.Common.GatewayOrBuilder> internalGetGatewaysFieldBuilder() { if (gatewaysBuilder_ == null) { gatewaysBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< @@ -29839,7 +29839,7 @@ public java.lang.String getReason() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); reason_ = s; @@ -29855,7 +29855,7 @@ public java.lang.String getReason() { getReasonBytes() { java.lang.Object ref = reason_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); reason_ = b; @@ -30239,7 +30239,7 @@ public java.lang.String getReason() { getReasonBytes() { java.lang.Object ref = reason_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); reason_ = b; @@ -30344,7 +30344,7 @@ public interface PortFailureRequestOrBuilder extends /** * repeated .message.PortFailure data = 1; */ - java.util.List + java.util.List getDataList(); /** * repeated .message.PortFailure data = 1; @@ -30357,7 +30357,7 @@ public interface PortFailureRequestOrBuilder extends /** * repeated .message.PortFailure data = 1; */ - java.util.List + java.util.List getDataOrBuilderList(); /** * repeated .message.PortFailure data = 1; @@ -30417,7 +30417,7 @@ public java.util.List getData * repeated .message.PortFailure data = 1; */ @java.lang.Override - public java.util.List + public java.util.List getDataOrBuilderList() { return data_; } @@ -30722,7 +30722,7 @@ public Builder mergeFrom(org.jumpserver.wisp.ServiceOuterClass.PortFailureReques dataBuilder_ = null; data_ = other.data_; bitField0_ = (bitField0_ & ~0x00000001); - dataBuilder_ = + dataBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? internalGetDataFieldBuilder() : null; } else { @@ -30981,7 +30981,7 @@ public org.jumpserver.wisp.ServiceOuterClass.PortFailureOrBuilder getDataOrBuild /** * repeated .message.PortFailure data = 1; */ - public java.util.List + public java.util.List getDataOrBuilderList() { if (dataBuilder_ != null) { return dataBuilder_.getMessageOrBuilderList(); @@ -31007,12 +31007,12 @@ public org.jumpserver.wisp.ServiceOuterClass.PortFailure.Builder addDataBuilder( /** * repeated .message.PortFailure data = 1; */ - public java.util.List + public java.util.List getDataBuilderList() { return internalGetDataFieldBuilder().getBuilderList(); } private com.google.protobuf.RepeatedFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.PortFailure, org.jumpserver.wisp.ServiceOuterClass.PortFailure.Builder, org.jumpserver.wisp.ServiceOuterClass.PortFailureOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.PortFailure, org.jumpserver.wisp.ServiceOuterClass.PortFailure.Builder, org.jumpserver.wisp.ServiceOuterClass.PortFailureOrBuilder> internalGetDataFieldBuilder() { if (dataBuilder_ == null) { dataBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< @@ -31084,7 +31084,7 @@ public interface CookiesRequestOrBuilder extends /** * repeated .message.Cookie cookies = 1; */ - java.util.List + java.util.List getCookiesList(); /** * repeated .message.Cookie cookies = 1; @@ -31097,7 +31097,7 @@ public interface CookiesRequestOrBuilder extends /** * repeated .message.Cookie cookies = 1; */ - java.util.List + java.util.List getCookiesOrBuilderList(); /** * repeated .message.Cookie cookies = 1; @@ -31157,7 +31157,7 @@ public java.util.List getCookiesList() { * repeated .message.Cookie cookies = 1; */ @java.lang.Override - public java.util.List + public java.util.List getCookiesOrBuilderList() { return cookies_; } @@ -31462,7 +31462,7 @@ public Builder mergeFrom(org.jumpserver.wisp.ServiceOuterClass.CookiesRequest ot cookiesBuilder_ = null; cookies_ = other.cookies_; bitField0_ = (bitField0_ & ~0x00000001); - cookiesBuilder_ = + cookiesBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? internalGetCookiesFieldBuilder() : null; } else { @@ -31721,7 +31721,7 @@ public org.jumpserver.wisp.Common.CookieOrBuilder getCookiesOrBuilder( /** * repeated .message.Cookie cookies = 1; */ - public java.util.List + public java.util.List getCookiesOrBuilderList() { if (cookiesBuilder_ != null) { return cookiesBuilder_.getMessageOrBuilderList(); @@ -31747,12 +31747,12 @@ public org.jumpserver.wisp.Common.Cookie.Builder addCookiesBuilder( /** * repeated .message.Cookie cookies = 1; */ - public java.util.List + public java.util.List getCookiesBuilderList() { return internalGetCookiesFieldBuilder().getBuilderList(); } private com.google.protobuf.RepeatedFieldBuilder< - org.jumpserver.wisp.Common.Cookie, org.jumpserver.wisp.Common.Cookie.Builder, org.jumpserver.wisp.Common.CookieOrBuilder> + org.jumpserver.wisp.Common.Cookie, org.jumpserver.wisp.Common.Cookie.Builder, org.jumpserver.wisp.Common.CookieOrBuilder> internalGetCookiesFieldBuilder() { if (cookiesBuilder_ == null) { cookiesBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< @@ -32402,7 +32402,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -32523,7 +32523,7 @@ public org.jumpserver.wisp.Common.UserOrBuilder getDataOrBuilder() { * .message.User data = 2; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.Common.User, org.jumpserver.wisp.Common.User.Builder, org.jumpserver.wisp.Common.UserOrBuilder> + org.jumpserver.wisp.Common.User, org.jumpserver.wisp.Common.User.Builder, org.jumpserver.wisp.Common.UserOrBuilder> internalGetDataFieldBuilder() { if (dataBuilder_ == null) { dataBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -32908,7 +32908,7 @@ public java.lang.String getSessionId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); sessionId_ = s; @@ -32924,7 +32924,7 @@ public java.lang.String getSessionId() { getSessionIdBytes() { java.lang.Object ref = sessionId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); sessionId_ = b; @@ -32965,7 +32965,7 @@ public java.lang.String getReason() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); reason_ = s; @@ -32981,7 +32981,7 @@ public java.lang.String getReason() { getReasonBytes() { java.lang.Object ref = reason_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); reason_ = b; @@ -33004,7 +33004,7 @@ public java.lang.String getUser() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); user_ = s; @@ -33020,7 +33020,7 @@ public java.lang.String getUser() { getUserBytes() { java.lang.Object ref = user_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); user_ = b; @@ -33419,7 +33419,7 @@ public java.lang.String getSessionId() { getSessionIdBytes() { java.lang.Object ref = sessionId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); sessionId_ = b; @@ -33542,7 +33542,7 @@ public java.lang.String getReason() { getReasonBytes() { java.lang.Object ref = reason_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); reason_ = b; @@ -33614,7 +33614,7 @@ public java.lang.String getUser() { getUserBytes() { java.lang.Object ref = user_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); user_ = b; @@ -34297,7 +34297,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -34418,7 +34418,7 @@ public com.google.protobuf.StructOrBuilder getPayloadOrBuilder() { * .google.protobuf.Struct payload = 2; */ private com.google.protobuf.SingleFieldBuilder< - com.google.protobuf.Struct, com.google.protobuf.Struct.Builder, com.google.protobuf.StructOrBuilder> + com.google.protobuf.Struct, com.google.protobuf.Struct.Builder, com.google.protobuf.StructOrBuilder> internalGetPayloadFieldBuilder() { if (payloadBuilder_ == null) { payloadBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -34651,7 +34651,7 @@ public java.lang.String getMethod() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); method_ = s; @@ -34667,7 +34667,7 @@ public java.lang.String getMethod() { getMethodBytes() { java.lang.Object ref = method_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); method_ = b; @@ -34690,7 +34690,7 @@ public java.lang.String getPath() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); path_ = s; @@ -34706,7 +34706,7 @@ public java.lang.String getPath() { getPathBytes() { java.lang.Object ref = path_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); path_ = b; @@ -34722,7 +34722,7 @@ private static final class QueryDefaultEntryHolder { java.lang.String, java.lang.String> defaultEntry = com.google.protobuf.MapEntry .newDefaultInstance( - org.jumpserver.wisp.ServiceOuterClass.internal_static_message_HTTPRequest_QueryEntry_descriptor, + org.jumpserver.wisp.ServiceOuterClass.internal_static_message_HTTPRequest_QueryEntry_descriptor, com.google.protobuf.WireFormat.FieldType.STRING, "", com.google.protobuf.WireFormat.FieldType.STRING, @@ -34801,7 +34801,7 @@ private static final class HeaderDefaultEntryHolder { java.lang.String, java.lang.String> defaultEntry = com.google.protobuf.MapEntry .newDefaultInstance( - org.jumpserver.wisp.ServiceOuterClass.internal_static_message_HTTPRequest_HeaderEntry_descriptor, + org.jumpserver.wisp.ServiceOuterClass.internal_static_message_HTTPRequest_HeaderEntry_descriptor, com.google.protobuf.WireFormat.FieldType.STRING, "", com.google.protobuf.WireFormat.FieldType.STRING, @@ -35355,7 +35355,7 @@ public java.lang.String getMethod() { getMethodBytes() { java.lang.Object ref = method_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); method_ = b; @@ -35427,7 +35427,7 @@ public java.lang.String getPath() { getPathBytes() { java.lang.Object ref = path_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); path_ = b; @@ -36358,7 +36358,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -36456,252 +36456,252 @@ public org.jumpserver.wisp.ServiceOuterClass.HTTPResponse getDefaultInstanceForT private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_JoinFaceMonitorRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_JoinFaceMonitorRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_JoinFaceMonitorResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_JoinFaceMonitorResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_FaceMonitorCallbackRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_FaceMonitorCallbackRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_FaceMonitorCallbackResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_FaceMonitorCallbackResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_FaceRecognitionCallbackRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_FaceRecognitionCallbackRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_FaceRecognitionCallbackResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_FaceRecognitionCallbackResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_AssetLoginTicketRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_AssetLoginTicketRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_AssetLoginTicketResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_AssetLoginTicketResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_Status_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_Status_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_TokenRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_TokenRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_TokenResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_TokenResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_SessionCreateRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_SessionCreateRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_SessionCreateResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_SessionCreateResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_SessionFinishRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_SessionFinishRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_SessionFinishResp_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_SessionFinishResp_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_ReplayRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_ReplayRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_ReplayResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_ReplayResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_CommandRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_CommandRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_CommandResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_CommandResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_FinishedTaskRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_FinishedTaskRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_TaskResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_TaskResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_RemainReplayRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_RemainReplayRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_RemainReplayResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_RemainReplayResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_StatusResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_StatusResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_CommandConfirmRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_CommandConfirmRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_ReqInfo_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_ReqInfo_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_CommandConfirmResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_CommandConfirmResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_TicketInfo_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_TicketInfo_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_TicketRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_TicketRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_TicketStateResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_TicketStateResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_TicketState_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_TicketState_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_ForwardRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_ForwardRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_ForwardDeleteRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_ForwardDeleteRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_ForwardResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_ForwardResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_PublicSettingResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_PublicSettingResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_Empty_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_Empty_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_ListenPortResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_ListenPortResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_PortInfoRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_PortInfoRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_PortInfoResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_PortInfoResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_PortInfo_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_PortInfo_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_PortFailure_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_PortFailure_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_PortFailureRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_PortFailureRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_CookiesRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_CookiesRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_UserResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_UserResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_SessionLifecycleLogRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_SessionLifecycleLogRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_AccountDetailResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_AccountDetailResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_HTTPRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_HTTPRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_HTTPRequest_QueryEntry_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_HTTPRequest_QueryEntry_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_HTTPRequest_HeaderEntry_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_HTTPRequest_HeaderEntry_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_HTTPResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_HTTPResponse_fieldAccessorTable; diff --git a/config/application-dev.yml.example b/config/application-dev.yml.example new file mode 100644 index 00000000..1cda5cd4 --- /dev/null +++ b/config/application-dev.yml.example @@ -0,0 +1,9 @@ +mock: + enable: true + mysql: + db-type: mysql + host: host.docker.internal + port: 3306 + user: root + password: your_password + db: test diff --git a/config/application.yml b/config/application.yml index 5759db00..0197000c 100644 --- a/config/application.yml +++ b/config/application.yml @@ -16,6 +16,7 @@ logging: spring: messages: basename: i18n/chen + fallback-to-system-locale: false grpc: client: @@ -23,6 +24,3 @@ grpc: address: static://127.0.0.1:9090 enable-keep-alive: true negotiationType: PLAINTEXT - -i18n: - endpoint: http://core:8080 \ No newline at end of file diff --git a/docker-compose.wisp-dev.yml b/docker-compose.wisp-dev.yml new file mode 100644 index 00000000..9ae360da --- /dev/null +++ b/docker-compose.wisp-dev.yml @@ -0,0 +1,40 @@ +services: + chen-backend: + # Development runs Wisp as a sibling service. Start Java directly so the + # released Wisp binary bundled in the Chen image is not used. + command: + - java + - -Dfile.encoding=utf-8 + - -XX:+ExitOnOutOfMemoryError + - -jar + - /opt/chen/chen.jar + - --spring.config.additional-location=optional:file:/opt/chen/config/ + - --spring.profiles.active=dev + environment: + EXECUTE_PROGRAM: "" + GRPC_CLIENT_WISP_ADDRESS: static://wisp:9090 + depends_on: + wisp: + # Arbitrary Wisp images do not necessarily provide a healthcheck. + condition: service_started + cpus: ${CHEN_CPUS:-2.0} + mem_limit: ${CHEN_MEMORY_LIMIT:-2g} + + wisp: + image: ${WISP_IMAGE:-ghcr.io/jumpserver/wisp:latest} + pull_policy: ${WISP_PULL_POLICY:-missing} + container_name: chen-wisp-dev + environment: + COMPONENT_NAME: chen + CORE_HOST: ${CORE_HOST:-http://host.docker.internal:8080} + BOOTSTRAP_TOKEN: ${BOOTSTRAP_TOKEN:-PleaseChangeMe} + LOG_LEVEL: ${LOG_LEVEL:-INFO} + WORK_DIR: /opt/wisp + EXECUTE_PROGRAM: "" + volumes: + - ./data:/opt/wisp/data + expose: + - "9090" + restart: unless-stopped + cpus: ${WISP_CPUS:-1.0} + mem_limit: ${WISP_MEMORY_LIMIT:-512m} diff --git a/docker-compose.wisp-source.yml b/docker-compose.wisp-source.yml new file mode 100644 index 00000000..ea450ee9 --- /dev/null +++ b/docker-compose.wisp-source.yml @@ -0,0 +1,20 @@ +services: + chen-backend: + depends_on: + wisp: + condition: service_healthy + + wisp: + # This layer replaces the image-only Wisp service with an image built from + # a local directory or any other BuildKit context (for example, a Git URL). + image: ${WISP_SOURCE_IMAGE:-chen-wisp-dev:local} + pull_policy: build + build: + context: . + dockerfile: Dockerfile.wisp-dev + additional_contexts: + wisp-source: ${WISP_SOURCE:-../wisp} + args: + GO_VERSION: ${WISP_GO_VERSION:-1.25} + WISP_BUILD_PROCS: ${WISP_BUILD_PROCS:-2} + WISP_BUILD_MEMORY: ${WISP_BUILD_MEMORY:-1GiB} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..6e9aad0d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +services: + chen-backend: + build: + context: . + dockerfile: Dockerfile + container_name: chen-backend-dev + volumes: + - ./config:/opt/chen/config + - ./data:/opt/chen/data + ports: + - "8082:8082" + environment: + LOG_LEVEL: INFO + CORE_HOST: ${CORE_HOST:-http://host.docker.internal:8080} + BOOTSTRAP_TOKEN: ${BOOTSTRAP_TOKEN:-PleaseChangeMe} + EXECUTE_PROGRAM: >- + java -Dfile.encoding=utf-8 -XX:+ExitOnOutOfMemoryError + -jar /opt/chen/chen.jar + --spring.config.additional-location=optional:file:/opt/chen/config/ + --spring.profiles.active=dev + restart: unless-stopped + +volumes: + chen-data: diff --git a/entrypoint.sh b/entrypoint.sh index 722db90c..68ca079e 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -12,7 +12,8 @@ export GIN_MODE=release export WORK_DIR=/opt/chen export COMPONENT_NAME=chen export WISP_TRACE_PROCESS=1 -export EXECUTE_PROGRAM="java -Dfile.encoding=utf-8 -XX:+ExitOnOutOfMemoryError -jar /opt/chen/chen.jar --mock.enable=false" +: ${EXECUTE_PROGRAM:="java -Dfile.encoding=utf-8 -XX:+ExitOnOutOfMemoryError -jar /opt/chen/chen.jar --mock.enable=false"} +export EXECUTE_PROGRAM : ${LOG_LEVEL:='ERROR'} diff --git a/frontend/.editorconfig b/frontend/.editorconfig deleted file mode 100644 index 0e70861e..00000000 --- a/frontend/.editorconfig +++ /dev/null @@ -1,20 +0,0 @@ -# http://editorconfig.org -root = true - -[*] -charset = utf-8 -indent_style = space -end_of_line = lf -insert_final_newline = true -trim_trailing_whitespace = true - - -[*.{js,jsx,ts,tsx,vue}] -indent_size = 2 - -[*.py] -indent_size = 4 - -[*.md] -insert_final_newline = false -trim_trailing_whitespace = false diff --git a/frontend/.eslintignore b/frontend/.eslintignore deleted file mode 100644 index ae66d5e2..00000000 --- a/frontend/.eslintignore +++ /dev/null @@ -1,6 +0,0 @@ -build/*.js -src/assets -public -dist -lina -node_modules diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js deleted file mode 100644 index 538794b9..00000000 --- a/frontend/.eslintrc.js +++ /dev/null @@ -1,201 +0,0 @@ -module.exports = { - root: true, - parserOptions: { - parser: 'babel-eslint', - sourceType: 'module' - }, - env: { - browser: true, - node: true, - es6: true - }, - extends: ['plugin:vue/recommended', 'eslint:recommended'], - globals: { - window: true, - _: true - }, - // add your custom rules here - // it is base on https://github.com/vuejs/eslint-config-vue - rules: { - 'vue/max-attributes-per-line': [2, { - 'singleline': 4, - 'multiline': { - 'max': 1, - 'allowFirstLine': false - } - }], - 'vue/singleline-html-element-content-newline': 'off', - 'vue/multiline-html-element-content-newline': 'off', - 'vue/name-property-casing': ['error', 'PascalCase'], - 'vue/no-v-html': 'off', - 'accessor-pairs': 2, - 'arrow-spacing': [2, { - 'before': true, - 'after': true - }], - 'block-spacing': [2, 'always'], - 'brace-style': [2, '1tbs', { - 'allowSingleLine': true - }], - 'camelcase': [0, { - 'properties': 'always' - }], - 'comma-dangle': [2, 'never'], - 'comma-spacing': [2, { - 'before': false, - 'after': true - }], - 'comma-style': [2, 'last'], - 'constructor-super': 2, - 'curly': [2, 'multi-line'], - 'dot-location': [2, 'property'], - 'eol-last': 2, - 'eqeqeq': ['error', 'always', { 'null': 'ignore' }], - 'generator-star-spacing': [2, { - 'before': true, - 'after': true - }], - 'handle-callback-err': [2, '^(err|error)$'], - 'indent': [2, 2, { - 'SwitchCase': 1 - }], - 'jsx-quotes': [2, 'prefer-single'], - 'key-spacing': [2, { - 'beforeColon': false, - 'afterColon': true - }], - 'keyword-spacing': [2, { - 'before': true, - 'after': true - }], - 'new-cap': [2, { - 'newIsCap': true, - 'capIsNew': false - }], - 'new-parens': 2, - 'no-array-constructor': 2, - 'no-caller': 2, - 'no-console': 'off', - 'no-class-assign': 2, - 'no-cond-assign': 2, - 'no-const-assign': 2, - 'no-control-regex': 0, - 'no-delete-var': 2, - 'no-dupe-args': 2, - 'no-dupe-class-members': 2, - 'no-dupe-keys': 2, - 'no-duplicate-case': 2, - 'no-empty-character-class': 2, - 'no-empty-pattern': 2, - 'no-eval': 2, - 'no-ex-assign': 2, - 'no-extend-native': 2, - 'no-extra-bind': 2, - 'no-extra-boolean-cast': 2, - 'no-extra-parens': [2, 'functions'], - 'no-fallthrough': 2, - 'no-floating-decimal': 2, - 'no-func-assign': 2, - 'no-implied-eval': 2, - 'no-inner-declarations': [2, 'functions'], - 'no-invalid-regexp': 2, - 'no-irregular-whitespace': 2, - 'no-iterator': 2, - 'no-label-var': 2, - 'no-labels': [2, { - 'allowLoop': false, - 'allowSwitch': false - }], - 'no-lone-blocks': 2, - 'no-mixed-spaces-and-tabs': 2, - 'no-multi-spaces': 2, - 'no-multi-str': 2, - 'no-multiple-empty-lines': [2, { - 'max': 1 - }], - 'no-native-reassign': 2, - 'no-negated-in-lhs': 2, - 'no-new-object': 2, - 'no-new-require': 2, - 'no-new-symbol': 2, - 'no-new-wrappers': 2, - 'no-obj-calls': 2, - 'no-octal': 2, - 'no-octal-escape': 2, - 'no-path-concat': 2, - 'no-proto': 2, - 'no-redeclare': 2, - 'no-regex-spaces': 2, - 'no-return-assign': [2, 'except-parens'], - 'no-self-assign': 2, - 'no-self-compare': 2, - 'no-sequences': 2, - 'no-shadow-restricted-names': 2, - 'no-spaced-func': 2, - 'no-sparse-arrays': 2, - 'no-this-before-super': 2, - 'no-throw-literal': 2, - 'no-trailing-spaces': 2, - 'no-undef': 2, - 'no-undef-init': 2, - 'no-unexpected-multiline': 2, - 'no-unmodified-loop-condition': 2, - 'no-unneeded-ternary': [2, { - 'defaultAssignment': false - }], - 'no-unreachable': 2, - 'no-unsafe-finally': 2, - 'no-unused-vars': [2, { - 'vars': 'all', - 'args': 'none' - }], - 'no-useless-call': 2, - 'no-useless-computed-key': 2, - 'no-useless-constructor': 2, - 'no-useless-escape': 0, - 'no-whitespace-before-property': 2, - 'no-with': 2, - 'one-var': [2, { - 'initialized': 'never' - }], - 'operator-linebreak': [2, 'after', { - 'overrides': { - '?': 'before', - ':': 'before' - } - }], - 'padded-blocks': [2, 'never'], - 'quotes': [2, 'single', { - 'avoidEscape': true, - 'allowTemplateLiterals': true - }], - 'semi': [2, 'never'], - 'semi-spacing': [2, { - 'before': false, - 'after': true - }], - 'space-before-blocks': [2, 'always'], - 'space-before-function-paren': [2, 'never'], - 'space-in-parens': [2, 'never'], - 'space-infix-ops': 2, - 'space-unary-ops': [2, { - 'words': true, - 'nonwords': false - }], - 'spaced-comment': [2, 'always', { - 'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ','] - }], - 'template-curly-spacing': [2, 'never'], - 'use-isnan': 2, - 'valid-typeof': 2, - 'wrap-iife': [2, 'any'], - 'yield-star-spacing': [2, 'both'], - 'yoda': [2, 'never'], - 'prefer-const': 2, - 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, - 'object-curly-spacing': [2, 'always', { - objectsInObjects: false - }], - 'array-bracket-spacing': [2, 'never'] - } -} diff --git a/frontend/.gitignore b/frontend/.gitignore deleted file mode 100644 index b542de03..00000000 --- a/frontend/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -.DS_Store -node_modules -/dist -.node - -# local env files -.env.local -.env.*.local - -# Log files -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* - -# Editor directories and files -.idea -.vscode -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/frontend/README.md b/frontend/README.md deleted file mode 100644 index 88aef873..00000000 --- a/frontend/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# fronted - -## Project setup -``` -yarn install -``` - -### Compiles and hot-reloads for development -``` -yarn run serve -``` - -### Compiles and minifies for production -``` -yarn run build -``` - -### Lints and fixes files -``` -yarn run lint -``` - -### Customize configuration -See [Configuration Reference](https://cli.vuejs.org/config/). diff --git a/frontend/babel.config.js b/frontend/babel.config.js deleted file mode 100644 index e9558405..00000000 --- a/frontend/babel.config.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - presets: [ - '@vue/cli-plugin-babel/preset' - ] -} diff --git a/frontend/package-lock.json b/frontend/package-lock.json deleted file mode 100644 index 31341208..00000000 --- a/frontend/package-lock.json +++ /dev/null @@ -1,16661 +0,0 @@ -{ - "name": "fronted", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "fronted", - "version": "0.1.0", - "dependencies": { - "ag-grid-community": "^32.1.0", - "ag-grid-vue": "^31.3.4", - "axios": "^0.31.1", - "codemirror": "^5.65.2", - "core-js": "^3.6.5", - "element-ui": "^2.15.13", - "font-awesome": "^4.7.0", - "moment": "^2.29.4", - "rxjs": "^6.6.7", - "sql-formatter": "^9.0.0", - "vue": "^2.6.11", - "vue-codemirror": "^4.0.6", - "vue-contextmenujs": "^1.4.9", - "vue-cookie": "^1.1.4", - "vue-i18n": "^8.22.4", - "vue-property-decorator": "^8.5.1", - "vue-rx": "^6.2.0", - "vue-splitpane": "^1.0.6", - "vuex": "^3.6.0" - }, - "devDependencies": { - "@vue/cli-plugin-babel": "~4.5.14", - "@vue/cli-plugin-eslint": "~4.5.14", - "@vue/cli-service": "~4.5.14", - "babel-eslint": "^10.1.0", - "eslint": "^6.7.2", - "eslint-plugin-vue": "^6.2.2", - "sass": "^1.49.0", - "sass-loader": "^10.1.1", - "vue-template-compiler": "^2.6.11" - } - }, - "node_modules/@achrinza/node-ipc": { - "version": "9.2.2", - "resolved": "https://registry.npmmirror.com/@achrinza/node-ipc/-/node-ipc-9.2.2.tgz", - "integrity": "sha512-b90U39dx0cU6emsOvy5hxU4ApNXnE3+Tuo8XQZfiKTGelDwpMwBVgBP7QX6dGTcJgu/miyJuNJ/2naFBliNWEw==", - "dev": true, - "dependencies": { - "@node-ipc/js-queue": "2.0.3", - "event-pubsub": "4.3.0", - "js-message": "1.0.7" - }, - "engines": { - "node": "8 || 10 || 12 || 14 || 16 || 17" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.24.6.tgz", - "integrity": "sha512-ZJhac6FkEd1yhG2AHOmfcXG4ceoLltoCVJjN5XsWN9BifBQr+cHJbWi0h68HZuSORq+3WtJ2z0hwF2NG1b5kcA==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.24.6", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.24.6.tgz", - "integrity": "sha512-aC2DGhBq5eEdyXWqrDInSqQjO0k8xtPRf5YylULqx8MCd6jBtzqfta/3ETMRpuKIc5hyswfO80ObyA1MvkCcUQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.24.6.tgz", - "integrity": "sha512-qAHSfAdVyFmIvl0VHELib8xar7ONuSHrE2hLnsaWkYNTI68dmi1x8GYDhJjMI/e7XWal9QBlZkwbOnkcw7Z8gQ==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.6", - "@babel/generator": "^7.24.6", - "@babel/helper-compilation-targets": "^7.24.6", - "@babel/helper-module-transforms": "^7.24.6", - "@babel/helpers": "^7.24.6", - "@babel/parser": "^7.24.6", - "@babel/template": "^7.24.6", - "@babel/traverse": "^7.24.6", - "@babel/types": "^7.24.6", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.24.6.tgz", - "integrity": "sha512-S7m4eNa6YAPJRHmKsLHIDJhNAGNKoWNiWefz1MBbpnt8g9lvMDl1hir4P9bo/57bQEmuwEhnRU/AMWsD0G/Fbg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.6", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.6.tgz", - "integrity": "sha512-DitEzDfOMnd13kZnDqns1ccmftwJTS9DMkyn9pYTxulS7bZxUxpMly3Nf23QQ6NwA4UB8lAqjbqWtyvElEMAkg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.6.tgz", - "integrity": "sha512-+wnfqc5uHiMYtvRX7qu80Toef8BXeh4HHR1SPeonGb1SKPniNEd4a/nlaJJMv/OIEYvIVavvo0yR7u10Gqz0Iw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.6.tgz", - "integrity": "sha512-VZQ57UsDGlX/5fFA7GkVPplZhHsVc+vuErWgdOiysI9Ksnw0Pbbd6pnPiR/mmJyKHgyIW0c7KT32gmhiF+cirg==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.24.6", - "@babel/helper-validator-option": "^7.24.6", - "browserslist": "^4.22.2", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.6.tgz", - "integrity": "sha512-djsosdPJVZE6Vsw3kk7IPRWethP94WHGOhQTc67SNXE0ZzMhHgALw8iGmYS0TD1bbMM0VDROy43od7/hN6WYcA==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.6", - "@babel/helper-environment-visitor": "^7.24.6", - "@babel/helper-function-name": "^7.24.6", - "@babel/helper-member-expression-to-functions": "^7.24.6", - "@babel/helper-optimise-call-expression": "^7.24.6", - "@babel/helper-replace-supers": "^7.24.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.6", - "@babel/helper-split-export-declaration": "^7.24.6", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.24.6.tgz", - "integrity": "sha512-C875lFBIWWwyv6MHZUG9HmRrlTDgOsLWZfYR0nW69gaKJNe0/Mpxx5r0EID2ZdHQkdUmQo2t0uNckTL08/1BgA==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.6", - "regexpu-core": "^5.3.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.2", - "resolved": "https://registry.npmmirror.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", - "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.6.tgz", - "integrity": "sha512-Y50Cg3k0LKLMjxdPjIl40SdJgMB85iXn27Vk/qbHZCFx/o5XO3PSnpi675h1KEmmDb6OFArfd5SCQEQ5Q4H88g==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-function-name/-/helper-function-name-7.24.6.tgz", - "integrity": "sha512-xpeLqeeRkbxhnYimfr2PC+iA0Q7ljX/d1eZ9/inYbmfG2jpl8Lu3DyXvpOAnrS5kxkfOWJjioIMQsaMBXFI05w==", - "dev": true, - "dependencies": { - "@babel/template": "^7.24.6", - "@babel/types": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.6.tgz", - "integrity": "sha512-SF/EMrC3OD7dSta1bLJIlrsVxwtd0UpjRJqLno6125epQMJ/kyFmpTT4pbvPbdQHzCHg+biQ7Syo8lnDtbR+uA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.6.tgz", - "integrity": "sha512-OTsCufZTxDUsv2/eDXanw/mUZHWOxSbEmC3pP8cgjcy5rgeVPWWMStnv274DV60JtHxTk0adT0QrCzC4M9NWGg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.24.6.tgz", - "integrity": "sha512-a26dmxFJBF62rRO9mmpgrfTLsAuyHk4e1hKTUkD/fcMfynt8gvEKwQPQDVxWhca8dHoDck+55DFt42zV0QMw5g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.6.tgz", - "integrity": "sha512-Y/YMPm83mV2HJTbX1Qh2sjgjqcacvOlhbzdCCsSlblOKjSYmQqEbO6rUniWQyRo9ncyfjT8hnUjlG06RXDEmcA==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.24.6", - "@babel/helper-module-imports": "^7.24.6", - "@babel/helper-simple-access": "^7.24.6", - "@babel/helper-split-export-declaration": "^7.24.6", - "@babel/helper-validator-identifier": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.6.tgz", - "integrity": "sha512-3SFDJRbx7KuPRl8XDUr8O7GAEB8iGyWPjLKJh/ywP/Iy9WOmEfMrsWbaZpvBu2HSYn4KQygIsz0O7m8y10ncMA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.6.tgz", - "integrity": "sha512-MZG/JcWfxybKwsA9N9PmtF2lOSFSEMVCpIRrbxccZFLJPrJciJdG/UhSh5W96GEteJI2ARqm5UAHxISwRDLSNg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.24.6.tgz", - "integrity": "sha512-1Qursq9ArRZPAMOZf/nuzVW8HgJLkTB9y9LfP4lW2MVp4e9WkLJDovfKBxoDcCk6VuzIxyqWHyBoaCtSRP10yg==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.6", - "@babel/helper-environment-visitor": "^7.24.6", - "@babel/helper-wrap-function": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.24.6.tgz", - "integrity": "sha512-mRhfPwDqDpba8o1F8ESxsEkJMQkUF8ZIWrAc0FtWhxnjfextxMWxr22RtFizxxSYLjVHDeMgVsRq8BBZR2ikJQ==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.24.6", - "@babel/helper-member-expression-to-functions": "^7.24.6", - "@babel/helper-optimise-call-expression": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-simple-access/-/helper-simple-access-7.24.6.tgz", - "integrity": "sha512-nZzcMMD4ZhmB35MOOzQuiGO5RzL6tJbsT37Zx8M5L/i9KSrukGXWTjLe1knIbb/RmxoJE9GON9soq0c0VEMM5g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.6.tgz", - "integrity": "sha512-jhbbkK3IUKc4T43WadP96a27oYti9gEf1LdyGSP2rHGH77kwLwfhO7TgwnWvxxQVmke0ImmCSS47vcuxEMGD3Q==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.6.tgz", - "integrity": "sha512-CvLSkwXGWnYlF9+J3iZUvwgAxKiYzK3BWuo+mLzD/MDGOZDj7Gq8+hqaOkMxmJwmlv0iu86uH5fdADd9Hxkymw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.24.6.tgz", - "integrity": "sha512-WdJjwMEkmBicq5T9fm/cHND3+UlFa2Yj8ALLgmoSQAJZysYbBjw+azChSGPN4DSPLXOcooGRvDwZWMcF/mLO2Q==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.6.tgz", - "integrity": "sha512-4yA7s865JHaqUdRbnaxarZREuPTHrjpDT+pXoAZ1yhyo6uFnIEpS8VMu16siFOHDpZNKYv5BObhsB//ycbICyw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.24.6.tgz", - "integrity": "sha512-Jktc8KkF3zIkePb48QO+IapbXlSapOW9S+ogZZkcO6bABgYAxtZcjZ/O005111YLf+j4M84uEgwYoidDkXbCkQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-wrap-function/-/helper-wrap-function-7.24.6.tgz", - "integrity": "sha512-f1JLrlw/jbiNfxvdrfBgio/gRBk3yTAEJWirpAkiJG2Hb22E7cEYKHWo0dFPTv/niPovzIdPdEDetrv6tC6gPQ==", - "dev": true, - "dependencies": { - "@babel/helper-function-name": "^7.24.6", - "@babel/template": "^7.24.6", - "@babel/types": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.24.6.tgz", - "integrity": "sha512-V2PI+NqnyFu1i0GyTd/O/cTpxzQCYioSkUIRmgo7gFEHKKCg5w46+r/A6WeUR1+P3TeQ49dspGPNd/E3n9AnnA==", - "dev": true, - "dependencies": { - "@babel/template": "^7.24.6", - "@babel/types": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/highlight/-/highlight-7.24.6.tgz", - "integrity": "sha512-2YnuOp4HAk2BsBrJJvYCbItHx0zWscI1C3zgWkz+wDyD9I7GIVrfnLyrR4Y1VR+7p+chAEcrgRQYZAGIKMV7vQ==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.24.6", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.24.6.tgz", - "integrity": "sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q==", - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.6.tgz", - "integrity": "sha512-bYndrJ6Ph6Ar+GaB5VAc0JPoP80bQCm4qon6JEzXfRl5QZyQ8Ur1K6k7htxWmPA5z+k7JQvaMUrtXlqclWYzKw==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.24.6", - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.6.tgz", - "integrity": "sha512-iVuhb6poq5ikqRq2XWU6OQ+R5o9wF+r/or9CeUyovgptz0UlnK4/seOQ1Istu/XybYjAhQv1FRSSfHHufIku5Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.6.tgz", - "integrity": "sha512-c8TER5xMDYzzFcGqOEp9l4hvB7dcbhcGjcLVwxWfe4P5DOafdwjsBJZKsmv+o3aXh7NhopvayQIovHrh2zSRUQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.6", - "@babel/plugin-transform-optional-chaining": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.6.tgz", - "integrity": "sha512-z8zEjYmwBUHN/pCF3NuWBhHQjJCrd33qAi8MgANfMrAvn72k2cImT8VjK9LJFu4ysOLJqhfkYYb3MvwANRUNZQ==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.24.6", - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-decorators": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.24.6.tgz", - "integrity": "sha512-8DjR0/DzlBhz2SVi9a19/N2U5+C3y3rseXuyoKL9SP8vnbewscj1eHZtL6kpEn4UCuUmqEo0mvqyDYRFoN2gpA==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.6", - "@babel/helper-plugin-utils": "^7.24.6", - "@babel/plugin-syntax-decorators": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "dev": true, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-decorators": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.24.6.tgz", - "integrity": "sha512-gInH8LEqBp+wkwTVihCd/qf+4s28g81FZyvlIbAurHk9eSiItEKG7E0uNK2UdpgsD79aJVAW3R3c85h0YJ0jsw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.6.tgz", - "integrity": "sha512-BE6o2BogJKJImTmGpkmOic4V0hlRRxVtzqxiSPa8TIFxyhi4EFjHm08nq1M4STK4RytuLMgnSz0/wfflvGFNOg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.6.tgz", - "integrity": "sha512-D+CfsVZousPXIdudSII7RGy52+dYRtbyKAZcvtQKq/NpsivyMVduepzcLqG5pMBugtMdedxdC8Ramdpcne9ZWQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.6.tgz", - "integrity": "sha512-lWfvAIFNWMlCsU0DRUun2GpFwZdGTukLaHJqRh1JRb80NdAP5Sb1HDHB5X9P9OtgZHQl089UzQkpYlBq2VTPRw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.6.tgz", - "integrity": "sha512-jSSSDt4ZidNMggcLx8SaKsbGNEfIl0PHx/4mFEulorE7bpYLbN0d3pDW3eJ7Y5Z3yPhy3L3NaPCYyTUY7TuugQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.6.tgz", - "integrity": "sha512-VEP2o4iR2DqQU6KPgizTW2mnMx6BG5b5O9iQdrW9HesLkv8GIA8x2daXBQxw1MrsIkFQGA/iJ204CKoQ8UcnAA==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.24.6", - "@babel/helper-plugin-utils": "^7.24.6", - "@babel/helper-remap-async-to-generator": "^7.24.6", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.6.tgz", - "integrity": "sha512-NTBA2SioI3OsHeIn6sQmhvXleSl9T70YY/hostQLveWs0ic+qvbA3fa0kwAwQ0OA/XGaAerNZRQGJyRfhbJK4g==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.24.6", - "@babel/helper-plugin-utils": "^7.24.6", - "@babel/helper-remap-async-to-generator": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.6.tgz", - "integrity": "sha512-XNW7jolYHW9CwORrZgA/97tL/k05qe/HL0z/qqJq1mdWhwwCM6D4BJBV7wAz9HgFziN5dTOG31znkVIzwxv+vw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.6.tgz", - "integrity": "sha512-S/t1Xh4ehW7sGA7c1j/hiOBLnEYCp/c2sEG4ZkL8kI1xX9tW2pqJTCHKtdhe/jHKt8nG0pFCrDHUXd4DvjHS9w==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.6.tgz", - "integrity": "sha512-j6dZ0Z2Z2slWLR3kt9aOmSIrBvnntWjMDN/TVcMPxhXMLmJVqX605CBRlcGI4b32GMbfifTEsdEjGjiE+j/c3A==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.6", - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.6.tgz", - "integrity": "sha512-1QSRfoPI9RoLRa8Mnakc6v3e0gJxiZQTYrMfLn+mD0sz5+ndSzwymp2hDcYJTyT0MOn0yuWzj8phlIvO72gTHA==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.6", - "@babel/helper-plugin-utils": "^7.24.6", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.6.tgz", - "integrity": "sha512-+fN+NO2gh8JtRmDSOB6gaCVo36ha8kfCW1nMq2Gc0DABln0VcHN4PrALDvF5/diLzIRKptC7z/d7Lp64zk92Fg==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.6", - "@babel/helper-compilation-targets": "^7.24.6", - "@babel/helper-environment-visitor": "^7.24.6", - "@babel/helper-function-name": "^7.24.6", - "@babel/helper-plugin-utils": "^7.24.6", - "@babel/helper-replace-supers": "^7.24.6", - "@babel/helper-split-export-declaration": "^7.24.6", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.6.tgz", - "integrity": "sha512-cRzPobcfRP0ZtuIEkA8QzghoUpSB3X3qSH5W2+FzG+VjWbJXExtx0nbRqwumdBN1x/ot2SlTNQLfBCnPdzp6kg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6", - "@babel/template": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.6.tgz", - "integrity": "sha512-YLW6AE5LQpk5npNXL7i/O+U9CE4XsBCuRPgyjl1EICZYKmcitV+ayuuUGMJm2lC1WWjXYszeTnIxF/dq/GhIZQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.6.tgz", - "integrity": "sha512-rCXPnSEKvkm/EjzOtLoGvKseK+dS4kZwx1HexO3BtRtgL0fQ34awHn34aeSHuXtZY2F8a1X8xqBBPRtOxDVmcA==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.6", - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.6.tgz", - "integrity": "sha512-/8Odwp/aVkZwPFJMllSbawhDAO3UJi65foB00HYnK/uXvvCPm0TAXSByjz1mpRmp0q6oX2SIxpkUOpPFHk7FLA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.6.tgz", - "integrity": "sha512-vpq8SSLRTBLOHUZHSnBqVo0AKX3PBaoPs2vVzYVWslXDTDIpwAcCDtfhUcHSQQoYoUvcFPTdC8TZYXu9ZnLT/w==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.6.tgz", - "integrity": "sha512-EemYpHtmz0lHE7hxxxYEuTYOOBZ43WkDgZ4arQ4r+VX9QHuNZC+WH3wUWmRNvR8ECpTRne29aZV6XO22qpOtdA==", - "dev": true, - "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.6", - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.6.tgz", - "integrity": "sha512-inXaTM1SVrIxCkIJ5gqWiozHfFMStuGbGJAxZFBoHcRRdDP0ySLb3jH6JOwmfiinPwyMZqMBX+7NBDCO4z0NSA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.6.tgz", - "integrity": "sha512-n3Sf72TnqK4nw/jziSqEl1qaWPbCRw2CziHH+jdRYvw4J6yeCzsj4jdw8hIntOEeDGTmHVe2w4MVL44PN0GMzg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.6.tgz", - "integrity": "sha512-sOajCu6V0P1KPljWHKiDq6ymgqB+vfo3isUS4McqW1DZtvSVU2v/wuMhmRmkg3sFoq6GMaUUf8W4WtoSLkOV/Q==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.24.6", - "@babel/helper-function-name": "^7.24.6", - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.6.tgz", - "integrity": "sha512-Uvgd9p2gUnzYJxVdBLcU0KurF8aVhkmVyMKW4MIY1/BByvs3EBpv45q01o7pRTVmTvtQq5zDlytP3dcUgm7v9w==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6", - "@babel/plugin-syntax-json-strings": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.6.tgz", - "integrity": "sha512-f2wHfR2HF6yMj+y+/y07+SLqnOSwRp8KYLpQKOzS58XLVlULhXbiYcygfXQxJlMbhII9+yXDwOUFLf60/TL5tw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.6.tgz", - "integrity": "sha512-EKaWvnezBCMkRIHxMJSIIylzhqK09YpiJtDbr2wsXTwnO0TxyjMUkaw4RlFIZMIS0iDj0KyIg7H7XCguHu/YDA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.6.tgz", - "integrity": "sha512-9g8iV146szUo5GWgXpRbq/GALTnY+WnNuRTuRHWWFfWGbP9ukRL0aO/jpu9dmOPikclkxnNsjY8/gsWl6bmZJQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.6.tgz", - "integrity": "sha512-eAGogjZgcwqAxhyFgqghvoHRr+EYRQPFjUXrTYKBRb5qPnAVxOOglaxc4/byHqjvq/bqO2F3/CGwTHsgKJYHhQ==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.24.6", - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.6.tgz", - "integrity": "sha512-JEV8l3MHdmmdb7S7Cmx6rbNEjRCgTQMZxllveHO0mx6uiclB0NflCawlQQ6+o5ZrwjUBYPzHm2XoK4wqGVUFuw==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.24.6", - "@babel/helper-plugin-utils": "^7.24.6", - "@babel/helper-simple-access": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.6.tgz", - "integrity": "sha512-xg1Z0J5JVYxtpX954XqaaAT6NpAY6LtZXvYFCJmGFJWwtlz2EmJoR8LycFRGNE8dBKizGWkGQZGegtkV8y8s+w==", - "dev": true, - "dependencies": { - "@babel/helper-hoist-variables": "^7.24.6", - "@babel/helper-module-transforms": "^7.24.6", - "@babel/helper-plugin-utils": "^7.24.6", - "@babel/helper-validator-identifier": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.6.tgz", - "integrity": "sha512-esRCC/KsSEUvrSjv5rFYnjZI6qv4R1e/iHQrqwbZIoRJqk7xCvEUiN7L1XrmW5QSmQe3n1XD88wbgDTWLbVSyg==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.24.6", - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.6.tgz", - "integrity": "sha512-6DneiCiu91wm3YiNIGDWZsl6GfTTbspuj/toTEqLh9d4cx50UIzSdg+T96p8DuT7aJOBRhFyaE9ZvTHkXrXr6Q==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.6", - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.6.tgz", - "integrity": "sha512-f8liz9JG2Va8A4J5ZBuaSdwfPqN6axfWRK+y66fjKYbwf9VBLuq4WxtinhJhvp1w6lamKUwLG0slK2RxqFgvHA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.6.tgz", - "integrity": "sha512-+QlAiZBMsBK5NqrBWFXCYeXyiU1y7BQ/OYaiPAcQJMomn5Tyg+r5WuVtyEuvTbpV7L25ZSLfE+2E9ywj4FD48A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.6.tgz", - "integrity": "sha512-6voawq8T25Jvvnc4/rXcWZQKKxUNZcKMS8ZNrjxQqoRFernJJKjE3s18Qo6VFaatG5aiX5JV1oPD7DbJhn0a4Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.6.tgz", - "integrity": "sha512-OKmi5wiMoRW5Smttne7BwHM8s/fb5JFs+bVGNSeHWzwZkWXWValR1M30jyXo1s/RaqgwwhEC62u4rFH/FBcBPg==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.24.6", - "@babel/helper-plugin-utils": "^7.24.6", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.6.tgz", - "integrity": "sha512-N/C76ihFKlZgKfdkEYKtaRUtXZAgK7sOY4h2qrbVbVTXPrKGIi8aww5WGe/+Wmg8onn8sr2ut6FXlsbu/j6JHg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6", - "@babel/helper-replace-supers": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.6.tgz", - "integrity": "sha512-L5pZ+b3O1mSzJ71HmxSCmTVd03VOT2GXOigug6vDYJzE5awLI7P1g0wFcdmGuwSDSrQ0L2rDOe/hHws8J1rv3w==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.6.tgz", - "integrity": "sha512-cHbqF6l1QP11OkYTYQ+hhVx1E017O5ZcSPXk9oODpqhcAD1htsWG2NpHrrhthEO2qZomLK0FXS+u7NfrkF5aOQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.6", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.6.tgz", - "integrity": "sha512-ST7guE8vLV+vI70wmAxuZpIKzVjvFX9Qs8bl5w6tN/6gOypPWUmMQL2p7LJz5E63vEGrDhAiYetniJFyBH1RkA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.6.tgz", - "integrity": "sha512-T9LtDI0BgwXOzyXrvgLTT8DFjCC/XgWLjflczTLXyvxbnSR/gpv0hbmzlHE/kmh9nOvlygbamLKRo6Op4yB6aw==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.6", - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.6.tgz", - "integrity": "sha512-Qu/ypFxCY5NkAnEhCF86Mvg3NSabKsh/TPpBVswEdkGl7+FbsYHy1ziRqJpwGH4thBdQHh8zx+z7vMYmcJ7iaQ==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.6", - "@babel/helper-create-class-features-plugin": "^7.24.6", - "@babel/helper-plugin-utils": "^7.24.6", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.6.tgz", - "integrity": "sha512-oARaglxhRsN18OYsnPTpb8TcKQWDYNsPNmTnx5++WOAsUJ0cSC/FZVlIJCKvPbU4yn/UXsS0551CFKJhN0CaMw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.6.tgz", - "integrity": "sha512-SMDxO95I8WXRtXhTAc8t/NFQUT7VYbIWwJCJgEli9ml4MhqUMh4S6hxgH6SmAC3eAQNWCDJFxcFeEt9w2sDdXg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6", - "regenerator-transform": "^0.15.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.6.tgz", - "integrity": "sha512-DcrgFXRRlK64dGE0ZFBPD5egM2uM8mgfrvTMOSB2yKzOtjpGegVYkzh3s1zZg1bBck3nkXiaOamJUqK3Syk+4A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.6.tgz", - "integrity": "sha512-W3gQydMb0SY99y/2lV0Okx2xg/8KzmZLQsLaiCmwNRl1kKomz14VurEm+2TossUb+sRvBCnGe+wx8KtIgDtBbQ==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.24.6", - "@babel/helper-plugin-utils": "^7.24.6", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.1", - "babel-plugin-polyfill-regenerator": "^0.6.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.6.tgz", - "integrity": "sha512-xnEUvHSMr9eOWS5Al2YPfc32ten7CXdH7Zwyyk7IqITg4nX61oHj+GxpNvl+y5JHjfN3KXE2IV55wAWowBYMVw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.6.tgz", - "integrity": "sha512-h/2j7oIUDjS+ULsIrNZ6/TKG97FgmEk1PXryk/HQq6op4XUUUwif2f69fJrzK0wza2zjCS1xhXmouACaWV5uPA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.6.tgz", - "integrity": "sha512-fN8OcTLfGmYv7FnDrsjodYBo1DhPL3Pze/9mIIE2MGCT1KgADYIOD7rEglpLHZj8PZlC/JFX5WcD+85FLAQusw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.6.tgz", - "integrity": "sha512-BJbEqJIcKwrqUP+KfUIkxz3q8VzXe2R8Wv8TaNgO1cx+nNavxn/2+H8kp9tgFSOL6wYPPEgFvU6IKS4qoGqhmg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.6.tgz", - "integrity": "sha512-IshCXQ+G9JIFJI7bUpxTE/oA2lgVLAIK8q1KdJNoPXOpvRaNjMySGuvLfBw/Xi2/1lLo953uE8hyYSDW3TSYig==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.6.tgz", - "integrity": "sha512-bKl3xxcPbkQQo5eX9LjjDpU2xYHeEeNQbOhj0iPvetSzA+Tu9q/o5lujF4Sek60CM6MgYvOS/DJuwGbiEYAnLw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.6.tgz", - "integrity": "sha512-8EIgImzVUxy15cZiPii9GvLZwsy7Vxc+8meSlR3cXFmBIl5W5Tn9LGBf7CDKkHj4uVfNXCJB8RsVfnmY61iedA==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.6", - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.6.tgz", - "integrity": "sha512-pssN6ExsvxaKU638qcWb81RrvvgZom3jDgU/r5xFZ7TONkZGFf4MhI2ltMb8OcQWhHyxgIavEU+hgqtbKOmsPA==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.6", - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.6.tgz", - "integrity": "sha512-quiMsb28oXWIDK0gXLALOJRXLgICLiulqdZGOaPPd0vRT7fQp74NtdADAVu+D8s00C+0Xs0MxVP0VKF/sZEUgw==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.6", - "@babel/helper-plugin-utils": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/preset-env/-/preset-env-7.24.6.tgz", - "integrity": "sha512-CrxEAvN7VxfjOG8JNF2Y/eMqMJbZPZ185amwGUBp8D9USK90xQmv7dLdFSa+VbD7fdIqcy/Mfv7WtzG8+/qxKg==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.24.6", - "@babel/helper-compilation-targets": "^7.24.6", - "@babel/helper-plugin-utils": "^7.24.6", - "@babel/helper-validator-option": "^7.24.6", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.6", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.6", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.6", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.24.6", - "@babel/plugin-syntax-import-attributes": "^7.24.6", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.24.6", - "@babel/plugin-transform-async-generator-functions": "^7.24.6", - "@babel/plugin-transform-async-to-generator": "^7.24.6", - "@babel/plugin-transform-block-scoped-functions": "^7.24.6", - "@babel/plugin-transform-block-scoping": "^7.24.6", - "@babel/plugin-transform-class-properties": "^7.24.6", - "@babel/plugin-transform-class-static-block": "^7.24.6", - "@babel/plugin-transform-classes": "^7.24.6", - "@babel/plugin-transform-computed-properties": "^7.24.6", - "@babel/plugin-transform-destructuring": "^7.24.6", - "@babel/plugin-transform-dotall-regex": "^7.24.6", - "@babel/plugin-transform-duplicate-keys": "^7.24.6", - "@babel/plugin-transform-dynamic-import": "^7.24.6", - "@babel/plugin-transform-exponentiation-operator": "^7.24.6", - "@babel/plugin-transform-export-namespace-from": "^7.24.6", - "@babel/plugin-transform-for-of": "^7.24.6", - "@babel/plugin-transform-function-name": "^7.24.6", - "@babel/plugin-transform-json-strings": "^7.24.6", - "@babel/plugin-transform-literals": "^7.24.6", - "@babel/plugin-transform-logical-assignment-operators": "^7.24.6", - "@babel/plugin-transform-member-expression-literals": "^7.24.6", - "@babel/plugin-transform-modules-amd": "^7.24.6", - "@babel/plugin-transform-modules-commonjs": "^7.24.6", - "@babel/plugin-transform-modules-systemjs": "^7.24.6", - "@babel/plugin-transform-modules-umd": "^7.24.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.6", - "@babel/plugin-transform-new-target": "^7.24.6", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.6", - "@babel/plugin-transform-numeric-separator": "^7.24.6", - "@babel/plugin-transform-object-rest-spread": "^7.24.6", - "@babel/plugin-transform-object-super": "^7.24.6", - "@babel/plugin-transform-optional-catch-binding": "^7.24.6", - "@babel/plugin-transform-optional-chaining": "^7.24.6", - "@babel/plugin-transform-parameters": "^7.24.6", - "@babel/plugin-transform-private-methods": "^7.24.6", - "@babel/plugin-transform-private-property-in-object": "^7.24.6", - "@babel/plugin-transform-property-literals": "^7.24.6", - "@babel/plugin-transform-regenerator": "^7.24.6", - "@babel/plugin-transform-reserved-words": "^7.24.6", - "@babel/plugin-transform-shorthand-properties": "^7.24.6", - "@babel/plugin-transform-spread": "^7.24.6", - "@babel/plugin-transform-sticky-regex": "^7.24.6", - "@babel/plugin-transform-template-literals": "^7.24.6", - "@babel/plugin-transform-typeof-symbol": "^7.24.6", - "@babel/plugin-transform-unicode-escapes": "^7.24.6", - "@babel/plugin-transform-unicode-property-regex": "^7.24.6", - "@babel/plugin-transform-unicode-regex": "^7.24.6", - "@babel/plugin-transform-unicode-sets-regex": "^7.24.6", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.4", - "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.31.0", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmmirror.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmmirror.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", - "dev": true - }, - "node_modules/@babel/runtime": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.24.6.tgz", - "integrity": "sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==", - "dev": true, - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.24.6.tgz", - "integrity": "sha512-3vgazJlLwNXi9jhrR1ef8qiB65L1RK90+lEQwv4OxveHnqC3BfmnHdgySwRLzf6akhlOYenT+b7AfWq+a//AHw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.24.6", - "@babel/parser": "^7.24.6", - "@babel/types": "^7.24.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.24.6.tgz", - "integrity": "sha512-OsNjaJwT9Zn8ozxcfoBc+RaHdj3gFmCmYoQLUII1o6ZrUwku0BMg80FoOTPx+Gi6XhcQxAYE4xyjPTo4SxEQqw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.24.6", - "@babel/generator": "^7.24.6", - "@babel/helper-environment-visitor": "^7.24.6", - "@babel/helper-function-name": "^7.24.6", - "@babel/helper-hoist-variables": "^7.24.6", - "@babel/helper-split-export-declaration": "^7.24.6", - "@babel/parser": "^7.24.6", - "@babel/types": "^7.24.6", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.24.6", - "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.24.6.tgz", - "integrity": "sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ==", - "dev": true, - "dependencies": { - "@babel/helper-string-parser": "^7.24.6", - "@babel/helper-validator-identifier": "^7.24.6", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@hapi/address": { - "version": "2.1.4", - "resolved": "https://registry.npmmirror.com/@hapi/address/-/address-2.1.4.tgz", - "integrity": "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==", - "dev": true - }, - "node_modules/@hapi/bourne": { - "version": "1.3.2", - "resolved": "https://registry.npmmirror.com/@hapi/bourne/-/bourne-1.3.2.tgz", - "integrity": "sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA==", - "dev": true - }, - "node_modules/@hapi/hoek": { - "version": "8.5.1", - "resolved": "https://registry.npmmirror.com/@hapi/hoek/-/hoek-8.5.1.tgz", - "integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==", - "dev": true - }, - "node_modules/@hapi/joi": { - "version": "15.1.1", - "resolved": "https://registry.npmmirror.com/@hapi/joi/-/joi-15.1.1.tgz", - "integrity": "sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ==", - "dev": true, - "dependencies": { - "@hapi/address": "2.x.x", - "@hapi/bourne": "1.x.x", - "@hapi/hoek": "8.x.x", - "@hapi/topo": "3.x.x" - } - }, - "node_modules/@hapi/topo": { - "version": "3.1.6", - "resolved": "https://registry.npmmirror.com/@hapi/topo/-/topo-3.1.6.tgz", - "integrity": "sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ==", - "dev": true, - "dependencies": { - "@hapi/hoek": "^8.3.0" - } - }, - "node_modules/@intervolga/optimize-cssnano-plugin": { - "version": "1.0.6", - "resolved": "https://registry.npmmirror.com/@intervolga/optimize-cssnano-plugin/-/optimize-cssnano-plugin-1.0.6.tgz", - "integrity": "sha512-zN69TnSr0viRSU6cEDIcuPcP67QcpQ6uHACg58FiN9PDrU6SLyGW3MR4tiISbYxy1kDWAVPwD+XwQTWE5cigAA==", - "dev": true, - "dependencies": { - "cssnano": "^4.0.0", - "cssnano-preset-default": "^4.0.0", - "postcss": "^7.0.0" - }, - "peerDependencies": { - "webpack": "^4.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@mrmlnc/readdir-enhanced": { - "version": "2.2.1", - "resolved": "https://registry.npmmirror.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", - "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", - "dev": true, - "dependencies": { - "call-me-maybe": "^1.0.1", - "glob-to-regexp": "^0.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@node-ipc/js-queue": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/@node-ipc/js-queue/-/js-queue-2.0.3.tgz", - "integrity": "sha512-fL1wpr8hhD5gT2dA1qifeVaoDFlQR5es8tFuKqjHX+kdOtdNHnxkVZbtIrR2rxnMFvehkjaZRNV2H/gPXlb0hw==", - "dev": true, - "dependencies": { - "easy-stack": "1.0.1" - }, - "engines": { - "node": ">=1.0.0" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "1.1.3", - "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", - "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@soda/friendly-errors-webpack-plugin": { - "version": "1.8.1", - "resolved": "https://registry.npmmirror.com/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.8.1.tgz", - "integrity": "sha512-h2ooWqP8XuFqTXT+NyAFbrArzfQA7R6HTezADrvD9Re8fxMLTPPniLdqVTdDaO0eIoLaAwKT+d6w+5GeTk7Vbg==", - "dev": true, - "dependencies": { - "chalk": "^3.0.0", - "error-stack-parser": "^2.0.6", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8.0.0" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/@soda/friendly-errors-webpack-plugin/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@soda/friendly-errors-webpack-plugin/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@soda/friendly-errors-webpack-plugin/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@soda/friendly-errors-webpack-plugin/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@soda/friendly-errors-webpack-plugin/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@soda/friendly-errors-webpack-plugin/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@soda/get-current-script": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/@soda/get-current-script/-/get-current-script-1.0.2.tgz", - "integrity": "sha512-T7VNNlYVM1SgQ+VsMYhnDkcGmWhQdL0bDyGm5TlQ3GBXnJscEClUUOKduWTmm2zCnvNLC1hc3JpuXjs/nFOc5w==", - "dev": true - }, - "node_modules/@types/body-parser": { - "version": "1.19.5", - "resolved": "https://registry.npmmirror.com/@types/body-parser/-/body-parser-1.19.5.tgz", - "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", - "dev": true, - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmmirror.com/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect-history-api-fallback": { - "version": "1.5.4", - "resolved": "https://registry.npmmirror.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", - "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", - "dev": true, - "dependencies": { - "@types/express-serve-static-core": "*", - "@types/node": "*" - } - }, - "node_modules/@types/express": { - "version": "4.17.21", - "resolved": "https://registry.npmmirror.com/@types/express/-/express-4.17.21.tgz", - "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", - "dev": true, - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.19.2", - "resolved": "https://registry.npmmirror.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.2.tgz", - "integrity": "sha512-dPSEQElyVJ97BuGduAqQjpBocZWAs0GR94z+ptL7JXQJeJdHw2WBG3EWdFrK36b8Q6j8P4cXOMhgUoi0IIfIsg==", - "dev": true, - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmmirror.com/@types/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", - "dev": true, - "dependencies": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "node_modules/@types/http-errors": { - "version": "2.0.4", - "resolved": "https://registry.npmmirror.com/@types/http-errors/-/http-errors-2.0.4.tgz", - "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", - "dev": true - }, - "node_modules/@types/http-proxy": { - "version": "1.17.14", - "resolved": "https://registry.npmmirror.com/@types/http-proxy/-/http-proxy-1.17.14.tgz", - "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmmirror.com/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true - }, - "node_modules/@types/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmmirror.com/@types/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", - "dev": true - }, - "node_modules/@types/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmmirror.com/@types/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", - "dev": true - }, - "node_modules/@types/node": { - "version": "20.12.13", - "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.12.13.tgz", - "integrity": "sha512-gBGeanV41c1L171rR7wjbMiEpEI/l5XFQdLLfhr/REwpgDy/4U8y89+i8kRiLzDyZdOkXh+cRaTetUnCYutoXA==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.4", - "resolved": "https://registry.npmmirror.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", - "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", - "dev": true - }, - "node_modules/@types/q": { - "version": "1.5.8", - "resolved": "https://registry.npmmirror.com/@types/q/-/q-1.5.8.tgz", - "integrity": "sha512-hroOstUScF6zhIi+5+x0dzqrHA1EJi+Irri6b1fxolMTqqHIV/Cg77EtnQcZqZCu8hR3mX2BzIxN4/GzI68Kfw==", - "dev": true - }, - "node_modules/@types/qs": { - "version": "6.9.15", - "resolved": "https://registry.npmmirror.com/@types/qs/-/qs-6.9.15.tgz", - "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", - "dev": true - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmmirror.com/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true - }, - "node_modules/@types/send": { - "version": "0.17.4", - "resolved": "https://registry.npmmirror.com/@types/send/-/send-0.17.4.tgz", - "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", - "dev": true, - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.7", - "resolved": "https://registry.npmmirror.com/@types/serve-static/-/serve-static-1.15.7.tgz", - "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", - "dev": true, - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "*" - } - }, - "node_modules/@types/source-list-map": { - "version": "0.1.6", - "resolved": "https://registry.npmmirror.com/@types/source-list-map/-/source-list-map-0.1.6.tgz", - "integrity": "sha512-5JcVt1u5HDmlXkwOD2nslZVllBBc7HDuOICfiZah2Z0is8M8g+ddAEawbmd3VjedfDHBzxCaXLs07QEmb7y54g==", - "dev": true - }, - "node_modules/@types/tapable": { - "version": "1.0.12", - "resolved": "https://registry.npmmirror.com/@types/tapable/-/tapable-1.0.12.tgz", - "integrity": "sha512-bTHG8fcxEqv1M9+TD14P8ok8hjxoOCkfKc8XXLaaD05kI7ohpeI956jtDOD3XHKBQrlyPughUtzm1jtVhHpA5Q==", - "dev": true - }, - "node_modules/@types/uglify-js": { - "version": "3.17.5", - "resolved": "https://registry.npmmirror.com/@types/uglify-js/-/uglify-js-3.17.5.tgz", - "integrity": "sha512-TU+fZFBTBcXj/GpDpDaBmgWk/gn96kMZ+uocaFUlV2f8a6WdMzzI44QBCmGcCiYR0Y6ZlNRiyUyKKt5nl/lbzQ==", - "dev": true, - "dependencies": { - "source-map": "^0.6.1" - } - }, - "node_modules/@types/webpack": { - "version": "4.41.38", - "resolved": "https://registry.npmmirror.com/@types/webpack/-/webpack-4.41.38.tgz", - "integrity": "sha512-oOW7E931XJU1mVfCnxCVgv8GLFL768pDO5u2Gzk82i8yTIgX6i7cntyZOkZYb/JtYM8252SN9bQp9tgkVDSsRw==", - "dev": true, - "dependencies": { - "@types/node": "*", - "@types/tapable": "^1", - "@types/uglify-js": "*", - "@types/webpack-sources": "*", - "anymatch": "^3.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/@types/webpack-dev-server": { - "version": "3.11.6", - "resolved": "https://registry.npmmirror.com/@types/webpack-dev-server/-/webpack-dev-server-3.11.6.tgz", - "integrity": "sha512-XCph0RiiqFGetukCTC3KVnY1jwLcZ84illFRMbyFzCcWl90B/76ew0tSqF46oBhnLC4obNDG7dMO0JfTN0MgMQ==", - "dev": true, - "dependencies": { - "@types/connect-history-api-fallback": "*", - "@types/express": "*", - "@types/serve-static": "*", - "@types/webpack": "^4", - "http-proxy-middleware": "^1.0.0" - } - }, - "node_modules/@types/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmmirror.com/@types/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-4nZOdMwSPHZ4pTEZzSp0AsTM4K7Qmu40UKW4tJDiOVs20UzYF9l+qUe4s0ftfN0pin06n+5cWWDJXH+sbhAiDw==", - "dev": true, - "dependencies": { - "@types/node": "*", - "@types/source-list-map": "*", - "source-map": "^0.7.3" - } - }, - "node_modules/@types/webpack-sources/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@vue/babel-helper-vue-jsx-merge-props": { - "version": "1.4.0", - "resolved": "https://registry.npmmirror.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.4.0.tgz", - "integrity": "sha512-JkqXfCkUDp4PIlFdDQ0TdXoIejMtTHP67/pvxlgeY+u5k3LEdKuWZ3LK6xkxo52uDoABIVyRwqVkfLQJhk7VBA==", - "dev": true - }, - "node_modules/@vue/babel-helper-vue-transform-on": { - "version": "1.2.2", - "resolved": "https://registry.npmmirror.com/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.2.2.tgz", - "integrity": "sha512-nOttamHUR3YzdEqdM/XXDyCSdxMA9VizUKoroLX6yTyRtggzQMHXcmwh8a7ZErcJttIBIc9s68a1B8GZ+Dmvsw==", - "dev": true - }, - "node_modules/@vue/babel-plugin-jsx": { - "version": "1.2.2", - "resolved": "https://registry.npmmirror.com/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.2.2.tgz", - "integrity": "sha512-nYTkZUVTu4nhP199UoORePsql0l+wj7v/oyQjtThUVhJl1U+6qHuoVhIvR3bf7eVKjbCK+Cs2AWd7mi9Mpz9rA==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "~7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-jsx": "^7.23.3", - "@babel/template": "^7.23.9", - "@babel/traverse": "^7.23.9", - "@babel/types": "^7.23.9", - "@vue/babel-helper-vue-transform-on": "1.2.2", - "@vue/babel-plugin-resolve-type": "1.2.2", - "camelcase": "^6.3.0", - "html-tags": "^3.3.1", - "svg-tags": "^1.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - } - } - }, - "node_modules/@vue/babel-plugin-jsx/node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.15" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@vue/babel-plugin-resolve-type": { - "version": "1.2.2", - "resolved": "https://registry.npmmirror.com/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.2.2.tgz", - "integrity": "sha512-EntyroPwNg5IPVdUJupqs0CFzuf6lUrVvCspmv2J1FITLeGnUCuoGNNk78dgCusxEiYj6RMkTJflGSxk5aIC4A==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/helper-module-imports": "~7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/parser": "^7.23.9", - "@vue/compiler-sfc": "^3.4.15" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@vue/babel-plugin-resolve-type/node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.15" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@vue/babel-plugin-transform-vue-jsx": { - "version": "1.4.0", - "resolved": "https://registry.npmmirror.com/@vue/babel-plugin-transform-vue-jsx/-/babel-plugin-transform-vue-jsx-1.4.0.tgz", - "integrity": "sha512-Fmastxw4MMx0vlgLS4XBX0XiBbUFzoMGeVXuMV08wyOfXdikAFqBTuYPR0tlk+XskL19EzHc39SgjrPGY23JnA==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/plugin-syntax-jsx": "^7.2.0", - "@vue/babel-helper-vue-jsx-merge-props": "^1.4.0", - "html-tags": "^2.0.0", - "lodash.kebabcase": "^4.1.1", - "svg-tags": "^1.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@vue/babel-plugin-transform-vue-jsx/node_modules/html-tags": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/html-tags/-/html-tags-2.0.0.tgz", - "integrity": "sha512-+Il6N8cCo2wB/Vd3gqy/8TZhTD3QvcVeQLCnZiGkGCH3JP28IgGAY41giccp2W4R3jfyJPAP318FQTa1yU7K7g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@vue/babel-preset-app": { - "version": "4.5.19", - "resolved": "https://registry.npmmirror.com/@vue/babel-preset-app/-/babel-preset-app-4.5.19.tgz", - "integrity": "sha512-VCNRiAt2P/bLo09rYt3DLe6xXUMlhJwrvU18Ddd/lYJgC7s8+wvhgYs+MTx4OiAXdu58drGwSBO9SPx7C6J82Q==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.0", - "@babel/helper-compilation-targets": "^7.9.6", - "@babel/helper-module-imports": "^7.8.3", - "@babel/plugin-proposal-class-properties": "^7.8.3", - "@babel/plugin-proposal-decorators": "^7.8.3", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-jsx": "^7.8.3", - "@babel/plugin-transform-runtime": "^7.11.0", - "@babel/preset-env": "^7.11.0", - "@babel/runtime": "^7.11.0", - "@vue/babel-plugin-jsx": "^1.0.3", - "@vue/babel-preset-jsx": "^1.2.4", - "babel-plugin-dynamic-import-node": "^2.3.3", - "core-js": "^3.6.5", - "core-js-compat": "^3.6.5", - "semver": "^6.1.0" - }, - "peerDependencies": { - "@babel/core": "*", - "core-js": "^3", - "vue": "^2 || ^3.0.0-0" - }, - "peerDependenciesMeta": { - "core-js": { - "optional": true - }, - "vue": { - "optional": true - } - } - }, - "node_modules/@vue/babel-preset-jsx": { - "version": "1.4.0", - "resolved": "https://registry.npmmirror.com/@vue/babel-preset-jsx/-/babel-preset-jsx-1.4.0.tgz", - "integrity": "sha512-QmfRpssBOPZWL5xw7fOuHNifCQcNQC1PrOo/4fu6xlhlKJJKSA3HqX92Nvgyx8fqHZTUGMPHmFA+IDqwXlqkSA==", - "dev": true, - "dependencies": { - "@vue/babel-helper-vue-jsx-merge-props": "^1.4.0", - "@vue/babel-plugin-transform-vue-jsx": "^1.4.0", - "@vue/babel-sugar-composition-api-inject-h": "^1.4.0", - "@vue/babel-sugar-composition-api-render-instance": "^1.4.0", - "@vue/babel-sugar-functional-vue": "^1.4.0", - "@vue/babel-sugar-inject-h": "^1.4.0", - "@vue/babel-sugar-v-model": "^1.4.0", - "@vue/babel-sugar-v-on": "^1.4.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0", - "vue": "*" - }, - "peerDependenciesMeta": { - "vue": { - "optional": true - } - } - }, - "node_modules/@vue/babel-sugar-composition-api-inject-h": { - "version": "1.4.0", - "resolved": "https://registry.npmmirror.com/@vue/babel-sugar-composition-api-inject-h/-/babel-sugar-composition-api-inject-h-1.4.0.tgz", - "integrity": "sha512-VQq6zEddJHctnG4w3TfmlVp5FzDavUSut/DwR0xVoe/mJKXyMcsIibL42wPntozITEoY90aBV0/1d2KjxHU52g==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-jsx": "^7.2.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@vue/babel-sugar-composition-api-render-instance": { - "version": "1.4.0", - "resolved": "https://registry.npmmirror.com/@vue/babel-sugar-composition-api-render-instance/-/babel-sugar-composition-api-render-instance-1.4.0.tgz", - "integrity": "sha512-6ZDAzcxvy7VcnCjNdHJ59mwK02ZFuP5CnucloidqlZwVQv5CQLijc3lGpR7MD3TWFi78J7+a8J56YxbCtHgT9Q==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-jsx": "^7.2.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@vue/babel-sugar-functional-vue": { - "version": "1.4.0", - "resolved": "https://registry.npmmirror.com/@vue/babel-sugar-functional-vue/-/babel-sugar-functional-vue-1.4.0.tgz", - "integrity": "sha512-lTEB4WUFNzYt2In6JsoF9sAYVTo84wC4e+PoZWSgM6FUtqRJz7wMylaEhSRgG71YF+wfLD6cc9nqVeXN2rwBvw==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-jsx": "^7.2.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@vue/babel-sugar-inject-h": { - "version": "1.4.0", - "resolved": "https://registry.npmmirror.com/@vue/babel-sugar-inject-h/-/babel-sugar-inject-h-1.4.0.tgz", - "integrity": "sha512-muwWrPKli77uO2fFM7eA3G1lAGnERuSz2NgAxuOLzrsTlQl8W4G+wwbM4nB6iewlKbwKRae3nL03UaF5ffAPMA==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-jsx": "^7.2.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@vue/babel-sugar-v-model": { - "version": "1.4.0", - "resolved": "https://registry.npmmirror.com/@vue/babel-sugar-v-model/-/babel-sugar-v-model-1.4.0.tgz", - "integrity": "sha512-0t4HGgXb7WHYLBciZzN5s0Hzqan4Ue+p/3FdQdcaHAb7s5D9WZFGoSxEZHrR1TFVZlAPu1bejTKGeAzaaG3NCQ==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-jsx": "^7.2.0", - "@vue/babel-helper-vue-jsx-merge-props": "^1.4.0", - "@vue/babel-plugin-transform-vue-jsx": "^1.4.0", - "camelcase": "^5.0.0", - "html-tags": "^2.0.0", - "svg-tags": "^1.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@vue/babel-sugar-v-model/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@vue/babel-sugar-v-model/node_modules/html-tags": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/html-tags/-/html-tags-2.0.0.tgz", - "integrity": "sha512-+Il6N8cCo2wB/Vd3gqy/8TZhTD3QvcVeQLCnZiGkGCH3JP28IgGAY41giccp2W4R3jfyJPAP318FQTa1yU7K7g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@vue/babel-sugar-v-on": { - "version": "1.4.0", - "resolved": "https://registry.npmmirror.com/@vue/babel-sugar-v-on/-/babel-sugar-v-on-1.4.0.tgz", - "integrity": "sha512-m+zud4wKLzSKgQrWwhqRObWzmTuyzl6vOP7024lrpeJM4x2UhQtRDLgYjXAw9xBXjCwS0pP9kXjg91F9ZNo9JA==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-jsx": "^7.2.0", - "@vue/babel-plugin-transform-vue-jsx": "^1.4.0", - "camelcase": "^5.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@vue/babel-sugar-v-on/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@vue/cli-overlay": { - "version": "4.5.19", - "resolved": "https://registry.npmmirror.com/@vue/cli-overlay/-/cli-overlay-4.5.19.tgz", - "integrity": "sha512-GdxvNSmOw7NHIazCO8gTK+xZbaOmScTtxj6eHVeMbYpDYVPJ+th3VMLWNpw/b6uOjwzzcyKlA5dRQ1DAb+gF/g==", - "dev": true - }, - "node_modules/@vue/cli-plugin-babel": { - "version": "4.5.19", - "resolved": "https://registry.npmmirror.com/@vue/cli-plugin-babel/-/cli-plugin-babel-4.5.19.tgz", - "integrity": "sha512-8ebXzaMW9KNTMAN6+DzkhFsjty1ieqT7hIW5Lbk4v30Qhfjkms7lBWyXPGkoq+wAikXFa1Gnam2xmWOBqDDvWg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.0", - "@vue/babel-preset-app": "^4.5.19", - "@vue/cli-shared-utils": "^4.5.19", - "babel-loader": "^8.1.0", - "cache-loader": "^4.1.0", - "thread-loader": "^2.1.3", - "webpack": "^4.0.0" - }, - "peerDependencies": { - "@vue/cli-service": "^3.0.0 || ^4.0.0-0" - } - }, - "node_modules/@vue/cli-plugin-eslint": { - "version": "4.5.19", - "resolved": "https://registry.npmmirror.com/@vue/cli-plugin-eslint/-/cli-plugin-eslint-4.5.19.tgz", - "integrity": "sha512-53sa4Pu9j5KajesFlj494CcO8vVo3e3nnZ1CCKjGGnrF90id1rUeepcFfz5XjwfEtbJZp2x/NoX/EZE6zCzSFQ==", - "dev": true, - "dependencies": { - "@vue/cli-shared-utils": "^4.5.19", - "eslint-loader": "^2.2.1", - "globby": "^9.2.0", - "inquirer": "^7.1.0", - "webpack": "^4.0.0", - "yorkie": "^2.0.0" - }, - "peerDependencies": { - "@vue/cli-service": "^3.0.0 || ^4.0.0-0", - "eslint": ">= 1.6.0 < 7.0.0" - } - }, - "node_modules/@vue/cli-plugin-router": { - "version": "4.5.19", - "resolved": "https://registry.npmmirror.com/@vue/cli-plugin-router/-/cli-plugin-router-4.5.19.tgz", - "integrity": "sha512-3icGzH1IbVYmMMsOwYa0lal/gtvZLebFXdE5hcQJo2mnTwngXGMTyYAzL56EgHBPjbMmRpyj6Iw9k4aVInVX6A==", - "dev": true, - "dependencies": { - "@vue/cli-shared-utils": "^4.5.19" - }, - "peerDependencies": { - "@vue/cli-service": "^3.0.0 || ^4.0.0-0" - } - }, - "node_modules/@vue/cli-plugin-vuex": { - "version": "4.5.19", - "resolved": "https://registry.npmmirror.com/@vue/cli-plugin-vuex/-/cli-plugin-vuex-4.5.19.tgz", - "integrity": "sha512-DUmfdkG3pCdkP7Iznd87RfE9Qm42mgp2hcrNcYQYSru1W1gX2dG/JcW8bxmeGSa06lsxi9LEIc/QD1yPajSCZw==", - "dev": true, - "peerDependencies": { - "@vue/cli-service": "^3.0.0 || ^4.0.0-0" - } - }, - "node_modules/@vue/cli-service": { - "version": "4.5.19", - "resolved": "https://registry.npmmirror.com/@vue/cli-service/-/cli-service-4.5.19.tgz", - "integrity": "sha512-+Wpvj8fMTCt9ZPOLu5YaLkFCQmB4MrZ26aRmhhKiCQ/4PMoL6mLezfqdt6c+m2htM+1WV5RunRo+0WHl2DfwZA==", - "dev": true, - "dependencies": { - "@intervolga/optimize-cssnano-plugin": "^1.0.5", - "@soda/friendly-errors-webpack-plugin": "^1.7.1", - "@soda/get-current-script": "^1.0.0", - "@types/minimist": "^1.2.0", - "@types/webpack": "^4.0.0", - "@types/webpack-dev-server": "^3.11.0", - "@vue/cli-overlay": "^4.5.19", - "@vue/cli-plugin-router": "^4.5.19", - "@vue/cli-plugin-vuex": "^4.5.19", - "@vue/cli-shared-utils": "^4.5.19", - "@vue/component-compiler-utils": "^3.1.2", - "@vue/preload-webpack-plugin": "^1.1.0", - "@vue/web-component-wrapper": "^1.2.0", - "acorn": "^7.4.0", - "acorn-walk": "^7.1.1", - "address": "^1.1.2", - "autoprefixer": "^9.8.6", - "browserslist": "^4.12.0", - "cache-loader": "^4.1.0", - "case-sensitive-paths-webpack-plugin": "^2.3.0", - "cli-highlight": "^2.1.4", - "clipboardy": "^2.3.0", - "cliui": "^6.0.0", - "copy-webpack-plugin": "^5.1.1", - "css-loader": "^3.5.3", - "cssnano": "^4.1.10", - "debug": "^4.1.1", - "default-gateway": "^5.0.5", - "dotenv": "^8.2.0", - "dotenv-expand": "^5.1.0", - "file-loader": "^4.2.0", - "fs-extra": "^7.0.1", - "globby": "^9.2.0", - "hash-sum": "^2.0.0", - "html-webpack-plugin": "^3.2.0", - "launch-editor-middleware": "^2.2.1", - "lodash.defaultsdeep": "^4.6.1", - "lodash.mapvalues": "^4.6.0", - "lodash.transform": "^4.6.0", - "mini-css-extract-plugin": "^0.9.0", - "minimist": "^1.2.5", - "pnp-webpack-plugin": "^1.6.4", - "portfinder": "^1.0.26", - "postcss-loader": "^3.0.0", - "ssri": "^8.0.1", - "terser-webpack-plugin": "^1.4.4", - "thread-loader": "^2.1.3", - "url-loader": "^2.2.0", - "vue-loader": "^15.9.2", - "vue-style-loader": "^4.1.2", - "webpack": "^4.0.0", - "webpack-bundle-analyzer": "^3.8.0", - "webpack-chain": "^6.4.0", - "webpack-dev-server": "^3.11.0", - "webpack-merge": "^4.2.2" - }, - "bin": { - "vue-cli-service": "bin/vue-cli-service.js" - }, - "engines": { - "node": ">=8" - }, - "optionalDependencies": { - "vue-loader-v16": "npm:vue-loader@^16.1.0" - }, - "peerDependencies": { - "@vue/compiler-sfc": "^3.0.0-beta.14", - "vue-template-compiler": "^2.0.0" - }, - "peerDependenciesMeta": { - "@vue/compiler-sfc": { - "optional": true - }, - "less-loader": { - "optional": true - }, - "pug-plain-loader": { - "optional": true - }, - "raw-loader": { - "optional": true - }, - "sass-loader": { - "optional": true - }, - "stylus-loader": { - "optional": true - }, - "vue-template-compiler": { - "optional": true - } - } - }, - "node_modules/@vue/cli-shared-utils": { - "version": "4.5.19", - "resolved": "https://registry.npmmirror.com/@vue/cli-shared-utils/-/cli-shared-utils-4.5.19.tgz", - "integrity": "sha512-JYpdsrC/d9elerKxbEUtmSSU6QRM60rirVubOewECHkBHj+tLNznWq/EhCjswywtePyLaMUK25eTqnTSZlEE+g==", - "dev": true, - "dependencies": { - "@achrinza/node-ipc": "9.2.2", - "@hapi/joi": "^15.0.1", - "chalk": "^2.4.2", - "execa": "^1.0.0", - "launch-editor": "^2.2.1", - "lru-cache": "^5.1.1", - "open": "^6.3.0", - "ora": "^3.4.0", - "read-pkg": "^5.1.1", - "request": "^2.88.2", - "semver": "^6.1.0", - "strip-ansi": "^6.0.0" - } - }, - "node_modules/@vue/compiler-core": { - "version": "3.4.27", - "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.4.27.tgz", - "integrity": "sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.24.4", - "@vue/shared": "3.4.27", - "entities": "^4.5.0", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.0" - } - }, - "node_modules/@vue/compiler-dom": { - "version": "3.4.27", - "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.4.27.tgz", - "integrity": "sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==", - "dev": true, - "dependencies": { - "@vue/compiler-core": "3.4.27", - "@vue/shared": "3.4.27" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.4.27", - "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.4.27.tgz", - "integrity": "sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.24.4", - "@vue/compiler-core": "3.4.27", - "@vue/compiler-dom": "3.4.27", - "@vue/compiler-ssr": "3.4.27", - "@vue/shared": "3.4.27", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.10", - "postcss": "^8.4.38", - "source-map-js": "^1.2.0" - } - }, - "node_modules/@vue/compiler-sfc/node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.4.27", - "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.4.27.tgz", - "integrity": "sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==", - "dev": true, - "dependencies": { - "@vue/compiler-dom": "3.4.27", - "@vue/shared": "3.4.27" - } - }, - "node_modules/@vue/component-compiler-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmmirror.com/@vue/component-compiler-utils/-/component-compiler-utils-3.3.0.tgz", - "integrity": "sha512-97sfH2mYNU+2PzGrmK2haqffDpVASuib9/w2/noxiFi31Z54hW+q3izKQXXQZSNhtiUpAI36uSuYepeBe4wpHQ==", - "dev": true, - "dependencies": { - "consolidate": "^0.15.1", - "hash-sum": "^1.0.2", - "lru-cache": "^4.1.2", - "merge-source-map": "^1.1.0", - "postcss": "^7.0.36", - "postcss-selector-parser": "^6.0.2", - "source-map": "~0.6.1", - "vue-template-es2015-compiler": "^1.9.0" - }, - "optionalDependencies": { - "prettier": "^1.18.2 || ^2.0.0" - } - }, - "node_modules/@vue/component-compiler-utils/node_modules/hash-sum": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/hash-sum/-/hash-sum-1.0.2.tgz", - "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==", - "dev": true - }, - "node_modules/@vue/component-compiler-utils/node_modules/lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "node_modules/@vue/component-compiler-utils/node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", - "dev": true - }, - "node_modules/@vue/preload-webpack-plugin": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/@vue/preload-webpack-plugin/-/preload-webpack-plugin-1.1.2.tgz", - "integrity": "sha512-LIZMuJk38pk9U9Ur4YzHjlIyMuxPlACdBIHH9/nGYVTsaGKOSnSuELiE8vS9wa+dJpIYspYUOqk+L1Q4pgHQHQ==", - "dev": true, - "engines": { - "node": ">=6.0.0" - }, - "peerDependencies": { - "html-webpack-plugin": ">=2.26.0", - "webpack": ">=4.0.0" - } - }, - "node_modules/@vue/shared": { - "version": "3.4.27", - "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.4.27.tgz", - "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==", - "dev": true - }, - "node_modules/@vue/web-component-wrapper": { - "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/@vue/web-component-wrapper/-/web-component-wrapper-1.3.0.tgz", - "integrity": "sha512-Iu8Tbg3f+emIIMmI2ycSI8QcEuAUgPTgHwesDU1eKMLE4YC/c/sFbGc70QgMq31ijRftV0R7vCm9co6rldCeOA==", - "dev": true - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.9.0", - "resolved": "https://registry.npmmirror.com/@webassemblyjs/ast/-/ast-1.9.0.tgz", - "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", - "dev": true, - "dependencies": { - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmmirror.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", - "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.9.0", - "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", - "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.9.0", - "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", - "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-code-frame": { - "version": "1.9.0", - "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", - "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", - "dev": true, - "dependencies": { - "@webassemblyjs/wast-printer": "1.9.0" - } - }, - "node_modules/@webassemblyjs/helper-fsm": { - "version": "1.9.0", - "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", - "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-module-context": { - "version": "1.9.0", - "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", - "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.9.0", - "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", - "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.9.0", - "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", - "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.9.0", - "resolved": "https://registry.npmmirror.com/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", - "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", - "dev": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.9.0", - "resolved": "https://registry.npmmirror.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", - "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", - "dev": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.9.0", - "resolved": "https://registry.npmmirror.com/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", - "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", - "dev": true - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.9.0", - "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", - "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/helper-wasm-section": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-opt": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "@webassemblyjs/wast-printer": "1.9.0" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.9.0", - "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", - "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.9.0", - "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", - "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", - "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" - } - }, - "node_modules/@webassemblyjs/wast-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmmirror.com/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", - "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/floating-point-hex-parser": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-code-frame": "1.9.0", - "@webassemblyjs/helper-fsm": "1.9.0", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.9.0", - "resolved": "https://registry.npmmirror.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", - "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmmirror.com/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dev": true, - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmmirror.com/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/address": { - "version": "1.2.2", - "resolved": "https://registry.npmmirror.com/address/-/address-1.2.2.tgz", - "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/ag-charts-types": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ag-charts-types/-/ag-charts-types-10.1.0.tgz", - "integrity": "sha512-pk9ft8hbgTXJ/thI/SEUR1BoauNplYExpcHh7tMOqVikoDsta1O15TB1ZL4XWnl4TPIzROBmONKsz7d8a2HBuQ==" - }, - "node_modules/ag-grid-community": { - "version": "32.1.0", - "resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-32.1.0.tgz", - "integrity": "sha512-RVvkjRH61nuCXwIqTKQPqNbKR+8cGBKw7S1qmmMXsy0pCBAJaQn4kL3v31hKHxDtV4bPscBXLFKGnKzHuss0GQ==", - "dependencies": { - "ag-charts-types": "10.1.0" - } - }, - "node_modules/ag-grid-vue": { - "version": "31.3.4", - "resolved": "https://registry.npmjs.org/ag-grid-vue/-/ag-grid-vue-31.3.4.tgz", - "integrity": "sha512-fB+PqaYuyyis1DzGnIeCPZvlc19NfIwA+C/9kmLzVCwUW6Iovp0MT5J5sD3S+kEVKsztWzgVyKQ7DxhMsEGPtg==", - "dependencies": { - "ag-grid-community": "31.3.4" - }, - "peerDependencies": { - "vue": ">= 2.2 <= 2.5.17 || >= 2.5.20", - "vue-property-decorator": "^7.2.0 || ^8.0.0 || ^9.1.2" - } - }, - "node_modules/ag-grid-vue/node_modules/ag-grid-community": { - "version": "31.3.4", - "resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-31.3.4.tgz", - "integrity": "sha512-jOxQO86C6eLnk1GdP24HB6aqaouFzMWizgfUwNY5MnetiWzz9ZaAmOGSnW/XBvdjXvC5Fpk3gSbvVKKQ7h9kBw==" - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-errors": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "dev": true, - "peerDependencies": { - "ajv": ">=5.0.0" - } - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/alphanum-sort": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz", - "integrity": "sha512-0FcBfdcmaumGPQ0qPn7Q5qTgz/ooXgIyp1rf8ik5bGX8mpE2YHjC0P/eyQvxu1GURYQgq9ozf2mteQ5ZD9YiyQ==", - "dev": true - }, - "node_modules/ansi-colors": { - "version": "3.2.4", - "resolved": "https://registry.npmmirror.com/ansi-colors/-/ansi-colors-3.2.4.tgz", - "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-html-community": { - "version": "0.0.8", - "resolved": "https://registry.npmmirror.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz", - "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", - "dev": true, - "engines": [ - "node >= 0.8.0" - ], - "bin": { - "ansi-html": "bin/ansi-html" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true - }, - "node_modules/arch": { - "version": "2.2.0", - "resolved": "https://registry.npmmirror.com/arch/-/arch-2.2.0.tgz", - "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", - "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.5", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true - }, - "node_modules/array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", - "dev": true, - "dependencies": { - "array-uniq": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmmirror.com/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array.prototype.reduce": { - "version": "1.0.7", - "resolved": "https://registry.npmmirror.com/array.prototype.reduce/-/array.prototype.reduce-1.0.7.tgz", - "integrity": "sha512-mzmiUCVwtiD4lgxYP8g7IYy8El8p2CSMePvIbTS7gchKir/L1fgJrk0yDKmAX6mnRQFKNADYIk8nNlTris5H1Q==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-array-method-boxes-properly": "^1.0.0", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", - "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.2.1", - "get-intrinsic": "^1.2.3", - "is-array-buffer": "^3.0.4", - "is-shared-array-buffer": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmmirror.com/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/asn1.js": { - "version": "4.10.1", - "resolved": "https://registry.npmmirror.com/asn1.js/-/asn1.js-4.10.1.tgz", - "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", - "dev": true, - "dependencies": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/asn1.js/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/assert": { - "version": "1.5.1", - "resolved": "https://registry.npmmirror.com/assert/-/assert-1.5.1.tgz", - "integrity": "sha512-zzw1uCAgLbsKwBfFc8CX78DDg+xZeBksSO3vwVIDDN5i94eOrPsSSyiVhmsSABFDM/OcpE2aagCat9dnWQLG1A==", - "dev": true, - "dependencies": { - "object.assign": "^4.1.4", - "util": "^0.10.4" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/assert/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true - }, - "node_modules/assert/node_modules/util": { - "version": "0.10.4", - "resolved": "https://registry.npmmirror.com/util/-/util-0.10.4.tgz", - "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", - "dev": true, - "dependencies": { - "inherits": "2.0.3" - } - }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmmirror.com/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "dev": true, - "dependencies": { - "lodash": "^4.17.14" - } - }, - "node_modules/async-each": { - "version": "1.0.6", - "resolved": "https://registry.npmmirror.com/async-each/-/async-each-1.0.6.tgz", - "integrity": "sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ] - }, - "node_modules/async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "dev": true - }, - "node_modules/async-validator": { - "version": "1.8.5", - "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-1.8.5.tgz", - "integrity": "sha512-tXBM+1m056MAX0E8TL2iCjg8WvSyXu0Zc8LNtYqrVeyoL3+esHRZ4SieE9fKQyyU09uONjnMEjrNBMqT0mbvmA==", - "dependencies": { - "babel-runtime": "6.x" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true, - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/autoprefixer": { - "version": "9.8.8", - "resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-9.8.8.tgz", - "integrity": "sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA==", - "dev": true, - "dependencies": { - "browserslist": "^4.12.0", - "caniuse-lite": "^1.0.30001109", - "normalize-range": "^0.1.2", - "num2fraction": "^1.2.2", - "picocolors": "^0.2.1", - "postcss": "^7.0.32", - "postcss-value-parser": "^4.1.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "funding": { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - } - }, - "node_modules/autoprefixer/node_modules/picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "dev": true - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmmirror.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmmirror.com/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.13.0", - "resolved": "https://registry.npmmirror.com/aws4/-/aws4-1.13.0.tgz", - "integrity": "sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==", - "dev": true - }, - "node_modules/axios": { - "version": "0.31.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.31.1.tgz", - "integrity": "sha512-Ef8DUZSZQP6igY48mjGaoEjwhely97lserep0IFJifBH4YdKvwH5eMLniy3kig2HQoBNR8EkZpDjowxwTJcmbg==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.4", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/axios/node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/babel-eslint": { - "version": "10.1.0", - "resolved": "https://registry.npmmirror.com/babel-eslint/-/babel-eslint-10.1.0.tgz", - "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.0", - "@babel/traverse": "^7.7.0", - "@babel/types": "^7.7.0", - "eslint-visitor-keys": "^1.0.0", - "resolve": "^1.12.0" - }, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "eslint": ">= 4.12.1" - } - }, - "node_modules/babel-helper-vue-jsx-merge-props": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz", - "integrity": "sha512-gsLiKK7Qrb7zYJNgiXKpXblxbV5ffSwR0f5whkPAaBAR4fhi6bwRZxX9wBlIc5M/v8CCkXUbXZL4N/nSE97cqg==" - }, - "node_modules/babel-loader": { - "version": "8.3.0", - "resolved": "https://registry.npmmirror.com/babel-loader/-/babel-loader-8.3.0.tgz", - "integrity": "sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==", - "dev": true, - "dependencies": { - "find-cache-dir": "^3.3.1", - "loader-utils": "^2.0.0", - "make-dir": "^3.1.0", - "schema-utils": "^2.6.5" - }, - "engines": { - "node": ">= 8.9" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "webpack": ">=2" - } - }, - "node_modules/babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmmirror.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dev": true, - "dependencies": { - "object.assign": "^4.1.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.11", - "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", - "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.2", - "semver": "^6.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.10.4", - "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", - "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.1", - "core-js-compat": "^3.36.1" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.2", - "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", - "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmmirror.com/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", - "dependencies": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "node_modules/babel-runtime/node_modules/core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmmirror.com/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "hasInstallScript": true - }, - "node_modules/babel-runtime/node_modules/regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmmirror.com/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmmirror.com/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", - "dev": true - }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, - "node_modules/bfj": { - "version": "6.1.2", - "resolved": "https://registry.npmmirror.com/bfj/-/bfj-6.1.2.tgz", - "integrity": "sha512-BmBJa4Lip6BPRINSZ0BPEIfB1wUY/9rwbwvIHQA1KjX9om29B6id0wnWXq7m3bn5JrUVjeOTnVuhPT1FiHwPGw==", - "dev": true, - "dependencies": { - "bluebird": "^3.5.5", - "check-types": "^8.0.3", - "hoopy": "^0.1.4", - "tryer": "^1.0.1" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmmirror.com/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmmirror.com/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "optional": true, - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmmirror.com/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - }, - "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmmirror.com/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/bonjour": { - "version": "3.5.0", - "resolved": "https://registry.npmmirror.com/bonjour/-/bonjour-3.5.0.tgz", - "integrity": "sha512-RaVTblr+OnEli0r/ud8InrU7D+G0y6aJhlxaLa6Pwty4+xoxboF1BsUI45tujvRpbj9dQVoglChqonGAsjEBYg==", - "dev": true, - "dependencies": { - "array-flatten": "^2.1.0", - "deep-equal": "^1.0.1", - "dns-equal": "^1.0.0", - "dns-txt": "^2.0.2", - "multicast-dns": "^6.0.1", - "multicast-dns-service-types": "^1.1.0" - } - }, - "node_modules/bonjour/node_modules/array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", - "dev": true - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmmirror.com/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/braces/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", - "dev": true - }, - "node_modules/browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "dependencies": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dev": true, - "dependencies": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "node_modules/browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/browserify-rsa": { - "version": "4.1.0", - "resolved": "https://registry.npmmirror.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz", - "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", - "dev": true, - "dependencies": { - "bn.js": "^5.0.0", - "randombytes": "^2.0.1" - } - }, - "node_modules/browserify-sign": { - "version": "4.2.3", - "resolved": "https://registry.npmmirror.com/browserify-sign/-/browserify-sign-4.2.3.tgz", - "integrity": "sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==", - "dev": true, - "dependencies": { - "bn.js": "^5.2.1", - "browserify-rsa": "^4.1.0", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.5", - "hash-base": "~3.0", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.7", - "readable-stream": "^2.3.8", - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/browserify-sign/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmmirror.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "dependencies": { - "pako": "~1.0.5" - } - }, - "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmmirror.com/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "dev": true, - "dependencies": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/buffer-indexof": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz", - "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", - "dev": true - }, - "node_modules/buffer-json": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/buffer-json/-/buffer-json-2.0.0.tgz", - "integrity": "sha512-+jjPFVqyfF1esi9fvfUs3NqM0pH1ziZ36VP4hmA/y/Ssfo/5w5xHKfTw9BwQjoJ1w/oVtpLomqwUHKdefGyuHw==", - "dev": true - }, - "node_modules/buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", - "dev": true - }, - "node_modules/builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==", - "dev": true - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/cacache": { - "version": "12.0.4", - "resolved": "https://registry.npmmirror.com/cacache/-/cacache-12.0.4.tgz", - "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", - "dev": true, - "dependencies": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - } - }, - "node_modules/cacache/node_modules/ssri": { - "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/ssri/-/ssri-6.0.2.tgz", - "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", - "dev": true, - "dependencies": { - "figgy-pudding": "^3.5.1" - } - }, - "node_modules/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cache-loader": { - "version": "4.1.0", - "resolved": "https://registry.npmmirror.com/cache-loader/-/cache-loader-4.1.0.tgz", - "integrity": "sha512-ftOayxve0PwKzBF/GLsZNC9fJBXl8lkZE3TOsjkboHfVHVkL39iUEs1FO07A33mizmci5Dudt38UZrrYXDtbhw==", - "dev": true, - "dependencies": { - "buffer-json": "^2.0.0", - "find-cache-dir": "^3.0.0", - "loader-utils": "^1.2.3", - "mkdirp": "^0.5.1", - "neo-async": "^2.6.1", - "schema-utils": "^2.0.0" - }, - "engines": { - "node": ">= 8.9.0" - }, - "peerDependencies": { - "webpack": "^4.0.0" - } - }, - "node_modules/cache-loader/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/cache-loader/node_modules/loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-me-maybe": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/call-me-maybe/-/call-me-maybe-1.0.2.tgz", - "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", - "dev": true - }, - "node_modules/caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", - "dev": true, - "dependencies": { - "callsites": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", - "dev": true, - "dependencies": { - "caller-callsite": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/camel-case": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/camel-case/-/camel-case-3.0.0.tgz", - "integrity": "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==", - "dev": true, - "dependencies": { - "no-case": "^2.2.0", - "upper-case": "^1.1.1" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "dev": true, - "dependencies": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001625", - "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001625.tgz", - "integrity": "sha512-4KE9N2gcRH+HQhpeiRZXd+1niLB/XNLAhSy4z7fI8EzcbcPoAqjNInxVHTiTwWfTIV4w096XG8OtCOCQQKPv3w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, - "node_modules/case-sensitive-paths-webpack-plugin": { - "version": "2.4.0", - "resolved": "https://registry.npmmirror.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", - "integrity": "sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmmirror.com/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmmirror.com/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "node_modules/check-types": { - "version": "8.0.3", - "resolved": "https://registry.npmmirror.com/check-types/-/check-types-8.0.3.tgz", - "integrity": "sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ==", - "dev": true - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chokidar/node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/chokidar/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/chokidar/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, - "node_modules/chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", - "dev": true, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/ci-info": { - "version": "1.6.0", - "resolved": "https://registry.npmmirror.com/ci-info/-/ci-info-1.6.0.tgz", - "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", - "dev": true - }, - "node_modules/cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmmirror.com/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmmirror.com/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-descriptor": { - "version": "0.1.7", - "resolved": "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-0.1.7.tgz", - "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/clean-css": { - "version": "4.2.4", - "resolved": "https://registry.npmmirror.com/clean-css/-/clean-css-4.2.4.tgz", - "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", - "dev": true, - "dependencies": { - "source-map": "~0.6.0" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-highlight": { - "version": "2.1.11", - "resolved": "https://registry.npmmirror.com/cli-highlight/-/cli-highlight-2.1.11.tgz", - "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "highlight.js": "^10.7.1", - "mz": "^2.4.0", - "parse5": "^5.1.1", - "parse5-htmlparser2-tree-adapter": "^6.0.0", - "yargs": "^16.0.0" - }, - "bin": { - "highlight": "bin/highlight" - }, - "engines": { - "node": ">=8.0.0", - "npm": ">=5.0.0" - } - }, - "node_modules/cli-highlight/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/cli-highlight/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/cli-highlight/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/cli-highlight/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/cli-highlight/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-highlight/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmmirror.com/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/clipboardy": { - "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/clipboardy/-/clipboardy-2.3.0.tgz", - "integrity": "sha512-mKhiIL2DrQIsuXMgBgnfEHOZOryC7kY7YO//TN6c63wlEm3NG5tz+YgY5rVi29KCmq/QQjKYvM7a19+MDOTHOQ==", - "dev": true, - "dependencies": { - "arch": "^2.1.1", - "execa": "^1.0.0", - "is-wsl": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmmirror.com/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/coa": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/coa/-/coa-2.0.2.tgz", - "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", - "dev": true, - "dependencies": { - "@types/q": "^1.5.1", - "chalk": "^2.4.1", - "q": "^1.1.2" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/codemirror": { - "version": "5.65.16", - "resolved": "https://registry.npmmirror.com/codemirror/-/codemirror-5.65.16.tgz", - "integrity": "sha512-br21LjYmSlVL0vFCPWPfhzUCT34FM/pAdK7rRIZwa0rrtrIdotvP4Oh4GUHsu2E3IrQMCfRkL/fN3ytMNxVQvg==" - }, - "node_modules/collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", - "dev": true, - "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color": { - "version": "3.2.1", - "resolved": "https://registry.npmmirror.com/color/-/color-3.2.1.tgz", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmmirror.com/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "dev": true, - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "2.17.1", - "resolved": "https://registry.npmmirror.com/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", - "dev": true - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true - }, - "node_modules/component-emitter": { - "version": "1.3.1", - "resolved": "https://registry.npmmirror.com/component-emitter/-/component-emitter-1.3.1.tgz", - "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmmirror.com/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmmirror.com/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "dev": true, - "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmmirror.com/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "engines": [ - "node >= 0.8" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/connect-history-api-fallback": { - "version": "1.6.0", - "resolved": "https://registry.npmmirror.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", - "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", - "dev": true - }, - "node_modules/consolidate": { - "version": "0.15.1", - "resolved": "https://registry.npmmirror.com/consolidate/-/consolidate-0.15.1.tgz", - "integrity": "sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==", - "dev": true, - "dependencies": { - "bluebird": "^3.1.1" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==", - "dev": true - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dev": true, - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-disposition/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true - }, - "node_modules/copy-concurrently": { - "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "dev": true, - "dependencies": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" - } - }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmmirror.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/copy-webpack-plugin": { - "version": "5.1.2", - "resolved": "https://registry.npmmirror.com/copy-webpack-plugin/-/copy-webpack-plugin-5.1.2.tgz", - "integrity": "sha512-Uh7crJAco3AjBvgAy9Z75CjK8IG+gxaErro71THQ+vv/bl4HaQcpkexAY8KVW/T6D2W2IRr+couF/knIRkZMIQ==", - "dev": true, - "dependencies": { - "cacache": "^12.0.3", - "find-cache-dir": "^2.1.0", - "glob-parent": "^3.1.0", - "globby": "^7.1.1", - "is-glob": "^4.0.1", - "loader-utils": "^1.2.3", - "minimatch": "^3.0.4", - "normalize-path": "^3.0.0", - "p-limit": "^2.2.1", - "schema-utils": "^1.0.0", - "serialize-javascript": "^4.0.0", - "webpack-log": "^2.0.0" - }, - "engines": { - "node": ">= 6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/copy-webpack-plugin/node_modules/find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/copy-webpack-plugin/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/copy-webpack-plugin/node_modules/globby": { - "version": "7.1.1", - "resolved": "https://registry.npmmirror.com/globby/-/globby-7.1.1.tgz", - "integrity": "sha512-yANWAN2DUcBtuus5Cpd+SKROzXHs2iVXFZt/Ykrfz6SAXqacLX25NZpltE+39ceMexYF4TtEadjuSTw8+3wX4g==", - "dev": true, - "dependencies": { - "array-union": "^1.0.1", - "dir-glob": "^2.0.0", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/copy-webpack-plugin/node_modules/globby/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/copy-webpack-plugin/node_modules/ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmmirror.com/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true - }, - "node_modules/copy-webpack-plugin/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/copy-webpack-plugin/node_modules/loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/copy-webpack-plugin/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/copy-webpack-plugin/node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/copy-webpack-plugin/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/copy-webpack-plugin/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/copy-webpack-plugin/node_modules/pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/copy-webpack-plugin/node_modules/schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "dependencies": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/copy-webpack-plugin/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/copy-webpack-plugin/node_modules/slash": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/slash/-/slash-1.0.0.tgz", - "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/core-js": { - "version": "3.37.1", - "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.37.1.tgz", - "integrity": "sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==", - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-compat": { - "version": "3.37.1", - "resolved": "https://registry.npmmirror.com/core-js-compat/-/core-js-compat-3.37.1.tgz", - "integrity": "sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==", - "dev": true, - "dependencies": { - "browserslist": "^4.23.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, - "node_modules/cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "dev": true, - "dependencies": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmmirror.com/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "dev": true, - "dependencies": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - } - }, - "node_modules/create-ecdh/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "node_modules/create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmmirror.com/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/cross-spawn/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmmirror.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, - "dependencies": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - }, - "engines": { - "node": "*" - } - }, - "node_modules/css-color-names": { - "version": "0.0.4", - "resolved": "https://registry.npmmirror.com/css-color-names/-/css-color-names-0.0.4.tgz", - "integrity": "sha512-zj5D7X1U2h2zsXOAM8EyUREBnnts6H+Jm+d1M2DbiQQcUtnqgQsMrdo8JW9R80YFUmIdBZeMu5wvYM7hcgWP/Q==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/css-declaration-sorter": { - "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", - "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", - "dev": true, - "dependencies": { - "postcss": "^7.0.1", - "timsort": "^0.3.0" - }, - "engines": { - "node": ">4" - } - }, - "node_modules/css-loader": { - "version": "3.6.0", - "resolved": "https://registry.npmmirror.com/css-loader/-/css-loader-3.6.0.tgz", - "integrity": "sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "cssesc": "^3.0.0", - "icss-utils": "^4.1.1", - "loader-utils": "^1.2.3", - "normalize-path": "^3.0.0", - "postcss": "^7.0.32", - "postcss-modules-extract-imports": "^2.0.0", - "postcss-modules-local-by-default": "^3.0.2", - "postcss-modules-scope": "^2.2.0", - "postcss-modules-values": "^3.0.0", - "postcss-value-parser": "^4.1.0", - "schema-utils": "^2.7.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">= 8.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/css-loader/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/css-loader/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/css-loader/node_modules/loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmmirror.com/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-select-base-adapter": { - "version": "0.1.1", - "resolved": "https://registry.npmmirror.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", - "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", - "dev": true - }, - "node_modules/css-tree": { - "version": "1.0.0-alpha.37", - "resolved": "https://registry.npmmirror.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz", - "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", - "dev": true, - "dependencies": { - "mdn-data": "2.0.4", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmmirror.com/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "dev": true, - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cssnano": { - "version": "4.1.11", - "resolved": "https://registry.npmmirror.com/cssnano/-/cssnano-4.1.11.tgz", - "integrity": "sha512-6gZm2htn7xIPJOHY824ERgj8cNPgPxyCSnkXc4v7YvNW+TdVfzgngHcEhy/8D11kUWRUMbke+tC+AUcUsnMz2g==", - "dev": true, - "dependencies": { - "cosmiconfig": "^5.0.0", - "cssnano-preset-default": "^4.0.8", - "is-resolvable": "^1.0.0", - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/cssnano-preset-default": { - "version": "4.0.8", - "resolved": "https://registry.npmmirror.com/cssnano-preset-default/-/cssnano-preset-default-4.0.8.tgz", - "integrity": "sha512-LdAyHuq+VRyeVREFmuxUZR1TXjQm8QQU/ktoo/x7bz+SdOge1YKc5eMN6pRW7YWBmyq59CqYba1dJ5cUukEjLQ==", - "dev": true, - "dependencies": { - "css-declaration-sorter": "^4.0.1", - "cssnano-util-raw-cache": "^4.0.1", - "postcss": "^7.0.0", - "postcss-calc": "^7.0.1", - "postcss-colormin": "^4.0.3", - "postcss-convert-values": "^4.0.1", - "postcss-discard-comments": "^4.0.2", - "postcss-discard-duplicates": "^4.0.2", - "postcss-discard-empty": "^4.0.1", - "postcss-discard-overridden": "^4.0.1", - "postcss-merge-longhand": "^4.0.11", - "postcss-merge-rules": "^4.0.3", - "postcss-minify-font-values": "^4.0.2", - "postcss-minify-gradients": "^4.0.2", - "postcss-minify-params": "^4.0.2", - "postcss-minify-selectors": "^4.0.2", - "postcss-normalize-charset": "^4.0.1", - "postcss-normalize-display-values": "^4.0.2", - "postcss-normalize-positions": "^4.0.2", - "postcss-normalize-repeat-style": "^4.0.2", - "postcss-normalize-string": "^4.0.2", - "postcss-normalize-timing-functions": "^4.0.2", - "postcss-normalize-unicode": "^4.0.1", - "postcss-normalize-url": "^4.0.1", - "postcss-normalize-whitespace": "^4.0.2", - "postcss-ordered-values": "^4.1.2", - "postcss-reduce-initial": "^4.0.3", - "postcss-reduce-transforms": "^4.0.2", - "postcss-svgo": "^4.0.3", - "postcss-unique-selectors": "^4.0.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/cssnano-util-get-arguments": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", - "integrity": "sha512-6RIcwmV3/cBMG8Aj5gucQRsJb4vv4I4rn6YjPbVWd5+Pn/fuG+YseGvXGk00XLkoZkaj31QOD7vMUpNPC4FIuw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/cssnano-util-get-match": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", - "integrity": "sha512-JPMZ1TSMRUPVIqEalIBNoBtAYbi8okvcFns4O0YIhcdGebeYZK7dMyHJiQ6GqNBA9kE0Hym4Aqym5rPdsV/4Cw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/cssnano-util-raw-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", - "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/cssnano-util-same-parent": { - "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", - "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/csso": { - "version": "4.2.0", - "resolved": "https://registry.npmmirror.com/csso/-/csso-4.2.0.tgz", - "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", - "dev": true, - "dependencies": { - "css-tree": "^1.1.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/csso/node_modules/css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmmirror.com/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "dev": true, - "dependencies": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/csso/node_modules/mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", - "dev": true - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" - }, - "node_modules/cyclist": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/cyclist/-/cyclist-1.0.2.tgz", - "integrity": "sha512-0sVXIohTfLqVIW3kb/0n6IiWF3Ifj5nm2XaSrLq2DI6fKIGa2fYAZdk917rUneaeLVpYfFcyXE2ft0fe3remsA==", - "dev": true - }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmmirror.com/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/data-view-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz", - "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", - "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", - "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/de-indent": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz", - "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", - "dev": true - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decode-uri-component": { - "version": "0.2.2", - "resolved": "https://registry.npmmirror.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/deep-equal": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/deep-equal/-/deep-equal-1.1.2.tgz", - "integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==", - "dev": true, - "dependencies": { - "is-arguments": "^1.1.1", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.5.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/deepmerge": { - "version": "1.5.2", - "resolved": "https://registry.npmmirror.com/deepmerge/-/deepmerge-1.5.2.tgz", - "integrity": "sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-gateway": { - "version": "5.0.5", - "resolved": "https://registry.npmmirror.com/default-gateway/-/default-gateway-5.0.5.tgz", - "integrity": "sha512-z2RnruVmj8hVMmAnEJMTIJNijhKCDiGjbLP+BHJFOT7ld3Bo5qcIBpVYDniqhbMIIf+jZDlkP2MkPXiQy/DBLA==", - "dev": true, - "dependencies": { - "execa": "^3.3.0" - }, - "engines": { - "node": "^8.12.0 || >=9.7.0" - } - }, - "node_modules/default-gateway/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/default-gateway/node_modules/execa": { - "version": "3.4.0", - "resolved": "https://registry.npmmirror.com/execa/-/execa-3.4.0.tgz", - "integrity": "sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "p-finally": "^2.0.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": "^8.12.0 || >=9.7.0" - } - }, - "node_modules/default-gateway/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-gateway/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-gateway/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/default-gateway/node_modules/p-finally": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/p-finally/-/p-finally-2.0.1.tgz", - "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/default-gateway/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/default-gateway/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/default-gateway/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/default-gateway/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dev": true, - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/del": { - "version": "4.1.1", - "resolved": "https://registry.npmmirror.com/del/-/del-4.1.1.tgz", - "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", - "dev": true, - "dependencies": { - "@types/glob": "^7.1.1", - "globby": "^6.1.0", - "is-path-cwd": "^2.0.0", - "is-path-in-cwd": "^2.0.0", - "p-map": "^2.0.0", - "pify": "^4.0.1", - "rimraf": "^2.6.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/del/node_modules/globby": { - "version": "6.1.0", - "resolved": "https://registry.npmmirror.com/globby/-/globby-6.1.0.tgz", - "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", - "dev": true, - "dependencies": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/del/node_modules/globby/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/des.js": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/des.js/-/des.js-1.1.0.tgz", - "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "dev": true, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true - }, - "node_modules/diff-match-patch": { - "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/diff-match-patch/-/diff-match-patch-1.0.5.tgz", - "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==" - }, - "node_modules/diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmmirror.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dev": true, - "dependencies": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - } - }, - "node_modules/diffie-hellman/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/dir-glob": { - "version": "2.2.2", - "resolved": "https://registry.npmmirror.com/dir-glob/-/dir-glob-2.2.2.tgz", - "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", - "dev": true, - "dependencies": { - "path-type": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", - "dev": true - }, - "node_modules/dns-packet": { - "version": "1.3.4", - "resolved": "https://registry.npmmirror.com/dns-packet/-/dns-packet-1.3.4.tgz", - "integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==", - "dev": true, - "dependencies": { - "ip": "^1.1.0", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/dns-txt": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/dns-txt/-/dns-txt-2.0.2.tgz", - "integrity": "sha512-Ix5PrWjphuSoUXV/Zv5gaFHjnaJtb02F2+Si3Ht9dyJ87+Z/lMmy+dpNHtTGraNK958ndXq2i+GLkWsWHcKaBQ==", - "dev": true, - "dependencies": { - "buffer-indexof": "^1.0.0" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dom-converter": { - "version": "0.2.0", - "resolved": "https://registry.npmmirror.com/dom-converter/-/dom-converter-0.2.0.tgz", - "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", - "dev": true, - "dependencies": { - "utila": "~0.4" - } - }, - "node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "dev": true, - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/dom-serializer/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmmirror.com/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "dev": true, - "engines": { - "node": ">=0.4", - "npm": ">=1.2" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmmirror.com/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "dev": true, - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmmirror.com/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dev": true, - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmmirror.com/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dotenv": { - "version": "8.6.0", - "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-8.6.0.tgz", - "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/dotenv-expand": { - "version": "5.1.0", - "resolved": "https://registry.npmmirror.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz", - "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", - "dev": true - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmmirror.com/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, - "node_modules/duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmmirror.com/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/easy-stack": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/easy-stack/-/easy-stack-1.0.1.tgz", - "integrity": "sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmmirror.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true - }, - "node_modules/ejs": { - "version": "2.7.4", - "resolved": "https://registry.npmmirror.com/ejs/-/ejs-2.7.4.tgz", - "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==", - "dev": true, - "hasInstallScript": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.4.785", - "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.785.tgz", - "integrity": "sha512-V94FSLwW6V1JYeYT/pl23zIdwiAbeod7gBjyud2uhuWTa2yV3gZ4IaWt6l0t808y4l6NwOspyR5hiENGrYQqvA==", - "dev": true - }, - "node_modules/element-ui": { - "version": "2.15.14", - "resolved": "https://registry.npmmirror.com/element-ui/-/element-ui-2.15.14.tgz", - "integrity": "sha512-2v9fHL0ZGINotOlRIAJD5YuVB8V7WKxrE9Qy7dXhRipa035+kF7WuU/z+tEmLVPBcJ0zt8mOu1DKpWcVzBK8IA==", - "dependencies": { - "async-validator": "~1.8.1", - "babel-helper-vue-jsx-merge-props": "^2.0.0", - "deepmerge": "^1.2.0", - "normalize-wheel": "^1.0.1", - "resize-observer-polyfill": "^1.5.0", - "throttle-debounce": "^1.0.1" - }, - "peerDependencies": { - "vue": "^2.5.17" - } - }, - "node_modules/elliptic": { - "version": "6.5.5", - "resolved": "https://registry.npmmirror.com/elliptic/-/elliptic-6.5.5.tgz", - "integrity": "sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw==", - "dev": true, - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/elliptic/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/enhanced-resolve": { - "version": "4.5.0", - "resolved": "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", - "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.5.0", - "tapable": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/enhanced-resolve/node_modules/memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmmirror.com/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "dev": true, - "dependencies": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - }, - "engines": { - "node": ">=4.3.0 <5.0.0 || >=5.10" - } - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/errno": { - "version": "0.1.8", - "resolved": "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "dev": true, - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/error-stack-parser": { - "version": "2.1.4", - "resolved": "https://registry.npmmirror.com/error-stack-parser/-/error-stack-parser-2.1.4.tgz", - "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", - "dev": true, - "dependencies": { - "stackframe": "^1.3.4" - } - }, - "node_modules/es-abstract": { - "version": "1.23.3", - "resolved": "https://registry.npmmirror.com/es-abstract/-/es-abstract-1.23.3.tgz", - "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.3", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "data-view-buffer": "^1.0.1", - "data-view-byte-length": "^1.0.1", - "data-view-byte-offset": "^1.0.0", - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.0.3", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.4", - "get-symbol-description": "^1.0.2", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", - "hasown": "^2.0.2", - "internal-slot": "^1.0.7", - "is-array-buffer": "^3.0.4", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.1", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.3", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.13", - "is-weakref": "^1.0.2", - "object-inspect": "^1.13.1", - "object-keys": "^1.1.1", - "object.assign": "^4.1.5", - "regexp.prototype.flags": "^1.5.2", - "safe-array-concat": "^1.1.2", - "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.9", - "string.prototype.trimend": "^1.0.8", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.2", - "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.6", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.15" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-array-method-boxes-properly": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", - "dev": true - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eslint": { - "version": "6.8.0", - "resolved": "https://registry.npmmirror.com/eslint/-/eslint-6.8.0.tgz", - "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "ajv": "^6.10.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^1.4.3", - "eslint-visitor-keys": "^1.1.0", - "espree": "^6.1.2", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "inquirer": "^7.0.0", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.14", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.3", - "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^6.1.2", - "strip-ansi": "^5.2.0", - "strip-json-comments": "^3.0.1", - "table": "^5.2.3", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-loader": { - "version": "2.2.1", - "resolved": "https://registry.npmmirror.com/eslint-loader/-/eslint-loader-2.2.1.tgz", - "integrity": "sha512-RLgV9hoCVsMLvOxCuNjdqOrUqIj9oJg8hF44vzJaYqsAHuY9G2YAeN3joQ9nxP0p5Th9iFSIpKo+SD8KISxXRg==", - "dev": true, - "dependencies": { - "loader-fs-cache": "^1.0.0", - "loader-utils": "^1.0.2", - "object-assign": "^4.0.1", - "object-hash": "^1.1.4", - "rimraf": "^2.6.1" - }, - "peerDependencies": { - "eslint": ">=1.6.0 <7.0.0", - "webpack": ">=2.0.0 <5.0.0" - } - }, - "node_modules/eslint-loader/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/eslint-loader/node_modules/loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/eslint-plugin-vue": { - "version": "6.2.2", - "resolved": "https://registry.npmmirror.com/eslint-plugin-vue/-/eslint-plugin-vue-6.2.2.tgz", - "integrity": "sha512-Nhc+oVAHm0uz/PkJAWscwIT4ijTrK5fqNqz9QB1D35SbbuMG1uB6Yr5AJpvPSWg+WOw7nYNswerYh0kOk64gqQ==", - "dev": true, - "dependencies": { - "natural-compare": "^1.4.0", - "semver": "^5.6.0", - "vue-eslint-parser": "^7.0.0" - }, - "engines": { - "node": ">=8.10" - }, - "peerDependencies": { - "eslint": "^5.0.0 || ^6.0.0" - } - }, - "node_modules/eslint-plugin-vue/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmmirror.com/eslint-utils/-/eslint-utils-1.4.3.tgz", - "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/eslint/node_modules/globals": { - "version": "12.4.0", - "resolved": "https://registry.npmmirror.com/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", - "dev": true, - "dependencies": { - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/espree": { - "version": "6.2.1", - "resolved": "https://registry.npmmirror.com/espree/-/espree-6.2.1.tgz", - "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", - "dev": true, - "dependencies": { - "acorn": "^7.1.1", - "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmmirror.com/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/event-pubsub": { - "version": "4.3.0", - "resolved": "https://registry.npmmirror.com/event-pubsub/-/event-pubsub-4.3.0.tgz", - "integrity": "sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmmirror.com/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/eventsource": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/eventsource/-/eventsource-2.0.2.tgz", - "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", - "dev": true, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "dependencies": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmmirror.com/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", - "dev": true, - "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmmirror.com/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-descriptor": { - "version": "0.1.7", - "resolved": "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-0.1.7.tgz", - "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/expand-brackets/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmmirror.com/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", - "dev": true, - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.2", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.6.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/express/node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmmirror.com/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/express/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmmirror.com/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true, - "engines": [ - "node >=0.6.0" - ] - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "2.2.7", - "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-2.2.7.tgz", - "integrity": "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==", - "dev": true, - "dependencies": { - "@mrmlnc/readdir-enhanced": "^2.2.1", - "@nodelib/fs.stat": "^1.1.2", - "glob-parent": "^3.1.0", - "is-glob": "^4.0.0", - "merge2": "^1.2.3", - "micromatch": "^3.1.10" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmmirror.com/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "dev": true, - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/figgy-pudding": { - "version": "3.5.2", - "resolved": "https://registry.npmmirror.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz", - "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", - "dev": true - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmmirror.com/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", - "dev": true, - "dependencies": { - "flat-cache": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/file-loader": { - "version": "4.3.0", - "resolved": "https://registry.npmmirror.com/file-loader/-/file-loader-4.3.0.tgz", - "integrity": "sha512-aKrYPYjF1yG3oX0kWRrqrSMfgftm7oJW5M+m4owoldH5C51C0RkIwB++JbRvEW3IU6/ZG5n8UvEcdgwOt2UOWA==", - "dev": true, - "dependencies": { - "loader-utils": "^1.2.3", - "schema-utils": "^2.5.0" - }, - "engines": { - "node": ">= 8.9.0" - }, - "peerDependencies": { - "webpack": "^4.0.0" - } - }, - "node_modules/file-loader/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/file-loader/node_modules/loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "optional": true - }, - "node_modules/filesize": { - "version": "3.6.1", - "resolved": "https://registry.npmmirror.com/filesize/-/filesize-3.6.1.tgz", - "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==", - "dev": true, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fill-range/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmmirror.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", - "dev": true, - "dependencies": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/flat-cache/node_modules/rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", - "dev": true - }, - "node_modules/flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/font-awesome": { - "version": "4.7.0", - "resolved": "https://registry.npmmirror.com/font-awesome/-/font-awesome-4.7.0.tgz", - "integrity": "sha512-U6kGnykA/6bFmg1M/oT9EkFeIYv7JlX3bozwQJWiiLz6L0w3F5vBVPxHlwyX/vtNq1ckcpRKOB9f2Qal/VtFpg==", - "engines": { - "node": ">=0.10.3" - } - }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmmirror.com/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmmirror.com/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmmirror.com/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmmirror.com/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", - "dev": true, - "dependencies": { - "map-cache": "^0.2.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/from2": { - "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/from2/-/from2-2.3.0.tgz", - "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "node_modules/fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/fs-write-stream-atomic": { - "version": "1.0.10", - "resolved": "https://registry.npmmirror.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha512-gehEzmPn2nAwr39eay+x3X34Ra+M2QlVUTLhkXPjWdeO8RF9kszk116avgBJM3ZyNHgHXBNx+VmPaFC36k0PzA==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/function.prototype.name": { - "version": "1.1.6", - "resolved": "https://registry.npmmirror.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz", - "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "functions-have-names": "^1.2.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz", - "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmmirror.com/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmmirror.com/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", - "dev": true, - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "node_modules/glob-parent/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.3.0", - "resolved": "https://registry.npmmirror.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", - "integrity": "sha512-Iozmtbqv0noj0uDDqoL0zNq0VBEfK2YFoMAZoxJe4cwphvLR+JskfF30QhXHOR4m3KrE6NLRYw+U9MRXvifyig==", - "dev": true - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/globby": { - "version": "9.2.0", - "resolved": "https://registry.npmmirror.com/globby/-/globby-9.2.0.tgz", - "integrity": "sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==", - "dev": true, - "dependencies": { - "@types/glob": "^7.1.1", - "array-union": "^1.0.2", - "dir-glob": "^2.2.2", - "fast-glob": "^2.2.6", - "glob": "^7.1.3", - "ignore": "^4.0.3", - "pify": "^4.0.1", - "slash": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/gzip-size": { - "version": "5.1.1", - "resolved": "https://registry.npmmirror.com/gzip-size/-/gzip-size-5.1.1.tgz", - "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==", - "dev": true, - "dependencies": { - "duplexer": "^0.1.1", - "pify": "^4.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "dev": true - }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmmirror.com/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "dev": true, - "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/has": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/has/-/has-1.0.4.tgz", - "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", - "dev": true, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", - "dev": true, - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hash-base": { - "version": "3.0.4", - "resolved": "https://registry.npmmirror.com/hash-base/-/hash-base-3.0.4.tgz", - "integrity": "sha512-EeeoJKjTyt868liAlVmcv2ZsUfGHlE3Q+BICOXcZiwN3osr5Q/zFGYmTJpoIzuaSTAwndFy+GqhEwlU4L3j4Ow==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/hash-sum": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/hash-sum/-/hash-sum-2.0.0.tgz", - "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==", - "dev": true - }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmmirror.com/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/hex-color-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz", - "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==", - "dev": true - }, - "node_modules/highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "dev": true, - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/hoopy": { - "version": "0.1.4", - "resolved": "https://registry.npmmirror.com/hoopy/-/hoopy-0.1.4.tgz", - "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==", - "dev": true, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmmirror.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmmirror.com/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "node_modules/hsl-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/hsl-regex/-/hsl-regex-1.0.0.tgz", - "integrity": "sha512-M5ezZw4LzXbBKMruP+BNANf0k+19hDQMgpzBIYnya//Al+fjNct9Wf3b1WedLqdEs2hKBvxq/jh+DsHJLj0F9A==", - "dev": true - }, - "node_modules/hsla-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/hsla-regex/-/hsla-regex-1.0.0.tgz", - "integrity": "sha512-7Wn5GMLuHBjZCb2bTmnDOycho0p/7UVaAeqXZGbHrBCl6Yd/xDhQJAXe6Ga9AXJH2I5zY1dEdYw2u1UptnSBJA==", - "dev": true - }, - "node_modules/html-entities": { - "version": "1.4.0", - "resolved": "https://registry.npmmirror.com/html-entities/-/html-entities-1.4.0.tgz", - "integrity": "sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==", - "dev": true - }, - "node_modules/html-minifier": { - "version": "3.5.21", - "resolved": "https://registry.npmmirror.com/html-minifier/-/html-minifier-3.5.21.tgz", - "integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==", - "dev": true, - "dependencies": { - "camel-case": "3.0.x", - "clean-css": "4.2.x", - "commander": "2.17.x", - "he": "1.2.x", - "param-case": "2.1.x", - "relateurl": "0.2.x", - "uglify-js": "3.4.x" - }, - "bin": { - "html-minifier": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/html-tags": { - "version": "3.3.1", - "resolved": "https://registry.npmmirror.com/html-tags/-/html-tags-3.3.1.tgz", - "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/html-webpack-plugin": { - "version": "3.2.0", - "resolved": "https://registry.npmmirror.com/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", - "integrity": "sha512-Br4ifmjQojUP4EmHnRBoUIYcZ9J7M4bTMcm7u6xoIAIuq2Nte4TzXX0533owvkQKQD1WeMTTTyD4Ni4QKxS0Bg==", - "dev": true, - "dependencies": { - "html-minifier": "^3.2.3", - "loader-utils": "^0.2.16", - "lodash": "^4.17.3", - "pretty-error": "^2.0.2", - "tapable": "^1.0.0", - "toposort": "^1.0.0", - "util.promisify": "1.0.0" - }, - "engines": { - "node": ">=6.9" - }, - "peerDependencies": { - "webpack": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0" - } - }, - "node_modules/html-webpack-plugin/node_modules/big.js": { - "version": "3.2.0", - "resolved": "https://registry.npmmirror.com/big.js/-/big.js-3.2.0.tgz", - "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/html-webpack-plugin/node_modules/emojis-list": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha512-knHEZMgs8BB+MInokmNTg/OyPlAddghe1YBgNwJBc5zsJi/uyIcXoSDsL/W9ymOsBoBGdPIHXYJ9+qKFwRwDng==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/html-webpack-plugin/node_modules/json5": { - "version": "0.5.1", - "resolved": "https://registry.npmmirror.com/json5/-/json5-0.5.1.tgz", - "integrity": "sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/html-webpack-plugin/node_modules/loader-utils": { - "version": "0.2.17", - "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-0.2.17.tgz", - "integrity": "sha512-tiv66G0SmiOx+pLWMtGEkfSEejxvb6N6uRrQjfWJIT79W9GMpgKeCAmm9aVBKtd4WEgntciI8CsGqjpDoCWJug==", - "dev": true, - "dependencies": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0", - "object-assign": "^4.0.1" - } - }, - "node_modules/htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "node_modules/htmlparser2/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmmirror.com/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmmirror.com/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", - "dev": true - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-parser-js": { - "version": "0.5.8", - "resolved": "https://registry.npmmirror.com/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", - "dev": true - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmmirror.com/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/http-proxy-middleware": { - "version": "1.3.1", - "resolved": "https://registry.npmmirror.com/http-proxy-middleware/-/http-proxy-middleware-1.3.1.tgz", - "integrity": "sha512-13eVVDYS4z79w7f1+NPllJtOQFx/FdUW4btIvVRMaRlUY9VGstAbo5MOhLEuUgZFRHn3x50ufn25zkj/boZnEg==", - "dev": true, - "dependencies": { - "@types/http-proxy": "^1.17.5", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/http-proxy-middleware/node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/http-proxy-middleware/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", - "dev": true, - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/http-proxy-middleware/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" - } - }, - "node_modules/https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==", - "dev": true - }, - "node_modules/human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true, - "engines": { - "node": ">=8.12.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/icss-utils": { - "version": "4.1.1", - "resolved": "https://registry.npmmirror.com/icss-utils/-/icss-utils-4.1.1.tgz", - "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", - "dev": true, - "dependencies": { - "postcss": "^7.0.14" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmmirror.com/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA==", - "dev": true - }, - "node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmmirror.com/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/immutable": { - "version": "4.3.6", - "resolved": "https://registry.npmmirror.com/immutable/-/immutable-4.3.6.tgz", - "integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==", - "dev": true - }, - "node_modules/import-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/import-cwd/-/import-cwd-2.1.0.tgz", - "integrity": "sha512-Ew5AZzJQFqrOV5BTW3EIoHAnoie1LojZLXKcCQ/yTRyVZosBhK1x1ViYjHGf5pAFOq8ZyChZp6m/fSN7pJyZtg==", - "dev": true, - "dependencies": { - "import-from": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", - "dev": true, - "dependencies": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/import-from": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/import-from/-/import-from-2.1.0.tgz", - "integrity": "sha512-0vdnLL2wSGnhlRmzHJAg5JHjt1l2vYhzJ7tNLGbeVg0fse56tpGaH0uzH+r9Slej+BSXXEHvBKDEnVSLLE9/+w==", - "dev": true, - "dependencies": { - "resolve-from": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/import-local": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/import-local/-/import-local-2.0.0.tgz", - "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", - "dev": true, - "dependencies": { - "pkg-dir": "^3.0.0", - "resolve-cwd": "^2.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/import-local/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/import-local/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/import-local/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/import-local/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/import-local/node_modules/pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indexes-of": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/indexes-of/-/indexes-of-1.0.1.tgz", - "integrity": "sha512-bup+4tap3Hympa+JBJUG7XuOsdNQ6fxt0MHyXMKuLBKn0OqsTfvUxkUrroEX1+B2VsSHvCjiIcZVxRtYa4nllA==", - "dev": true - }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/inquirer": { - "version": "7.3.3", - "resolved": "https://registry.npmmirror.com/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.19", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.6.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/inquirer/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/inquirer/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/inquirer/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/inquirer/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/inquirer/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/internal-ip": { - "version": "4.3.0", - "resolved": "https://registry.npmmirror.com/internal-ip/-/internal-ip-4.3.0.tgz", - "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", - "dev": true, - "dependencies": { - "default-gateway": "^4.2.0", - "ipaddr.js": "^1.9.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/internal-ip/node_modules/default-gateway": { - "version": "4.2.0", - "resolved": "https://registry.npmmirror.com/default-gateway/-/default-gateway-4.2.0.tgz", - "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", - "dev": true, - "dependencies": { - "execa": "^1.0.0", - "ip-regex": "^2.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/internal-slot": { - "version": "1.0.7", - "resolved": "https://registry.npmmirror.com/internal-slot/-/internal-slot-1.0.7.tgz", - "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.0", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ip": { - "version": "1.1.9", - "resolved": "https://registry.npmmirror.com/ip/-/ip-1.1.9.tgz", - "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==", - "dev": true - }, - "node_modules/ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-absolute-url": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz", - "integrity": "sha512-vOx7VprsKyllwjSkLV79NIhpyLfr3jAp7VaTCMXOJHu4m0Ew1CZ2fcjASwmV1jI3BWuWHB013M48eyeldk9gYg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-accessor-descriptor": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.1.tgz", - "integrity": "sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==", - "dev": true, - "dependencies": { - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.4", - "resolved": "https://registry.npmmirror.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz", - "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-ci": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/is-ci/-/is-ci-1.2.1.tgz", - "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", - "dev": true, - "dependencies": { - "ci-info": "^1.5.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "node_modules/is-color-stop": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/is-color-stop/-/is-color-stop-1.1.0.tgz", - "integrity": "sha512-H1U8Vz0cfXNujrJzEcvvwMDW9Ra+biSYA3ThdQvAnMLJkEHQXn6bWzLkxHtVYJ+Sdbx0b6finn3jZiaVe7MAHA==", - "dev": true, - "dependencies": { - "css-color-names": "^0.0.4", - "hex-color-regex": "^1.1.0", - "hsl-regex": "^1.0.0", - "hsla-regex": "^1.0.0", - "rgb-regex": "^1.0.1", - "rgba-regex": "^1.0.0" - } - }, - "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "dev": true, - "dependencies": { - "hasown": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-descriptor": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/is-data-descriptor/-/is-data-descriptor-1.0.1.tgz", - "integrity": "sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==", - "dev": true, - "dependencies": { - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-data-view": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/is-data-view/-/is-data-view-1.0.1.tgz", - "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", - "dev": true, - "dependencies": { - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-descriptor": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-1.0.3.tgz", - "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmmirror.com/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmmirror.com/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmmirror.com/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmmirror.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-path-in-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", - "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", - "dev": true, - "dependencies": { - "is-path-inside": "^2.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-path-inside": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/is-path-inside/-/is-path-inside-2.1.0.tgz", - "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", - "dev": true, - "dependencies": { - "path-is-inside": "^1.0.2" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", - "dev": true - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", - "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmmirror.com/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmmirror.com/is-typed-array/-/is-typed-array-1.1.13.tgz", - "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", - "dev": true, - "dependencies": { - "which-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmmirror.com/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true - }, - "node_modules/javascript-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/javascript-stringify/-/javascript-stringify-2.1.0.tgz", - "integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==", - "dev": true - }, - "node_modules/js-message": { - "version": "1.0.7", - "resolved": "https://registry.npmmirror.com/js-message/-/js-message-1.0.7.tgz", - "integrity": "sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA==", - "dev": true, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmmirror.com/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmmirror.com/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmmirror.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmmirror.com/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dev": true, - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/killable": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/killable/-/killable-1.0.1.tgz", - "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", - "dev": true - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/klona": { - "version": "2.0.6", - "resolved": "https://registry.npmmirror.com/klona/-/klona-2.0.6.tgz", - "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/launch-editor": { - "version": "2.6.1", - "resolved": "https://registry.npmmirror.com/launch-editor/-/launch-editor-2.6.1.tgz", - "integrity": "sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw==", - "dev": true, - "dependencies": { - "picocolors": "^1.0.0", - "shell-quote": "^1.8.1" - } - }, - "node_modules/launch-editor-middleware": { - "version": "2.6.1", - "resolved": "https://registry.npmmirror.com/launch-editor-middleware/-/launch-editor-middleware-2.6.1.tgz", - "integrity": "sha512-Fg/xYhf7ARmRp40n18wIfJyuAMEjXo67Yull7uF7d0OJ3qA4EYJISt1XfPPn69IIJ5jKgQwzcg6DqHYo95LL/g==", - "dev": true, - "dependencies": { - "launch-editor": "^2.6.1" - } - }, - "node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmmirror.com/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/loader-fs-cache": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/loader-fs-cache/-/loader-fs-cache-1.0.3.tgz", - "integrity": "sha512-ldcgZpjNJj71n+2Mf6yetz+c9bM4xpKtNds4LbqXzU/PTdeAX0g3ytnU1AJMEcTk2Lex4Smpe3Q/eCTsvUBxbA==", - "dev": true, - "dependencies": { - "find-cache-dir": "^0.1.1", - "mkdirp": "^0.5.1" - } - }, - "node_modules/loader-fs-cache/node_modules/find-cache-dir": { - "version": "0.1.1", - "resolved": "https://registry.npmmirror.com/find-cache-dir/-/find-cache-dir-0.1.1.tgz", - "integrity": "sha512-Z9XSBoNE7xQiV6MSgPuCfyMokH2K7JdpRkOYE1+mu3d4BFJtx3GW+f6Bo4q8IX6rlf5MYbLBKW0pjl2cWdkm2A==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "mkdirp": "^0.5.1", - "pkg-dir": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/loader-fs-cache/node_modules/find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", - "dev": true, - "dependencies": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/loader-fs-cache/node_modules/path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", - "dev": true, - "dependencies": { - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/loader-fs-cache/node_modules/pkg-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-1.0.0.tgz", - "integrity": "sha512-c6pv3OE78mcZ92ckebVDqg0aWSoKhOTbwCV6qbCWMk546mAL9pZln0+QsN/yQ7fkucd4+yJPLrCBXNt8Ruk+Eg==", - "dev": true, - "dependencies": { - "find-up": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/loader-runner": { - "version": "2.4.0", - "resolved": "https://registry.npmmirror.com/loader-runner/-/loader-runner-2.4.0.tgz", - "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", - "dev": true, - "engines": { - "node": ">=4.3.0 <5.0.0 || >=5.10" - } - }, - "node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true - }, - "node_modules/lodash.defaultsdeep": { - "version": "4.6.1", - "resolved": "https://registry.npmmirror.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz", - "integrity": "sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==", - "dev": true - }, - "node_modules/lodash.kebabcase": { - "version": "4.1.1", - "resolved": "https://registry.npmmirror.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", - "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", - "dev": true - }, - "node_modules/lodash.mapvalues": { - "version": "4.6.0", - "resolved": "https://registry.npmmirror.com/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz", - "integrity": "sha512-JPFqXFeZQ7BfS00H58kClY7SPVeHertPE0lNuCyZ26/XlN8TvakYD7b9bGyNmXbT/D3BbtPAAmq90gPWqLkxlQ==", - "dev": true - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmmirror.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, - "node_modules/lodash.transform": { - "version": "4.6.0", - "resolved": "https://registry.npmmirror.com/lodash.transform/-/lodash.transform-4.6.0.tgz", - "integrity": "sha512-LO37ZnhmBVx0GvOU/caQuipEh4GN82TcWv3yHlebGDgOxbxiwwzW5Pcx2AcvpIv2WmvmSMoC492yQFNhy/l/UQ==", - "dev": true - }, - "node_modules/lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmmirror.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", - "dev": true - }, - "node_modules/log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmmirror.com/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "dev": true, - "dependencies": { - "chalk": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/loglevel": { - "version": "1.9.1", - "resolved": "https://registry.npmmirror.com/loglevel/-/loglevel-1.9.1.tgz", - "integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==", - "dev": true, - "engines": { - "node": ">= 0.6.0" - }, - "funding": { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/loglevel" - } - }, - "node_modules/lower-case": { - "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/lower-case/-/lower-case-1.1.4.tgz", - "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", - "dev": true - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/magic-string": { - "version": "0.30.10", - "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.10.tgz", - "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmmirror.com/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", - "dev": true, - "dependencies": { - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmmirror.com/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/mdn-data": { - "version": "2.0.4", - "resolved": "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.4.tgz", - "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", - "dev": true - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmmirror.com/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha512-cda4JKCxReDXFXRqOHPQscuIYg1PvxbE2S2GP45rnwfEK+vZaXC8C1OFvdHIbgw0DLzowXGVoxLaAmlgRy14GQ==", - "dev": true, - "dependencies": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "dev": true - }, - "node_modules/merge-source-map": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/merge-source-map/-/merge-source-map-1.1.0.tgz", - "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", - "dev": true, - "dependencies": { - "source-map": "^0.6.1" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, - "dependencies": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "bin": { - "miller-rabin": "bin/miller-rabin" - } - }, - "node_modules/miller-rabin/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/mini-css-extract-plugin": { - "version": "0.9.0", - "resolved": "https://registry.npmmirror.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz", - "integrity": "sha512-lp3GeY7ygcgAmVIcRPBVhIkf8Us7FZjA+ILpal44qLdSu11wmjKQ3d9k15lfD7pO4esu9eUIAW7qiYIBppv40A==", - "dev": true, - "dependencies": { - "loader-utils": "^1.1.0", - "normalize-url": "1.9.1", - "schema-utils": "^1.0.0", - "webpack-sources": "^1.1.0" - }, - "engines": { - "node": ">= 6.9.0" - }, - "peerDependencies": { - "webpack": "^4.4.0" - } - }, - "node_modules/mini-css-extract-plugin/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/mini-css-extract-plugin/node_modules/loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "dependencies": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", - "dev": true - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/mississippi": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/mississippi/-/mississippi-3.0.0.tgz", - "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", - "dev": true, - "dependencies": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmmirror.com/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/moment": { - "version": "2.30.1", - "resolved": "https://registry.npmmirror.com/moment/-/moment-2.30.1.tgz", - "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", - "engines": { - "node": "*" - } - }, - "node_modules/move-concurrently": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha512-hdrFxZOycD/g6A6SoI2bB5NA/5NEqD0569+S47WZhPvm46sD50ZHdYaFmnua5lndde9rCHGjmfK7Z8BuCt/PcQ==", - "dev": true, - "dependencies": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/multicast-dns": { - "version": "6.2.3", - "resolved": "https://registry.npmmirror.com/multicast-dns/-/multicast-dns-6.2.3.tgz", - "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", - "dev": true, - "dependencies": { - "dns-packet": "^1.3.1", - "thunky": "^1.0.2" - }, - "bin": { - "multicast-dns": "cli.js" - } - }, - "node_modules/multicast-dns-service-types": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", - "integrity": "sha512-cnAsSVxIDsYt0v7HmC0hWZFwwXSh+E6PgCrREDuN/EsjgLwA5XRmlMHhSiDPrt6HxY1gTivEa/Zh7GtODoLevQ==", - "dev": true - }, - "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmmirror.com/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmmirror.com/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/nan": { - "version": "2.19.0", - "resolved": "https://registry.npmmirror.com/nan/-/nan-2.19.0.tgz", - "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==", - "dev": true, - "optional": true - }, - "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmmirror.com/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node_modules/no-case": { - "version": "2.3.2", - "resolved": "https://registry.npmmirror.com/no-case/-/no-case-2.3.2.tgz", - "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", - "dev": true, - "dependencies": { - "lower-case": "^1.1.1" - } - }, - "node_modules/node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmmirror.com/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", - "dev": true, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/node-libs-browser": { - "version": "2.2.1", - "resolved": "https://registry.npmmirror.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz", - "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", - "dev": true, - "dependencies": { - "assert": "^1.1.1", - "browserify-zlib": "^0.2.0", - "buffer": "^4.3.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.1", - "events": "^3.0.0", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "path-browserify": "0.0.1", - "process": "^0.11.10", - "punycode": "^1.2.4", - "querystring-es3": "^0.2.0", - "readable-stream": "^2.3.3", - "stream-browserify": "^2.0.1", - "stream-http": "^2.7.2", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", - "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.11.0", - "vm-browserify": "^1.0.1" - } - }, - "node_modules/node-libs-browser/node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmmirror.com/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", - "dev": true - }, - "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "dev": true - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmmirror.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmmirror.com/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-url": { - "version": "1.9.1", - "resolved": "https://registry.npmmirror.com/normalize-url/-/normalize-url-1.9.1.tgz", - "integrity": "sha512-A48My/mtCklowHBlI8Fq2jFWK4tX4lJ5E6ytFsSOq1fzpvT0SQSgKhSg7lN5c2uYFOrUAOQp6zhhJnpp1eMloQ==", - "dev": true, - "dependencies": { - "object-assign": "^4.0.1", - "prepend-http": "^1.0.0", - "query-string": "^4.1.0", - "sort-keys": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/normalize-wheel": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/normalize-wheel/-/normalize-wheel-1.0.1.tgz", - "integrity": "sha512-1OnlAPZ3zgrk8B91HyRj+eVv+kS5u+Z0SCsak6Xil/kmgEia50ga7zfkumayonZrImffAxPU/5WcyGhzetHNPA==" - }, - "node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "dev": true, - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/num2fraction": { - "version": "1.2.2", - "resolved": "https://registry.npmmirror.com/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg==", - "dev": true - }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmmirror.com/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmmirror.com/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", - "dev": true, - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmmirror.com/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor": { - "version": "0.1.7", - "resolved": "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-0.1.7.tgz", - "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object-copy/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-hash": { - "version": "1.3.1", - "resolved": "https://registry.npmmirror.com/object-hash/-/object-hash-1.3.1.tgz", - "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==", - "dev": true, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-is": { - "version": "1.1.6", - "resolved": "https://registry.npmmirror.com/object-is/-/object-is-1.1.6.tgz", - "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", - "dev": true, - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmmirror.com/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.getownpropertydescriptors": { - "version": "2.1.8", - "resolved": "https://registry.npmmirror.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.8.tgz", - "integrity": "sha512-qkHIGe4q0lSYMv0XI4SsBTJz3WaURhLvd0lKSgtVuOsJ2krg4SgMw3PIRQFMp07yi++UR3se2mkcLqsBNpBb/A==", - "dev": true, - "dependencies": { - "array.prototype.reduce": "^1.0.6", - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0", - "gopd": "^1.0.1", - "safe-array-concat": "^1.1.2" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.values": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/object.values/-/object.values-1.2.0.tgz", - "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "dev": true - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmmirror.com/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "6.4.0", - "resolved": "https://registry.npmmirror.com/open/-/open-6.4.0.tgz", - "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", - "dev": true, - "dependencies": { - "is-wsl": "^1.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/open/node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/opener": { - "version": "1.5.2", - "resolved": "https://registry.npmmirror.com/opener/-/opener-1.5.2.tgz", - "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", - "dev": true, - "bin": { - "opener": "bin/opener-bin.js" - } - }, - "node_modules/opn": { - "version": "5.5.0", - "resolved": "https://registry.npmmirror.com/opn/-/opn-5.5.0.tgz", - "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", - "dev": true, - "dependencies": { - "is-wsl": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/opn/node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ora": { - "version": "3.4.0", - "resolved": "https://registry.npmmirror.com/ora/-/ora-3.4.0.tgz", - "integrity": "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", - "cli-spinners": "^2.0.0", - "log-symbols": "^2.2.0", - "strip-ansi": "^5.2.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/ora/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ora/node_modules/cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", - "dev": true, - "dependencies": { - "restore-cursor": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ora/node_modules/mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/ora/node_modules/onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", - "dev": true, - "dependencies": { - "mimic-fn": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ora/node_modules/restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", - "dev": true, - "dependencies": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ora/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmmirror.com/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", - "dev": true - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/p-retry": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/p-retry/-/p-retry-3.0.1.tgz", - "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", - "dev": true, - "dependencies": { - "retry": "^0.12.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, - "node_modules/parallel-transform": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/parallel-transform/-/parallel-transform-1.2.0.tgz", - "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", - "dev": true, - "dependencies": { - "cyclist": "^1.0.1", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" - } - }, - "node_modules/param-case": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/param-case/-/param-case-2.1.1.tgz", - "integrity": "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==", - "dev": true, - "dependencies": { - "no-case": "^2.2.0" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parent-module/node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-asn1": { - "version": "5.1.7", - "resolved": "https://registry.npmmirror.com/parse-asn1/-/parse-asn1-5.1.7.tgz", - "integrity": "sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==", - "dev": true, - "dependencies": { - "asn1.js": "^4.10.1", - "browserify-aes": "^1.2.0", - "evp_bytestokey": "^1.0.3", - "hash-base": "~3.0", - "pbkdf2": "^3.1.2", - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/parse-asn1/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "dev": true, - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmmirror.com/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", - "dev": true - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmmirror.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", - "dev": true, - "dependencies": { - "parse5": "^6.0.1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmmirror.com/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmmirror.com/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-0.0.1.tgz", - "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", - "dev": true - }, - "node_modules/path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==", - "dev": true - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", - "dev": true - }, - "node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", - "dev": true - }, - "node_modules/path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-type/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmmirror.com/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "dev": true, - "dependencies": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true - }, - "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmmirror.com/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", - "dev": true, - "dependencies": { - "pinkie": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pnp-webpack-plugin": { - "version": "1.7.0", - "resolved": "https://registry.npmmirror.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.7.0.tgz", - "integrity": "sha512-2Rb3vm+EXble/sMXNSu6eoBx8e79gKqhNq9F5ZWW6ERNCTE/Q0wQNne5541tE5vKjfM8hpNCYL+LGc1YTfI0dg==", - "dev": true, - "dependencies": { - "ts-pnp": "^1.1.6" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/portfinder": { - "version": "1.0.32", - "resolved": "https://registry.npmmirror.com/portfinder/-/portfinder-1.0.32.tgz", - "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==", - "dev": true, - "dependencies": { - "async": "^2.6.4", - "debug": "^3.2.7", - "mkdirp": "^0.5.6" - }, - "engines": { - "node": ">= 0.12.0" - } - }, - "node_modules/portfinder/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmmirror.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/possible-typed-array-names": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmmirror.com/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "dev": true, - "dependencies": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - } - }, - "node_modules/postcss-calc": { - "version": "7.0.5", - "resolved": "https://registry.npmmirror.com/postcss-calc/-/postcss-calc-7.0.5.tgz", - "integrity": "sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg==", - "dev": true, - "dependencies": { - "postcss": "^7.0.27", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.0.2" - } - }, - "node_modules/postcss-colormin": { - "version": "4.0.3", - "resolved": "https://registry.npmmirror.com/postcss-colormin/-/postcss-colormin-4.0.3.tgz", - "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", - "dev": true, - "dependencies": { - "browserslist": "^4.0.0", - "color": "^3.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-colormin/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-convert-values": { - "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", - "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-convert-values/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-discard-comments": { - "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", - "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-discard-duplicates": { - "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", - "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-discard-empty": { - "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", - "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-discard-overridden": { - "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", - "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-load-config": { - "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/postcss-load-config/-/postcss-load-config-2.1.2.tgz", - "integrity": "sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw==", - "dev": true, - "dependencies": { - "cosmiconfig": "^5.0.0", - "import-cwd": "^2.0.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - } - }, - "node_modules/postcss-loader": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/postcss-loader/-/postcss-loader-3.0.0.tgz", - "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", - "dev": true, - "dependencies": { - "loader-utils": "^1.1.0", - "postcss": "^7.0.0", - "postcss-load-config": "^2.0.0", - "schema-utils": "^1.0.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/postcss-loader/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/postcss-loader/node_modules/loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/postcss-loader/node_modules/schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "dependencies": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/postcss-merge-longhand": { - "version": "4.0.11", - "resolved": "https://registry.npmmirror.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", - "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", - "dev": true, - "dependencies": { - "css-color-names": "0.0.4", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "stylehacks": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-merge-longhand/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-merge-rules": { - "version": "4.0.3", - "resolved": "https://registry.npmmirror.com/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", - "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", - "dev": true, - "dependencies": { - "browserslist": "^4.0.0", - "caniuse-api": "^3.0.0", - "cssnano-util-same-parent": "^4.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0", - "vendors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-merge-rules/node_modules/postcss-selector-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", - "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", - "dev": true, - "dependencies": { - "dot-prop": "^5.2.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/postcss-minify-font-values": { - "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", - "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-minify-font-values/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-minify-gradients": { - "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", - "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", - "dev": true, - "dependencies": { - "cssnano-util-get-arguments": "^4.0.0", - "is-color-stop": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-minify-gradients/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-minify-params": { - "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", - "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", - "dev": true, - "dependencies": { - "alphanum-sort": "^1.0.0", - "browserslist": "^4.0.0", - "cssnano-util-get-arguments": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "uniqs": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-minify-params/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-minify-selectors": { - "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", - "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", - "dev": true, - "dependencies": { - "alphanum-sort": "^1.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-minify-selectors/node_modules/postcss-selector-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", - "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", - "dev": true, - "dependencies": { - "dot-prop": "^5.2.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/postcss-modules-extract-imports": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", - "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", - "dev": true, - "dependencies": { - "postcss": "^7.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/postcss-modules-local-by-default": { - "version": "3.0.3", - "resolved": "https://registry.npmmirror.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz", - "integrity": "sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==", - "dev": true, - "dependencies": { - "icss-utils": "^4.1.1", - "postcss": "^7.0.32", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/postcss-modules-scope": { - "version": "2.2.0", - "resolved": "https://registry.npmmirror.com/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz", - "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==", - "dev": true, - "dependencies": { - "postcss": "^7.0.6", - "postcss-selector-parser": "^6.0.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/postcss-modules-values": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz", - "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==", - "dev": true, - "dependencies": { - "icss-utils": "^4.0.0", - "postcss": "^7.0.6" - } - }, - "node_modules/postcss-normalize-charset": { - "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", - "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-display-values": { - "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", - "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", - "dev": true, - "dependencies": { - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-display-values/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-normalize-positions": { - "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", - "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", - "dev": true, - "dependencies": { - "cssnano-util-get-arguments": "^4.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-positions/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-normalize-repeat-style": { - "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", - "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", - "dev": true, - "dependencies": { - "cssnano-util-get-arguments": "^4.0.0", - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-repeat-style/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-normalize-string": { - "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", - "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", - "dev": true, - "dependencies": { - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-string/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-normalize-timing-functions": { - "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", - "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", - "dev": true, - "dependencies": { - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-timing-functions/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-normalize-unicode": { - "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", - "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", - "dev": true, - "dependencies": { - "browserslist": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-unicode/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-normalize-url": { - "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", - "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", - "dev": true, - "dependencies": { - "is-absolute-url": "^2.0.0", - "normalize-url": "^3.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-url/node_modules/normalize-url": { - "version": "3.3.0", - "resolved": "https://registry.npmmirror.com/normalize-url/-/normalize-url-3.3.0.tgz", - "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-normalize-url/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-normalize-whitespace": { - "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", - "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-whitespace/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-ordered-values": { - "version": "4.1.2", - "resolved": "https://registry.npmmirror.com/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", - "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", - "dev": true, - "dependencies": { - "cssnano-util-get-arguments": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-ordered-values/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-reduce-initial": { - "version": "4.0.3", - "resolved": "https://registry.npmmirror.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", - "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", - "dev": true, - "dependencies": { - "browserslist": "^4.0.0", - "caniuse-api": "^3.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-reduce-transforms": { - "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", - "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", - "dev": true, - "dependencies": { - "cssnano-util-get-match": "^4.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-reduce-transforms/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-selector-parser": { - "version": "6.1.0", - "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz", - "integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==", - "dev": true, - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-svgo": { - "version": "4.0.3", - "resolved": "https://registry.npmmirror.com/postcss-svgo/-/postcss-svgo-4.0.3.tgz", - "integrity": "sha512-NoRbrcMWTtUghzuKSoIm6XV+sJdvZ7GZSc3wdBN0W19FTtp2ko8NqLsgoh/m9CzNhU3KLPvQmjIwtaNFkaFTvw==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "svgo": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-svgo/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-unique-selectors": { - "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", - "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", - "dev": true, - "dependencies": { - "alphanum-sort": "^1.0.0", - "postcss": "^7.0.0", - "uniqs": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true - }, - "node_modules/postcss/node_modules/picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "dev": true - }, - "node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmmirror.com/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "optional": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/pretty-error": { - "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/pretty-error/-/pretty-error-2.1.2.tgz", - "integrity": "sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw==", - "dev": true, - "dependencies": { - "lodash": "^4.17.20", - "renderkid": "^2.0.4" - } - }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmmirror.com/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "dev": true, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "dev": true - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz", - "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", - "dev": true - }, - "node_modules/pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", - "dev": true - }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmmirror.com/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true - }, - "node_modules/public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmmirror.com/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "dev": true, - "dependencies": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/public-encrypt/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmmirror.com/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dev": true, - "dependencies": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - } - }, - "node_modules/pumpify/node_modules/pump": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/q": { - "version": "1.5.1", - "resolved": "https://registry.npmmirror.com/q/-/q-1.5.1.tgz", - "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", - "dev": true, - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" - } - }, - "node_modules/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmmirror.com/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/query-string": { - "version": "4.3.4", - "resolved": "https://registry.npmmirror.com/query-string/-/query-string-4.3.4.tgz", - "integrity": "sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q==", - "dev": true, - "dependencies": { - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmmirror.com/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", - "dev": true, - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmmirror.com/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, - "dependencies": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmmirror.com/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg/node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmmirror.com/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmmirror.com/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.1.1", - "resolved": "https://registry.npmmirror.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", - "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", - "dev": true, - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true - }, - "node_modules/regenerator-transform": { - "version": "0.15.2", - "resolved": "https://registry.npmmirror.com/regenerator-transform/-/regenerator-transform-0.15.2.tgz", - "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.2", - "resolved": "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", - "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.6", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "set-function-name": "^2.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", - "dev": true, - "engines": { - "node": ">=6.5.0" - } - }, - "node_modules/regexpu-core": { - "version": "5.3.2", - "resolved": "https://registry.npmmirror.com/regexpu-core/-/regexpu-core-5.3.2.tgz", - "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", - "dev": true, - "dependencies": { - "@babel/regjsgen": "^0.8.0", - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmmirror.com/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "dev": true, - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmmirror.com/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", - "dev": true - }, - "node_modules/renderkid": { - "version": "2.0.7", - "resolved": "https://registry.npmmirror.com/renderkid/-/renderkid-2.0.7.tgz", - "integrity": "sha512-oCcFyxaMrKsKcTY59qnCAtmDVSLfPbrv6A3tVbPdFMMrv5jaK10V6m40cKsoPNhAqN6rmHW9sswW4o3ruSrwUQ==", - "dev": true, - "dependencies": { - "css-select": "^4.1.3", - "dom-converter": "^0.2.0", - "htmlparser2": "^6.1.0", - "lodash": "^4.17.21", - "strip-ansi": "^3.0.1" - } - }, - "node_modules/renderkid/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/renderkid/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmmirror.com/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/request": { - "version": "2.88.2", - "resolved": "https://registry.npmmirror.com/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, - "node_modules/resize-observer-polyfill": { - "version": "1.5.1", - "resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", - "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" - }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha512-ccu8zQTrzVr954472aUVPLEcB3YpKSYR3cg/3lo1okzobPBM+1INXBbBZlDbnI/hbEocnf8j0QVo43hQKrbchg==", - "dev": true, - "dependencies": { - "resolve-from": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmmirror.com/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", - "dev": true - }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmmirror.com/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmmirror.com/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/rgb-regex": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/rgb-regex/-/rgb-regex-1.0.1.tgz", - "integrity": "sha512-gDK5mkALDFER2YLqH6imYvK6g02gpNGM4ILDZ472EwWfXZnC2ZEpoB2ECXTyOVUKuk/bPJZMzwQPBYICzP+D3w==", - "dev": true - }, - "node_modules/rgba-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/rgba-regex/-/rgba-regex-1.0.0.tgz", - "integrity": "sha512-zgn5OjNQXLUTdq8m17KdaicF6w89TZs8ZU8y0AYENIU6wG8GG6LLm0yLSiPY8DmaYmHdgRW8rnApjoT0fQRfMg==", - "dev": true - }, - "node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "node_modules/run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmmirror.com/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/run-queue": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/run-queue/-/run-queue-1.0.3.tgz", - "integrity": "sha512-ntymy489o0/QQplUDnpYAYUsO50K9SBrIVaKCWDOJzYJts0f9WH9RFJkyagebkw5+y1oi00R7ynNW/d12GBumg==", - "dev": true, - "dependencies": { - "aproba": "^1.1.1" - } - }, - "node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/safe-array-concat": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz", - "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4", - "has-symbols": "^1.0.3", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-array-concat/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmmirror.com/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", - "dev": true, - "dependencies": { - "ret": "~0.1.10" - } - }, - "node_modules/safe-regex-test": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz", - "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-regex": "^1.1.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/sass": { - "version": "1.77.3", - "resolved": "https://registry.npmmirror.com/sass/-/sass-1.77.3.tgz", - "integrity": "sha512-WJHo+jmFp0dwRuymPmIovuxHaBntcCyja5hCB0yYY9wWrViEp4kF5Cdai98P72v6FzroPuABqu+ddLMbQWmwzA==", - "dev": true, - "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/sass-loader": { - "version": "10.5.2", - "resolved": "https://registry.npmmirror.com/sass-loader/-/sass-loader-10.5.2.tgz", - "integrity": "sha512-vMUoSNOUKJILHpcNCCyD23X34gve1TS7Rjd9uXHeKqhvBG39x6XbswFDtpbTElj6XdMFezoWhkh5vtKudf2cgQ==", - "dev": true, - "dependencies": { - "klona": "^2.0.4", - "loader-utils": "^2.0.0", - "neo-async": "^2.6.2", - "schema-utils": "^3.0.0", - "semver": "^7.3.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "fibers": ">= 3.1.0", - "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", - "sass": "^1.3.0", - "webpack": "^4.36.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "fibers": { - "optional": true - }, - "node-sass": { - "optional": true - }, - "sass": { - "optional": true - } - } - }, - "node_modules/sass-loader/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/sass-loader/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmmirror.com/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmmirror.com/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - }, - "node_modules/schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 8.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", - "dev": true - }, - "node_modules/selfsigned": { - "version": "1.10.14", - "resolved": "https://registry.npmmirror.com/selfsigned/-/selfsigned-1.10.14.tgz", - "integrity": "sha512-lkjaiAye+wBZDCBsu5BGi0XiLRxeUlsGod5ZP924CRSEoGuZAw/f7y9RKu28rwTfiHVhdavhB0qH0INV6P1lEA==", - "dev": true, - "dependencies": { - "node-forge": "^0.10.0" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmmirror.com/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/send/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmmirror.com/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", - "dev": true, - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-index/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-index/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "dev": true, - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true - }, - "node_modules/serve-index/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "node_modules/serve-index/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmmirror.com/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "dev": true, - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, - "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmmirror.com/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" - } - }, - "node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/shell-quote": { - "version": "1.8.1", - "resolved": "https://registry.npmmirror.com/shell-quote/-/shell-quote-1.8.1.tgz", - "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmmirror.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/simple-swizzle/node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "dev": true - }, - "node_modules/slash": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmmirror.com/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "dependencies": { - "kind-of": "^3.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/snapdragon/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmmirror.com/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-descriptor": { - "version": "0.1.7", - "resolved": "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-0.1.7.tgz", - "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/snapdragon/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/snapdragon/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sockjs": { - "version": "0.3.24", - "resolved": "https://registry.npmmirror.com/sockjs/-/sockjs-0.3.24.tgz", - "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", - "dev": true, - "dependencies": { - "faye-websocket": "^0.11.3", - "uuid": "^8.3.2", - "websocket-driver": "^0.7.4" - } - }, - "node_modules/sockjs-client": { - "version": "1.6.1", - "resolved": "https://registry.npmmirror.com/sockjs-client/-/sockjs-client-1.6.1.tgz", - "integrity": "sha512-2g0tjOR+fRs0amxENLi/q5TiJTqY+WXFOzb5UwXndlK6TO3U/mirZznpx6w34HVMoc3g7cY24yC/ZMIYnDlfkw==", - "dev": true, - "dependencies": { - "debug": "^3.2.7", - "eventsource": "^2.0.2", - "faye-websocket": "^0.11.4", - "inherits": "^2.0.4", - "url-parse": "^1.5.10" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://tidelift.com/funding/github/npm/sockjs-client" - } - }, - "node_modules/sockjs-client/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/sockjs/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/sort-keys": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==", - "dev": true, - "dependencies": { - "is-plain-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sort-keys/node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", - "dev": true - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmmirror.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmmirror.com/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "dev": true - }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmmirror.com/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmmirror.com/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.18", - "resolved": "https://registry.npmmirror.com/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz", - "integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==", - "dev": true - }, - "node_modules/spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "dev": true, - "dependencies": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "dev": true, - "dependencies": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, - "node_modules/spdy-transport/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "node_modules/sql-formatter": { - "version": "9.2.0", - "resolved": "https://registry.npmmirror.com/sql-formatter/-/sql-formatter-9.2.0.tgz", - "integrity": "sha512-Dn4lEpUeAhfNDR2LnEs9Uaq92TSHjhcNrzhllPuMnp188P4sLU7UcdcB9UqIfMfcN62gWXJlJ3KocaAf/SOzXQ==", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "sql-formatter": "bin/sql-formatter-cli.js" - } - }, - "node_modules/sql-formatter/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "node_modules/sshpk": { - "version": "1.18.0", - "resolved": "https://registry.npmmirror.com/sshpk/-/sshpk-1.18.0.tgz", - "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", - "dev": true, - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ssri": { - "version": "8.0.1", - "resolved": "https://registry.npmmirror.com/ssri/-/ssri-8.0.1.tgz", - "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "dev": true, - "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/stable": { - "version": "0.1.8", - "resolved": "https://registry.npmmirror.com/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", - "dev": true - }, - "node_modules/stackframe": { - "version": "1.3.4", - "resolved": "https://registry.npmmirror.com/stackframe/-/stackframe-1.3.4.tgz", - "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", - "dev": true - }, - "node_modules/static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmmirror.com/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", - "dev": true, - "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmmirror.com/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-descriptor": { - "version": "0.1.7", - "resolved": "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-0.1.7.tgz", - "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/stream-browserify": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/stream-browserify/-/stream-browserify-2.0.2.tgz", - "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", - "dev": true, - "dependencies": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" - } - }, - "node_modules/stream-each": { - "version": "1.2.3", - "resolved": "https://registry.npmmirror.com/stream-each/-/stream-each-1.2.3.tgz", - "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/stream-http": { - "version": "2.8.3", - "resolved": "https://registry.npmmirror.com/stream-http/-/stream-http-2.8.3.tgz", - "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", - "dev": true, - "dependencies": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" - } - }, - "node_modules/stream-shift": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/stream-shift/-/stream-shift-1.0.3.tgz", - "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", - "dev": true - }, - "node_modules/strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.9", - "resolved": "https://registry.npmmirror.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", - "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", - "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-indent": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/strip-indent/-/strip-indent-2.0.0.tgz", - "integrity": "sha512-RsSNPLpq6YUL7QYy44RnPVTn/lcVZtb48Uof3X5JLbF4zD/Gs7ZFDv2HWol+leoQN2mT86LAzSshGfkTlSOpsA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/stylehacks": { - "version": "4.0.3", - "resolved": "https://registry.npmmirror.com/stylehacks/-/stylehacks-4.0.3.tgz", - "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", - "dev": true, - "dependencies": { - "browserslist": "^4.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/stylehacks/node_modules/postcss-selector-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", - "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", - "dev": true, - "dependencies": { - "dot-prop": "^5.2.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/svg-tags": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/svg-tags/-/svg-tags-1.0.0.tgz", - "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", - "dev": true - }, - "node_modules/svgo": { - "version": "1.3.2", - "resolved": "https://registry.npmmirror.com/svgo/-/svgo-1.3.2.tgz", - "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", - "dev": true, - "dependencies": { - "chalk": "^2.4.1", - "coa": "^2.0.2", - "css-select": "^2.0.0", - "css-select-base-adapter": "^0.1.1", - "css-tree": "1.0.0-alpha.37", - "csso": "^4.0.2", - "js-yaml": "^3.13.1", - "mkdirp": "~0.5.1", - "object.values": "^1.1.0", - "sax": "~1.2.4", - "stable": "^0.1.8", - "unquote": "~1.1.1", - "util.promisify": "~1.0.0" - }, - "bin": { - "svgo": "bin/svgo" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/svgo/node_modules/css-select": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/css-select/-/css-select-2.1.0.tgz", - "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^3.2.1", - "domutils": "^1.7.0", - "nth-check": "^1.0.2" - } - }, - "node_modules/svgo/node_modules/css-what": { - "version": "3.4.2", - "resolved": "https://registry.npmmirror.com/css-what/-/css-what-3.4.2.tgz", - "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==", - "dev": true, - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/svgo/node_modules/dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", - "dev": true, - "dependencies": { - "domelementtype": "^2.0.1", - "entities": "^2.0.0" - } - }, - "node_modules/svgo/node_modules/domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmmirror.com/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "dev": true, - "dependencies": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "node_modules/svgo/node_modules/domutils/node_modules/domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmmirror.com/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", - "dev": true - }, - "node_modules/svgo/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmmirror.com/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/svgo/node_modules/nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "dev": true, - "dependencies": { - "boolbase": "~1.0.0" - } - }, - "node_modules/table": { - "version": "5.4.6", - "resolved": "https://registry.npmmirror.com/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", - "dev": true, - "dependencies": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/table/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/table/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "node_modules/table/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/table/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/table/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmmirror.com/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/terser": { - "version": "4.8.1", - "resolved": "https://registry.npmmirror.com/terser/-/terser-4.8.1.tgz", - "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==", - "dev": true, - "dependencies": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "1.4.5", - "resolved": "https://registry.npmmirror.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", - "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", - "dev": true, - "dependencies": { - "cacache": "^12.0.2", - "find-cache-dir": "^2.1.0", - "is-wsl": "^1.1.0", - "schema-utils": "^1.0.0", - "serialize-javascript": "^4.0.0", - "source-map": "^0.6.1", - "terser": "^4.1.2", - "webpack-sources": "^1.4.0", - "worker-farm": "^1.7.0" - }, - "engines": { - "node": ">= 6.9.0" - }, - "peerDependencies": { - "webpack": "^4.0.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/terser-webpack-plugin/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/terser-webpack-plugin/node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/terser-webpack-plugin/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/terser-webpack-plugin/node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/terser-webpack-plugin/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/terser-webpack-plugin/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/terser-webpack-plugin/node_modules/pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "dependencies": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/terser-webpack-plugin/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/thread-loader": { - "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/thread-loader/-/thread-loader-2.1.3.tgz", - "integrity": "sha512-wNrVKH2Lcf8ZrWxDF/khdlLlsTMczdcwPA9VEK4c2exlEPynYWxi9op3nPTo5lAnDIkE0rQEB3VBP+4Zncc9Hg==", - "dev": true, - "dependencies": { - "loader-runner": "^2.3.1", - "loader-utils": "^1.1.0", - "neo-async": "^2.6.0" - }, - "engines": { - "node": ">= 6.9.0 <7.0.0 || >= 8.9.0" - }, - "peerDependencies": { - "webpack": "^2.0.0 || ^3.0.0 || ^4.0.0" - } - }, - "node_modules/thread-loader/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/thread-loader/node_modules/loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/throttle-debounce": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-1.1.0.tgz", - "integrity": "sha512-XH8UiPCQcWNuk2LYePibW/4qL97+ZQ1AN3FNXwZRBNPPowo/NRU5fAlDCSNBJIYCKbioZfuYtMhG4quqoJhVzg==", - "engines": { - "node": ">=4" - } - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmmirror.com/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, - "node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmmirror.com/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", - "dev": true - }, - "node_modules/timers-browserify": { - "version": "2.0.12", - "resolved": "https://registry.npmmirror.com/timers-browserify/-/timers-browserify-2.0.12.tgz", - "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", - "dev": true, - "dependencies": { - "setimmediate": "^1.0.4" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/timsort": { - "version": "0.3.0", - "resolved": "https://registry.npmmirror.com/timsort/-/timsort-0.3.0.tgz", - "integrity": "sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==", - "dev": true - }, - "node_modules/tiny-cookie": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/tiny-cookie/-/tiny-cookie-1.0.1.tgz", - "integrity": "sha512-4KJ21HYTNumxnHvCbLrtrrhSwtWXzpmaRJIUg4biBii5AiSe2izod5bauXiiQxVuf2Fta57U2DTW1Fa4/6Cmgw==" - }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmmirror.com/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==", - "dev": true - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmmirror.com/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-object-path/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmmirror.com/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/toposort": { - "version": "1.0.7", - "resolved": "https://registry.npmmirror.com/toposort/-/toposort-1.0.7.tgz", - "integrity": "sha512-FclLrw8b9bMWf4QlCJuHBEVhSRsqDj6u3nIjAzPeJvgl//1hBlffdlk0MALceL14+koWEdU4ofRAXofbODxQzg==", - "dev": true - }, - "node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/tryer": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/tryer/-/tryer-1.0.1.tgz", - "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==", - "dev": true - }, - "node_modules/ts-pnp": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/ts-pnp/-/ts-pnp-1.2.0.tgz", - "integrity": "sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmmirror.com/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw==", - "dev": true - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmmirror.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmmirror.com/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true - }, - "node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typed-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", - "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", - "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", - "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.6", - "resolved": "https://registry.npmmirror.com/typed-array-length/-/typed-array-length-1.0.6.tgz", - "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmmirror.com/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "dev": true - }, - "node_modules/uglify-js": { - "version": "3.4.10", - "resolved": "https://registry.npmmirror.com/uglify-js/-/uglify-js-3.4.10.tgz", - "integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==", - "dev": true, - "dependencies": { - "commander": "~2.19.0", - "source-map": "~0.6.1" - }, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/uglify-js/node_modules/commander": { - "version": "2.19.0", - "resolved": "https://registry.npmmirror.com/commander/-/commander-2.19.0.tgz", - "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", - "dev": true - }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/union-value/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/uniq": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==", - "dev": true - }, - "node_modules/uniqs": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/uniqs/-/uniqs-2.0.0.tgz", - "integrity": "sha512-mZdDpf3vBV5Efh29kMw5tXoup/buMgxLzOt/XKFKcVmi+15ManNQWr6HfZ2aiZTYlYixbdNJ0KFmIZIv52tHSQ==", - "dev": true - }, - "node_modules/unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "dev": true, - "dependencies": { - "unique-slug": "^2.0.0" - } - }, - "node_modules/unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4" - } - }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmmirror.com/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/unquote": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/unquote/-/unquote-1.1.1.tgz", - "integrity": "sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==", - "dev": true - }, - "node_modules/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", - "dev": true, - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmmirror.com/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", - "dev": true, - "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", - "dev": true, - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmmirror.com/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/upath": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true, - "engines": { - "node": ">=4", - "yarn": "*" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.16", - "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", - "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/upper-case": { - "version": "1.1.3", - "resolved": "https://registry.npmmirror.com/upper-case/-/upper-case-1.1.3.tgz", - "integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==", - "dev": true - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmmirror.com/urix/-/urix-0.1.0.tgz", - "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", - "dev": true - }, - "node_modules/url": { - "version": "0.11.3", - "resolved": "https://registry.npmmirror.com/url/-/url-0.11.3.tgz", - "integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==", - "dev": true, - "dependencies": { - "punycode": "^1.4.1", - "qs": "^6.11.2" - } - }, - "node_modules/url-loader": { - "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/url-loader/-/url-loader-2.3.0.tgz", - "integrity": "sha512-goSdg8VY+7nPZKUEChZSEtW5gjbS66USIGCeSJ1OVOJ7Yfuh/36YxCwMi5HVEJh6mqUYOoy3NJ0vlOMrWsSHog==", - "dev": true, - "dependencies": { - "loader-utils": "^1.2.3", - "mime": "^2.4.4", - "schema-utils": "^2.5.0" - }, - "engines": { - "node": ">= 8.9.0" - }, - "peerDependencies": { - "file-loader": "*", - "webpack": "^4.0.0" - }, - "peerDependenciesMeta": { - "file-loader": { - "optional": true - } - } - }, - "node_modules/url-loader/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/url-loader/node_modules/loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmmirror.com/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/url/node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmmirror.com/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", - "dev": true - }, - "node_modules/url/node_modules/qs": { - "version": "6.12.1", - "resolved": "https://registry.npmmirror.com/qs/-/qs-6.12.1.tgz", - "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/util": { - "version": "0.11.1", - "resolved": "https://registry.npmmirror.com/util/-/util-0.11.1.tgz", - "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", - "dev": true, - "dependencies": { - "inherits": "2.0.3" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "node_modules/util.promisify": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/util.promisify/-/util.promisify-1.0.0.tgz", - "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.2", - "object.getownpropertydescriptors": "^2.0.3" - } - }, - "node_modules/util/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true - }, - "node_modules/utila": { - "version": "0.4.0", - "resolved": "https://registry.npmmirror.com/utila/-/utila-0.4.0.tgz", - "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", - "dev": true - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "dev": true, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true, - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/v8-compile-cache": { - "version": "2.4.0", - "resolved": "https://registry.npmmirror.com/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", - "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", - "dev": true - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmmirror.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vendors": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/vendors/-/vendors-1.0.4.tgz", - "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmmirror.com/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "node_modules/verror/node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true - }, - "node_modules/vm-browserify": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", - "dev": true - }, - "node_modules/vue": { - "version": "2.7.16", - "resolved": "https://registry.npmmirror.com/vue/-/vue-2.7.16.tgz", - "integrity": "sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==", - "dependencies": { - "@vue/compiler-sfc": "2.7.16", - "csstype": "^3.1.0" - } - }, - "node_modules/vue-class-component": { - "version": "7.2.6", - "resolved": "https://registry.npmmirror.com/vue-class-component/-/vue-class-component-7.2.6.tgz", - "integrity": "sha512-+eaQXVrAm/LldalI272PpDe3+i4mPis0ORiMYxF6Ae4hyuCh15W8Idet7wPUEs4N4YptgFHGys4UrgNQOMyO6w==", - "peerDependencies": { - "vue": "^2.0.0" - } - }, - "node_modules/vue-codemirror": { - "version": "4.0.6", - "resolved": "https://registry.npmmirror.com/vue-codemirror/-/vue-codemirror-4.0.6.tgz", - "integrity": "sha512-ilU7Uf0mqBNSSV3KT7FNEeRIxH4s1fmpG4TfHlzvXn0QiQAbkXS9lLfwuZpaBVEnpP5CSE62iGJjoliTuA8poQ==", - "dependencies": { - "codemirror": "^5.41.0", - "diff-match-patch": "^1.0.0" - }, - "engines": { - "node": ">= 4.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/vue-contextmenujs": { - "version": "1.4.11", - "resolved": "https://registry.npmmirror.com/vue-contextmenujs/-/vue-contextmenujs-1.4.11.tgz", - "integrity": "sha512-k8y0ExOL5yn7fRRCo6UDT81ekq/Ls1JBz70eBBpIgcf1p++84amDL6uCMmlt6D6WK5a9f0NhW2kc2pkCUb2bHA==" - }, - "node_modules/vue-cookie": { - "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/vue-cookie/-/vue-cookie-1.1.4.tgz", - "integrity": "sha512-lxWIzmUTCVra0H7YPbNpYx0wbTQThV+n7ueUOcqtK8dlckmfB8fcyf4OEiRuQUd7iiVub2T3IDlWiisEM5Ku0Q==", - "dependencies": { - "tiny-cookie": "^1.0" - } - }, - "node_modules/vue-eslint-parser": { - "version": "7.11.0", - "resolved": "https://registry.npmmirror.com/vue-eslint-parser/-/vue-eslint-parser-7.11.0.tgz", - "integrity": "sha512-qh3VhDLeh773wjgNTl7ss0VejY9bMMa0GoDG2fQVyDzRFdiU3L7fw74tWZDHNQXdZqxO3EveQroa9ct39D2nqg==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "eslint-scope": "^5.1.1", - "eslint-visitor-keys": "^1.1.0", - "espree": "^6.2.1", - "esquery": "^1.4.0", - "lodash": "^4.17.21", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8.10" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5.0.0" - } - }, - "node_modules/vue-hot-reload-api": { - "version": "2.3.4", - "resolved": "https://registry.npmmirror.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz", - "integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==", - "dev": true - }, - "node_modules/vue-i18n": { - "version": "8.28.2", - "resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-8.28.2.tgz", - "integrity": "sha512-C5GZjs1tYlAqjwymaaCPDjCyGo10ajUphiwA922jKt9n7KPpqR7oM1PCwYzhB/E7+nT3wfdG3oRre5raIT1rKA==" - }, - "node_modules/vue-loader": { - "version": "15.11.1", - "resolved": "https://registry.npmmirror.com/vue-loader/-/vue-loader-15.11.1.tgz", - "integrity": "sha512-0iw4VchYLePqJfJu9s62ACWUXeSqM30SQqlIftbYWM3C+jpPcEHKSPUZBLjSF9au4HTHQ/naF6OGnO3Q/qGR3Q==", - "dev": true, - "dependencies": { - "@vue/component-compiler-utils": "^3.1.0", - "hash-sum": "^1.0.2", - "loader-utils": "^1.1.0", - "vue-hot-reload-api": "^2.3.0", - "vue-style-loader": "^4.1.0" - }, - "peerDependencies": { - "css-loader": "*", - "webpack": "^3.0.0 || ^4.1.0 || ^5.0.0-0" - }, - "peerDependenciesMeta": { - "cache-loader": { - "optional": true - }, - "prettier": { - "optional": true - }, - "vue-template-compiler": { - "optional": true - } - } - }, - "node_modules/vue-loader-v16": { - "name": "vue-loader", - "version": "16.8.3", - "resolved": "https://registry.npmmirror.com/vue-loader/-/vue-loader-16.8.3.tgz", - "integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==", - "dev": true, - "optional": true, - "dependencies": { - "chalk": "^4.1.0", - "hash-sum": "^2.0.0", - "loader-utils": "^2.0.0" - }, - "peerDependencies": { - "webpack": "^4.1.0 || ^5.0.0-0" - } - }, - "node_modules/vue-loader-v16/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "optional": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/vue-loader-v16/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "optional": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/vue-loader-v16/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "optional": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/vue-loader-v16/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "optional": true - }, - "node_modules/vue-loader-v16/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/vue-loader-v16/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "optional": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/vue-loader/node_modules/hash-sum": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/hash-sum/-/hash-sum-1.0.2.tgz", - "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==", - "dev": true - }, - "node_modules/vue-loader/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/vue-loader/node_modules/loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/vue-property-decorator": { - "version": "8.5.1", - "resolved": "https://registry.npmmirror.com/vue-property-decorator/-/vue-property-decorator-8.5.1.tgz", - "integrity": "sha512-O6OUN2OMsYTGPvgFtXeBU3jPnX5ffQ9V4I1WfxFQ6dqz6cOUbR3Usou7kgFpfiXDvV7dJQSFcJ5yUPgOtPPm1Q==", - "dependencies": { - "vue-class-component": "^7.1.0" - }, - "peerDependencies": { - "vue": "*" - } - }, - "node_modules/vue-rx": { - "version": "6.2.0", - "resolved": "https://registry.npmmirror.com/vue-rx/-/vue-rx-6.2.0.tgz", - "integrity": "sha512-tpKUcqS5IUYS+HsdbR5TlE5LL9PK4zwlplEtmMeydnbqaUTa9ciLMplJXAtFSiQw1vuURoyEJmCXqMxaVEIloQ==", - "peerDependencies": { - "rxjs": "^6.0.0" - } - }, - "node_modules/vue-splitpane": { - "version": "1.0.6", - "resolved": "https://registry.npmmirror.com/vue-splitpane/-/vue-splitpane-1.0.6.tgz", - "integrity": "sha512-6sneVSEeF1VsCD025HP93nRxwzYhv3rotCeYi2Ah1FmGxwtfqbCZEIzaHGberdGNCpMlEzGD/1OIE1kK5QukLA==" - }, - "node_modules/vue-style-loader": { - "version": "4.1.3", - "resolved": "https://registry.npmmirror.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz", - "integrity": "sha512-sFuh0xfbtpRlKfm39ss/ikqs9AbKCoXZBpHeVZ8Tx650o0k0q/YCM7FRvigtxpACezfq6af+a7JeqVTWvncqDg==", - "dev": true, - "dependencies": { - "hash-sum": "^1.0.2", - "loader-utils": "^1.0.2" - } - }, - "node_modules/vue-style-loader/node_modules/hash-sum": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/hash-sum/-/hash-sum-1.0.2.tgz", - "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==", - "dev": true - }, - "node_modules/vue-style-loader/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/vue-style-loader/node_modules/loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/vue-template-compiler": { - "version": "2.7.16", - "resolved": "https://registry.npmmirror.com/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz", - "integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==", - "dev": true, - "dependencies": { - "de-indent": "^1.0.2", - "he": "^1.2.0" - } - }, - "node_modules/vue-template-es2015-compiler": { - "version": "1.9.1", - "resolved": "https://registry.npmmirror.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz", - "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", - "dev": true - }, - "node_modules/vue/node_modules/@vue/compiler-sfc": { - "version": "2.7.16", - "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-2.7.16.tgz", - "integrity": "sha512-KWhJ9k5nXuNtygPU7+t1rX6baZeqOYLEforUPjgNDBnLicfHCoi48H87Q8XyLZOrNNsmhuwKqtpDQWjEFe6Ekg==", - "dependencies": { - "@babel/parser": "^7.23.5", - "postcss": "^8.4.14", - "source-map": "^0.6.1" - }, - "optionalDependencies": { - "prettier": "^1.18.2 || ^2.0.0" - } - }, - "node_modules/vue/node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/vuex": { - "version": "3.6.2", - "resolved": "https://registry.npmmirror.com/vuex/-/vuex-3.6.2.tgz", - "integrity": "sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw==", - "peerDependencies": { - "vue": "^2.0.0" - } - }, - "node_modules/watchpack": { - "version": "1.7.5", - "resolved": "https://registry.npmmirror.com/watchpack/-/watchpack-1.7.5.tgz", - "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0" - }, - "optionalDependencies": { - "chokidar": "^3.4.1", - "watchpack-chokidar2": "^2.0.1" - } - }, - "node_modules/watchpack-chokidar2": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", - "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", - "dev": true, - "optional": true, - "dependencies": { - "chokidar": "^2.1.8" - } - }, - "node_modules/watchpack-chokidar2/node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "optional": true, - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/watchpack-chokidar2/node_modules/anymatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", - "dev": true, - "optional": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "dev": true, - "optional": true, - "dependencies": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - }, - "optionalDependencies": { - "fsevents": "^1.2.7" - } - }, - "node_modules/watchpack-chokidar2/node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==", - "dev": true, - "optional": true, - "dependencies": { - "binary-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "optional": true, - "dependencies": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmmirror.com/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "dev": true, - "dependencies": { - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dev": true, - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/webpack": { - "version": "4.47.0", - "resolved": "https://registry.npmmirror.com/webpack/-/webpack-4.47.0.tgz", - "integrity": "sha512-td7fYwgLSrky3fI1EuU5cneU4+pbH6GgOfuKNS1tNPcfdGinGELAqsb/BP4nnvZyKSG2i/xFGU7+n2PvZA8HJQ==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/wasm-edit": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "acorn": "^6.4.1", - "ajv": "^6.10.2", - "ajv-keywords": "^3.4.1", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^4.5.0", - "eslint-scope": "^4.0.3", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^2.4.0", - "loader-utils": "^1.2.3", - "memory-fs": "^0.4.1", - "micromatch": "^3.1.10", - "mkdirp": "^0.5.3", - "neo-async": "^2.6.1", - "node-libs-browser": "^2.2.1", - "schema-utils": "^1.0.0", - "tapable": "^1.1.3", - "terser-webpack-plugin": "^1.4.3", - "watchpack": "^1.7.4", - "webpack-sources": "^1.4.1" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=6.11.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - }, - "webpack-command": { - "optional": true - } - } - }, - "node_modules/webpack-bundle-analyzer": { - "version": "3.9.0", - "resolved": "https://registry.npmmirror.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.9.0.tgz", - "integrity": "sha512-Ob8amZfCm3rMB1ScjQVlbYYUEJyEjdEtQ92jqiFUYt5VkEeO2v5UMbv49P/gnmCZm3A6yaFQzCBvpZqN4MUsdA==", - "dev": true, - "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1", - "bfj": "^6.1.1", - "chalk": "^2.4.1", - "commander": "^2.18.0", - "ejs": "^2.6.1", - "express": "^4.16.3", - "filesize": "^3.6.1", - "gzip-size": "^5.0.0", - "lodash": "^4.17.19", - "mkdirp": "^0.5.1", - "opener": "^1.5.1", - "ws": "^6.0.0" - }, - "bin": { - "webpack-bundle-analyzer": "lib/bin/analyzer.js" - }, - "engines": { - "node": ">= 6.14.4" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/webpack-chain": { - "version": "6.5.1", - "resolved": "https://registry.npmmirror.com/webpack-chain/-/webpack-chain-6.5.1.tgz", - "integrity": "sha512-7doO/SRtLu8q5WM0s7vPKPWX580qhi0/yBHkOxNkv50f6qB76Zy9o2wRTrrPULqYTvQlVHuvbA8v+G5ayuUDsA==", - "dev": true, - "dependencies": { - "deepmerge": "^1.5.2", - "javascript-stringify": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/webpack-dev-middleware": { - "version": "3.7.3", - "resolved": "https://registry.npmmirror.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz", - "integrity": "sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ==", - "dev": true, - "dependencies": { - "memory-fs": "^0.4.1", - "mime": "^2.4.4", - "mkdirp": "^0.5.1", - "range-parser": "^1.2.1", - "webpack-log": "^2.0.0" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/webpack-dev-server": { - "version": "3.11.3", - "resolved": "https://registry.npmmirror.com/webpack-dev-server/-/webpack-dev-server-3.11.3.tgz", - "integrity": "sha512-3x31rjbEQWKMNzacUZRE6wXvUFuGpH7vr0lIEbYpMAG9BOxi0928QU1BBswOAP3kg3H1O4hiS+sq4YyAn6ANnA==", - "dev": true, - "dependencies": { - "ansi-html-community": "0.0.8", - "bonjour": "^3.5.0", - "chokidar": "^2.1.8", - "compression": "^1.7.4", - "connect-history-api-fallback": "^1.6.0", - "debug": "^4.1.1", - "del": "^4.1.1", - "express": "^4.17.1", - "html-entities": "^1.3.1", - "http-proxy-middleware": "0.19.1", - "import-local": "^2.0.0", - "internal-ip": "^4.3.0", - "ip": "^1.1.5", - "is-absolute-url": "^3.0.3", - "killable": "^1.0.1", - "loglevel": "^1.6.8", - "opn": "^5.5.0", - "p-retry": "^3.0.1", - "portfinder": "^1.0.26", - "schema-utils": "^1.0.0", - "selfsigned": "^1.10.8", - "semver": "^6.3.0", - "serve-index": "^1.9.1", - "sockjs": "^0.3.21", - "sockjs-client": "^1.5.0", - "spdy": "^4.0.2", - "strip-ansi": "^3.0.1", - "supports-color": "^6.1.0", - "url": "^0.11.0", - "webpack-dev-middleware": "^3.7.2", - "webpack-log": "^2.0.0", - "ws": "^6.2.1", - "yargs": "^13.3.2" - }, - "bin": { - "webpack-dev-server": "bin/webpack-dev-server.js" - }, - "engines": { - "node": ">= 6.11.5" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-dev-server/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/webpack-dev-server/node_modules/anymatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", - "dev": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "dev": true, - "dependencies": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - }, - "optionalDependencies": { - "fsevents": "^1.2.7" - } - }, - "node_modules/webpack-dev-server/node_modules/cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "dependencies": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - } - }, - "node_modules/webpack-dev-server/node_modules/cliui/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/cliui/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "node_modules/webpack-dev-server/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { - "version": "0.19.1", - "resolved": "https://registry.npmmirror.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", - "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", - "dev": true, - "dependencies": { - "http-proxy": "^1.17.0", - "is-glob": "^4.0.0", - "lodash": "^4.17.11", - "micromatch": "^3.1.10" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/webpack-dev-server/node_modules/is-absolute-url": { - "version": "3.0.3", - "resolved": "https://registry.npmmirror.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz", - "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/webpack-dev-server/node_modules/is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==", - "dev": true, - "dependencies": { - "binary-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/webpack-dev-server/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/webpack-dev-server/node_modules/readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/webpack-dev-server/node_modules/schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "dependencies": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/webpack-dev-server/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/string-width/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/string-width/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmmirror.com/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "dependencies": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - } - }, - "node_modules/webpack-dev-server/node_modules/yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "node_modules/webpack-log": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/webpack-log/-/webpack-log-2.0.0.tgz", - "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", - "dev": true, - "dependencies": { - "ansi-colors": "^3.0.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/webpack-merge": { - "version": "4.2.2", - "resolved": "https://registry.npmmirror.com/webpack-merge/-/webpack-merge-4.2.2.tgz", - "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", - "dev": true, - "dependencies": { - "lodash": "^4.17.15" - } - }, - "node_modules/webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmmirror.com/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "dev": true, - "dependencies": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - } - }, - "node_modules/webpack/node_modules/acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmmirror.com/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/webpack/node_modules/eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "dev": true, - "dependencies": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/webpack/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/webpack/node_modules/loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "dependencies": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmmirror.com/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dev": true, - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmmirror.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmmirror.com/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "dev": true - }, - "node_modules/which-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.15.tgz", - "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/worker-farm": { - "version": "1.7.0", - "resolved": "https://registry.npmmirror.com/worker-farm/-/worker-farm-1.7.0.tgz", - "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", - "dev": true, - "dependencies": { - "errno": "~0.1.7" - } - }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/write": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "dependencies": { - "mkdirp": "^0.5.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ws": { - "version": "6.2.2", - "resolved": "https://registry.npmmirror.com/ws/-/ws-6.2.2.tgz", - "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", - "dev": true, - "dependencies": { - "async-limiter": "~1.0.0" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmmirror.com/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/yargs/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmmirror.com/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/yargs/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/yargs/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/yargs/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/yargs/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yorkie": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/yorkie/-/yorkie-2.0.0.tgz", - "integrity": "sha512-jcKpkthap6x63MB4TxwCyuIGkV0oYP/YRyuQU5UO0Yz/E/ZAu+653/uov+phdmO54n6BcvFRyyt0RRrWdN2mpw==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "execa": "^0.8.0", - "is-ci": "^1.0.10", - "normalize-path": "^1.0.0", - "strip-indent": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/yorkie/node_modules/cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", - "dev": true, - "dependencies": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "node_modules/yorkie/node_modules/execa": { - "version": "0.8.0", - "resolved": "https://registry.npmmirror.com/execa/-/execa-0.8.0.tgz", - "integrity": "sha512-zDWS+Rb1E8BlqqhALSt9kUhss8Qq4nN3iof3gsOdyINksElaPyNBtKUMTR62qhvgVWR0CqCX7sdnKe4MnUbFEA==", - "dev": true, - "dependencies": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/yorkie/node_modules/get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/yorkie/node_modules/lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "node_modules/yorkie/node_modules/normalize-path": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-1.0.0.tgz", - "integrity": "sha512-7WyT0w8jhpDStXRq5836AMmihQwq2nrUVQrgjvUo/p/NZf9uy/MeJ246lBJVmWuYXMlJuG9BNZHF0hWjfTbQUA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yorkie/node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", - "dev": true - } - } -} diff --git a/frontend/package.json b/frontend/package.json deleted file mode 100644 index 4cb74c1d..00000000 --- a/frontend/package.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "name": "fronted", - "version": "0.1.0", - "private": true, - "scripts": { - "serve": "NODE_OPTIONS=--openssl-legacy-provider vue-cli-service serve", - "build": "NODE_OPTIONS=--openssl-legacy-provider vue-cli-service build", - "lint": "NODE_OPTIONS=--openssl-legacy-provider vue-cli-service lint" - }, - "dependencies": { - "ag-grid-community": "^32.1.0", - "ag-grid-vue": "^31.3.4", - "axios": "^0.32.0", - "codemirror": "^5.65.2", - "core-js": "^3.6.5", - "element-ui": "^2.15.13", - "font-awesome": "^4.7.0", - "moment": "^2.29.4", - "rxjs": "^6.6.7", - "sql-formatter": "^9.0.0", - "vue": "^2.6.11", - "vue-codemirror": "^4.0.6", - "vue-contextmenujs": "^1.4.9", - "vue-cookie": "^1.1.4", - "vue-i18n": "^8.22.4", - "vue-property-decorator": "^8.5.1", - "vue-rx": "^6.2.0", - "vue-splitpane": "^1.0.6", - "vuex": "^3.6.0" - }, - "devDependencies": { - "@vue/cli-plugin-babel": "~4.5.14", - "@vue/cli-plugin-eslint": "~4.5.14", - "@vue/cli-service": "~4.5.14", - "babel-eslint": "^10.1.0", - "eslint": "^6.7.2", - "eslint-plugin-vue": "^6.2.2", - "sass": "^1.49.0", - "sass-loader": "^10.1.1", - "vue-template-compiler": "^2.6.11" - }, - "eslintConfig": { - "root": true, - "env": { - "node": true - }, - "extends": [ - "plugin:vue/essential", - "eslint:recommended" - ], - "parserOptions": { - "parser": "babel-eslint" - }, - "rules": {} - }, - "browserslist": [ - "> 1%", - "last 2 versions", - "not dead" - ], - "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" -} diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico deleted file mode 100644 index df36fcfb..00000000 Binary files a/frontend/public/favicon.ico and /dev/null differ diff --git a/frontend/public/index.html b/frontend/public/index.html deleted file mode 100644 index 17ca5641..00000000 --- a/frontend/public/index.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - Chen: JumpServer Web Database - - - -
- - - diff --git a/frontend/run-server.sh b/frontend/run-server.sh deleted file mode 100755 index 827cc177..00000000 --- a/frontend/run-server.sh +++ /dev/null @@ -1,8 +0,0 @@ -#! /bin/bash - -cd .. - -mvn clean package -DskipTests - -java -Dspring.profiles.active=dev -jar backend/web/target/web-0.0.1.jar - diff --git a/frontend/src/App.vue b/frontend/src/App.vue deleted file mode 100644 index 1c930041..00000000 --- a/frontend/src/App.vue +++ /dev/null @@ -1,38 +0,0 @@ - - - - - diff --git a/frontend/src/api/app.js b/frontend/src/api/app.js deleted file mode 100644 index 1ab4a958..00000000 --- a/frontend/src/api/app.js +++ /dev/null @@ -1,11 +0,0 @@ -import { get, post } from '@/request' -import { appApiUrl } from '@/utils/path' - -export function auth(token, disableAutoHash) { - const body = { token: token, disableAutoHash: disableAutoHash } - return post(appApiUrl('auth'), body) -} - -export function getProfile() { - return get(appApiUrl('profile')) -} diff --git a/frontend/src/api/jms.js b/frontend/src/api/jms.js deleted file mode 100644 index fa06b686..00000000 --- a/frontend/src/api/jms.js +++ /dev/null @@ -1,10 +0,0 @@ -import { get, post } from '@/request' -import { apiUrl } from '@/utils/path' - -export function getSnippets() { - return get(apiUrl('v1/ops/adhocs/')) -} - -export function saveSnippet(item) { - return post(apiUrl('v1/ops/adhocs/'), item) -} diff --git a/frontend/src/api/resource.js b/frontend/src/api/resource.js deleted file mode 100644 index 5ae26bd4..00000000 --- a/frontend/src/api/resource.js +++ /dev/null @@ -1,26 +0,0 @@ -import { post } from '@/request' -import { appApiUrl } from '@/utils/path' - -export function getResourceTreeChildren(parent, force) { - let url = appApiUrl('resources/children') - if (force) { - url += '?force=true' - } - return post(url, parent) -} - -export function getActions(node) { - return post(appApiUrl('resources/actions'), node) -} - -export function doAction(node, action) { - return post(appApiUrl('resources/actions/do'), { node: node, action: action }) -} - -export function submitResourceForm(form) { - return post(appApiUrl('resources/forms'), form) -} - -export function getHints(nodeKey, context) { - return post(appApiUrl('resources/hints'), { nodeKey: nodeKey, context: context }) -} diff --git a/frontend/src/assets/iconfont/iconfont.css b/frontend/src/assets/iconfont/iconfont.css deleted file mode 100644 index e4378f06..00000000 --- a/frontend/src/assets/iconfont/iconfont.css +++ /dev/null @@ -1,179 +0,0 @@ -@font-face { - font-family: "iconfont"; /* Project id 3233354 */ - src: url('iconfont.woff2?t=1697022017860') format('woff2'), - url('iconfont.woff?t=1697022017860') format('woff'), - url('iconfont.ttf?t=1697022017860') format('truetype'); -} - -.iconfont { - font-family: "iconfont" !important; - font-size: 16px; - font-style: normal; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.icon-chen-arrow-to-bottom:before { - content: "\e83c"; -} - -.icon-chen-arrow-to-top:before { - content: "\e83d"; -} - -.icon-chen-file-open:before { - content: "\e670"; -} - -.icon-chen-save:before { - content: "\e67c"; -} - -.icon-chen-disconnect:before { - content: "\e621"; -} - -.icon-chen-database:before { - content: "\e61b"; -} - -.icon-chen-postgresql:before { - content: "\ec5d"; -} - -.icon-chen-refresh:before { - content: "\e654"; -} - -.icon-chen-new:before { - content: "\e624"; -} - -.icon-chen-listview:before { - content: "\e67b"; -} - -.icon-chen-columns:before { - content: "\e65a"; -} - -.icon-chen-zidingyilie:before { - content: "\e6b6"; -} - -.icon-chen-jurassic_table:before { - content: "\e6ac"; -} - -.icon-chen-icon_caret-right:before { - content: "\e60c"; -} - -.icon-chen-caret-left:before { - content: "\e603"; -} - -.icon-chen-caret-down:before { - content: "\e604"; -} - -.icon-chen-folder:before { - content: "\eb33"; -} - -.icon-chen-last-page:before { - content: "\e7b2"; -} - -.icon-chen-first_page:before { - content: "\e64c"; -} - -.icon-chen-pin-fill:before { - content: "\e863"; -} - -.icon-chen-reload1:before { - content: "\e602"; -} - -.icon-chen-m-geshihuawenzi:before { - content: "\e7f8"; -} - -.icon-chen-tubiaozhizuo-:before { - content: "\e605"; -} - -.icon-chen-play:before { - content: "\e911"; -} - -.icon-chen-minus:before { - content: "\e7fd"; -} - -.icon-chen-arrow_up_fill:before { - content: "\e817"; -} - -.icon-chen-plus:before { - content: "\e8fe"; -} - -.icon-chen-reload:before { - content: "\e944"; -} - -.icon-chen-stop:before { - content: "\e63c"; -} - -.icon-chen-icon_paging_left:before { - content: "\e652"; -} - -.icon-chen-icon_paging_right:before { - content: "\e653"; -} - -.icon-chen-query:before { - content: "\e64b"; -} - -.icon-chen-submit:before { - content: "\e619"; -} - -.icon-chen-column:before { - content: "\e741"; -} - -.icon-chen-primary:before { - content: "\e617"; -} - -.icon-chen-function:before { - content: "\e833"; -} - -.icon-chen-view:before { - content: "\e68e"; -} - -.icon-chen-table:before { - content: "\e600"; -} - -.icon-chen-schema:before { - content: "\e7e9"; -} - -.icon-chen-mysql:before { - content: "\ec6d"; -} - -.icon-chen-oracle:before { - content: "\ec48"; -} - diff --git a/frontend/src/assets/iconfont/iconfont.js b/frontend/src/assets/iconfont/iconfont.js deleted file mode 100644 index 356857a3..00000000 --- a/frontend/src/assets/iconfont/iconfont.js +++ /dev/null @@ -1 +0,0 @@ -window._iconfont_svg_string_3233354='',function(l){var c=(c=document.getElementsByTagName("script"))[c.length-1],a=c.getAttribute("data-injectcss"),c=c.getAttribute("data-disable-injectsvg");if(!c){var h,t,o,i,e,v=function(c,a){a.parentNode.insertBefore(c,a)};if(a&&!l.__iconfont__svg__cssinject__){l.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(c){console&&console.log(c)}}h=function(){var c,a=document.createElement("div");a.innerHTML=l._iconfont_svg_string_3233354,(a=a.getElementsByTagName("svg")[0])&&(a.setAttribute("aria-hidden","true"),a.style.position="absolute",a.style.width=0,a.style.height=0,a.style.overflow="hidden",a=a,(c=document.body).firstChild?v(a,c.firstChild):c.appendChild(a))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(h,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),h()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(o=h,i=l.document,e=!1,s(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,n())})}function n(){e||(e=!0,o())}function s(){try{i.documentElement.doScroll("left")}catch(c){return void setTimeout(s,50)}n()}}(window); \ No newline at end of file diff --git a/frontend/src/assets/iconfont/iconfont.json b/frontend/src/assets/iconfont/iconfont.json deleted file mode 100644 index f12a6c5b..00000000 --- a/frontend/src/assets/iconfont/iconfont.json +++ /dev/null @@ -1,296 +0,0 @@ -{ - "id": "3233354", - "name": "chen", - "font_family": "iconfont", - "css_prefix_text": "icon-chen-", - "description": "", - "glyphs": [ - { - "icon_id": "6151360", - "name": "arrow-to-bottom", - "font_class": "arrow-to-bottom", - "unicode": "e83c", - "unicode_decimal": 59452 - }, - { - "icon_id": "6151361", - "name": "arrow-to-top", - "font_class": "arrow-to-top", - "unicode": "e83d", - "unicode_decimal": 59453 - }, - { - "icon_id": "15838487", - "name": "file-open", - "font_class": "file-open", - "unicode": "e670", - "unicode_decimal": 58992 - }, - { - "icon_id": "15838524", - "name": "save", - "font_class": "save", - "unicode": "e67c", - "unicode_decimal": 59004 - }, - { - "icon_id": "32907697", - "name": "disconnect", - "font_class": "disconnect", - "unicode": "e621", - "unicode_decimal": 58913 - }, - { - "icon_id": "1196830", - "name": "database", - "font_class": "database", - "unicode": "e61b", - "unicode_decimal": 58907 - }, - { - "icon_id": "15378704", - "name": "postgresql", - "font_class": "postgresql", - "unicode": "ec5d", - "unicode_decimal": 60509 - }, - { - "icon_id": "383834", - "name": "refresh", - "font_class": "refresh", - "unicode": "e654", - "unicode_decimal": 58964 - }, - { - "icon_id": "17082304", - "name": "new", - "font_class": "new", - "unicode": "e624", - "unicode_decimal": 58916 - }, - { - "icon_id": "658064", - "name": "list view", - "font_class": "listview", - "unicode": "e67b", - "unicode_decimal": 59003 - }, - { - "icon_id": "1898653", - "name": "columns", - "font_class": "columns", - "unicode": "e65a", - "unicode_decimal": 58970 - }, - { - "icon_id": "1915020", - "name": "符号-自定义列", - "font_class": "zidingyilie", - "unicode": "e6b6", - "unicode_decimal": 59062 - }, - { - "icon_id": "10594742", - "name": "表-表格_jurassic", - "font_class": "jurassic_table", - "unicode": "e6ac", - "unicode_decimal": 59052 - }, - { - "icon_id": "1312339", - "name": "icon_caret-right", - "font_class": "icon_caret-right", - "unicode": "e60c", - "unicode_decimal": 58892 - }, - { - "icon_id": "14629115", - "name": "caret-left", - "font_class": "caret-left", - "unicode": "e603", - "unicode_decimal": 58883 - }, - { - "icon_id": "14629119", - "name": "caret-down", - "font_class": "caret-down", - "unicode": "e604", - "unicode_decimal": 58884 - }, - { - "icon_id": "27363502", - "name": "folder", - "font_class": "folder", - "unicode": "eb33", - "unicode_decimal": 60211 - }, - { - "icon_id": "5127384", - "name": "last-page", - "font_class": "last-page", - "unicode": "e7b2", - "unicode_decimal": 59314 - }, - { - "icon_id": "9745405", - "name": "first_page", - "font_class": "first_page", - "unicode": "e64c", - "unicode_decimal": 58956 - }, - { - "icon_id": "34453251", - "name": "pin-fill", - "font_class": "pin-fill", - "unicode": "e863", - "unicode_decimal": 59491 - }, - { - "icon_id": "11887171", - "name": "reload", - "font_class": "reload1", - "unicode": "e602", - "unicode_decimal": 58882 - }, - { - "icon_id": "21164516", - "name": "m-格式化文字", - "font_class": "m-geshihuawenzi", - "unicode": "e7f8", - "unicode_decimal": 59384 - }, - { - "icon_id": "6237070", - "name": "竖线", - "font_class": "tubiaozhizuo-", - "unicode": "e605", - "unicode_decimal": 58885 - }, - { - "icon_id": "7736335", - "name": "play", - "font_class": "play", - "unicode": "e911", - "unicode_decimal": 59665 - }, - { - "icon_id": "4767099", - "name": "minus", - "font_class": "minus", - "unicode": "e7fd", - "unicode_decimal": 59389 - }, - { - "icon_id": "5858607", - "name": "arrow_up_fill", - "font_class": "arrow_up_fill", - "unicode": "e817", - "unicode_decimal": 59415 - }, - { - "icon_id": "7834345", - "name": "plus", - "font_class": "plus", - "unicode": "e8fe", - "unicode_decimal": 59646 - }, - { - "icon_id": "8827258", - "name": "reload", - "font_class": "reload", - "unicode": "e944", - "unicode_decimal": 59716 - }, - { - "icon_id": "382979", - "name": "stop", - "font_class": "stop", - "unicode": "e63c", - "unicode_decimal": 58940 - }, - { - "icon_id": "6447211", - "name": "分页向左", - "font_class": "icon_paging_left", - "unicode": "e652", - "unicode_decimal": 58962 - }, - { - "icon_id": "6447212", - "name": "分页向右", - "font_class": "icon_paging_right", - "unicode": "e653", - "unicode_decimal": 58963 - }, - { - "icon_id": "18483836", - "name": "hrzquery", - "font_class": "query", - "unicode": "e64b", - "unicode_decimal": 58955 - }, - { - "icon_id": "19300992", - "name": "submit", - "font_class": "submit", - "unicode": "e619", - "unicode_decimal": 58905 - }, - { - "icon_id": "11487969", - "name": "3column", - "font_class": "column", - "unicode": "e741", - "unicode_decimal": 59201 - }, - { - "icon_id": "11493429", - "name": "主键", - "font_class": "primary", - "unicode": "e617", - "unicode_decimal": 58903 - }, - { - "icon_id": "12309945", - "name": "function", - "font_class": "function", - "unicode": "e833", - "unicode_decimal": 59443 - }, - { - "icon_id": "16082913", - "name": "视图", - "font_class": "view", - "unicode": "e68e", - "unicode_decimal": 59022 - }, - { - "icon_id": "5524670", - "name": "table", - "font_class": "table", - "unicode": "e600", - "unicode_decimal": 58880 - }, - { - "icon_id": "12579707", - "name": "database", - "font_class": "schema", - "unicode": "e7e9", - "unicode_decimal": 59369 - }, - { - "icon_id": "5961392", - "name": "mysql", - "font_class": "mysql", - "unicode": "ec6d", - "unicode_decimal": 60525 - }, - { - "icon_id": "15378663", - "name": "oracle", - "font_class": "oracle", - "unicode": "ec48", - "unicode_decimal": 60488 - } - ] -} diff --git a/frontend/src/assets/iconfont/iconfont.ttf b/frontend/src/assets/iconfont/iconfont.ttf deleted file mode 100644 index 75b89fb7..00000000 Binary files a/frontend/src/assets/iconfont/iconfont.ttf and /dev/null differ diff --git a/frontend/src/assets/iconfont/iconfont.woff b/frontend/src/assets/iconfont/iconfont.woff deleted file mode 100644 index 8b8e789e..00000000 Binary files a/frontend/src/assets/iconfont/iconfont.woff and /dev/null differ diff --git a/frontend/src/assets/iconfont/iconfont.woff2 b/frontend/src/assets/iconfont/iconfont.woff2 deleted file mode 100644 index f44513b1..00000000 Binary files a/frontend/src/assets/iconfont/iconfont.woff2 and /dev/null differ diff --git a/frontend/src/assets/logo.png b/frontend/src/assets/logo.png deleted file mode 100644 index f3d2503f..00000000 Binary files a/frontend/src/assets/logo.png and /dev/null differ diff --git a/frontend/src/bus/index.js b/frontend/src/bus/index.js deleted file mode 100644 index 5c8ded8e..00000000 --- a/frontend/src/bus/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import Vue from 'vue' - -export const bus = new Vue() diff --git a/frontend/src/components/AppMain.vue b/frontend/src/components/AppMain.vue deleted file mode 100644 index 09f4f317..00000000 --- a/frontend/src/components/AppMain.vue +++ /dev/null @@ -1,56 +0,0 @@ - - - - - diff --git a/frontend/src/components/Controller/index.vue b/frontend/src/components/Controller/index.vue deleted file mode 100644 index d23c1744..00000000 --- a/frontend/src/components/Controller/index.vue +++ /dev/null @@ -1,219 +0,0 @@ - - - - - diff --git a/frontend/src/components/Main/Explore/DataView/DataView.vue b/frontend/src/components/Main/Explore/DataView/DataView.vue deleted file mode 100644 index dd4c27c0..00000000 --- a/frontend/src/components/Main/Explore/DataView/DataView.vue +++ /dev/null @@ -1,347 +0,0 @@ - - - - - diff --git a/frontend/src/components/Main/Explore/DataView/ExportDataDialog.vue b/frontend/src/components/Main/Explore/DataView/ExportDataDialog.vue deleted file mode 100644 index 4dadeedd..00000000 --- a/frontend/src/components/Main/Explore/DataView/ExportDataDialog.vue +++ /dev/null @@ -1,75 +0,0 @@ - - - - - diff --git a/frontend/src/components/Main/Explore/DataView/ResultGrid.vue b/frontend/src/components/Main/Explore/DataView/ResultGrid.vue deleted file mode 100644 index fe095a99..00000000 --- a/frontend/src/components/Main/Explore/DataView/ResultGrid.vue +++ /dev/null @@ -1,376 +0,0 @@ - - - - - diff --git a/frontend/src/components/Main/Explore/DataView/RightMenu.vue b/frontend/src/components/Main/Explore/DataView/RightMenu.vue deleted file mode 100644 index 8c6fb822..00000000 --- a/frontend/src/components/Main/Explore/DataView/RightMenu.vue +++ /dev/null @@ -1,130 +0,0 @@ - - - - - diff --git a/frontend/src/components/Main/Explore/DataView/const.js b/frontend/src/components/Main/Explore/DataView/const.js deleted file mode 100644 index d155671f..00000000 --- a/frontend/src/components/Main/Explore/DataView/const.js +++ /dev/null @@ -1,12 +0,0 @@ -export const GeneralUpdateSQL = 'UPDATE {table_name} SET {updated_attrs} WHERE {conditional_attrs};' -export const GeneralInsertSQL = 'INSERT INTO {table_name} ({fields}) VALUES ({values});' - -export const SpecialCharacters = { - mysql: '`', - mariadb: '`', - postgresql: '"', - sqlserver: '', - oracle: '"', - dameng: '"', - db2: '' -} diff --git a/frontend/src/components/Main/Explore/DataView/index.vue b/frontend/src/components/Main/Explore/DataView/index.vue deleted file mode 100644 index c3ad4c69..00000000 --- a/frontend/src/components/Main/Explore/DataView/index.vue +++ /dev/null @@ -1,177 +0,0 @@ - - - - - diff --git a/frontend/src/components/Main/Explore/Dialog/Dialog.vue b/frontend/src/components/Main/Explore/Dialog/Dialog.vue deleted file mode 100644 index 4bf7256b..00000000 --- a/frontend/src/components/Main/Explore/Dialog/Dialog.vue +++ /dev/null @@ -1,77 +0,0 @@ - - - - - diff --git a/frontend/src/components/Main/Explore/Dialog/FormTemplate.vue b/frontend/src/components/Main/Explore/Dialog/FormTemplate.vue deleted file mode 100644 index ebd2f98a..00000000 --- a/frontend/src/components/Main/Explore/Dialog/FormTemplate.vue +++ /dev/null @@ -1,152 +0,0 @@ - - - - - diff --git a/frontend/src/components/Main/Explore/Dialog/components/DetailContent.vue b/frontend/src/components/Main/Explore/Dialog/components/DetailContent.vue deleted file mode 100644 index d10339f4..00000000 --- a/frontend/src/components/Main/Explore/Dialog/components/DetailContent.vue +++ /dev/null @@ -1,43 +0,0 @@ - - - - - diff --git a/frontend/src/components/Main/Explore/Message.vue b/frontend/src/components/Main/Explore/Message.vue deleted file mode 100644 index 3c55dc34..00000000 --- a/frontend/src/components/Main/Explore/Message.vue +++ /dev/null @@ -1,55 +0,0 @@ - - - - - diff --git a/frontend/src/components/Main/Explore/QueryConsole/CodeEditor.vue b/frontend/src/components/Main/Explore/QueryConsole/CodeEditor.vue deleted file mode 100644 index e09475b8..00000000 --- a/frontend/src/components/Main/Explore/QueryConsole/CodeEditor.vue +++ /dev/null @@ -1,309 +0,0 @@ - - - - - diff --git a/frontend/src/components/Main/Explore/QueryConsole/Log.vue b/frontend/src/components/Main/Explore/QueryConsole/Log.vue deleted file mode 100644 index 0294a975..00000000 --- a/frontend/src/components/Main/Explore/QueryConsole/Log.vue +++ /dev/null @@ -1,64 +0,0 @@ - - - - - diff --git a/frontend/src/components/Main/Explore/QueryConsole/ResultBar.vue b/frontend/src/components/Main/Explore/QueryConsole/ResultBar.vue deleted file mode 100644 index 32661153..00000000 --- a/frontend/src/components/Main/Explore/QueryConsole/ResultBar.vue +++ /dev/null @@ -1,146 +0,0 @@ - - - - diff --git a/frontend/src/components/Main/Explore/QueryConsole/SaveSnippetDialog.vue b/frontend/src/components/Main/Explore/QueryConsole/SaveSnippetDialog.vue deleted file mode 100644 index b3a011e4..00000000 --- a/frontend/src/components/Main/Explore/QueryConsole/SaveSnippetDialog.vue +++ /dev/null @@ -1,92 +0,0 @@ - - - - - diff --git a/frontend/src/components/Main/Explore/QueryConsole/SelectSnippetDialog.vue b/frontend/src/components/Main/Explore/QueryConsole/SelectSnippetDialog.vue deleted file mode 100644 index 0b6fc2b2..00000000 --- a/frontend/src/components/Main/Explore/QueryConsole/SelectSnippetDialog.vue +++ /dev/null @@ -1,119 +0,0 @@ - - - - - diff --git a/frontend/src/components/Main/Explore/QueryConsole/index.vue b/frontend/src/components/Main/Explore/QueryConsole/index.vue deleted file mode 100644 index 51ef092f..00000000 --- a/frontend/src/components/Main/Explore/QueryConsole/index.vue +++ /dev/null @@ -1,178 +0,0 @@ - - - - - diff --git a/frontend/src/components/Main/Explore/index.vue b/frontend/src/components/Main/Explore/index.vue deleted file mode 100644 index 3ec1627b..00000000 --- a/frontend/src/components/Main/Explore/index.vue +++ /dev/null @@ -1,155 +0,0 @@ - - - - - diff --git a/frontend/src/components/Main/ResourceManage/Tree.vue b/frontend/src/components/Main/ResourceManage/Tree.vue deleted file mode 100644 index bf5813e0..00000000 --- a/frontend/src/components/Main/ResourceManage/Tree.vue +++ /dev/null @@ -1,287 +0,0 @@ - - - - - diff --git a/frontend/src/components/Main/ResourceManage/TreeNavbar.vue b/frontend/src/components/Main/ResourceManage/TreeNavbar.vue deleted file mode 100644 index c2954b2a..00000000 --- a/frontend/src/components/Main/ResourceManage/TreeNavbar.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - - - diff --git a/frontend/src/components/Main/ResourceManage/index.vue b/frontend/src/components/Main/ResourceManage/index.vue deleted file mode 100644 index add1d808..00000000 --- a/frontend/src/components/Main/ResourceManage/index.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - - - diff --git a/frontend/src/components/Main/index.vue b/frontend/src/components/Main/index.vue deleted file mode 100644 index 5340380a..00000000 --- a/frontend/src/components/Main/index.vue +++ /dev/null @@ -1,15 +0,0 @@ - - - - - diff --git a/frontend/src/constant/packet.js b/frontend/src/constant/packet.js deleted file mode 100644 index 01dd5028..00000000 --- a/frontend/src/constant/packet.js +++ /dev/null @@ -1,14 +0,0 @@ -export const PacketType = { - query: "query", - describeTable: "describeTable", - viewTable: "viewTable", - deleteRows: "deleteRows", - insertRows: "insertRows", - resultOk: "resultOk", - resultSet: "resultSet", - columnSet: "columnSet", - error: "error", - editTables: "editTables" - -} - diff --git a/frontend/src/framework/components/DataTable/index.vue b/frontend/src/framework/components/DataTable/index.vue deleted file mode 100644 index dc2c3895..00000000 --- a/frontend/src/framework/components/DataTable/index.vue +++ /dev/null @@ -1,56 +0,0 @@ - - - - - diff --git a/frontend/src/framework/components/Dialog.vue b/frontend/src/framework/components/Dialog.vue deleted file mode 100644 index d3c3dfa4..00000000 --- a/frontend/src/framework/components/Dialog.vue +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/frontend/src/framework/components/Toolbar/Items.vue b/frontend/src/framework/components/Toolbar/Items.vue deleted file mode 100644 index bd8a5fba..00000000 --- a/frontend/src/framework/components/Toolbar/Items.vue +++ /dev/null @@ -1,146 +0,0 @@ - - - - - diff --git a/frontend/src/framework/components/Toolbar/index.vue b/frontend/src/framework/components/Toolbar/index.vue deleted file mode 100644 index d8a43d23..00000000 --- a/frontend/src/framework/components/Toolbar/index.vue +++ /dev/null @@ -1,144 +0,0 @@ - - - - - - - diff --git a/frontend/src/i18n/date.js b/frontend/src/i18n/date.js deleted file mode 100644 index 6d25522f..00000000 --- a/frontend/src/i18n/date.js +++ /dev/null @@ -1,58 +0,0 @@ -export default { - 'en': { - short: { - year: 'numeric', month: 'short', day: 'numeric' - }, - medium: { - year: 'numeric', month: '2-digit', day: '2-digit', - hour: '2-digit', minute: '2-digit', second: '2-digit', - hourCycle: 'h23', hour12: false - }, - long: { - year: 'numeric', month: 'short', day: 'numeric', - hour: 'numeric', minute: 'numeric' - } - }, - 'cn': { - short: { - year: 'numeric', month: 'short', day: 'numeric' - }, - medium: { - year: 'numeric', month: '2-digit', day: '2-digit', - hour: '2-digit', minute: '2-digit', second: '2-digit', - hourCycle: 'h23', hour12: false - }, - long: { - year: 'numeric', month: 'short', day: 'numeric', - hour: 'numeric', minute: 'numeric', hour12: true - } - }, - 'zh_hant': { - short: { - year: 'numeric', month: 'short', day: 'numeric' - }, - medium: { - year: 'numeric', month: '2-digit', day: '2-digit', - hour: '2-digit', minute: '2-digit', second: '2-digit', - hourCycle: 'h23', hour12: false - }, - long: { - year: 'numeric', month: 'short', day: 'numeric', - hour: 'numeric', minute: 'numeric', hour12: true - } - }, - 'ja': { - short: { - year: 'numeric', month: 'short', day: 'numeric' - }, - medium: { - year: 'numeric', month: '2-digit', day: '2-digit', - hour: '2-digit', minute: '2-digit', second: '2-digit', - hourCycle: 'h23', hour12: false - }, - long: { - year: 'numeric', month: 'short', day: 'numeric', - hour: 'numeric', minute: 'numeric', hour12: true - } - } -} diff --git a/frontend/src/i18n/index.js b/frontend/src/i18n/index.js deleted file mode 100644 index 43972e48..00000000 --- a/frontend/src/i18n/index.js +++ /dev/null @@ -1,52 +0,0 @@ -// i18n.js -import Vue from 'vue' -import locale from 'element-ui/lib/locale' -import VueI18n from 'vue-i18n' -import messages from './langs' -import date from './date' -import VueCookie from 'vue-cookie' -import axios from 'axios' -import store from '@/store' -import { apiUrl } from '@/utils/path' - -Vue.use(VueI18n) -const cookieLang = VueCookie.get('django_language') -const storeLang = VueCookie.get('lang') -const browserLang = navigator.systemLanguage || navigator.language -let lang = cookieLang || storeLang || browserLang || 'zh' -if (lang === 'zh-hant') { - lang = 'zh_hant' -} else { - lang = lang.slice(0, 2) -} -const i18n = new VueI18n({ - locale: lang, - fallbackLocale: 'en', - silentFallbackWarn: true, - silentTranslationWarn: true, - dateTimeFormats: date, - messages -}) -locale.i18n((key, value) => i18n.t(key, value)) // 重点: 为了实现element插件的多语言切换 - -Vue.prototype.$tr = (key) => { - return i18n.t('' + key) -} - -axios.get(apiUrl(`v1/settings/i18n/chen/?lang=${lang}&flat=0`)) - .then((res) => { - if (res.status !== 200) { - return - } - const data = res.data - for (const key in data) { - if (Object.prototype.hasOwnProperty.call(data, key)) { - i18n.mergeLocaleMessage(key, data[key]) - } - } - }) - .finally(() => { - store.dispatch('app/setI18nLoaded', true) - }) - -export default i18n diff --git a/frontend/src/i18n/lang/en-US.js b/frontend/src/i18n/lang/en-US.js deleted file mode 100644 index 88fbe4df..00000000 --- a/frontend/src/i18n/lang/en-US.js +++ /dev/null @@ -1,43 +0,0 @@ -import el from 'element-ui/lib/locale/lang/en' - -const message = { - title: { - database_explorer: 'Database Explorer', - save_sql: 'Save SQL', - select_sql: 'Select SQL', - export_data: 'Export Data' - }, - button: { - run: 'Run', - run_selected: 'Run selected', - refresh: 'Refresh', - total: 'Total' - }, - common: { - num_row: '{num} rows', - log: 'Log Output', - current: 'Current' - }, - action: { - confirm: 'Confirm', - cancel: 'Cancel' - }, - tip: { - run: 'Run (Ctrl + Enter)', - stop: 'Stop (Ctrl + C)', - format: 'Format (Ctrl + L)' - }, - option: { - export_all: 'Export all data', - export_current: 'Export current page' - }, - msg: { - copy_not_allowed: 'You are not allowed to copy, please contact the administrator to open it!', - paste_not_allowed: 'You are not allowed to paste, please contact the administrator to open it!' - } -} - -export default { - ...el, - ...message -} diff --git a/frontend/src/i18n/lang/ja-JP.js b/frontend/src/i18n/lang/ja-JP.js deleted file mode 100644 index 7085467e..00000000 --- a/frontend/src/i18n/lang/ja-JP.js +++ /dev/null @@ -1,42 +0,0 @@ -import el from 'element-ui/lib/locale/lang/ja' - -const message = { - title: { - database_explorer: 'データベースエクスプローラ', - save_sql: 'SQLを保存する', - select_sql: 'SQLを選択する', - export_data: 'データをエクスポートする' - }, - button: { - run: '実行', - run_selected: '選択実行', - refresh: '更新', - total: '合計' - }, - common: { - num_row: '{num}行', - log: 'ログ出力', - current: '現在' - }, - action: { - confirm: '確認', - cancel: 'キャンセル' - }, - tip: { - run: '実行 (Ctrl + Enter)', - stop: '停止 (Ctrl + C)', - format: 'フォーマット (Ctrl + L)' - }, - option: { - export_all: 'エクスポートすべて', - export_current: 'エクスポート現在' - }, - msg: { - copy_not_allowed: 'コピーは許可されていません。管理者に連絡して開放してもらってください!', - paste_not_allowed: '貼り付けは許可されていません。管理者に連絡して開放してもらってください!' - } -} -export default { - ...el, - ...message -} diff --git a/frontend/src/i18n/lang/zh-CN.js b/frontend/src/i18n/lang/zh-CN.js deleted file mode 100644 index 8601b32f..00000000 --- a/frontend/src/i18n/lang/zh-CN.js +++ /dev/null @@ -1,53 +0,0 @@ -import el from 'element-ui/lib/locale/lang/zh-CN' - -const message = { - title: { - database_explorer: '数据库浏览器', - save_sql: '保存SQL', - select_sql: '选择SQL', - export_data: '导出数据' - }, - button: { - run: '运行', - run_selected: '运行选中', - refresh: '刷新', - total: '共', - insert: '插入' - }, - common: { - num_row: '{num}行', - log: '日志输出', - current: '当前', - name: '名称', - content: '内容' - }, - action: { - confirm: '确认', - cancel: '取消' - }, - tip: { - upload: '运行SQL文件', - run: '运行 (Ctrl + Enter)', - stop: '停止 (Ctrl + C)', - format: '格式化 (Ctrl + L)', - open: '打开 (Ctrl + R)', - save: '保存 (Ctrl + S)' - }, - option: { - export_all: '导出全部', - export_current: '导出当前' - }, - message: { - save_success: '保存成功' - }, - msg: { - copy_not_allowed: '不允许复制,请联系管理员开启!', - paste_not_allowed: '不允许粘贴,请联系管理员开启!' - } - -} - -export default { - ...el, - ...message -} diff --git a/frontend/src/i18n/lang/zh-Hant.js b/frontend/src/i18n/lang/zh-Hant.js deleted file mode 100644 index e31b6f57..00000000 --- a/frontend/src/i18n/lang/zh-Hant.js +++ /dev/null @@ -1,53 +0,0 @@ -import el from 'element-ui/lib/locale/lang/zh-TW' - -const message = { - title: { - database_explorer: '資料庫瀏覽器', - save_sql: '保存SQL', - select_sql: '選擇SQL', - export_data: '導出數據' - }, - button: { - run: '運行', - run_selected: '運行選中', - refresh: '刷新', - total: '共', - insert: '插入' - }, - common: { - num_row: '{num}行', - log: '日誌輸出', - current: '當前', - name: '名稱', - content: '內容' - }, - action: { - confirm: '確認', - cancel: '取消' - }, - tip: { - upload: '運行SQL文件', - run: '運行 (Ctrl + Enter)', - stop: '停止 (Ctrl + C)', - format: '格式化 (Ctrl + L)', - open: '打開 (Ctrl + R)', - save: '保存 (Ctrl + S)' - }, - option: { - export_all: '導出全部', - export_current: '導出當前' - }, - message: { - save_success: '保存成功' - }, - msg: { - copy_not_allowed: '不允許複製,請聯絡管理員開啟!', - paste_not_allowed: '不允許黏貼,請聯絡管理員開啟!' - } - -} - -export default { - ...el, - ...message -} diff --git a/frontend/src/i18n/langs/en.json b/frontend/src/i18n/langs/en.json deleted file mode 100644 index 3b4c929f..00000000 --- a/frontend/src/i18n/langs/en.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "FieldRequiredError": "This field is required.", - "ServerError": "An error occurred while processing your request. Please try again later.", - "InputEmailAddress": "Please enter a valid email address.", - "FormatError": "The value entered is not in the correct format.", - "NotSpecialEmoji": "Emoji is not allowed.", - "notAlphanumericUnderscore": "Only alphanumeric characters and underscores are allowed.", - "notParenthesis": "Parentheses are not allowed.", - "InvalidJson": "Invalid JSON format.", - "requiredHasUserNameMapped": "Username attr is required." -} - diff --git a/frontend/src/i18n/langs/index.js b/frontend/src/i18n/langs/index.js deleted file mode 100644 index b7b26bfc..00000000 --- a/frontend/src/i18n/langs/index.js +++ /dev/null @@ -1,29 +0,0 @@ -import zhLocale from 'element-ui/lib/locale/lang/zh-CN' -import zhTWLocale from 'element-ui/lib/locale/lang/zh-TW' -import enLocale from 'element-ui/lib/locale/lang/en' -import jaLocale from 'element-ui/lib/locale/lang/ja' -import zh from './zh.json' -import zhHant from './zh_Hant.json' -import en from './en.json' -import ja from './ja.json' - -const messages = { - zh: { - ...zhLocale, - ...zh - }, - zh_hant: { - ...zhTWLocale, - ...zhHant - }, - en: { - ...enLocale, - ...en - }, - ja: { - ...jaLocale, - ...ja - } -} - -export default messages diff --git a/frontend/src/i18n/langs/ja.json b/frontend/src/i18n/langs/ja.json deleted file mode 100644 index cd776527..00000000 --- a/frontend/src/i18n/langs/ja.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "FieldRequiredError": "这个字段是必需的。", - "InputEmailAddress": "输入一个电子邮件地址。", - "ServerError": "服务错误,请联系管理员。", - "FormatError": "格式错误。", - "NotSpecialEmoji": "不支持表情字符。", - "notAlphanumericUnderscore": "仅支持字母、数字和下划线。", - "notParenthesis": "不能包含括号。", - "InvalidJson": "JSON 格式错误.", - "requiredHasUserNameMapped": "用户名属性是必需的。" -} diff --git a/frontend/src/i18n/langs/zh.json b/frontend/src/i18n/langs/zh.json deleted file mode 100644 index cd776527..00000000 --- a/frontend/src/i18n/langs/zh.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "FieldRequiredError": "这个字段是必需的。", - "InputEmailAddress": "输入一个电子邮件地址。", - "ServerError": "服务错误,请联系管理员。", - "FormatError": "格式错误。", - "NotSpecialEmoji": "不支持表情字符。", - "notAlphanumericUnderscore": "仅支持字母、数字和下划线。", - "notParenthesis": "不能包含括号。", - "InvalidJson": "JSON 格式错误.", - "requiredHasUserNameMapped": "用户名属性是必需的。" -} diff --git a/frontend/src/i18n/langs/zh_Hant.json b/frontend/src/i18n/langs/zh_Hant.json deleted file mode 100644 index cd776527..00000000 --- a/frontend/src/i18n/langs/zh_Hant.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "FieldRequiredError": "这个字段是必需的。", - "InputEmailAddress": "输入一个电子邮件地址。", - "ServerError": "服务错误,请联系管理员。", - "FormatError": "格式错误。", - "NotSpecialEmoji": "不支持表情字符。", - "notAlphanumericUnderscore": "仅支持字母、数字和下划线。", - "notParenthesis": "不能包含括号。", - "InvalidJson": "JSON 格式错误.", - "requiredHasUserNameMapped": "用户名属性是必需的。" -} diff --git a/frontend/src/main.js b/frontend/src/main.js deleted file mode 100644 index 5f3ad550..00000000 --- a/frontend/src/main.js +++ /dev/null @@ -1,40 +0,0 @@ -import Vue from 'vue' -import App from './App.vue' -import ElementUI from 'element-ui' -import 'element-ui/lib/theme-chalk/index.css' -import 'font-awesome/css/font-awesome.min.css' -import '@/assets/iconfont/iconfont.css' -import VueRx from 'vue-rx' -import 'codemirror/lib/codemirror.css' -import VueCodemirror from 'vue-codemirror' -import i18n from './i18n' -import request from './request' -import store from './store' -import '@/styles/index.scss' -import moment from 'moment' -import Contextmenu from 'vue-contextmenujs' - -Vue.config.productionTip = false - -Vue.use(VueRx) -Vue.use(VueCodemirror) -Vue.use(request) -Vue.use(Contextmenu) - -Vue.use(ElementUI, { - size: 'small', - i18n: (key, value) => i18n.t(key, value) -}) - -Vue.filter('dateTime', function(value) { - return moment(value * 1).format('YYYY-MM-DD HH:mm:ss') -}) - -new Vue({ - i18n, - store, - beforeCreate() { - Vue.prototype.$bus = this - }, - render: h => h(App) -}).$mount('#app') diff --git a/frontend/src/request/index.js b/frontend/src/request/index.js deleted file mode 100644 index e0ff5447..00000000 --- a/frontend/src/request/index.js +++ /dev/null @@ -1,77 +0,0 @@ -import axios from 'axios' -import store from '@/store' -import { Message } from 'element-ui' -// import { Message } from 'element-ui' - -const instance = axios.create({ - withCredentials: true, - timeout: 60000 // request timeout, default 1 min -}) - -export const index = instance - -index.interceptors.request.use((config) => { - if (store.getters.authenticated) { - config.headers['token'] = store.getters.token - } - return config -}) - -index.interceptors.response.use((resp) => { - return resp -}, (error) => { - const reader = new FileReader() - reader.onload = function(event) { - const text = event.target.result - Message.error(text) - } - reader.readAsText(error.response.data) - return Promise.reject(error) -}) - -const promise = (request, loading = {}) => { - return new Promise((resolve, reject) => { - loading.status = true - request.then(response => { - resolve(response.data) - loading.status = false - }).catch(error => { - reject(error) - loading.status = false - }) - }) -} - -export const get = (url, data, loading) => { - return promise(index({ url: url, method: 'get', params: data }), loading) -} - -export const post = (url, data, loading) => { - return promise(index({ url: url, method: 'post', data }), loading) -} - -export const put = (url, data, loading) => { - return promise(index({ url: url, method: 'put', data }), loading) -} - -export const del = (url, loading) => { - return promise(index({ url: url, method: 'delete' }), loading) -} - -export const patch = (url, data, headers, loading) => { - if (headers) { - return promise(index({ url: url, headers: headers, method: 'patch', data }), - loading) - } - return promise(index({ url: url, method: 'patch', data }), loading) -} - -export default { - install(Vue) { - Vue.prototype.$get = get - Vue.prototype.$post = post - Vue.prototype.$put = put - Vue.prototype.$delete = del - Vue.prototype.$request = index - } -} diff --git a/frontend/src/store/getters.js b/frontend/src/store/getters.js deleted file mode 100644 index 94a788dc..00000000 --- a/frontend/src/store/getters.js +++ /dev/null @@ -1,8 +0,0 @@ -const getters = { - token: state => state.app.token, - authenticated: state => state.app.authenticated, - profile: state => state.app.profile, - disableautohash: state => state.app.disableautohash, - i18nLoaded: state => state.app.i18nLoaded -} -export default getters diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js deleted file mode 100644 index 99e44bff..00000000 --- a/frontend/src/store/index.js +++ /dev/null @@ -1,23 +0,0 @@ -import Vue from 'vue' -import Vuex from 'vuex' -import getters from './getters' - -Vue.use(Vuex) - -// 自动从modules目录下获取模块 -const MODULES_FILES = require.context('./modules', true, /\.js$/) - -// 模块名为js文件名,例如user.js 则模块名为user -const modules = MODULES_FILES.keys().reduce((modules, modulePath) => { - const value = MODULES_FILES(modulePath) - const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1') - modules[moduleName] = value.default - return modules -}, {}) - -const store = new Vuex.Store({ - modules, - getters -}) - -export default store diff --git a/frontend/src/store/modules/app.js b/frontend/src/store/modules/app.js deleted file mode 100644 index 7c514996..00000000 --- a/frontend/src/store/modules/app.js +++ /dev/null @@ -1,81 +0,0 @@ -import { auth, getProfile } from '@/api/app' -import { Message } from 'element-ui' -import i18n from '@/i18n' - -const state = { - authenticated: false, - token: '', - lang: 'zh', - disableautohash: false, - i18nLoaded: false, - profile: { - dbType: '', - username: '', - assetName: '', - canCopy: false, - canPaste: false - } -} - -const mutations = { - DISABLE_AUTO_HASH: (state, disableautohash) => { - state.disableautohash = disableautohash - }, - AUTH: (state, authResponse) => { - state.authenticated = true - state.token = authResponse.token - state.lang = authResponse.lang - localStorage.setItem('chen_language', authResponse.lang) - }, - PROFILE: (state, profile) => { - state.profile = profile - if (!profile.canCopy) { - document.addEventListener('copy', (e) => { - Message.error(i18n.t('CopyNotAllowed')) - e.preventDefault() - navigator.clipboard.writeText('').then(r => {}) - }) - } - if (!profile.canPaste) { - document.addEventListener('paste', (e) => { - Message.error(i18n.t('PasteNotAllowed')) - e.preventDefault() - e.stopPropagation() - navigator.clipboard.writeText('').then(r => {}) - }, true) - } - }, - SET_I18N_LOADED: (state, loaded) => { - state.i18nLoaded = loaded - } -} - -const actions = { - setI18nLoaded: (state, loaded) => { - state.commit('SET_I18N_LOADED', loaded) - }, - loadProfile({ commit }) { - getProfile().then(data => { - commit('PROFILE', data) - }) - }, - - auth({ commit }, params) { - return new Promise((resolve, reject) => { - auth(params.token, params.disableautohash).then(response => { - commit('AUTH', response) - commit('DISABLE_AUTO_HASH', params.disableautohash) - resolve(response.token) - }).catch(error => { - reject(error) - }) - }) - } -} - -export default { - namespaced: true, - state, - mutations, - actions -} diff --git a/frontend/src/styles/index.scss b/frontend/src/styles/index.scss deleted file mode 100644 index 3069b385..00000000 --- a/frontend/src/styles/index.scss +++ /dev/null @@ -1,590 +0,0 @@ -@import "./variables"; - -body { - margin: 0; - padding: 0 !important; -} - -// 滚动条整体部分 — 默认较细,悬停时放大并高亮,便于点击 -::-webkit-scrollbar { - width: 6px; - height: 6px; - transition: width 0.15s ease; -} - -/* 当用户把鼠标移到页面或特定可滚动区域时放大滚动条,便于交互 */ -body:hover ::-webkit-scrollbar, -.data-view:hover ::-webkit-scrollbar, -.hot-table:hover ::-webkit-scrollbar, -.wtHolder:hover ::-webkit-scrollbar { - width: 10px; - height: 10px; -} - -::-webkit-scrollbar-thumb { - border-radius: 6px; - background-color: rgba(144, 147, 153, 0.3); - border: 2px solid transparent; /* 防止放大时内容溢出视觉问题 */ - background-clip: padding-box; - transition: background-color 0.15s ease, transform 0.12s ease; -} - -/* 鼠标悬停或按下时更显眼 */ -::-webkit-scrollbar-thumb:hover, -::-webkit-scrollbar-thumb:active { - background-color: rgba(144, 147, 153, 0.6); - transform: scale(1.08); - box-shadow: inset 0 0 0 3px rgba(0,0,0,0.12); -} - -::-webkit-scrollbar-track { - background: none; -} - -::-webkit-scrollbar-corner { - background: transparent; -} - -.splitter-pane-resizer { - opacity: 1; - background-color: $framework-border-color; - z-index: 1 !important; -} - -.splitter-pane-resizer.horizontal { - z-index: 100 !important; - height: 8px !important; - margin: -4px 0 !important; - border-top-width: 3px !important; - border-bottom-width: 3px !important; - cursor: row-resize !important; -} - -.splitter-pane-resizer.horizontal:hover { - background-color: $component-border-color; -} - -.icon-chen-first_page, -.icon-chen-last-page { - font-size: 14px !important; -} - -.el-loading-mask { - background-color: rgba(0, 0, 0, .2) !important; -} - -.el-select-dropdown { - background: $background-color; - border: 1px solid $component-border-color; - box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1); - - &.el-popper { - margin-top: 6px; - - .el-select-dropdown__empty { - color: $font-color; - padding: 6px 0; - } - - .popper__arrow { - &::after { - border-bottom-color: $background-color; - } - - border-bottom-color: $component-border-color; - } - } - - .el-select-dropdown__item { - padding: 0 12px; - color: $font-color; - font-size: 13px; - - &:hover, &:focus, &.selected { - color: #FFFFFF; - background-color: $active-background-color; - } - - &.hover { - background-color: $active-background-color; - } - } -} - -.splitter-pane.splitter-paneR, -.splitter-pane.splitter-paneL { - overflow: hidden; - - & > div { - height: 100% !important; - } -} - -.el-tree { - .el-tree-node__content { - font-size: 12px; - height: 22px; - - &:hover { - color: $font-color; - background-color: rgba(255, 255, 255, 0.1) !important; - } - - .node { - user-select: none; - } - } - - .el-tree-node:focus > .el-tree-node__content { - color: #FFFFFF; - background-color: $active-background-color !important; - } -} - -.el-dropdown-menu { - background: $background-color; - border: 1px solid #323232; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); - - .popper__arrow { - &::after { - border-top-color: $background-color !important; - border-bottom-color: $background-color !important; - } - - border-top-color: $component-border-color !important; - border-bottom-color: $component-border-color !important; - } - - .el-dropdown-menu__item { - padding: 0 12px; - color: $font-color; - - &:hover, &:focus { - color: #FFFFFF; - background-color: $active-background-color; - } - } -} - -.el-cascader-panel { - &.is-bordered { - border: 1px solid $component-border-color; - border-right: none; - } - - & > .el-cascader-menu__wrap { - height: initial !important; - } - - .el-cascader-node__label { - text-align: left; - padding: 0 10px 0 0; - font-weight: 500; - } - - .el-cascader-menu { - min-width: 170px; - border-right: 1px solid $component-border-color; - border-radius: 3px; - padding: 5px 0; - - .el-cascader-node { - line-height: 1.5; - font-size: 12px; - height: 28px; - padding: 0 15px; - color: $font-color; - - &:hover, &:focus { - color: #FFFFFF; - background: $active-background-color; - } - } - } -} - -.el-dialog { - color: $font-color; - background-color: $background-color; - box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1); - - .el-dialog__title { - color: $font-color; - } - - .el-dialog__body { - padding: 10px 16px; - } - - .el-form-item__label { - color: $font-color; - } -} - -.el-alert { - &.query-message { - padding: 0; - width: 100%; - border-radius: 0; - - .el-alert__content { - width: 100%; - - .el-alert__description { - display: inline-block; - padding-left: 4px; - margin: 4px 0 0; - } - - .el-alert__closebtn { - top: 7px; - } - } - } -} - -.el-input { - &:hover { - .el-input__inner { - border-color: $active-background-color !important; - } - } -} - -.el-select .el-input__inner, -.el-input .el-input__inner { - &:hover { - border-color: $active-background-color; - } - - color: $font-color; - border: 1px solid #60605e; - background-color: $background-color;; -} - -.el-button { - &:hover { - color: $active-background-color; - } -} - -.iconfont { - font-size: 13px; -} - -.el-dialog { - .el-button { - &.el-button--default { - color: #FFFFFF; - border-color: #5b5b5b; - background-color: $background-color; - - &:hover, &:focus { - background-color: #505254; - } - } - - &.el-button--primary { - color: #FFFFFF; - border-color: $active-background-color; - background-color: $active-background-color; - - &:hover, &:focus { - background-color: #679aff; - } - } - } -} - -.toolbar { - .el-button { - & > span { - margin-left: 0 !important; - } - - &.is-loading:before { - background-color: transparent !important; - } - - &.is-disabled { - color: #5F5F5F; - background-color: transparent; - } - } -} - -.toolbar-select.el-select { - height: 100%; - vertical-align: top; - - .el-input--mini { - &.el-input--suffix { - height: 100%; - line-height: 28px; - } - - .el-input__inner { - height: 26px; - line-height: 26px; - border: none; - } - } - - .el-input__inner { - background: transparent; - } -} - -.el-tabs--card { - .el-tabs__nav-prev, .el-tabs__nav-next { - width: 20px; - height: 28px; - line-height: 28px; - - &:hover { - color: #FFFFFF; - } - } - - & > .el-tabs__header { - height: 28px; - line-height: 28px; - margin: 0; - border-bottom: 1px solid $framework-border-color; - - .el-tabs__nav-wrap { - height: 100%; - - .el-tabs__nav-scroll { - background-color: $background-color; - - .el-tabs__item { - height: 28px; - line-height: 28px; - padding: 0 8px; - font-size: 12px; - color: $font-color; - border-left: none; - background-color: $background-color; - - &:hover { - .el-icon-close { - opacity: 1; - } - } - - &:nth-child(2) { - padding-left: 8px !important; - } - - &.is-closable:hover { - color: #ffffff; - padding-left: 8px !important; - padding-right: 8px !important; - } - - &.is-active { - padding-left: 8px; - padding-right: 8px; - border-bottom: 2px solid $active-background-color; - color: #ffffff; - background-color: rgba(255, 255, 255, 0.1) !important; - - .el-icon-close { - opacity: 1; - } - } - - .tab-pane-label { - display: inline-block; - max-width: 300px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - user-select: none; - - & > i { - margin-right: 4px; - } - } - - .el-icon-close { - top: -2px; - vertical-align: super; - margin-left: 0; - color: #798389; - font-size: 14px; - width: 14px !important; - opacity: 0; - } - } - - .el-tabs__nav { - border: none; - } - } - } - } - - .el-tabs__content { - height: calc(100% - 28px); - - .el-tab-pane { - height: 100%; - } - } -} - -.hot-table.handsontable { - height: calc(100% - 28px) !important; - - .ht_master { - height: 100% !important; - - tr th { - visibility: inherit; - border: 1px solid transparent; - } - - .wtHolder { - height: 100% !important; - overflow-x: auto !important; - cursor: pointer; - } - } - - .ht_clone_left { - tbody.htCore { - & > tr { - border-bottom: 1px solid #373737 !important; - } - } - } - - .manualRowResizer:hover, - .manualRowResizer.active, - .manualColumnResizer:hover, - .manualColumnResizer.active { - &:hover { - background-color: $active-background-color; - } - - background-color: $active-background-color; - } - - thead th { - height: 24px; - color: $font-color; - border: transparent !important; - border-right: 1px solid #373737 !important; - border-bottom: 1px solid #373737 !important; - background: $component-border-color !important; - - &:last-child { - border-right: 1px solid #373737; - border-bottom: none !important; - } - - & > .relative { - font-weight: 600; - - & > .colHeader.columnSorting.ascending::before, - & > .colHeader.columnSorting.descending::before { - filter: invert(35%) sepia(67%) saturate(1363%) hue-rotate(197deg) brightness(85%) contrast(92%); - } - } - } - - tbody { - tr:first-child td { - border-top: none !important; - } - - & > tr { - & > th { - color: $font-color; - border-bottom: 1px solid #373737 !important; - border-left: 1px solid transparent !important; - background: $component-border-color !important; - - &:first-child { - border-top: 1px solid #373737 !important; - border: 1px solid transparent !important; - border-bottom: 1px solid #373737 !important; - } - } - - & > td { - border-right: 1px solid #373737 !important; - } - } - - td { - color: $font-color !important; - background: $framework-border-color; - border-right: none; - border-bottom: 1px solid #373737; - - &:first-of-type { - border-left: none; - } - } - } -} - - -.text-primary { - color: #1ab394 -} - -.text-danger { - color: #FF0000; -} - -.text-warning { - color: #FFFF00 -} - -.material-theme.menu { - background: #383a3c !important; - box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.2), - 0px 8px 10px 1px rgba(0, 0, 0, 0.14), 0px 3px 14px 2px rgba(0, 0, 0, 0.12) !important; -} - -.material-theme .menu_item { - color: #BBBBBB !important; - font-size: 12px !important; -} - -.material-theme .menu_item__divided { - border-bottom-color: rgb(77, 76, 76) !important; -} - -.material-theme .menu_item__available:hover, -.material-theme .menu_item_expand { - background: rgba(0, 0, 0, 0.09) !important; - -} - -.contextmenu_menu.menu { - padding: 5px 0 !important; -} - -.CodeMirror .CodeMirror-selected { - background-color: rgba(62, 162, 223, 0.53) !important; -} - -// 已注释掉,因为全局设置了滚动条样式 -// .CodeMirror-hscrollbar:hover { -// background-color: rgba(0, 0, 0, .5); -// } - -// .CodeMirror-vscrollbar:hover { -// background-color: rgba(0, 0, 0, .5); -// } - -.splitter-pane { - z-index: 99; -} - diff --git a/frontend/src/styles/variables-workbench.scss b/frontend/src/styles/variables-workbench.scss deleted file mode 100644 index 0be1df67..00000000 --- a/frontend/src/styles/variables-workbench.scss +++ /dev/null @@ -1,48 +0,0 @@ -/* Color --------------------------- */ -$workbench-color-primary: #1e1e1e !default; - -$workbench-color-white: #FFFFFF !default; - -$workbench-color-black: #000000 !default; - -$workbench-color-primary-light-1: mix($workbench-color-white, $workbench-color-primary, 2%) !default; -// #232323 -$workbench-color-primary-light-3: mix($workbench-color-white, $workbench-color-primary, 3%) !default; -// #252525 -$workbench-color-primary-light-6: mix($workbench-color-white, $workbench-color-primary, 6%) !default; -// 2c2c2c -$workbench-color-primary-light-10: mix($workbench-color-white, $workbench-color-primary, 10%) !default; -// #353535 -$workbench-color-primary-light-15: mix($workbench-color-white, $workbench-color-primary, 15%) !default; -// #404040 -$workbench-color-primary-light-20: mix($workbench-color-white, $workbench-color-primary, 20%) !default; -// #4b4b4b -$workbench-color-primary-light-30: mix($workbench-color-white, $workbench-color-primary, 30%) !default; -// #626262 - -/// color|Font Color -$workbench-color-text-primary: #cccccc !default; -$workbench-color-text-secondary: #909399 !default; -/// color|Border Color -$workbench-color-border-base: $workbench-color-primary-light-20 !default; -/// color|hover Color -$workbench-hover-color: #1471af !default; - - -/* Size --------------------------- */ -$workbench-size-base: 13px !default; -$workbench-size-small: 12px !default; - -/* Height --------------------------- */ -$workbench-header-height: 35px !default; -$workbench-footer-height: 22px !default; - -/* Width --------------------------- */ -$workbench-aside-width: 48px !default; - -// Box-shadow -$workbench-box-shadow-base: rgba(0, 0, 0, .36) 0px 2px 4px!default; \ No newline at end of file diff --git a/frontend/src/styles/variables.scss b/frontend/src/styles/variables.scss deleted file mode 100644 index 8011abb5..00000000 --- a/frontend/src/styles/variables.scss +++ /dev/null @@ -1,8 +0,0 @@ -// workbench variables - -$icon-color: #A6A6A6; -$font-color: #BBBBBB; -$background-color: #383a3c; -$active-background-color: #2f65ca; -$framework-border-color: #2B2B2B; -$component-border-color: #323232; diff --git a/frontend/src/utils/field.js b/frontend/src/utils/field.js deleted file mode 100644 index e20ceed0..00000000 --- a/frontend/src/utils/field.js +++ /dev/null @@ -1,27 +0,0 @@ -export function parseFieldType(t) { - // if (t.indexOf("char") !== -1) { - // return "" - // } - if (t.indexOf('int') !== -1) { - return 'numeric' - } - // if (t.indexOf("datetime") !== -1) { - // return "time" - // } - return 'text' -} - -export function getUrlParams(url) { - if (url.indexOf('?') === -1) { - return {} - } - const urlStr = url.split('?')[1] - const obj = {} - const paramsArr = urlStr.split('&') - for (let i = 0, len = paramsArr.length; i < len; i++) { - const arr = paramsArr[i].split('=') - obj[arr[0]] = arr[1] - } - return obj -} - diff --git a/frontend/src/utils/luna.js b/frontend/src/utils/luna.js deleted file mode 100644 index 2a2e35af..00000000 --- a/frontend/src/utils/luna.js +++ /dev/null @@ -1,47 +0,0 @@ -export const MESSAGES = { - PONG: 'PONG', - PING: 'PING', - CLOSE: 'CLOSE', - CONNECTED: 'CONNECTED', - KEYEVENT: 'KEYEVENT', - MOUSEEVENT: 'MOUSEEVENT', - INPUT_ACTIVE: 'INPUT_ACTIVE' -} - -export class LunaEvent { - init() { - window.addEventListener('message', - this.handleEventFromLuna.bind(this), - false) - } - - handleEventFromLuna(event) { - const msg = event.data - switch (msg.name) { - case MESSAGES.PING: - if (this.lunaId != null) { - return - } - this.lunaId = msg.id - this.origin = event.origin - this.sendEventToLuna(MESSAGES.PONG) - break - case MESSAGES.INPUT_ACTIVE: - this.inputActiveHandler && this.inputActiveHandler() - break - } - } - - setInputActiveHandler(func) { - this.inputActiveHandler = func - } - - sendEventToLuna(name, data) { - data == null && (data = '') - if (this.lunaId != null) { - const msg = { name: name, id: this.lunaId, data: data } - window.parent.postMessage(msg, this.origin) - console.log('Chen send post message: ', msg) - } - } -} diff --git a/frontend/src/utils/path.js b/frontend/src/utils/path.js deleted file mode 100644 index 152e417e..00000000 --- a/frontend/src/utils/path.js +++ /dev/null @@ -1,93 +0,0 @@ -function stripLeadingSlash(path) { - return `${path || ''}`.replace(/^\/+/, '') -} - -const UI_MARKER = '/chen/' - -function normalizeBasePath(path) { - const normalized = `/${stripLeadingSlash(path)}`.replace(/\/+$/, '') - return normalized === '/' ? '' : normalized -} - -function normalizeUiBase(path) { - const normalized = `/${stripLeadingSlash(path)}`.replace(/\/+$/, '') - return normalized === '/' ? '/' : `${normalized}/` -} - -function joinPath(basePath, path) { - const normalizedBasePath = `${basePath || ''}`.replace(/\/+$/, '') - const normalizedPath = stripLeadingSlash(path) - - if (!normalizedPath) { - return normalizedBasePath || '/' - } - if (!normalizedBasePath) { - return `/${normalizedPath}` - } - return `${normalizedBasePath}/${normalizedPath}` -} - -function buildRuntimePathInfo(pathname) { - const normalizedPathname = `${pathname || '/'}`.replace(/\/+$/, '') + '/' - const markerIndex = normalizedPathname.indexOf(UI_MARKER) - - if (markerIndex < 0) { - return { basePath: '', uiBase: UI_MARKER } - } - - const basePath = markerIndex > 0 - ? normalizedPathname.slice(0, markerIndex).replace(/\/+$/, '') - : '' - - return { - basePath, - uiBase: `${basePath}${UI_MARKER}` - } -} - -function getRuntimePathInfo() { - if (typeof window === 'undefined') { - return { basePath: '', uiBase: '/' } - } - - const basePath = normalizeBasePath(window.__BASE_PATH__) - const uiBase = normalizeUiBase(window.__CHEN_BASE__) - - if (basePath || uiBase !== '/') { - return { basePath, uiBase } - } - return buildRuntimePathInfo(window.location.pathname) -} - -export function getBasePath() { - return getRuntimePathInfo().basePath -} - -export function getAppBase() { - return getRuntimePathInfo().uiBase -} - -export function baseUrl(path = '') { - return joinPath(getBasePath(), path) -} - -export function appUrl(path = '') { - return joinPath(getAppBase(), path) -} - -export function apiUrl(path = '') { - return baseUrl(`api/${stripLeadingSlash(path)}`) -} - -export function appApiUrl(path = '') { - return appUrl(`api/${stripLeadingSlash(path)}`) -} - -export function wsUrl(path = '') { - const scheme = document.location.protocol === 'https:' ? 'wss' : 'ws' - return `${scheme}://${window.location.host}${appUrl(path)}` -} - -export function appWsUrl(path = '') { - return wsUrl(`ws/${stripLeadingSlash(path)}`) -} diff --git a/frontend/src/utils/sql.js b/frontend/src/utils/sql.js deleted file mode 100644 index 1f3aef49..00000000 --- a/frontend/src/utils/sql.js +++ /dev/null @@ -1,19 +0,0 @@ -export function compileSQL(template, params) { - const paramNames = [] - const matcher = /:(\w+)/g - - let match = matcher.exec(template) - while (match) { - paramNames.push(match[1]) - match = matcher.exec(template) - } - - for (const paramName of paramNames) { - let paramValue = params[paramName] - if (paramValue === undefined) { - paramValue = '' - } - template = template.replace(new RegExp(`:${paramName}`, 'g'), paramValue) - } - return template -} diff --git a/frontend/src/utils/sqlChunkProtocol.js b/frontend/src/utils/sqlChunkProtocol.js deleted file mode 100644 index 05daac46..00000000 --- a/frontend/src/utils/sqlChunkProtocol.js +++ /dev/null @@ -1,49 +0,0 @@ -const CHUNK_SIZE = 4096 - -function createRequestId() { - if (typeof window !== 'undefined' && - window.crypto && - typeof window.crypto.randomUUID === 'function') { - return window.crypto.randomUUID() - } - - let seed = Date.now() - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (character) => { - const value = (seed + Math.random() * 16) % 16 | 0 - seed = Math.floor(seed / 16) - return (character === 'x' ? value : (value & 0x3) | 0x8).toString(16) - }) -} - -function buildSQLRunActions(sql, requestIdFactory = createRequestId) { - if (sql.length <= CHUNK_SIZE) { - return [{ action: 'run_sql', data: sql }] - } - - const requestId = requestIdFactory() - const total = Math.ceil(sql.length / CHUNK_SIZE) - const actions = [] - for (let index = 0; index < total; index++) { - actions.push({ - action: 'run_sql_chunk', - data: { - requestId, - chunk: sql.slice(index * CHUNK_SIZE, (index + 1) * CHUNK_SIZE), - index, - total - } - }) - } - actions.push({ - action: 'run_sql_complete', - data: { requestId, total } - }) - return actions -} - -module.exports = { - CHUNK_SIZE, - buildSQLRunActions, - createRequestId -} - diff --git a/frontend/src/utils/sqlEditorSupport.js b/frontend/src/utils/sqlEditorSupport.js deleted file mode 100644 index 520fe7a3..00000000 --- a/frontend/src/utils/sqlEditorSupport.js +++ /dev/null @@ -1,55 +0,0 @@ -const { format } = require('sql-formatter') - -// Front-end formatting and highlighting only. Dameng has no dedicated dialect -// in these libraries, so PLSQL is the best-effort fallback for editor assistance. -const formatterLanguageMap = { - clickhouse: 'sql', - mariadb: 'mariadb', - mysql: 'mysql', - postgresql: 'postgresql', - oracle: 'plsql', - sqlserver: 'tsql', - db2: 'db2', - dameng: 'plsql' -} - -const editorModeMap = { - clickhouse: 'text/x-sql', - mariadb: 'text/x-mariadb', - mysql: 'text/x-mysql', - postgresql: 'text/x-pgsql', - oracle: 'text/x-plsql', - sqlserver: 'text/x-mssql', - db2: 'text/x-sql', - dameng: 'text/x-plsql' -} - -function getFormatterLanguage(databaseType) { - return Object.prototype.hasOwnProperty.call(formatterLanguageMap, databaseType) - ? formatterLanguageMap[databaseType] - : 'sql' -} - -function getEditorMode(databaseType) { - return Object.prototype.hasOwnProperty.call(editorModeMap, databaseType) - ? editorModeMap[databaseType] - : 'text/x-sql' -} - -function formatSqlForEditor(originalSql, databaseType, formatter = format) { - try { - return formatter(originalSql, { - language: getFormatterLanguage(databaseType) - }) - } catch { - return originalSql - } -} - -module.exports = { - editorModeMap, - formatSqlForEditor, - formatterLanguageMap, - getEditorMode, - getFormatterLanguage -} diff --git a/frontend/src/utils/ws.js b/frontend/src/utils/ws.js deleted file mode 100644 index 8b137891..00000000 --- a/frontend/src/utils/ws.js +++ /dev/null @@ -1 +0,0 @@ - diff --git a/frontend/vue.config.js b/frontend/vue.config.js deleted file mode 100644 index 7affd75e..00000000 --- a/frontend/vue.config.js +++ /dev/null @@ -1,52 +0,0 @@ -const path = require('path') - -function resolve(dir) { - return path.join(__dirname, dir) -} - -module.exports = { - productionSourceMap: true, - publicPath: '/chen', - devServer: { - port: 9523, - open: true, - overlay: { - warnings: false, - errors: true - }, - proxy: { - '/chen/api': { - target: 'http://0.0.0.0:8082', - ws: true, - secure: false - }, - '/chen/ws': { - target: 'ws://0.0.0.0:8082', - ws: true, - secure: false - } - } - }, - configureWebpack: { - resolve: { - alias: { - '@': resolve('src') - }, - extensions: ['.vue', '.js', '.json'] - } - }, - chainWebpack(config) { - config.plugins.delete('preload') - config.plugins.delete('prefetch') - config - .when(process.env.NODE_ENV === 'development', - config => config.devtool('cheap-source-map') - ) - config - .when(process.env.NODE_ENV !== 'development', - config => { - config.optimization.runtimeChunk('single') - } - ) - } -} diff --git a/frontend/yarn.lock b/frontend/yarn.lock deleted file mode 100644 index 51175de6..00000000 --- a/frontend/yarn.lock +++ /dev/null @@ -1,9408 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@achrinza/node-ipc@9.2.2": - version "9.2.2" - resolved "https://registry.npmmirror.com/@achrinza/node-ipc/-/node-ipc-9.2.2.tgz" - integrity sha512-b90U39dx0cU6emsOvy5hxU4ApNXnE3+Tuo8XQZfiKTGelDwpMwBVgBP7QX6dGTcJgu/miyJuNJ/2naFBliNWEw== - dependencies: - "@node-ipc/js-queue" "2.0.3" - event-pubsub "4.3.0" - js-message "1.0.7" - -"@ampproject/remapping@^2.2.0": - version "2.3.0" - resolved "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.3.0.tgz" - integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.24" - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.24.6.tgz" - integrity sha512-ZJhac6FkEd1yhG2AHOmfcXG4ceoLltoCVJjN5XsWN9BifBQr+cHJbWi0h68HZuSORq+3WtJ2z0hwF2NG1b5kcA== - dependencies: - "@babel/highlight" "^7.24.6" - picocolors "^1.0.0" - -"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.24.6.tgz" - integrity sha512-aC2DGhBq5eEdyXWqrDInSqQjO0k8xtPRf5YylULqx8MCd6jBtzqfta/3ETMRpuKIc5hyswfO80ObyA1MvkCcUQ== - -"@babel/core@^7.11.0": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/core/-/core-7.24.6.tgz" - integrity sha512-qAHSfAdVyFmIvl0VHELib8xar7ONuSHrE2hLnsaWkYNTI68dmi1x8GYDhJjMI/e7XWal9QBlZkwbOnkcw7Z8gQ== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.24.6" - "@babel/generator" "^7.24.6" - "@babel/helper-compilation-targets" "^7.24.6" - "@babel/helper-module-transforms" "^7.24.6" - "@babel/helpers" "^7.24.6" - "@babel/parser" "^7.24.6" - "@babel/template" "^7.24.6" - "@babel/traverse" "^7.24.6" - "@babel/types" "^7.24.6" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/generator@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/generator/-/generator-7.24.6.tgz" - integrity sha512-S7m4eNa6YAPJRHmKsLHIDJhNAGNKoWNiWefz1MBbpnt8g9lvMDl1hir4P9bo/57bQEmuwEhnRU/AMWsD0G/Fbg== - dependencies: - "@babel/types" "^7.24.6" - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - jsesc "^2.5.1" - -"@babel/helper-annotate-as-pure@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.6.tgz" - integrity sha512-DitEzDfOMnd13kZnDqns1ccmftwJTS9DMkyn9pYTxulS7bZxUxpMly3Nf23QQ6NwA4UB8lAqjbqWtyvElEMAkg== - dependencies: - "@babel/types" "^7.24.6" - -"@babel/helper-builder-binary-assignment-operator-visitor@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.6.tgz" - integrity sha512-+wnfqc5uHiMYtvRX7qu80Toef8BXeh4HHR1SPeonGb1SKPniNEd4a/nlaJJMv/OIEYvIVavvo0yR7u10Gqz0Iw== - dependencies: - "@babel/types" "^7.24.6" - -"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.24.6", "@babel/helper-compilation-targets@^7.9.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.6.tgz" - integrity sha512-VZQ57UsDGlX/5fFA7GkVPplZhHsVc+vuErWgdOiysI9Ksnw0Pbbd6pnPiR/mmJyKHgyIW0c7KT32gmhiF+cirg== - dependencies: - "@babel/compat-data" "^7.24.6" - "@babel/helper-validator-option" "^7.24.6" - browserslist "^4.22.2" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.6.tgz" - integrity sha512-djsosdPJVZE6Vsw3kk7IPRWethP94WHGOhQTc67SNXE0ZzMhHgALw8iGmYS0TD1bbMM0VDROy43od7/hN6WYcA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.24.6" - "@babel/helper-environment-visitor" "^7.24.6" - "@babel/helper-function-name" "^7.24.6" - "@babel/helper-member-expression-to-functions" "^7.24.6" - "@babel/helper-optimise-call-expression" "^7.24.6" - "@babel/helper-replace-supers" "^7.24.6" - "@babel/helper-skip-transparent-expression-wrappers" "^7.24.6" - "@babel/helper-split-export-declaration" "^7.24.6" - semver "^6.3.1" - -"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.24.6.tgz" - integrity sha512-C875lFBIWWwyv6MHZUG9HmRrlTDgOsLWZfYR0nW69gaKJNe0/Mpxx5r0EID2ZdHQkdUmQo2t0uNckTL08/1BgA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.24.6" - regexpu-core "^5.3.1" - semver "^6.3.1" - -"@babel/helper-define-polyfill-provider@^0.6.1", "@babel/helper-define-polyfill-provider@^0.6.2": - version "0.6.2" - resolved "https://registry.npmmirror.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz" - integrity sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ== - dependencies: - "@babel/helper-compilation-targets" "^7.22.6" - "@babel/helper-plugin-utils" "^7.22.5" - debug "^4.1.1" - lodash.debounce "^4.0.8" - resolve "^1.14.2" - -"@babel/helper-environment-visitor@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.6.tgz" - integrity sha512-Y50Cg3k0LKLMjxdPjIl40SdJgMB85iXn27Vk/qbHZCFx/o5XO3PSnpi675h1KEmmDb6OFArfd5SCQEQ5Q4H88g== - -"@babel/helper-function-name@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/helper-function-name/-/helper-function-name-7.24.6.tgz" - integrity sha512-xpeLqeeRkbxhnYimfr2PC+iA0Q7ljX/d1eZ9/inYbmfG2jpl8Lu3DyXvpOAnrS5kxkfOWJjioIMQsaMBXFI05w== - dependencies: - "@babel/template" "^7.24.6" - "@babel/types" "^7.24.6" - -"@babel/helper-hoist-variables@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.6.tgz" - integrity sha512-SF/EMrC3OD7dSta1bLJIlrsVxwtd0UpjRJqLno6125epQMJ/kyFmpTT4pbvPbdQHzCHg+biQ7Syo8lnDtbR+uA== - dependencies: - "@babel/types" "^7.24.6" - -"@babel/helper-member-expression-to-functions@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.6.tgz" - integrity sha512-OTsCufZTxDUsv2/eDXanw/mUZHWOxSbEmC3pP8cgjcy5rgeVPWWMStnv274DV60JtHxTk0adT0QrCzC4M9NWGg== - dependencies: - "@babel/types" "^7.24.6" - -"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.24.6", "@babel/helper-module-imports@^7.8.3": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.24.6.tgz" - integrity sha512-a26dmxFJBF62rRO9mmpgrfTLsAuyHk4e1hKTUkD/fcMfynt8gvEKwQPQDVxWhca8dHoDck+55DFt42zV0QMw5g== - dependencies: - "@babel/types" "^7.24.6" - -"@babel/helper-module-imports@~7.22.15": - version "7.22.15" - resolved "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz" - integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== - dependencies: - "@babel/types" "^7.22.15" - -"@babel/helper-module-transforms@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.6.tgz" - integrity sha512-Y/YMPm83mV2HJTbX1Qh2sjgjqcacvOlhbzdCCsSlblOKjSYmQqEbO6rUniWQyRo9ncyfjT8hnUjlG06RXDEmcA== - dependencies: - "@babel/helper-environment-visitor" "^7.24.6" - "@babel/helper-module-imports" "^7.24.6" - "@babel/helper-simple-access" "^7.24.6" - "@babel/helper-split-export-declaration" "^7.24.6" - "@babel/helper-validator-identifier" "^7.24.6" - -"@babel/helper-optimise-call-expression@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.6.tgz" - integrity sha512-3SFDJRbx7KuPRl8XDUr8O7GAEB8iGyWPjLKJh/ywP/Iy9WOmEfMrsWbaZpvBu2HSYn4KQygIsz0O7m8y10ncMA== - dependencies: - "@babel/types" "^7.24.6" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.6", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.6.tgz" - integrity sha512-MZG/JcWfxybKwsA9N9PmtF2lOSFSEMVCpIRrbxccZFLJPrJciJdG/UhSh5W96GEteJI2ARqm5UAHxISwRDLSNg== - -"@babel/helper-remap-async-to-generator@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.24.6.tgz" - integrity sha512-1Qursq9ArRZPAMOZf/nuzVW8HgJLkTB9y9LfP4lW2MVp4e9WkLJDovfKBxoDcCk6VuzIxyqWHyBoaCtSRP10yg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.24.6" - "@babel/helper-environment-visitor" "^7.24.6" - "@babel/helper-wrap-function" "^7.24.6" - -"@babel/helper-replace-supers@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.24.6.tgz" - integrity sha512-mRhfPwDqDpba8o1F8ESxsEkJMQkUF8ZIWrAc0FtWhxnjfextxMWxr22RtFizxxSYLjVHDeMgVsRq8BBZR2ikJQ== - dependencies: - "@babel/helper-environment-visitor" "^7.24.6" - "@babel/helper-member-expression-to-functions" "^7.24.6" - "@babel/helper-optimise-call-expression" "^7.24.6" - -"@babel/helper-simple-access@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/helper-simple-access/-/helper-simple-access-7.24.6.tgz" - integrity sha512-nZzcMMD4ZhmB35MOOzQuiGO5RzL6tJbsT37Zx8M5L/i9KSrukGXWTjLe1knIbb/RmxoJE9GON9soq0c0VEMM5g== - dependencies: - "@babel/types" "^7.24.6" - -"@babel/helper-skip-transparent-expression-wrappers@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.6.tgz" - integrity sha512-jhbbkK3IUKc4T43WadP96a27oYti9gEf1LdyGSP2rHGH77kwLwfhO7TgwnWvxxQVmke0ImmCSS47vcuxEMGD3Q== - dependencies: - "@babel/types" "^7.24.6" - -"@babel/helper-split-export-declaration@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.6.tgz" - integrity sha512-CvLSkwXGWnYlF9+J3iZUvwgAxKiYzK3BWuo+mLzD/MDGOZDj7Gq8+hqaOkMxmJwmlv0iu86uH5fdADd9Hxkymw== - dependencies: - "@babel/types" "^7.24.6" - -"@babel/helper-string-parser@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.24.6.tgz" - integrity sha512-WdJjwMEkmBicq5T9fm/cHND3+UlFa2Yj8ALLgmoSQAJZysYbBjw+azChSGPN4DSPLXOcooGRvDwZWMcF/mLO2Q== - -"@babel/helper-validator-identifier@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.6.tgz" - integrity sha512-4yA7s865JHaqUdRbnaxarZREuPTHrjpDT+pXoAZ1yhyo6uFnIEpS8VMu16siFOHDpZNKYv5BObhsB//ycbICyw== - -"@babel/helper-validator-option@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.24.6.tgz" - integrity sha512-Jktc8KkF3zIkePb48QO+IapbXlSapOW9S+ogZZkcO6bABgYAxtZcjZ/O005111YLf+j4M84uEgwYoidDkXbCkQ== - -"@babel/helper-wrap-function@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/helper-wrap-function/-/helper-wrap-function-7.24.6.tgz" - integrity sha512-f1JLrlw/jbiNfxvdrfBgio/gRBk3yTAEJWirpAkiJG2Hb22E7cEYKHWo0dFPTv/niPovzIdPdEDetrv6tC6gPQ== - dependencies: - "@babel/helper-function-name" "^7.24.6" - "@babel/template" "^7.24.6" - "@babel/types" "^7.24.6" - -"@babel/helpers@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.24.6.tgz" - integrity sha512-V2PI+NqnyFu1i0GyTd/O/cTpxzQCYioSkUIRmgo7gFEHKKCg5w46+r/A6WeUR1+P3TeQ49dspGPNd/E3n9AnnA== - dependencies: - "@babel/template" "^7.24.6" - "@babel/types" "^7.24.6" - -"@babel/highlight@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/highlight/-/highlight-7.24.6.tgz" - integrity sha512-2YnuOp4HAk2BsBrJJvYCbItHx0zWscI1C3zgWkz+wDyD9I7GIVrfnLyrR4Y1VR+7p+chAEcrgRQYZAGIKMV7vQ== - dependencies: - "@babel/helper-validator-identifier" "^7.24.6" - chalk "^2.4.2" - js-tokens "^4.0.0" - picocolors "^1.0.0" - -"@babel/parser@^7.23.5", "@babel/parser@^7.23.9", "@babel/parser@^7.24.4", "@babel/parser@^7.24.6", "@babel/parser@^7.7.0": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.24.6.tgz" - integrity sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q== - -"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.6.tgz" - integrity sha512-bYndrJ6Ph6Ar+GaB5VAc0JPoP80bQCm4qon6JEzXfRl5QZyQ8Ur1K6k7htxWmPA5z+k7JQvaMUrtXlqclWYzKw== - dependencies: - "@babel/helper-environment-visitor" "^7.24.6" - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.6.tgz" - integrity sha512-iVuhb6poq5ikqRq2XWU6OQ+R5o9wF+r/or9CeUyovgptz0UlnK4/seOQ1Istu/XybYjAhQv1FRSSfHHufIku5Q== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.6.tgz" - integrity sha512-c8TER5xMDYzzFcGqOEp9l4hvB7dcbhcGjcLVwxWfe4P5DOafdwjsBJZKsmv+o3aXh7NhopvayQIovHrh2zSRUQ== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - "@babel/helper-skip-transparent-expression-wrappers" "^7.24.6" - "@babel/plugin-transform-optional-chaining" "^7.24.6" - -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.6.tgz" - integrity sha512-z8zEjYmwBUHN/pCF3NuWBhHQjJCrd33qAi8MgANfMrAvn72k2cImT8VjK9LJFu4ysOLJqhfkYYb3MvwANRUNZQ== - dependencies: - "@babel/helper-environment-visitor" "^7.24.6" - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-proposal-class-properties@^7.8.3": - version "7.18.6" - resolved "https://registry.npmmirror.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz" - integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-proposal-decorators@^7.8.3": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.24.6.tgz" - integrity sha512-8DjR0/DzlBhz2SVi9a19/N2U5+C3y3rseXuyoKL9SP8vnbewscj1eHZtL6kpEn4UCuUmqEo0mvqyDYRFoN2gpA== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.24.6" - "@babel/helper-plugin-utils" "^7.24.6" - "@babel/plugin-syntax-decorators" "^7.24.6" - -"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": - version "7.21.0-placeholder-for-preset-env.2" - resolved "https://registry.npmmirror.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz" - integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== - -"@babel/plugin-syntax-async-generators@^7.8.4": - version "7.8.4" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-class-properties@^7.12.13": - version "7.12.13" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz" - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-syntax-class-static-block@^7.14.5": - version "7.14.5" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz" - integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-decorators@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.24.6.tgz" - integrity sha512-gInH8LEqBp+wkwTVihCd/qf+4s28g81FZyvlIbAurHk9eSiItEKG7E0uNK2UdpgsD79aJVAW3R3c85h0YJ0jsw== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-syntax-dynamic-import@^7.8.3": - version "7.8.3" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz" - integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-export-namespace-from@^7.8.3": - version "7.8.3" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz" - integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-syntax-import-assertions@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.6.tgz" - integrity sha512-BE6o2BogJKJImTmGpkmOic4V0hlRRxVtzqxiSPa8TIFxyhi4EFjHm08nq1M4STK4RytuLMgnSz0/wfflvGFNOg== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-syntax-import-attributes@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.6.tgz" - integrity sha512-D+CfsVZousPXIdudSII7RGy52+dYRtbyKAZcvtQKq/NpsivyMVduepzcLqG5pMBugtMdedxdC8Ramdpcne9ZWQ== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-syntax-import-meta@^7.10.4": - version "7.10.4" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz" - integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-jsx@^7.2.0", "@babel/plugin-syntax-jsx@^7.23.3", "@babel/plugin-syntax-jsx@^7.8.3": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.6.tgz" - integrity sha512-lWfvAIFNWMlCsU0DRUun2GpFwZdGTukLaHJqRh1JRb80NdAP5Sb1HDHB5X9P9OtgZHQl089UzQkpYlBq2VTPRw== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": - version "7.10.4" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz" - integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz" - integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-numeric-separator@^7.10.4": - version "7.10.4" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz" - integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz" - integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-private-property-in-object@^7.14.5": - version "7.14.5" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz" - integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-top-level-await@^7.14.5": - version "7.14.5" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz" - integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-unicode-sets-regex@^7.18.6": - version "7.18.6" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz" - integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-arrow-functions@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.6.tgz" - integrity sha512-jSSSDt4ZidNMggcLx8SaKsbGNEfIl0PHx/4mFEulorE7bpYLbN0d3pDW3eJ7Y5Z3yPhy3L3NaPCYyTUY7TuugQ== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-transform-async-generator-functions@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.6.tgz" - integrity sha512-VEP2o4iR2DqQU6KPgizTW2mnMx6BG5b5O9iQdrW9HesLkv8GIA8x2daXBQxw1MrsIkFQGA/iJ204CKoQ8UcnAA== - dependencies: - "@babel/helper-environment-visitor" "^7.24.6" - "@babel/helper-plugin-utils" "^7.24.6" - "@babel/helper-remap-async-to-generator" "^7.24.6" - "@babel/plugin-syntax-async-generators" "^7.8.4" - -"@babel/plugin-transform-async-to-generator@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.6.tgz" - integrity sha512-NTBA2SioI3OsHeIn6sQmhvXleSl9T70YY/hostQLveWs0ic+qvbA3fa0kwAwQ0OA/XGaAerNZRQGJyRfhbJK4g== - dependencies: - "@babel/helper-module-imports" "^7.24.6" - "@babel/helper-plugin-utils" "^7.24.6" - "@babel/helper-remap-async-to-generator" "^7.24.6" - -"@babel/plugin-transform-block-scoped-functions@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.6.tgz" - integrity sha512-XNW7jolYHW9CwORrZgA/97tL/k05qe/HL0z/qqJq1mdWhwwCM6D4BJBV7wAz9HgFziN5dTOG31znkVIzwxv+vw== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-transform-block-scoping@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.6.tgz" - integrity sha512-S/t1Xh4ehW7sGA7c1j/hiOBLnEYCp/c2sEG4ZkL8kI1xX9tW2pqJTCHKtdhe/jHKt8nG0pFCrDHUXd4DvjHS9w== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-transform-class-properties@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.6.tgz" - integrity sha512-j6dZ0Z2Z2slWLR3kt9aOmSIrBvnntWjMDN/TVcMPxhXMLmJVqX605CBRlcGI4b32GMbfifTEsdEjGjiE+j/c3A== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.24.6" - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-transform-class-static-block@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.6.tgz" - integrity sha512-1QSRfoPI9RoLRa8Mnakc6v3e0gJxiZQTYrMfLn+mD0sz5+ndSzwymp2hDcYJTyT0MOn0yuWzj8phlIvO72gTHA== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.24.6" - "@babel/helper-plugin-utils" "^7.24.6" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - -"@babel/plugin-transform-classes@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.6.tgz" - integrity sha512-+fN+NO2gh8JtRmDSOB6gaCVo36ha8kfCW1nMq2Gc0DABln0VcHN4PrALDvF5/diLzIRKptC7z/d7Lp64zk92Fg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.24.6" - "@babel/helper-compilation-targets" "^7.24.6" - "@babel/helper-environment-visitor" "^7.24.6" - "@babel/helper-function-name" "^7.24.6" - "@babel/helper-plugin-utils" "^7.24.6" - "@babel/helper-replace-supers" "^7.24.6" - "@babel/helper-split-export-declaration" "^7.24.6" - globals "^11.1.0" - -"@babel/plugin-transform-computed-properties@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.6.tgz" - integrity sha512-cRzPobcfRP0ZtuIEkA8QzghoUpSB3X3qSH5W2+FzG+VjWbJXExtx0nbRqwumdBN1x/ot2SlTNQLfBCnPdzp6kg== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - "@babel/template" "^7.24.6" - -"@babel/plugin-transform-destructuring@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.6.tgz" - integrity sha512-YLW6AE5LQpk5npNXL7i/O+U9CE4XsBCuRPgyjl1EICZYKmcitV+ayuuUGMJm2lC1WWjXYszeTnIxF/dq/GhIZQ== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-transform-dotall-regex@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.6.tgz" - integrity sha512-rCXPnSEKvkm/EjzOtLoGvKseK+dS4kZwx1HexO3BtRtgL0fQ34awHn34aeSHuXtZY2F8a1X8xqBBPRtOxDVmcA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.24.6" - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-transform-duplicate-keys@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.6.tgz" - integrity sha512-/8Odwp/aVkZwPFJMllSbawhDAO3UJi65foB00HYnK/uXvvCPm0TAXSByjz1mpRmp0q6oX2SIxpkUOpPFHk7FLA== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-transform-dynamic-import@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.6.tgz" - integrity sha512-vpq8SSLRTBLOHUZHSnBqVo0AKX3PBaoPs2vVzYVWslXDTDIpwAcCDtfhUcHSQQoYoUvcFPTdC8TZYXu9ZnLT/w== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - -"@babel/plugin-transform-exponentiation-operator@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.6.tgz" - integrity sha512-EemYpHtmz0lHE7hxxxYEuTYOOBZ43WkDgZ4arQ4r+VX9QHuNZC+WH3wUWmRNvR8ECpTRne29aZV6XO22qpOtdA== - dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.24.6" - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-transform-export-namespace-from@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.6.tgz" - integrity sha512-inXaTM1SVrIxCkIJ5gqWiozHfFMStuGbGJAxZFBoHcRRdDP0ySLb3jH6JOwmfiinPwyMZqMBX+7NBDCO4z0NSA== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - -"@babel/plugin-transform-for-of@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.6.tgz" - integrity sha512-n3Sf72TnqK4nw/jziSqEl1qaWPbCRw2CziHH+jdRYvw4J6yeCzsj4jdw8hIntOEeDGTmHVe2w4MVL44PN0GMzg== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - "@babel/helper-skip-transparent-expression-wrappers" "^7.24.6" - -"@babel/plugin-transform-function-name@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.6.tgz" - integrity sha512-sOajCu6V0P1KPljWHKiDq6ymgqB+vfo3isUS4McqW1DZtvSVU2v/wuMhmRmkg3sFoq6GMaUUf8W4WtoSLkOV/Q== - dependencies: - "@babel/helper-compilation-targets" "^7.24.6" - "@babel/helper-function-name" "^7.24.6" - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-transform-json-strings@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.6.tgz" - integrity sha512-Uvgd9p2gUnzYJxVdBLcU0KurF8aVhkmVyMKW4MIY1/BByvs3EBpv45q01o7pRTVmTvtQq5zDlytP3dcUgm7v9w== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - "@babel/plugin-syntax-json-strings" "^7.8.3" - -"@babel/plugin-transform-literals@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.6.tgz" - integrity sha512-f2wHfR2HF6yMj+y+/y07+SLqnOSwRp8KYLpQKOzS58XLVlULhXbiYcygfXQxJlMbhII9+yXDwOUFLf60/TL5tw== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-transform-logical-assignment-operators@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.6.tgz" - integrity sha512-EKaWvnezBCMkRIHxMJSIIylzhqK09YpiJtDbr2wsXTwnO0TxyjMUkaw4RlFIZMIS0iDj0KyIg7H7XCguHu/YDA== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - -"@babel/plugin-transform-member-expression-literals@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.6.tgz" - integrity sha512-9g8iV146szUo5GWgXpRbq/GALTnY+WnNuRTuRHWWFfWGbP9ukRL0aO/jpu9dmOPikclkxnNsjY8/gsWl6bmZJQ== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-transform-modules-amd@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.6.tgz" - integrity sha512-eAGogjZgcwqAxhyFgqghvoHRr+EYRQPFjUXrTYKBRb5qPnAVxOOglaxc4/byHqjvq/bqO2F3/CGwTHsgKJYHhQ== - dependencies: - "@babel/helper-module-transforms" "^7.24.6" - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-transform-modules-commonjs@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.6.tgz" - integrity sha512-JEV8l3MHdmmdb7S7Cmx6rbNEjRCgTQMZxllveHO0mx6uiclB0NflCawlQQ6+o5ZrwjUBYPzHm2XoK4wqGVUFuw== - dependencies: - "@babel/helper-module-transforms" "^7.24.6" - "@babel/helper-plugin-utils" "^7.24.6" - "@babel/helper-simple-access" "^7.24.6" - -"@babel/plugin-transform-modules-systemjs@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.6.tgz" - integrity sha512-xg1Z0J5JVYxtpX954XqaaAT6NpAY6LtZXvYFCJmGFJWwtlz2EmJoR8LycFRGNE8dBKizGWkGQZGegtkV8y8s+w== - dependencies: - "@babel/helper-hoist-variables" "^7.24.6" - "@babel/helper-module-transforms" "^7.24.6" - "@babel/helper-plugin-utils" "^7.24.6" - "@babel/helper-validator-identifier" "^7.24.6" - -"@babel/plugin-transform-modules-umd@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.6.tgz" - integrity sha512-esRCC/KsSEUvrSjv5rFYnjZI6qv4R1e/iHQrqwbZIoRJqk7xCvEUiN7L1XrmW5QSmQe3n1XD88wbgDTWLbVSyg== - dependencies: - "@babel/helper-module-transforms" "^7.24.6" - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-transform-named-capturing-groups-regex@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.6.tgz" - integrity sha512-6DneiCiu91wm3YiNIGDWZsl6GfTTbspuj/toTEqLh9d4cx50UIzSdg+T96p8DuT7aJOBRhFyaE9ZvTHkXrXr6Q== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.24.6" - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-transform-new-target@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.6.tgz" - integrity sha512-f8liz9JG2Va8A4J5ZBuaSdwfPqN6axfWRK+y66fjKYbwf9VBLuq4WxtinhJhvp1w6lamKUwLG0slK2RxqFgvHA== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-transform-nullish-coalescing-operator@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.6.tgz" - integrity sha512-+QlAiZBMsBK5NqrBWFXCYeXyiU1y7BQ/OYaiPAcQJMomn5Tyg+r5WuVtyEuvTbpV7L25ZSLfE+2E9ywj4FD48A== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - -"@babel/plugin-transform-numeric-separator@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.6.tgz" - integrity sha512-6voawq8T25Jvvnc4/rXcWZQKKxUNZcKMS8ZNrjxQqoRFernJJKjE3s18Qo6VFaatG5aiX5JV1oPD7DbJhn0a4Q== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - -"@babel/plugin-transform-object-rest-spread@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.6.tgz" - integrity sha512-OKmi5wiMoRW5Smttne7BwHM8s/fb5JFs+bVGNSeHWzwZkWXWValR1M30jyXo1s/RaqgwwhEC62u4rFH/FBcBPg== - dependencies: - "@babel/helper-compilation-targets" "^7.24.6" - "@babel/helper-plugin-utils" "^7.24.6" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.24.6" - -"@babel/plugin-transform-object-super@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.6.tgz" - integrity sha512-N/C76ihFKlZgKfdkEYKtaRUtXZAgK7sOY4h2qrbVbVTXPrKGIi8aww5WGe/+Wmg8onn8sr2ut6FXlsbu/j6JHg== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - "@babel/helper-replace-supers" "^7.24.6" - -"@babel/plugin-transform-optional-catch-binding@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.6.tgz" - integrity sha512-L5pZ+b3O1mSzJ71HmxSCmTVd03VOT2GXOigug6vDYJzE5awLI7P1g0wFcdmGuwSDSrQ0L2rDOe/hHws8J1rv3w== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - -"@babel/plugin-transform-optional-chaining@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.6.tgz" - integrity sha512-cHbqF6l1QP11OkYTYQ+hhVx1E017O5ZcSPXk9oODpqhcAD1htsWG2NpHrrhthEO2qZomLK0FXS+u7NfrkF5aOQ== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - "@babel/helper-skip-transparent-expression-wrappers" "^7.24.6" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - -"@babel/plugin-transform-parameters@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.6.tgz" - integrity sha512-ST7guE8vLV+vI70wmAxuZpIKzVjvFX9Qs8bl5w6tN/6gOypPWUmMQL2p7LJz5E63vEGrDhAiYetniJFyBH1RkA== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-transform-private-methods@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.6.tgz" - integrity sha512-T9LtDI0BgwXOzyXrvgLTT8DFjCC/XgWLjflczTLXyvxbnSR/gpv0hbmzlHE/kmh9nOvlygbamLKRo6Op4yB6aw== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.24.6" - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-transform-private-property-in-object@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.6.tgz" - integrity sha512-Qu/ypFxCY5NkAnEhCF86Mvg3NSabKsh/TPpBVswEdkGl7+FbsYHy1ziRqJpwGH4thBdQHh8zx+z7vMYmcJ7iaQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.24.6" - "@babel/helper-create-class-features-plugin" "^7.24.6" - "@babel/helper-plugin-utils" "^7.24.6" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - -"@babel/plugin-transform-property-literals@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.6.tgz" - integrity sha512-oARaglxhRsN18OYsnPTpb8TcKQWDYNsPNmTnx5++WOAsUJ0cSC/FZVlIJCKvPbU4yn/UXsS0551CFKJhN0CaMw== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-transform-regenerator@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.6.tgz" - integrity sha512-SMDxO95I8WXRtXhTAc8t/NFQUT7VYbIWwJCJgEli9ml4MhqUMh4S6hxgH6SmAC3eAQNWCDJFxcFeEt9w2sDdXg== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - regenerator-transform "^0.15.2" - -"@babel/plugin-transform-reserved-words@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.6.tgz" - integrity sha512-DcrgFXRRlK64dGE0ZFBPD5egM2uM8mgfrvTMOSB2yKzOtjpGegVYkzh3s1zZg1bBck3nkXiaOamJUqK3Syk+4A== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-transform-runtime@^7.11.0": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.6.tgz" - integrity sha512-W3gQydMb0SY99y/2lV0Okx2xg/8KzmZLQsLaiCmwNRl1kKomz14VurEm+2TossUb+sRvBCnGe+wx8KtIgDtBbQ== - dependencies: - "@babel/helper-module-imports" "^7.24.6" - "@babel/helper-plugin-utils" "^7.24.6" - babel-plugin-polyfill-corejs2 "^0.4.10" - babel-plugin-polyfill-corejs3 "^0.10.1" - babel-plugin-polyfill-regenerator "^0.6.1" - semver "^6.3.1" - -"@babel/plugin-transform-shorthand-properties@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.6.tgz" - integrity sha512-xnEUvHSMr9eOWS5Al2YPfc32ten7CXdH7Zwyyk7IqITg4nX61oHj+GxpNvl+y5JHjfN3KXE2IV55wAWowBYMVw== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-transform-spread@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.6.tgz" - integrity sha512-h/2j7oIUDjS+ULsIrNZ6/TKG97FgmEk1PXryk/HQq6op4XUUUwif2f69fJrzK0wza2zjCS1xhXmouACaWV5uPA== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - "@babel/helper-skip-transparent-expression-wrappers" "^7.24.6" - -"@babel/plugin-transform-sticky-regex@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.6.tgz" - integrity sha512-fN8OcTLfGmYv7FnDrsjodYBo1DhPL3Pze/9mIIE2MGCT1KgADYIOD7rEglpLHZj8PZlC/JFX5WcD+85FLAQusw== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-transform-template-literals@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.6.tgz" - integrity sha512-BJbEqJIcKwrqUP+KfUIkxz3q8VzXe2R8Wv8TaNgO1cx+nNavxn/2+H8kp9tgFSOL6wYPPEgFvU6IKS4qoGqhmg== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-transform-typeof-symbol@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.6.tgz" - integrity sha512-IshCXQ+G9JIFJI7bUpxTE/oA2lgVLAIK8q1KdJNoPXOpvRaNjMySGuvLfBw/Xi2/1lLo953uE8hyYSDW3TSYig== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-transform-unicode-escapes@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.6.tgz" - integrity sha512-bKl3xxcPbkQQo5eX9LjjDpU2xYHeEeNQbOhj0iPvetSzA+Tu9q/o5lujF4Sek60CM6MgYvOS/DJuwGbiEYAnLw== - dependencies: - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-transform-unicode-property-regex@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.6.tgz" - integrity sha512-8EIgImzVUxy15cZiPii9GvLZwsy7Vxc+8meSlR3cXFmBIl5W5Tn9LGBf7CDKkHj4uVfNXCJB8RsVfnmY61iedA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.24.6" - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-transform-unicode-regex@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.6.tgz" - integrity sha512-pssN6ExsvxaKU638qcWb81RrvvgZom3jDgU/r5xFZ7TONkZGFf4MhI2ltMb8OcQWhHyxgIavEU+hgqtbKOmsPA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.24.6" - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/plugin-transform-unicode-sets-regex@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.6.tgz" - integrity sha512-quiMsb28oXWIDK0gXLALOJRXLgICLiulqdZGOaPPd0vRT7fQp74NtdADAVu+D8s00C+0Xs0MxVP0VKF/sZEUgw== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.24.6" - "@babel/helper-plugin-utils" "^7.24.6" - -"@babel/preset-env@^7.11.0": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/preset-env/-/preset-env-7.24.6.tgz" - integrity sha512-CrxEAvN7VxfjOG8JNF2Y/eMqMJbZPZ185amwGUBp8D9USK90xQmv7dLdFSa+VbD7fdIqcy/Mfv7WtzG8+/qxKg== - dependencies: - "@babel/compat-data" "^7.24.6" - "@babel/helper-compilation-targets" "^7.24.6" - "@babel/helper-plugin-utils" "^7.24.6" - "@babel/helper-validator-option" "^7.24.6" - "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.24.6" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.24.6" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.24.6" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.24.6" - "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-class-properties" "^7.12.13" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - "@babel/plugin-syntax-import-assertions" "^7.24.6" - "@babel/plugin-syntax-import-attributes" "^7.24.6" - "@babel/plugin-syntax-import-meta" "^7.10.4" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - "@babel/plugin-syntax-top-level-await" "^7.14.5" - "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" - "@babel/plugin-transform-arrow-functions" "^7.24.6" - "@babel/plugin-transform-async-generator-functions" "^7.24.6" - "@babel/plugin-transform-async-to-generator" "^7.24.6" - "@babel/plugin-transform-block-scoped-functions" "^7.24.6" - "@babel/plugin-transform-block-scoping" "^7.24.6" - "@babel/plugin-transform-class-properties" "^7.24.6" - "@babel/plugin-transform-class-static-block" "^7.24.6" - "@babel/plugin-transform-classes" "^7.24.6" - "@babel/plugin-transform-computed-properties" "^7.24.6" - "@babel/plugin-transform-destructuring" "^7.24.6" - "@babel/plugin-transform-dotall-regex" "^7.24.6" - "@babel/plugin-transform-duplicate-keys" "^7.24.6" - "@babel/plugin-transform-dynamic-import" "^7.24.6" - "@babel/plugin-transform-exponentiation-operator" "^7.24.6" - "@babel/plugin-transform-export-namespace-from" "^7.24.6" - "@babel/plugin-transform-for-of" "^7.24.6" - "@babel/plugin-transform-function-name" "^7.24.6" - "@babel/plugin-transform-json-strings" "^7.24.6" - "@babel/plugin-transform-literals" "^7.24.6" - "@babel/plugin-transform-logical-assignment-operators" "^7.24.6" - "@babel/plugin-transform-member-expression-literals" "^7.24.6" - "@babel/plugin-transform-modules-amd" "^7.24.6" - "@babel/plugin-transform-modules-commonjs" "^7.24.6" - "@babel/plugin-transform-modules-systemjs" "^7.24.6" - "@babel/plugin-transform-modules-umd" "^7.24.6" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.24.6" - "@babel/plugin-transform-new-target" "^7.24.6" - "@babel/plugin-transform-nullish-coalescing-operator" "^7.24.6" - "@babel/plugin-transform-numeric-separator" "^7.24.6" - "@babel/plugin-transform-object-rest-spread" "^7.24.6" - "@babel/plugin-transform-object-super" "^7.24.6" - "@babel/plugin-transform-optional-catch-binding" "^7.24.6" - "@babel/plugin-transform-optional-chaining" "^7.24.6" - "@babel/plugin-transform-parameters" "^7.24.6" - "@babel/plugin-transform-private-methods" "^7.24.6" - "@babel/plugin-transform-private-property-in-object" "^7.24.6" - "@babel/plugin-transform-property-literals" "^7.24.6" - "@babel/plugin-transform-regenerator" "^7.24.6" - "@babel/plugin-transform-reserved-words" "^7.24.6" - "@babel/plugin-transform-shorthand-properties" "^7.24.6" - "@babel/plugin-transform-spread" "^7.24.6" - "@babel/plugin-transform-sticky-regex" "^7.24.6" - "@babel/plugin-transform-template-literals" "^7.24.6" - "@babel/plugin-transform-typeof-symbol" "^7.24.6" - "@babel/plugin-transform-unicode-escapes" "^7.24.6" - "@babel/plugin-transform-unicode-property-regex" "^7.24.6" - "@babel/plugin-transform-unicode-regex" "^7.24.6" - "@babel/plugin-transform-unicode-sets-regex" "^7.24.6" - "@babel/preset-modules" "0.1.6-no-external-plugins" - babel-plugin-polyfill-corejs2 "^0.4.10" - babel-plugin-polyfill-corejs3 "^0.10.4" - babel-plugin-polyfill-regenerator "^0.6.1" - core-js-compat "^3.31.0" - semver "^6.3.1" - -"@babel/preset-modules@0.1.6-no-external-plugins": - version "0.1.6-no-external-plugins" - resolved "https://registry.npmmirror.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz" - integrity sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/types" "^7.4.4" - esutils "^2.0.2" - -"@babel/regjsgen@^0.8.0": - version "0.8.0" - resolved "https://registry.npmmirror.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz" - integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== - -"@babel/runtime@^7.11.0", "@babel/runtime@^7.8.4": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.24.6.tgz" - integrity sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw== - dependencies: - regenerator-runtime "^0.14.0" - -"@babel/template@^7.23.9", "@babel/template@^7.24.6": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/template/-/template-7.24.6.tgz" - integrity sha512-3vgazJlLwNXi9jhrR1ef8qiB65L1RK90+lEQwv4OxveHnqC3BfmnHdgySwRLzf6akhlOYenT+b7AfWq+a//AHw== - dependencies: - "@babel/code-frame" "^7.24.6" - "@babel/parser" "^7.24.6" - "@babel/types" "^7.24.6" - -"@babel/traverse@^7.23.9", "@babel/traverse@^7.24.6", "@babel/traverse@^7.7.0": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.24.6.tgz" - integrity sha512-OsNjaJwT9Zn8ozxcfoBc+RaHdj3gFmCmYoQLUII1o6ZrUwku0BMg80FoOTPx+Gi6XhcQxAYE4xyjPTo4SxEQqw== - dependencies: - "@babel/code-frame" "^7.24.6" - "@babel/generator" "^7.24.6" - "@babel/helper-environment-visitor" "^7.24.6" - "@babel/helper-function-name" "^7.24.6" - "@babel/helper-hoist-variables" "^7.24.6" - "@babel/helper-split-export-declaration" "^7.24.6" - "@babel/parser" "^7.24.6" - "@babel/types" "^7.24.6" - debug "^4.3.1" - globals "^11.1.0" - -"@babel/types@^7.22.15", "@babel/types@^7.23.9", "@babel/types@^7.24.6", "@babel/types@^7.4.4", "@babel/types@^7.7.0": - version "7.24.6" - resolved "https://registry.npmmirror.com/@babel/types/-/types-7.24.6.tgz" - integrity sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ== - dependencies: - "@babel/helper-string-parser" "^7.24.6" - "@babel/helper-validator-identifier" "^7.24.6" - to-fast-properties "^2.0.0" - -"@hapi/address@2.x.x": - version "2.1.4" - resolved "https://registry.npmmirror.com/@hapi/address/-/address-2.1.4.tgz" - integrity sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ== - -"@hapi/bourne@1.x.x": - version "1.3.2" - resolved "https://registry.npmmirror.com/@hapi/bourne/-/bourne-1.3.2.tgz" - integrity sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA== - -"@hapi/hoek@8.x.x", "@hapi/hoek@^8.3.0": - version "8.5.1" - resolved "https://registry.npmmirror.com/@hapi/hoek/-/hoek-8.5.1.tgz" - integrity sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow== - -"@hapi/joi@^15.0.1": - version "15.1.1" - resolved "https://registry.npmmirror.com/@hapi/joi/-/joi-15.1.1.tgz" - integrity sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ== - dependencies: - "@hapi/address" "2.x.x" - "@hapi/bourne" "1.x.x" - "@hapi/hoek" "8.x.x" - "@hapi/topo" "3.x.x" - -"@hapi/topo@3.x.x": - version "3.1.6" - resolved "https://registry.npmmirror.com/@hapi/topo/-/topo-3.1.6.tgz" - integrity sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ== - dependencies: - "@hapi/hoek" "^8.3.0" - -"@intervolga/optimize-cssnano-plugin@^1.0.5": - version "1.0.6" - resolved "https://registry.npmmirror.com/@intervolga/optimize-cssnano-plugin/-/optimize-cssnano-plugin-1.0.6.tgz" - integrity sha512-zN69TnSr0viRSU6cEDIcuPcP67QcpQ6uHACg58FiN9PDrU6SLyGW3MR4tiISbYxy1kDWAVPwD+XwQTWE5cigAA== - dependencies: - cssnano "^4.0.0" - cssnano-preset-default "^4.0.0" - postcss "^7.0.0" - -"@jridgewell/gen-mapping@^0.3.5": - version "0.3.5" - resolved "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz" - integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== - dependencies: - "@jridgewell/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.2" - resolved "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/set-array@^1.2.1": - version "1.2.1" - resolved "https://registry.npmmirror.com/@jridgewell/set-array/-/set-array-1.2.1.tgz" - integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": - version "1.4.15" - resolved "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== - -"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": - version "0.3.25" - resolved "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@mrmlnc/readdir-enhanced@^2.2.1": - version "2.2.1" - resolved "https://registry.npmmirror.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz" - integrity sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g== - dependencies: - call-me-maybe "^1.0.1" - glob-to-regexp "^0.3.0" - -"@node-ipc/js-queue@2.0.3": - version "2.0.3" - resolved "https://registry.npmmirror.com/@node-ipc/js-queue/-/js-queue-2.0.3.tgz" - integrity sha512-fL1wpr8hhD5gT2dA1qifeVaoDFlQR5es8tFuKqjHX+kdOtdNHnxkVZbtIrR2rxnMFvehkjaZRNV2H/gPXlb0hw== - dependencies: - easy-stack "1.0.1" - -"@nodelib/fs.stat@^1.1.2": - version "1.1.3" - resolved "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz" - integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== - -"@soda/friendly-errors-webpack-plugin@^1.7.1": - version "1.8.1" - resolved "https://registry.npmmirror.com/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.8.1.tgz" - integrity sha512-h2ooWqP8XuFqTXT+NyAFbrArzfQA7R6HTezADrvD9Re8fxMLTPPniLdqVTdDaO0eIoLaAwKT+d6w+5GeTk7Vbg== - dependencies: - chalk "^3.0.0" - error-stack-parser "^2.0.6" - string-width "^4.2.3" - strip-ansi "^6.0.1" - -"@soda/get-current-script@^1.0.0": - version "1.0.2" - resolved "https://registry.npmmirror.com/@soda/get-current-script/-/get-current-script-1.0.2.tgz" - integrity sha512-T7VNNlYVM1SgQ+VsMYhnDkcGmWhQdL0bDyGm5TlQ3GBXnJscEClUUOKduWTmm2zCnvNLC1hc3JpuXjs/nFOc5w== - -"@types/body-parser@*": - version "1.19.5" - resolved "https://registry.npmmirror.com/@types/body-parser/-/body-parser-1.19.5.tgz" - integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/connect-history-api-fallback@*": - version "1.5.4" - resolved "https://registry.npmmirror.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz" - integrity sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw== - dependencies: - "@types/express-serve-static-core" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.38" - resolved "https://registry.npmmirror.com/@types/connect/-/connect-3.4.38.tgz" - integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== - dependencies: - "@types/node" "*" - -"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": - version "4.19.2" - resolved "https://registry.npmmirror.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.2.tgz" - integrity sha512-dPSEQElyVJ97BuGduAqQjpBocZWAs0GR94z+ptL7JXQJeJdHw2WBG3EWdFrK36b8Q6j8P4cXOMhgUoi0IIfIsg== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - "@types/send" "*" - -"@types/express@*": - version "4.17.21" - resolved "https://registry.npmmirror.com/@types/express/-/express-4.17.21.tgz" - integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.33" - "@types/qs" "*" - "@types/serve-static" "*" - -"@types/glob@^7.1.1": - version "7.2.0" - resolved "https://registry.npmmirror.com/@types/glob/-/glob-7.2.0.tgz" - integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== - dependencies: - "@types/minimatch" "*" - "@types/node" "*" - -"@types/http-errors@*": - version "2.0.4" - resolved "https://registry.npmmirror.com/@types/http-errors/-/http-errors-2.0.4.tgz" - integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== - -"@types/http-proxy@^1.17.5": - version "1.17.14" - resolved "https://registry.npmmirror.com/@types/http-proxy/-/http-proxy-1.17.14.tgz" - integrity sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w== - dependencies: - "@types/node" "*" - -"@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8": - version "7.0.15" - resolved "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz" - integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== - -"@types/mime@^1": - version "1.3.5" - resolved "https://registry.npmmirror.com/@types/mime/-/mime-1.3.5.tgz" - integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== - -"@types/minimatch@*": - version "5.1.2" - resolved "https://registry.npmmirror.com/@types/minimatch/-/minimatch-5.1.2.tgz" - integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== - -"@types/minimist@^1.2.0": - version "1.2.5" - resolved "https://registry.npmmirror.com/@types/minimist/-/minimist-1.2.5.tgz" - integrity sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag== - -"@types/node@*": - version "20.12.13" - resolved "https://registry.npmmirror.com/@types/node/-/node-20.12.13.tgz" - integrity sha512-gBGeanV41c1L171rR7wjbMiEpEI/l5XFQdLLfhr/REwpgDy/4U8y89+i8kRiLzDyZdOkXh+cRaTetUnCYutoXA== - dependencies: - undici-types "~5.26.4" - -"@types/normalize-package-data@^2.4.0": - version "2.4.4" - resolved "https://registry.npmmirror.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz" - integrity sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA== - -"@types/q@^1.5.1": - version "1.5.8" - resolved "https://registry.npmmirror.com/@types/q/-/q-1.5.8.tgz" - integrity sha512-hroOstUScF6zhIi+5+x0dzqrHA1EJi+Irri6b1fxolMTqqHIV/Cg77EtnQcZqZCu8hR3mX2BzIxN4/GzI68Kfw== - -"@types/qs@*": - version "6.9.15" - resolved "https://registry.npmmirror.com/@types/qs/-/qs-6.9.15.tgz" - integrity sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg== - -"@types/range-parser@*": - version "1.2.7" - resolved "https://registry.npmmirror.com/@types/range-parser/-/range-parser-1.2.7.tgz" - integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== - -"@types/send@*": - version "0.17.4" - resolved "https://registry.npmmirror.com/@types/send/-/send-0.17.4.tgz" - integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== - dependencies: - "@types/mime" "^1" - "@types/node" "*" - -"@types/serve-static@*": - version "1.15.7" - resolved "https://registry.npmmirror.com/@types/serve-static/-/serve-static-1.15.7.tgz" - integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw== - dependencies: - "@types/http-errors" "*" - "@types/node" "*" - "@types/send" "*" - -"@types/source-list-map@*": - version "0.1.6" - resolved "https://registry.npmmirror.com/@types/source-list-map/-/source-list-map-0.1.6.tgz" - integrity sha512-5JcVt1u5HDmlXkwOD2nslZVllBBc7HDuOICfiZah2Z0is8M8g+ddAEawbmd3VjedfDHBzxCaXLs07QEmb7y54g== - -"@types/tapable@^1": - version "1.0.12" - resolved "https://registry.npmmirror.com/@types/tapable/-/tapable-1.0.12.tgz" - integrity sha512-bTHG8fcxEqv1M9+TD14P8ok8hjxoOCkfKc8XXLaaD05kI7ohpeI956jtDOD3XHKBQrlyPughUtzm1jtVhHpA5Q== - -"@types/uglify-js@*": - version "3.17.5" - resolved "https://registry.npmmirror.com/@types/uglify-js/-/uglify-js-3.17.5.tgz" - integrity sha512-TU+fZFBTBcXj/GpDpDaBmgWk/gn96kMZ+uocaFUlV2f8a6WdMzzI44QBCmGcCiYR0Y6ZlNRiyUyKKt5nl/lbzQ== - dependencies: - source-map "^0.6.1" - -"@types/webpack-dev-server@^3.11.0": - version "3.11.6" - resolved "https://registry.npmmirror.com/@types/webpack-dev-server/-/webpack-dev-server-3.11.6.tgz" - integrity sha512-XCph0RiiqFGetukCTC3KVnY1jwLcZ84illFRMbyFzCcWl90B/76ew0tSqF46oBhnLC4obNDG7dMO0JfTN0MgMQ== - dependencies: - "@types/connect-history-api-fallback" "*" - "@types/express" "*" - "@types/serve-static" "*" - "@types/webpack" "^4" - http-proxy-middleware "^1.0.0" - -"@types/webpack-sources@*": - version "3.2.3" - resolved "https://registry.npmmirror.com/@types/webpack-sources/-/webpack-sources-3.2.3.tgz" - integrity sha512-4nZOdMwSPHZ4pTEZzSp0AsTM4K7Qmu40UKW4tJDiOVs20UzYF9l+qUe4s0ftfN0pin06n+5cWWDJXH+sbhAiDw== - dependencies: - "@types/node" "*" - "@types/source-list-map" "*" - source-map "^0.7.3" - -"@types/webpack@^4", "@types/webpack@^4.0.0": - version "4.41.38" - resolved "https://registry.npmmirror.com/@types/webpack/-/webpack-4.41.38.tgz" - integrity sha512-oOW7E931XJU1mVfCnxCVgv8GLFL768pDO5u2Gzk82i8yTIgX6i7cntyZOkZYb/JtYM8252SN9bQp9tgkVDSsRw== - dependencies: - "@types/node" "*" - "@types/tapable" "^1" - "@types/uglify-js" "*" - "@types/webpack-sources" "*" - anymatch "^3.0.0" - source-map "^0.6.0" - -"@vue/babel-helper-vue-jsx-merge-props@^1.4.0": - version "1.4.0" - resolved "https://registry.npmmirror.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.4.0.tgz" - integrity sha512-JkqXfCkUDp4PIlFdDQ0TdXoIejMtTHP67/pvxlgeY+u5k3LEdKuWZ3LK6xkxo52uDoABIVyRwqVkfLQJhk7VBA== - -"@vue/babel-helper-vue-transform-on@1.2.2": - version "1.2.2" - resolved "https://registry.npmmirror.com/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.2.2.tgz" - integrity sha512-nOttamHUR3YzdEqdM/XXDyCSdxMA9VizUKoroLX6yTyRtggzQMHXcmwh8a7ZErcJttIBIc9s68a1B8GZ+Dmvsw== - -"@vue/babel-plugin-jsx@^1.0.3": - version "1.2.2" - resolved "https://registry.npmmirror.com/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.2.2.tgz" - integrity sha512-nYTkZUVTu4nhP199UoORePsql0l+wj7v/oyQjtThUVhJl1U+6qHuoVhIvR3bf7eVKjbCK+Cs2AWd7mi9Mpz9rA== - dependencies: - "@babel/helper-module-imports" "~7.22.15" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-jsx" "^7.23.3" - "@babel/template" "^7.23.9" - "@babel/traverse" "^7.23.9" - "@babel/types" "^7.23.9" - "@vue/babel-helper-vue-transform-on" "1.2.2" - "@vue/babel-plugin-resolve-type" "1.2.2" - camelcase "^6.3.0" - html-tags "^3.3.1" - svg-tags "^1.0.0" - -"@vue/babel-plugin-resolve-type@1.2.2": - version "1.2.2" - resolved "https://registry.npmmirror.com/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.2.2.tgz" - integrity sha512-EntyroPwNg5IPVdUJupqs0CFzuf6lUrVvCspmv2J1FITLeGnUCuoGNNk78dgCusxEiYj6RMkTJflGSxk5aIC4A== - dependencies: - "@babel/code-frame" "^7.23.5" - "@babel/helper-module-imports" "~7.22.15" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/parser" "^7.23.9" - "@vue/compiler-sfc" "^3.4.15" - -"@vue/babel-plugin-transform-vue-jsx@^1.4.0": - version "1.4.0" - resolved "https://registry.npmmirror.com/@vue/babel-plugin-transform-vue-jsx/-/babel-plugin-transform-vue-jsx-1.4.0.tgz" - integrity sha512-Fmastxw4MMx0vlgLS4XBX0XiBbUFzoMGeVXuMV08wyOfXdikAFqBTuYPR0tlk+XskL19EzHc39SgjrPGY23JnA== - dependencies: - "@babel/helper-module-imports" "^7.0.0" - "@babel/plugin-syntax-jsx" "^7.2.0" - "@vue/babel-helper-vue-jsx-merge-props" "^1.4.0" - html-tags "^2.0.0" - lodash.kebabcase "^4.1.1" - svg-tags "^1.0.0" - -"@vue/babel-preset-app@^4.5.19": - version "4.5.19" - resolved "https://registry.npmmirror.com/@vue/babel-preset-app/-/babel-preset-app-4.5.19.tgz" - integrity sha512-VCNRiAt2P/bLo09rYt3DLe6xXUMlhJwrvU18Ddd/lYJgC7s8+wvhgYs+MTx4OiAXdu58drGwSBO9SPx7C6J82Q== - dependencies: - "@babel/core" "^7.11.0" - "@babel/helper-compilation-targets" "^7.9.6" - "@babel/helper-module-imports" "^7.8.3" - "@babel/plugin-proposal-class-properties" "^7.8.3" - "@babel/plugin-proposal-decorators" "^7.8.3" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-syntax-jsx" "^7.8.3" - "@babel/plugin-transform-runtime" "^7.11.0" - "@babel/preset-env" "^7.11.0" - "@babel/runtime" "^7.11.0" - "@vue/babel-plugin-jsx" "^1.0.3" - "@vue/babel-preset-jsx" "^1.2.4" - babel-plugin-dynamic-import-node "^2.3.3" - core-js "^3.6.5" - core-js-compat "^3.6.5" - semver "^6.1.0" - -"@vue/babel-preset-jsx@^1.2.4": - version "1.4.0" - resolved "https://registry.npmmirror.com/@vue/babel-preset-jsx/-/babel-preset-jsx-1.4.0.tgz" - integrity sha512-QmfRpssBOPZWL5xw7fOuHNifCQcNQC1PrOo/4fu6xlhlKJJKSA3HqX92Nvgyx8fqHZTUGMPHmFA+IDqwXlqkSA== - dependencies: - "@vue/babel-helper-vue-jsx-merge-props" "^1.4.0" - "@vue/babel-plugin-transform-vue-jsx" "^1.4.0" - "@vue/babel-sugar-composition-api-inject-h" "^1.4.0" - "@vue/babel-sugar-composition-api-render-instance" "^1.4.0" - "@vue/babel-sugar-functional-vue" "^1.4.0" - "@vue/babel-sugar-inject-h" "^1.4.0" - "@vue/babel-sugar-v-model" "^1.4.0" - "@vue/babel-sugar-v-on" "^1.4.0" - -"@vue/babel-sugar-composition-api-inject-h@^1.4.0": - version "1.4.0" - resolved "https://registry.npmmirror.com/@vue/babel-sugar-composition-api-inject-h/-/babel-sugar-composition-api-inject-h-1.4.0.tgz" - integrity sha512-VQq6zEddJHctnG4w3TfmlVp5FzDavUSut/DwR0xVoe/mJKXyMcsIibL42wPntozITEoY90aBV0/1d2KjxHU52g== - dependencies: - "@babel/plugin-syntax-jsx" "^7.2.0" - -"@vue/babel-sugar-composition-api-render-instance@^1.4.0": - version "1.4.0" - resolved "https://registry.npmmirror.com/@vue/babel-sugar-composition-api-render-instance/-/babel-sugar-composition-api-render-instance-1.4.0.tgz" - integrity sha512-6ZDAzcxvy7VcnCjNdHJ59mwK02ZFuP5CnucloidqlZwVQv5CQLijc3lGpR7MD3TWFi78J7+a8J56YxbCtHgT9Q== - dependencies: - "@babel/plugin-syntax-jsx" "^7.2.0" - -"@vue/babel-sugar-functional-vue@^1.4.0": - version "1.4.0" - resolved "https://registry.npmmirror.com/@vue/babel-sugar-functional-vue/-/babel-sugar-functional-vue-1.4.0.tgz" - integrity sha512-lTEB4WUFNzYt2In6JsoF9sAYVTo84wC4e+PoZWSgM6FUtqRJz7wMylaEhSRgG71YF+wfLD6cc9nqVeXN2rwBvw== - dependencies: - "@babel/plugin-syntax-jsx" "^7.2.0" - -"@vue/babel-sugar-inject-h@^1.4.0": - version "1.4.0" - resolved "https://registry.npmmirror.com/@vue/babel-sugar-inject-h/-/babel-sugar-inject-h-1.4.0.tgz" - integrity sha512-muwWrPKli77uO2fFM7eA3G1lAGnERuSz2NgAxuOLzrsTlQl8W4G+wwbM4nB6iewlKbwKRae3nL03UaF5ffAPMA== - dependencies: - "@babel/plugin-syntax-jsx" "^7.2.0" - -"@vue/babel-sugar-v-model@^1.4.0": - version "1.4.0" - resolved "https://registry.npmmirror.com/@vue/babel-sugar-v-model/-/babel-sugar-v-model-1.4.0.tgz" - integrity sha512-0t4HGgXb7WHYLBciZzN5s0Hzqan4Ue+p/3FdQdcaHAb7s5D9WZFGoSxEZHrR1TFVZlAPu1bejTKGeAzaaG3NCQ== - dependencies: - "@babel/plugin-syntax-jsx" "^7.2.0" - "@vue/babel-helper-vue-jsx-merge-props" "^1.4.0" - "@vue/babel-plugin-transform-vue-jsx" "^1.4.0" - camelcase "^5.0.0" - html-tags "^2.0.0" - svg-tags "^1.0.0" - -"@vue/babel-sugar-v-on@^1.4.0": - version "1.4.0" - resolved "https://registry.npmmirror.com/@vue/babel-sugar-v-on/-/babel-sugar-v-on-1.4.0.tgz" - integrity sha512-m+zud4wKLzSKgQrWwhqRObWzmTuyzl6vOP7024lrpeJM4x2UhQtRDLgYjXAw9xBXjCwS0pP9kXjg91F9ZNo9JA== - dependencies: - "@babel/plugin-syntax-jsx" "^7.2.0" - "@vue/babel-plugin-transform-vue-jsx" "^1.4.0" - camelcase "^5.0.0" - -"@vue/cli-overlay@^4.5.19": - version "4.5.19" - resolved "https://registry.npmmirror.com/@vue/cli-overlay/-/cli-overlay-4.5.19.tgz" - integrity sha512-GdxvNSmOw7NHIazCO8gTK+xZbaOmScTtxj6eHVeMbYpDYVPJ+th3VMLWNpw/b6uOjwzzcyKlA5dRQ1DAb+gF/g== - -"@vue/cli-plugin-babel@~4.5.14": - version "4.5.19" - resolved "https://registry.npmmirror.com/@vue/cli-plugin-babel/-/cli-plugin-babel-4.5.19.tgz" - integrity sha512-8ebXzaMW9KNTMAN6+DzkhFsjty1ieqT7hIW5Lbk4v30Qhfjkms7lBWyXPGkoq+wAikXFa1Gnam2xmWOBqDDvWg== - dependencies: - "@babel/core" "^7.11.0" - "@vue/babel-preset-app" "^4.5.19" - "@vue/cli-shared-utils" "^4.5.19" - babel-loader "^8.1.0" - cache-loader "^4.1.0" - thread-loader "^2.1.3" - webpack "^4.0.0" - -"@vue/cli-plugin-eslint@~4.5.14": - version "4.5.19" - resolved "https://registry.npmmirror.com/@vue/cli-plugin-eslint/-/cli-plugin-eslint-4.5.19.tgz" - integrity sha512-53sa4Pu9j5KajesFlj494CcO8vVo3e3nnZ1CCKjGGnrF90id1rUeepcFfz5XjwfEtbJZp2x/NoX/EZE6zCzSFQ== - dependencies: - "@vue/cli-shared-utils" "^4.5.19" - eslint-loader "^2.2.1" - globby "^9.2.0" - inquirer "^7.1.0" - webpack "^4.0.0" - yorkie "^2.0.0" - -"@vue/cli-plugin-router@^4.5.19": - version "4.5.19" - resolved "https://registry.npmmirror.com/@vue/cli-plugin-router/-/cli-plugin-router-4.5.19.tgz" - integrity sha512-3icGzH1IbVYmMMsOwYa0lal/gtvZLebFXdE5hcQJo2mnTwngXGMTyYAzL56EgHBPjbMmRpyj6Iw9k4aVInVX6A== - dependencies: - "@vue/cli-shared-utils" "^4.5.19" - -"@vue/cli-plugin-vuex@^4.5.19": - version "4.5.19" - resolved "https://registry.npmmirror.com/@vue/cli-plugin-vuex/-/cli-plugin-vuex-4.5.19.tgz" - integrity sha512-DUmfdkG3pCdkP7Iznd87RfE9Qm42mgp2hcrNcYQYSru1W1gX2dG/JcW8bxmeGSa06lsxi9LEIc/QD1yPajSCZw== - -"@vue/cli-service@~4.5.14": - version "4.5.19" - resolved "https://registry.npmmirror.com/@vue/cli-service/-/cli-service-4.5.19.tgz" - integrity sha512-+Wpvj8fMTCt9ZPOLu5YaLkFCQmB4MrZ26aRmhhKiCQ/4PMoL6mLezfqdt6c+m2htM+1WV5RunRo+0WHl2DfwZA== - dependencies: - "@intervolga/optimize-cssnano-plugin" "^1.0.5" - "@soda/friendly-errors-webpack-plugin" "^1.7.1" - "@soda/get-current-script" "^1.0.0" - "@types/minimist" "^1.2.0" - "@types/webpack" "^4.0.0" - "@types/webpack-dev-server" "^3.11.0" - "@vue/cli-overlay" "^4.5.19" - "@vue/cli-plugin-router" "^4.5.19" - "@vue/cli-plugin-vuex" "^4.5.19" - "@vue/cli-shared-utils" "^4.5.19" - "@vue/component-compiler-utils" "^3.1.2" - "@vue/preload-webpack-plugin" "^1.1.0" - "@vue/web-component-wrapper" "^1.2.0" - acorn "^7.4.0" - acorn-walk "^7.1.1" - address "^1.1.2" - autoprefixer "^9.8.6" - browserslist "^4.12.0" - cache-loader "^4.1.0" - case-sensitive-paths-webpack-plugin "^2.3.0" - cli-highlight "^2.1.4" - clipboardy "^2.3.0" - cliui "^6.0.0" - copy-webpack-plugin "^5.1.1" - css-loader "^3.5.3" - cssnano "^4.1.10" - debug "^4.1.1" - default-gateway "^5.0.5" - dotenv "^8.2.0" - dotenv-expand "^5.1.0" - file-loader "^4.2.0" - fs-extra "^7.0.1" - globby "^9.2.0" - hash-sum "^2.0.0" - html-webpack-plugin "^3.2.0" - launch-editor-middleware "^2.2.1" - lodash.defaultsdeep "^4.6.1" - lodash.mapvalues "^4.6.0" - lodash.transform "^4.6.0" - mini-css-extract-plugin "^0.9.0" - minimist "^1.2.5" - pnp-webpack-plugin "^1.6.4" - portfinder "^1.0.26" - postcss-loader "^3.0.0" - ssri "^8.0.1" - terser-webpack-plugin "^1.4.4" - thread-loader "^2.1.3" - url-loader "^2.2.0" - vue-loader "^15.9.2" - vue-style-loader "^4.1.2" - webpack "^4.0.0" - webpack-bundle-analyzer "^3.8.0" - webpack-chain "^6.4.0" - webpack-dev-server "^3.11.0" - webpack-merge "^4.2.2" - optionalDependencies: - vue-loader-v16 "npm:vue-loader@^16.1.0" - -"@vue/cli-shared-utils@^4.5.19": - version "4.5.19" - resolved "https://registry.npmmirror.com/@vue/cli-shared-utils/-/cli-shared-utils-4.5.19.tgz" - integrity sha512-JYpdsrC/d9elerKxbEUtmSSU6QRM60rirVubOewECHkBHj+tLNznWq/EhCjswywtePyLaMUK25eTqnTSZlEE+g== - dependencies: - "@achrinza/node-ipc" "9.2.2" - "@hapi/joi" "^15.0.1" - chalk "^2.4.2" - execa "^1.0.0" - launch-editor "^2.2.1" - lru-cache "^5.1.1" - open "^6.3.0" - ora "^3.4.0" - read-pkg "^5.1.1" - request "^2.88.2" - semver "^6.1.0" - strip-ansi "^6.0.0" - -"@vue/compiler-core@3.4.27": - version "3.4.27" - resolved "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.4.27.tgz" - integrity sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg== - dependencies: - "@babel/parser" "^7.24.4" - "@vue/shared" "3.4.27" - entities "^4.5.0" - estree-walker "^2.0.2" - source-map-js "^1.2.0" - -"@vue/compiler-dom@3.4.27": - version "3.4.27" - resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.4.27.tgz" - integrity sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw== - dependencies: - "@vue/compiler-core" "3.4.27" - "@vue/shared" "3.4.27" - -"@vue/compiler-sfc@2.7.16": - version "2.7.16" - resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-2.7.16.tgz" - integrity sha512-KWhJ9k5nXuNtygPU7+t1rX6baZeqOYLEforUPjgNDBnLicfHCoi48H87Q8XyLZOrNNsmhuwKqtpDQWjEFe6Ekg== - dependencies: - "@babel/parser" "^7.23.5" - postcss "^8.4.14" - source-map "^0.6.1" - optionalDependencies: - prettier "^1.18.2 || ^2.0.0" - -"@vue/compiler-sfc@^3.4.15": - version "3.4.27" - resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.4.27.tgz" - integrity sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA== - dependencies: - "@babel/parser" "^7.24.4" - "@vue/compiler-core" "3.4.27" - "@vue/compiler-dom" "3.4.27" - "@vue/compiler-ssr" "3.4.27" - "@vue/shared" "3.4.27" - estree-walker "^2.0.2" - magic-string "^0.30.10" - postcss "^8.4.38" - source-map-js "^1.2.0" - -"@vue/compiler-ssr@3.4.27": - version "3.4.27" - resolved "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.4.27.tgz" - integrity sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw== - dependencies: - "@vue/compiler-dom" "3.4.27" - "@vue/shared" "3.4.27" - -"@vue/component-compiler-utils@^3.1.0", "@vue/component-compiler-utils@^3.1.2": - version "3.3.0" - resolved "https://registry.npmmirror.com/@vue/component-compiler-utils/-/component-compiler-utils-3.3.0.tgz" - integrity sha512-97sfH2mYNU+2PzGrmK2haqffDpVASuib9/w2/noxiFi31Z54hW+q3izKQXXQZSNhtiUpAI36uSuYepeBe4wpHQ== - dependencies: - consolidate "^0.15.1" - hash-sum "^1.0.2" - lru-cache "^4.1.2" - merge-source-map "^1.1.0" - postcss "^7.0.36" - postcss-selector-parser "^6.0.2" - source-map "~0.6.1" - vue-template-es2015-compiler "^1.9.0" - optionalDependencies: - prettier "^1.18.2 || ^2.0.0" - -"@vue/preload-webpack-plugin@^1.1.0": - version "1.1.2" - resolved "https://registry.npmmirror.com/@vue/preload-webpack-plugin/-/preload-webpack-plugin-1.1.2.tgz" - integrity sha512-LIZMuJk38pk9U9Ur4YzHjlIyMuxPlACdBIHH9/nGYVTsaGKOSnSuELiE8vS9wa+dJpIYspYUOqk+L1Q4pgHQHQ== - -"@vue/shared@3.4.27": - version "3.4.27" - resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.4.27.tgz" - integrity sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA== - -"@vue/web-component-wrapper@^1.2.0": - version "1.3.0" - resolved "https://registry.npmmirror.com/@vue/web-component-wrapper/-/web-component-wrapper-1.3.0.tgz" - integrity sha512-Iu8Tbg3f+emIIMmI2ycSI8QcEuAUgPTgHwesDU1eKMLE4YC/c/sFbGc70QgMq31ijRftV0R7vCm9co6rldCeOA== - -"@webassemblyjs/ast@1.9.0": - version "1.9.0" - resolved "https://registry.npmmirror.com/@webassemblyjs/ast/-/ast-1.9.0.tgz" - integrity sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA== - dependencies: - "@webassemblyjs/helper-module-context" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/wast-parser" "1.9.0" - -"@webassemblyjs/floating-point-hex-parser@1.9.0": - version "1.9.0" - resolved "https://registry.npmmirror.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz" - integrity sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA== - -"@webassemblyjs/helper-api-error@1.9.0": - version "1.9.0" - resolved "https://registry.npmmirror.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz" - integrity sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw== - -"@webassemblyjs/helper-buffer@1.9.0": - version "1.9.0" - resolved "https://registry.npmmirror.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz" - integrity sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA== - -"@webassemblyjs/helper-code-frame@1.9.0": - version "1.9.0" - resolved "https://registry.npmmirror.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz" - integrity sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA== - dependencies: - "@webassemblyjs/wast-printer" "1.9.0" - -"@webassemblyjs/helper-fsm@1.9.0": - version "1.9.0" - resolved "https://registry.npmmirror.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz" - integrity sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw== - -"@webassemblyjs/helper-module-context@1.9.0": - version "1.9.0" - resolved "https://registry.npmmirror.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz" - integrity sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g== - dependencies: - "@webassemblyjs/ast" "1.9.0" - -"@webassemblyjs/helper-wasm-bytecode@1.9.0": - version "1.9.0" - resolved "https://registry.npmmirror.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz" - integrity sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw== - -"@webassemblyjs/helper-wasm-section@1.9.0": - version "1.9.0" - resolved "https://registry.npmmirror.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz" - integrity sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - -"@webassemblyjs/ieee754@1.9.0": - version "1.9.0" - resolved "https://registry.npmmirror.com/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz" - integrity sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg== - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/leb128@1.9.0": - version "1.9.0" - resolved "https://registry.npmmirror.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz" - integrity sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw== - dependencies: - "@xtuc/long" "4.2.2" - -"@webassemblyjs/utf8@1.9.0": - version "1.9.0" - resolved "https://registry.npmmirror.com/@webassemblyjs/utf8/-/utf8-1.9.0.tgz" - integrity sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w== - -"@webassemblyjs/wasm-edit@1.9.0": - version "1.9.0" - resolved "https://registry.npmmirror.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz" - integrity sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/helper-wasm-section" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - "@webassemblyjs/wasm-opt" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - "@webassemblyjs/wast-printer" "1.9.0" - -"@webassemblyjs/wasm-gen@1.9.0": - version "1.9.0" - resolved "https://registry.npmmirror.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz" - integrity sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/ieee754" "1.9.0" - "@webassemblyjs/leb128" "1.9.0" - "@webassemblyjs/utf8" "1.9.0" - -"@webassemblyjs/wasm-opt@1.9.0": - version "1.9.0" - resolved "https://registry.npmmirror.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz" - integrity sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - -"@webassemblyjs/wasm-parser@1.9.0": - version "1.9.0" - resolved "https://registry.npmmirror.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz" - integrity sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-api-error" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/ieee754" "1.9.0" - "@webassemblyjs/leb128" "1.9.0" - "@webassemblyjs/utf8" "1.9.0" - -"@webassemblyjs/wast-parser@1.9.0": - version "1.9.0" - resolved "https://registry.npmmirror.com/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz" - integrity sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/floating-point-hex-parser" "1.9.0" - "@webassemblyjs/helper-api-error" "1.9.0" - "@webassemblyjs/helper-code-frame" "1.9.0" - "@webassemblyjs/helper-fsm" "1.9.0" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/wast-printer@1.9.0": - version "1.9.0" - resolved "https://registry.npmmirror.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz" - integrity sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/wast-parser" "1.9.0" - "@xtuc/long" "4.2.2" - -"@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.npmmirror.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz" - integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== - -"@xtuc/long@4.2.2": - version "4.2.2" - resolved "https://registry.npmmirror.com/@xtuc/long/-/long-4.2.2.tgz" - integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== - -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - -acorn-jsx@^5.2.0: - version "5.3.2" - resolved "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - -acorn-walk@^7.1.1: - version "7.2.0" - resolved "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-7.2.0.tgz" - integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== - -acorn@^6.4.1: - version "6.4.2" - resolved "https://registry.npmmirror.com/acorn/-/acorn-6.4.2.tgz" - integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== - -acorn@^7.1.1, acorn@^7.4.0: - version "7.4.1" - resolved "https://registry.npmmirror.com/acorn/-/acorn-7.4.1.tgz" - integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== - -address@^1.1.2: - version "1.2.2" - resolved "https://registry.npmmirror.com/address/-/address-1.2.2.tgz" - integrity sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA== - -ag-charts-types@10.1.0: - version "10.1.0" - resolved "https://registry.npmjs.org/ag-charts-types/-/ag-charts-types-10.1.0.tgz" - integrity sha512-pk9ft8hbgTXJ/thI/SEUR1BoauNplYExpcHh7tMOqVikoDsta1O15TB1ZL4XWnl4TPIzROBmONKsz7d8a2HBuQ== - -ag-grid-community@31.3.4: - version "31.3.4" - resolved "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-31.3.4.tgz" - integrity sha512-jOxQO86C6eLnk1GdP24HB6aqaouFzMWizgfUwNY5MnetiWzz9ZaAmOGSnW/XBvdjXvC5Fpk3gSbvVKKQ7h9kBw== - -ag-grid-community@^32.1.0: - version "32.1.0" - resolved "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-32.1.0.tgz" - integrity sha512-RVvkjRH61nuCXwIqTKQPqNbKR+8cGBKw7S1qmmMXsy0pCBAJaQn4kL3v31hKHxDtV4bPscBXLFKGnKzHuss0GQ== - dependencies: - ag-charts-types "10.1.0" - -ag-grid-vue@^31.3.4: - version "31.3.4" - resolved "https://registry.npmjs.org/ag-grid-vue/-/ag-grid-vue-31.3.4.tgz" - integrity sha512-fB+PqaYuyyis1DzGnIeCPZvlc19NfIwA+C/9kmLzVCwUW6Iovp0MT5J5sD3S+kEVKsztWzgVyKQ7DxhMsEGPtg== - dependencies: - ag-grid-community "31.3.4" - -ajv-errors@^1.0.0: - version "1.0.1" - resolved "https://registry.npmmirror.com/ajv-errors/-/ajv-errors-1.0.1.tgz" - integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== - -ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: - version "3.5.2" - resolved "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== - -ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: - version "6.12.6" - resolved "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -alphanum-sort@^1.0.0: - version "1.0.2" - resolved "https://registry.npmmirror.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz" - integrity sha512-0FcBfdcmaumGPQ0qPn7Q5qTgz/ooXgIyp1rf8ik5bGX8mpE2YHjC0P/eyQvxu1GURYQgq9ozf2mteQ5ZD9YiyQ== - -ansi-colors@^3.0.0: - version "3.2.4" - resolved "https://registry.npmmirror.com/ansi-colors/-/ansi-colors-3.2.4.tgz" - integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== - -ansi-escapes@^4.2.1: - version "4.3.2" - resolved "https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== - dependencies: - type-fest "^0.21.3" - -ansi-html-community@0.0.8: - version "0.0.8" - resolved "https://registry.npmmirror.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz" - integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-2.1.1.tgz" - integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== - -ansi-regex@^4.1.0: - version "4.1.1" - resolved "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-4.1.1.tgz" - integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^3.2.0, ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -any-promise@^1.0.0: - version "1.3.0" - resolved "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz" - integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== - -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/anymatch/-/anymatch-2.0.0.tgz" - integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== - dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" - -anymatch@^3.0.0, anymatch@~3.1.2: - version "3.1.3" - resolved "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -aproba@^1.1.1: - version "1.2.0" - resolved "https://registry.npmmirror.com/aproba/-/aproba-1.2.0.tgz" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== - -arch@^2.1.1: - version "2.2.0" - resolved "https://registry.npmmirror.com/arch/-/arch-2.2.0.tgz" - integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/arr-diff/-/arr-diff-4.0.0.tgz" - integrity sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA== - -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/arr-flatten/-/arr-flatten-1.1.0.tgz" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.npmmirror.com/arr-union/-/arr-union-3.1.0.tgz" - integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q== - -array-buffer-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz" - integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== - dependencies: - call-bind "^1.0.5" - is-array-buffer "^3.0.4" - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz" - integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== - -array-flatten@^2.1.0: - version "2.1.2" - resolved "https://registry.npmmirror.com/array-flatten/-/array-flatten-2.1.2.tgz" - integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== - -array-union@^1.0.1, array-union@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/array-union/-/array-union-1.0.2.tgz" - integrity sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng== - dependencies: - array-uniq "^1.0.1" - -array-uniq@^1.0.1: - version "1.0.3" - resolved "https://registry.npmmirror.com/array-uniq/-/array-uniq-1.0.3.tgz" - integrity sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q== - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.npmmirror.com/array-unique/-/array-unique-0.3.2.tgz" - integrity sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ== - -array.prototype.reduce@^1.0.6: - version "1.0.7" - resolved "https://registry.npmmirror.com/array.prototype.reduce/-/array.prototype.reduce-1.0.7.tgz" - integrity sha512-mzmiUCVwtiD4lgxYP8g7IYy8El8p2CSMePvIbTS7gchKir/L1fgJrk0yDKmAX6mnRQFKNADYIk8nNlTris5H1Q== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - es-array-method-boxes-properly "^1.0.0" - es-errors "^1.3.0" - es-object-atoms "^1.0.0" - is-string "^1.0.7" - -arraybuffer.prototype.slice@^1.0.3: - version "1.0.3" - resolved "https://registry.npmmirror.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz" - integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A== - dependencies: - array-buffer-byte-length "^1.0.1" - call-bind "^1.0.5" - define-properties "^1.2.1" - es-abstract "^1.22.3" - es-errors "^1.2.1" - get-intrinsic "^1.2.3" - is-array-buffer "^3.0.4" - is-shared-array-buffer "^1.0.2" - -asn1.js@^4.10.1: - version "4.10.1" - resolved "https://registry.npmmirror.com/asn1.js/-/asn1.js-4.10.1.tgz" - integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw== - dependencies: - bn.js "^4.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -asn1@~0.2.3: - version "0.2.6" - resolved "https://registry.npmmirror.com/asn1/-/asn1-0.2.6.tgz" - integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== - dependencies: - safer-buffer "~2.1.0" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/assert-plus/-/assert-plus-1.0.0.tgz" - integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== - -assert@^1.1.1: - version "1.5.1" - resolved "https://registry.npmmirror.com/assert/-/assert-1.5.1.tgz" - integrity sha512-zzw1uCAgLbsKwBfFc8CX78DDg+xZeBksSO3vwVIDDN5i94eOrPsSSyiVhmsSABFDM/OcpE2aagCat9dnWQLG1A== - dependencies: - object.assign "^4.1.4" - util "^0.10.4" - -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/assign-symbols/-/assign-symbols-1.0.0.tgz" - integrity sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw== - -astral-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/astral-regex/-/astral-regex-1.0.0.tgz" - integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== - -async-each@^1.0.1: - version "1.0.6" - resolved "https://registry.npmmirror.com/async-each/-/async-each-1.0.6.tgz" - integrity sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg== - -async-limiter@~1.0.0: - version "1.0.1" - resolved "https://registry.npmmirror.com/async-limiter/-/async-limiter-1.0.1.tgz" - integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== - -async-validator@~1.8.1: - version "1.8.5" - resolved "https://registry.npmmirror.com/async-validator/-/async-validator-1.8.5.tgz" - integrity sha512-tXBM+1m056MAX0E8TL2iCjg8WvSyXu0Zc8LNtYqrVeyoL3+esHRZ4SieE9fKQyyU09uONjnMEjrNBMqT0mbvmA== - dependencies: - babel-runtime "6.x" - -async@^2.6.4: - version "2.6.4" - resolved "https://registry.npmmirror.com/async/-/async-2.6.4.tgz" - integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== - dependencies: - lodash "^4.17.14" - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - -atob@^2.1.2: - version "2.1.2" - resolved "https://registry.npmmirror.com/atob/-/atob-2.1.2.tgz" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - -autoprefixer@^9.8.6: - version "9.8.8" - resolved "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-9.8.8.tgz" - integrity sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA== - dependencies: - browserslist "^4.12.0" - caniuse-lite "^1.0.30001109" - normalize-range "^0.1.2" - num2fraction "^1.2.2" - picocolors "^0.2.1" - postcss "^7.0.32" - postcss-value-parser "^4.1.0" - -available-typed-arrays@^1.0.7: - version "1.0.7" - resolved "https://registry.npmmirror.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz" - integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== - dependencies: - possible-typed-array-names "^1.0.0" - -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.npmmirror.com/aws-sign2/-/aws-sign2-0.7.0.tgz" - integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== - -aws4@^1.8.0: - version "1.13.0" - resolved "https://registry.npmmirror.com/aws4/-/aws4-1.13.0.tgz" - integrity sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g== - -axios@0.31.1: - version "0.31.1" - resolved "https://registry.npmmirror.com/axios/-/axios-0.31.1.tgz#05b4c8f99e0b6f897708c74d6788d7535c6ac0c3" - integrity sha512-Ef8DUZSZQP6igY48mjGaoEjwhely97lserep0IFJifBH4YdKvwH5eMLniy3kig2HQoBNR8EkZpDjowxwTJcmbg== - dependencies: - follow-redirects "^1.15.4" - form-data "^4.0.4" - proxy-from-env "^1.1.0" - -babel-eslint@^10.1.0: - version "10.1.0" - resolved "https://registry.npmmirror.com/babel-eslint/-/babel-eslint-10.1.0.tgz" - integrity sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg== - dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/parser" "^7.7.0" - "@babel/traverse" "^7.7.0" - "@babel/types" "^7.7.0" - eslint-visitor-keys "^1.0.0" - resolve "^1.12.0" - -babel-helper-vue-jsx-merge-props@^2.0.0: - version "2.0.3" - resolved "https://registry.npmmirror.com/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz" - integrity sha512-gsLiKK7Qrb7zYJNgiXKpXblxbV5ffSwR0f5whkPAaBAR4fhi6bwRZxX9wBlIc5M/v8CCkXUbXZL4N/nSE97cqg== - -babel-loader@^8.1.0: - version "8.3.0" - resolved "https://registry.npmmirror.com/babel-loader/-/babel-loader-8.3.0.tgz" - integrity sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q== - dependencies: - find-cache-dir "^3.3.1" - loader-utils "^2.0.0" - make-dir "^3.1.0" - schema-utils "^2.6.5" - -babel-plugin-dynamic-import-node@^2.3.3: - version "2.3.3" - resolved "https://registry.npmmirror.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz" - integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== - dependencies: - object.assign "^4.1.0" - -babel-plugin-polyfill-corejs2@^0.4.10: - version "0.4.11" - resolved "https://registry.npmmirror.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz" - integrity sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q== - dependencies: - "@babel/compat-data" "^7.22.6" - "@babel/helper-define-polyfill-provider" "^0.6.2" - semver "^6.3.1" - -babel-plugin-polyfill-corejs3@^0.10.1, babel-plugin-polyfill-corejs3@^0.10.4: - version "0.10.4" - resolved "https://registry.npmmirror.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz" - integrity sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.6.1" - core-js-compat "^3.36.1" - -babel-plugin-polyfill-regenerator@^0.6.1: - version "0.6.2" - resolved "https://registry.npmmirror.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz" - integrity sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.6.2" - -babel-runtime@6.x: - version "6.26.0" - resolved "https://registry.npmmirror.com/babel-runtime/-/babel-runtime-6.26.0.tgz" - integrity sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g== - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.11.0" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base64-js@^1.0.2: - version "1.5.1" - resolved "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -base@^0.11.1: - version "0.11.2" - resolved "https://registry.npmmirror.com/base/-/base-0.11.2.tgz" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - -batch@0.6.1: - version "0.6.1" - resolved "https://registry.npmmirror.com/batch/-/batch-0.6.1.tgz" - integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== - -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.npmmirror.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz" - integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== - dependencies: - tweetnacl "^0.14.3" - -bfj@^6.1.1: - version "6.1.2" - resolved "https://registry.npmmirror.com/bfj/-/bfj-6.1.2.tgz" - integrity sha512-BmBJa4Lip6BPRINSZ0BPEIfB1wUY/9rwbwvIHQA1KjX9om29B6id0wnWXq7m3bn5JrUVjeOTnVuhPT1FiHwPGw== - dependencies: - bluebird "^3.5.5" - check-types "^8.0.3" - hoopy "^0.1.4" - tryer "^1.0.1" - -big.js@^3.1.3: - version "3.2.0" - resolved "https://registry.npmmirror.com/big.js/-/big.js-3.2.0.tgz" - integrity sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q== - -big.js@^5.2.2: - version "5.2.2" - resolved "https://registry.npmmirror.com/big.js/-/big.js-5.2.2.tgz" - integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== - -binary-extensions@^1.0.0: - version "1.13.1" - resolved "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-1.13.1.tgz" - integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== - -binary-extensions@^2.0.0: - version "2.3.0" - resolved "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz" - integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== - -bindings@^1.5.0: - version "1.5.0" - resolved "https://registry.npmmirror.com/bindings/-/bindings-1.5.0.tgz" - integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== - dependencies: - file-uri-to-path "1.0.0" - -bluebird@^3.1.1, bluebird@^3.5.5: - version "3.7.2" - resolved "https://registry.npmmirror.com/bluebird/-/bluebird-3.7.2.tgz" - integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== - -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: - version "4.12.0" - resolved "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz" - integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== - -bn.js@^5.0.0, bn.js@^5.2.1: - version "5.2.1" - resolved "https://registry.npmmirror.com/bn.js/-/bn.js-5.2.1.tgz" - integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== - -body-parser@1.20.2: - version "1.20.2" - resolved "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.2.tgz" - integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== - dependencies: - bytes "3.1.2" - content-type "~1.0.5" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.11.0" - raw-body "2.5.2" - type-is "~1.6.18" - unpipe "1.0.0" - -bonjour@^3.5.0: - version "3.5.0" - resolved "https://registry.npmmirror.com/bonjour/-/bonjour-3.5.0.tgz" - integrity sha512-RaVTblr+OnEli0r/ud8InrU7D+G0y6aJhlxaLa6Pwty4+xoxboF1BsUI45tujvRpbj9dQVoglChqonGAsjEBYg== - dependencies: - array-flatten "^2.1.0" - deep-equal "^1.0.1" - dns-equal "^1.0.0" - dns-txt "^2.0.2" - multicast-dns "^6.0.1" - multicast-dns-service-types "^1.1.0" - -boolbase@^1.0.0, boolbase@~1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz" - integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^2.3.1, braces@^2.3.2: - version "2.3.2" - resolved "https://registry.npmmirror.com/braces/-/braces-2.3.2.tgz" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - -braces@^3.0.3, braces@~3.0.2: - version "3.0.3" - resolved "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz" - integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== - dependencies: - fill-range "^7.1.1" - -brorand@^1.0.1, brorand@^1.1.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/brorand/-/brorand-1.1.0.tgz" - integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== - -browserify-aes@^1.0.4, browserify-aes@^1.2.0: - version "1.2.0" - resolved "https://registry.npmmirror.com/browserify-aes/-/browserify-aes-1.2.0.tgz" - integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== - dependencies: - buffer-xor "^1.0.3" - cipher-base "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.3" - inherits "^2.0.1" - safe-buffer "^5.0.1" - -browserify-cipher@^1.0.0: - version "1.0.1" - resolved "https://registry.npmmirror.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz" - integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== - dependencies: - browserify-aes "^1.0.4" - browserify-des "^1.0.0" - evp_bytestokey "^1.0.0" - -browserify-des@^1.0.0: - version "1.0.2" - resolved "https://registry.npmmirror.com/browserify-des/-/browserify-des-1.0.2.tgz" - integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== - dependencies: - cipher-base "^1.0.1" - des.js "^1.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" - -browserify-rsa@^4.0.0, browserify-rsa@^4.1.0: - version "4.1.0" - resolved "https://registry.npmmirror.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz" - integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== - dependencies: - bn.js "^5.0.0" - randombytes "^2.0.1" - -browserify-sign@^4.0.0: - version "4.2.3" - resolved "https://registry.npmmirror.com/browserify-sign/-/browserify-sign-4.2.3.tgz" - integrity sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw== - dependencies: - bn.js "^5.2.1" - browserify-rsa "^4.1.0" - create-hash "^1.2.0" - create-hmac "^1.1.7" - elliptic "^6.5.5" - hash-base "~3.0" - inherits "^2.0.4" - parse-asn1 "^5.1.7" - readable-stream "^2.3.8" - safe-buffer "^5.2.1" - -browserify-zlib@^0.2.0: - version "0.2.0" - resolved "https://registry.npmmirror.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz" - integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== - dependencies: - pako "~1.0.5" - -browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.22.2, browserslist@^4.23.0: - version "4.23.0" - resolved "https://registry.npmmirror.com/browserslist/-/browserslist-4.23.0.tgz" - integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== - dependencies: - caniuse-lite "^1.0.30001587" - electron-to-chromium "^1.4.668" - node-releases "^2.0.14" - update-browserslist-db "^1.0.13" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -buffer-indexof@^1.0.0: - version "1.1.1" - resolved "https://registry.npmmirror.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz" - integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g== - -buffer-json@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/buffer-json/-/buffer-json-2.0.0.tgz" - integrity sha512-+jjPFVqyfF1esi9fvfUs3NqM0pH1ziZ36VP4hmA/y/Ssfo/5w5xHKfTw9BwQjoJ1w/oVtpLomqwUHKdefGyuHw== - -buffer-xor@^1.0.3: - version "1.0.3" - resolved "https://registry.npmmirror.com/buffer-xor/-/buffer-xor-1.0.3.tgz" - integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== - -buffer@^4.3.0: - version "4.9.2" - resolved "https://registry.npmmirror.com/buffer/-/buffer-4.9.2.tgz" - integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - isarray "^1.0.0" - -builtin-status-codes@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz" - integrity sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ== - -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/bytes/-/bytes-3.0.0.tgz" - integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== - -bytes@3.1.2: - version "3.1.2" - resolved "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - -cacache@^12.0.2, cacache@^12.0.3: - version "12.0.4" - resolved "https://registry.npmmirror.com/cacache/-/cacache-12.0.4.tgz" - integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ== - dependencies: - bluebird "^3.5.5" - chownr "^1.1.1" - figgy-pudding "^3.5.1" - glob "^7.1.4" - graceful-fs "^4.1.15" - infer-owner "^1.0.3" - lru-cache "^5.1.1" - mississippi "^3.0.0" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - promise-inflight "^1.0.1" - rimraf "^2.6.3" - ssri "^6.0.1" - unique-filename "^1.1.1" - y18n "^4.0.0" - -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/cache-base/-/cache-base-1.0.1.tgz" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - -cache-loader@^4.1.0: - version "4.1.0" - resolved "https://registry.npmmirror.com/cache-loader/-/cache-loader-4.1.0.tgz" - integrity sha512-ftOayxve0PwKzBF/GLsZNC9fJBXl8lkZE3TOsjkboHfVHVkL39iUEs1FO07A33mizmci5Dudt38UZrrYXDtbhw== - dependencies: - buffer-json "^2.0.0" - find-cache-dir "^3.0.0" - loader-utils "^1.2.3" - mkdirp "^0.5.1" - neo-async "^2.6.1" - schema-utils "^2.0.0" - -call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" - integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - -call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: - version "1.0.7" - resolved "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.7.tgz" - integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - set-function-length "^1.2.1" - -call-me-maybe@^1.0.1: - version "1.0.2" - resolved "https://registry.npmmirror.com/call-me-maybe/-/call-me-maybe-1.0.2.tgz" - integrity sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ== - -caller-callsite@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/caller-callsite/-/caller-callsite-2.0.0.tgz" - integrity sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ== - dependencies: - callsites "^2.0.0" - -caller-path@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/caller-path/-/caller-path-2.0.0.tgz" - integrity sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A== - dependencies: - caller-callsite "^2.0.0" - -callsites@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/callsites/-/callsites-2.0.0.tgz" - integrity sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ== - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camel-case@3.0.x: - version "3.0.0" - resolved "https://registry.npmmirror.com/camel-case/-/camel-case-3.0.0.tgz" - integrity sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w== - dependencies: - no-case "^2.2.0" - upper-case "^1.1.1" - -camelcase@^5.0.0, camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -camelcase@^6.3.0: - version "6.3.0" - resolved "https://registry.npmmirror.com/camelcase/-/camelcase-6.3.0.tgz" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -caniuse-api@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/caniuse-api/-/caniuse-api-3.0.0.tgz" - integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== - dependencies: - browserslist "^4.0.0" - caniuse-lite "^1.0.0" - lodash.memoize "^4.1.2" - lodash.uniq "^4.5.0" - -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001587: - version "1.0.30001625" - resolved "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001625.tgz" - integrity sha512-4KE9N2gcRH+HQhpeiRZXd+1niLB/XNLAhSy4z7fI8EzcbcPoAqjNInxVHTiTwWfTIV4w096XG8OtCOCQQKPv3w== - -case-sensitive-paths-webpack-plugin@^2.3.0: - version "2.4.0" - resolved "https://registry.npmmirror.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz" - integrity sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw== - -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.npmmirror.com/caseless/-/caseless-0.12.0.tgz" - integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== - -chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/chalk/-/chalk-3.0.0.tgz" - integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chalk@^4.0.0, chalk@^4.1.0: - version "4.1.2" - resolved "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chardet@^0.7.0: - version "0.7.0" - resolved "https://registry.npmmirror.com/chardet/-/chardet-0.7.0.tgz" - integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== - -check-types@^8.0.3: - version "8.0.3" - resolved "https://registry.npmmirror.com/check-types/-/check-types-8.0.3.tgz" - integrity sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ== - -"chokidar@>=3.0.0 <4.0.0", chokidar@^3.4.1: - version "3.6.0" - resolved "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz" - integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -chokidar@^2.1.8: - version "2.1.8" - resolved "https://registry.npmmirror.com/chokidar/-/chokidar-2.1.8.tgz" - integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== - dependencies: - anymatch "^2.0.0" - async-each "^1.0.1" - braces "^2.3.2" - glob-parent "^3.1.0" - inherits "^2.0.3" - is-binary-path "^1.0.0" - is-glob "^4.0.0" - normalize-path "^3.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.2.1" - upath "^1.1.1" - optionalDependencies: - fsevents "^1.2.7" - -chownr@^1.1.1: - version "1.1.4" - resolved "https://registry.npmmirror.com/chownr/-/chownr-1.1.4.tgz" - integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== - -chrome-trace-event@^1.0.2: - version "1.0.4" - resolved "https://registry.npmmirror.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz" - integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== - -ci-info@^1.5.0: - version "1.6.0" - resolved "https://registry.npmmirror.com/ci-info/-/ci-info-1.6.0.tgz" - integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A== - -cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: - version "1.0.4" - resolved "https://registry.npmmirror.com/cipher-base/-/cipher-base-1.0.4.tgz" - integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.npmmirror.com/class-utils/-/class-utils-0.3.6.tgz" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - -clean-css@4.2.x: - version "4.2.4" - resolved "https://registry.npmmirror.com/clean-css/-/clean-css-4.2.4.tgz" - integrity sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A== - dependencies: - source-map "~0.6.0" - -cli-cursor@^2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/cli-cursor/-/cli-cursor-2.1.0.tgz" - integrity sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw== - dependencies: - restore-cursor "^2.0.0" - -cli-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.npmmirror.com/cli-cursor/-/cli-cursor-3.1.0.tgz" - integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== - dependencies: - restore-cursor "^3.1.0" - -cli-highlight@^2.1.4: - version "2.1.11" - resolved "https://registry.npmmirror.com/cli-highlight/-/cli-highlight-2.1.11.tgz" - integrity sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg== - dependencies: - chalk "^4.0.0" - highlight.js "^10.7.1" - mz "^2.4.0" - parse5 "^5.1.1" - parse5-htmlparser2-tree-adapter "^6.0.0" - yargs "^16.0.0" - -cli-spinners@^2.0.0: - version "2.9.2" - resolved "https://registry.npmmirror.com/cli-spinners/-/cli-spinners-2.9.2.tgz" - integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== - -cli-width@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/cli-width/-/cli-width-3.0.0.tgz" - integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== - -clipboardy@^2.3.0: - version "2.3.0" - resolved "https://registry.npmmirror.com/clipboardy/-/clipboardy-2.3.0.tgz" - integrity sha512-mKhiIL2DrQIsuXMgBgnfEHOZOryC7kY7YO//TN6c63wlEm3NG5tz+YgY5rVi29KCmq/QQjKYvM7a19+MDOTHOQ== - dependencies: - arch "^2.1.1" - execa "^1.0.0" - is-wsl "^2.1.1" - -cliui@^5.0.0: - version "5.0.0" - resolved "https://registry.npmmirror.com/cliui/-/cliui-5.0.0.tgz" - integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== - dependencies: - string-width "^3.1.0" - strip-ansi "^5.2.0" - wrap-ansi "^5.1.0" - -cliui@^6.0.0: - version "6.0.0" - resolved "https://registry.npmmirror.com/cliui/-/cliui-6.0.0.tgz" - integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^6.2.0" - -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.npmmirror.com/cliui/-/cliui-7.0.4.tgz" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - -clone@^1.0.2: - version "1.0.4" - resolved "https://registry.npmmirror.com/clone/-/clone-1.0.4.tgz" - integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== - -coa@^2.0.2: - version "2.0.2" - resolved "https://registry.npmmirror.com/coa/-/coa-2.0.2.tgz" - integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== - dependencies: - "@types/q" "^1.5.1" - chalk "^2.4.1" - q "^1.1.2" - -codemirror@^5.41.0, codemirror@^5.65.2: - version "5.65.16" - resolved "https://registry.npmmirror.com/codemirror/-/codemirror-5.65.16.tgz" - integrity sha512-br21LjYmSlVL0vFCPWPfhzUCT34FM/pAdK7rRIZwa0rrtrIdotvP4Oh4GUHsu2E3IrQMCfRkL/fN3ytMNxVQvg== - -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/collection-visit/-/collection-visit-1.0.0.tgz" - integrity sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw== - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - -color-convert@^1.9.0, color-convert@^1.9.3: - version "1.9.3" - resolved "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@1.1.3, color-name@^1.0.0: - version "1.1.3" - resolved "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -color-string@^1.6.0: - version "1.9.1" - resolved "https://registry.npmmirror.com/color-string/-/color-string-1.9.1.tgz" - integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - -color@^3.0.0: - version "3.2.1" - resolved "https://registry.npmmirror.com/color/-/color-3.2.1.tgz" - integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== - dependencies: - color-convert "^1.9.3" - color-string "^1.6.0" - -combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: - version "1.0.8" - resolved "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -commander@2.17.x: - version "2.17.1" - resolved "https://registry.npmmirror.com/commander/-/commander-2.17.1.tgz" - integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== - -commander@^2.18.0, commander@^2.20.0: - version "2.20.3" - resolved "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -commander@~2.19.0: - version "2.19.0" - resolved "https://registry.npmmirror.com/commander/-/commander-2.19.0.tgz" - integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== - -commondir@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/commondir/-/commondir-1.0.1.tgz" - integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== - -component-emitter@^1.2.1: - version "1.3.1" - resolved "https://registry.npmmirror.com/component-emitter/-/component-emitter-1.3.1.tgz" - integrity sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ== - -compressible@~2.0.16: - version "2.0.18" - resolved "https://registry.npmmirror.com/compressible/-/compressible-2.0.18.tgz" - integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== - dependencies: - mime-db ">= 1.43.0 < 2" - -compression@^1.7.4: - version "1.7.4" - resolved "https://registry.npmmirror.com/compression/-/compression-1.7.4.tgz" - integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== - dependencies: - accepts "~1.3.5" - bytes "3.0.0" - compressible "~2.0.16" - debug "2.6.9" - on-headers "~1.0.2" - safe-buffer "5.1.2" - vary "~1.1.2" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -concat-stream@^1.5.0: - version "1.6.2" - resolved "https://registry.npmmirror.com/concat-stream/-/concat-stream-1.6.2.tgz" - integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - -connect-history-api-fallback@^1.6.0: - version "1.6.0" - resolved "https://registry.npmmirror.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz" - integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg== - -console-browserify@^1.1.0: - version "1.2.0" - resolved "https://registry.npmmirror.com/console-browserify/-/console-browserify-1.2.0.tgz" - integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== - -consolidate@^0.15.1: - version "0.15.1" - resolved "https://registry.npmmirror.com/consolidate/-/consolidate-0.15.1.tgz" - integrity sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw== - dependencies: - bluebird "^3.1.1" - -constants-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/constants-browserify/-/constants-browserify-1.0.0.tgz" - integrity sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ== - -content-disposition@0.5.4: - version "0.5.4" - resolved "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -content-type@~1.0.4, content-type@~1.0.5: - version "1.0.5" - resolved "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz" - integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== - -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.6.tgz" - integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== - -cookie@0.6.0: - version "0.6.0" - resolved "https://registry.npmmirror.com/cookie/-/cookie-0.6.0.tgz" - integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== - -copy-concurrently@^1.0.0: - version "1.0.5" - resolved "https://registry.npmmirror.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz" - integrity sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A== - dependencies: - aproba "^1.1.1" - fs-write-stream-atomic "^1.0.8" - iferr "^0.1.5" - mkdirp "^0.5.1" - rimraf "^2.5.4" - run-queue "^1.0.0" - -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.npmmirror.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz" - integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw== - -copy-webpack-plugin@^5.1.1: - version "5.1.2" - resolved "https://registry.npmmirror.com/copy-webpack-plugin/-/copy-webpack-plugin-5.1.2.tgz" - integrity sha512-Uh7crJAco3AjBvgAy9Z75CjK8IG+gxaErro71THQ+vv/bl4HaQcpkexAY8KVW/T6D2W2IRr+couF/knIRkZMIQ== - dependencies: - cacache "^12.0.3" - find-cache-dir "^2.1.0" - glob-parent "^3.1.0" - globby "^7.1.1" - is-glob "^4.0.1" - loader-utils "^1.2.3" - minimatch "^3.0.4" - normalize-path "^3.0.0" - p-limit "^2.2.1" - schema-utils "^1.0.0" - serialize-javascript "^4.0.0" - webpack-log "^2.0.0" - -core-js-compat@^3.31.0, core-js-compat@^3.36.1, core-js-compat@^3.6.5: - version "3.37.1" - resolved "https://registry.npmmirror.com/core-js-compat/-/core-js-compat-3.37.1.tgz" - integrity sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg== - dependencies: - browserslist "^4.23.0" - -core-js@^2.4.0: - version "2.6.12" - resolved "https://registry.npmmirror.com/core-js/-/core-js-2.6.12.tgz" - integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== - -core-js@^3.6.5: - version "3.37.1" - resolved "https://registry.npmmirror.com/core-js/-/core-js-3.37.1.tgz" - integrity sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw== - -core-util-is@1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.2.tgz" - integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== - -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - -cosmiconfig@^5.0.0: - version "5.2.1" - resolved "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz" - integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== - dependencies: - import-fresh "^2.0.0" - is-directory "^0.3.1" - js-yaml "^3.13.1" - parse-json "^4.0.0" - -create-ecdh@^4.0.0: - version "4.0.4" - resolved "https://registry.npmmirror.com/create-ecdh/-/create-ecdh-4.0.4.tgz" - integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== - dependencies: - bn.js "^4.1.0" - elliptic "^6.5.3" - -create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: - version "1.2.0" - resolved "https://registry.npmmirror.com/create-hash/-/create-hash-1.2.0.tgz" - integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== - dependencies: - cipher-base "^1.0.1" - inherits "^2.0.1" - md5.js "^1.3.4" - ripemd160 "^2.0.1" - sha.js "^2.4.0" - -create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: - version "1.1.7" - resolved "https://registry.npmmirror.com/create-hmac/-/create-hmac-1.1.7.tgz" - integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== - dependencies: - cipher-base "^1.0.3" - create-hash "^1.1.0" - inherits "^2.0.1" - ripemd160 "^2.0.0" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -cross-spawn@^5.0.1: - version "5.1.0" - resolved "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-5.1.0.tgz" - integrity sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A== - dependencies: - lru-cache "^4.0.1" - shebang-command "^1.2.0" - which "^1.2.9" - -cross-spawn@^6.0.0, cross-spawn@^6.0.5: - version "6.0.5" - resolved "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-6.0.5.tgz" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - -cross-spawn@^7.0.0: - version "7.0.3" - resolved "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -crypto-browserify@^3.11.0: - version "3.12.0" - resolved "https://registry.npmmirror.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz" - integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== - dependencies: - browserify-cipher "^1.0.0" - browserify-sign "^4.0.0" - create-ecdh "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.0" - diffie-hellman "^5.0.0" - inherits "^2.0.1" - pbkdf2 "^3.0.3" - public-encrypt "^4.0.0" - randombytes "^2.0.0" - randomfill "^1.0.3" - -css-color-names@0.0.4, css-color-names@^0.0.4: - version "0.0.4" - resolved "https://registry.npmmirror.com/css-color-names/-/css-color-names-0.0.4.tgz" - integrity sha512-zj5D7X1U2h2zsXOAM8EyUREBnnts6H+Jm+d1M2DbiQQcUtnqgQsMrdo8JW9R80YFUmIdBZeMu5wvYM7hcgWP/Q== - -css-declaration-sorter@^4.0.1: - version "4.0.1" - resolved "https://registry.npmmirror.com/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz" - integrity sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA== - dependencies: - postcss "^7.0.1" - timsort "^0.3.0" - -css-loader@^3.5.3: - version "3.6.0" - resolved "https://registry.npmmirror.com/css-loader/-/css-loader-3.6.0.tgz" - integrity sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ== - dependencies: - camelcase "^5.3.1" - cssesc "^3.0.0" - icss-utils "^4.1.1" - loader-utils "^1.2.3" - normalize-path "^3.0.0" - postcss "^7.0.32" - postcss-modules-extract-imports "^2.0.0" - postcss-modules-local-by-default "^3.0.2" - postcss-modules-scope "^2.2.0" - postcss-modules-values "^3.0.0" - postcss-value-parser "^4.1.0" - schema-utils "^2.7.0" - semver "^6.3.0" - -css-select-base-adapter@^0.1.1: - version "0.1.1" - resolved "https://registry.npmmirror.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz" - integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== - -css-select@^2.0.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/css-select/-/css-select-2.1.0.tgz" - integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ== - dependencies: - boolbase "^1.0.0" - css-what "^3.2.1" - domutils "^1.7.0" - nth-check "^1.0.2" - -css-select@^4.1.3: - version "4.3.0" - resolved "https://registry.npmmirror.com/css-select/-/css-select-4.3.0.tgz" - integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== - dependencies: - boolbase "^1.0.0" - css-what "^6.0.1" - domhandler "^4.3.1" - domutils "^2.8.0" - nth-check "^2.0.1" - -css-tree@1.0.0-alpha.37: - version "1.0.0-alpha.37" - resolved "https://registry.npmmirror.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz" - integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== - dependencies: - mdn-data "2.0.4" - source-map "^0.6.1" - -css-tree@^1.1.2: - version "1.1.3" - resolved "https://registry.npmmirror.com/css-tree/-/css-tree-1.1.3.tgz" - integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== - dependencies: - mdn-data "2.0.14" - source-map "^0.6.1" - -css-what@^3.2.1: - version "3.4.2" - resolved "https://registry.npmmirror.com/css-what/-/css-what-3.4.2.tgz" - integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ== - -css-what@^6.0.1: - version "6.1.0" - resolved "https://registry.npmmirror.com/css-what/-/css-what-6.1.0.tgz" - integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== - -cssesc@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz" - integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== - -cssnano-preset-default@^4.0.0, cssnano-preset-default@^4.0.8: - version "4.0.8" - resolved "https://registry.npmmirror.com/cssnano-preset-default/-/cssnano-preset-default-4.0.8.tgz" - integrity sha512-LdAyHuq+VRyeVREFmuxUZR1TXjQm8QQU/ktoo/x7bz+SdOge1YKc5eMN6pRW7YWBmyq59CqYba1dJ5cUukEjLQ== - dependencies: - css-declaration-sorter "^4.0.1" - cssnano-util-raw-cache "^4.0.1" - postcss "^7.0.0" - postcss-calc "^7.0.1" - postcss-colormin "^4.0.3" - postcss-convert-values "^4.0.1" - postcss-discard-comments "^4.0.2" - postcss-discard-duplicates "^4.0.2" - postcss-discard-empty "^4.0.1" - postcss-discard-overridden "^4.0.1" - postcss-merge-longhand "^4.0.11" - postcss-merge-rules "^4.0.3" - postcss-minify-font-values "^4.0.2" - postcss-minify-gradients "^4.0.2" - postcss-minify-params "^4.0.2" - postcss-minify-selectors "^4.0.2" - postcss-normalize-charset "^4.0.1" - postcss-normalize-display-values "^4.0.2" - postcss-normalize-positions "^4.0.2" - postcss-normalize-repeat-style "^4.0.2" - postcss-normalize-string "^4.0.2" - postcss-normalize-timing-functions "^4.0.2" - postcss-normalize-unicode "^4.0.1" - postcss-normalize-url "^4.0.1" - postcss-normalize-whitespace "^4.0.2" - postcss-ordered-values "^4.1.2" - postcss-reduce-initial "^4.0.3" - postcss-reduce-transforms "^4.0.2" - postcss-svgo "^4.0.3" - postcss-unique-selectors "^4.0.1" - -cssnano-util-get-arguments@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz" - integrity sha512-6RIcwmV3/cBMG8Aj5gucQRsJb4vv4I4rn6YjPbVWd5+Pn/fuG+YseGvXGk00XLkoZkaj31QOD7vMUpNPC4FIuw== - -cssnano-util-get-match@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz" - integrity sha512-JPMZ1TSMRUPVIqEalIBNoBtAYbi8okvcFns4O0YIhcdGebeYZK7dMyHJiQ6GqNBA9kE0Hym4Aqym5rPdsV/4Cw== - -cssnano-util-raw-cache@^4.0.1: - version "4.0.1" - resolved "https://registry.npmmirror.com/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz" - integrity sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA== - dependencies: - postcss "^7.0.0" - -cssnano-util-same-parent@^4.0.0: - version "4.0.1" - resolved "https://registry.npmmirror.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz" - integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q== - -cssnano@^4.0.0, cssnano@^4.1.10: - version "4.1.11" - resolved "https://registry.npmmirror.com/cssnano/-/cssnano-4.1.11.tgz" - integrity sha512-6gZm2htn7xIPJOHY824ERgj8cNPgPxyCSnkXc4v7YvNW+TdVfzgngHcEhy/8D11kUWRUMbke+tC+AUcUsnMz2g== - dependencies: - cosmiconfig "^5.0.0" - cssnano-preset-default "^4.0.8" - is-resolvable "^1.0.0" - postcss "^7.0.0" - -csso@^4.0.2: - version "4.2.0" - resolved "https://registry.npmmirror.com/csso/-/csso-4.2.0.tgz" - integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== - dependencies: - css-tree "^1.1.2" - -csstype@^3.1.0: - version "3.1.3" - resolved "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz" - integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== - -cyclist@^1.0.1: - version "1.0.2" - resolved "https://registry.npmmirror.com/cyclist/-/cyclist-1.0.2.tgz" - integrity sha512-0sVXIohTfLqVIW3kb/0n6IiWF3Ifj5nm2XaSrLq2DI6fKIGa2fYAZdk917rUneaeLVpYfFcyXE2ft0fe3remsA== - -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.npmmirror.com/dashdash/-/dashdash-1.14.1.tgz" - integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== - dependencies: - assert-plus "^1.0.0" - -data-view-buffer@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz" - integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA== - dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-data-view "^1.0.1" - -data-view-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz" - integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - is-data-view "^1.0.1" - -data-view-byte-offset@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz" - integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA== - dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-data-view "^1.0.1" - -de-indent@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz" - integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg== - -debug@2.6.9, debug@^2.2.0, debug@^2.3.3: - version "2.6.9" - resolved "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@^3.2.7: - version "3.2.7" - resolved "https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== - dependencies: - ms "^2.1.1" - -debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: - version "4.3.4" - resolved "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz" - integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== - -decode-uri-component@^0.2.0: - version "0.2.2" - resolved "https://registry.npmmirror.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz" - integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== - -deep-equal@^1.0.1: - version "1.1.2" - resolved "https://registry.npmmirror.com/deep-equal/-/deep-equal-1.1.2.tgz" - integrity sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg== - dependencies: - is-arguments "^1.1.1" - is-date-object "^1.0.5" - is-regex "^1.1.4" - object-is "^1.1.5" - object-keys "^1.1.1" - regexp.prototype.flags "^1.5.1" - -deep-is@~0.1.3: - version "0.1.4" - resolved "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - -deepmerge@^1.2.0, deepmerge@^1.5.2: - version "1.5.2" - resolved "https://registry.npmmirror.com/deepmerge/-/deepmerge-1.5.2.tgz" - integrity sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ== - -default-gateway@^4.2.0: - version "4.2.0" - resolved "https://registry.npmmirror.com/default-gateway/-/default-gateway-4.2.0.tgz" - integrity sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA== - dependencies: - execa "^1.0.0" - ip-regex "^2.1.0" - -default-gateway@^5.0.5: - version "5.0.5" - resolved "https://registry.npmmirror.com/default-gateway/-/default-gateway-5.0.5.tgz" - integrity sha512-z2RnruVmj8hVMmAnEJMTIJNijhKCDiGjbLP+BHJFOT7ld3Bo5qcIBpVYDniqhbMIIf+jZDlkP2MkPXiQy/DBLA== - dependencies: - execa "^3.3.0" - -defaults@^1.0.3: - version "1.0.4" - resolved "https://registry.npmmirror.com/defaults/-/defaults-1.0.4.tgz" - integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A== - dependencies: - clone "^1.0.2" - -define-data-property@^1.0.1, define-data-property@^1.1.4: - version "1.1.4" - resolved "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz" - integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - gopd "^1.0.1" - -define-properties@^1.1.2, define-properties@^1.2.0, define-properties@^1.2.1: - version "1.2.1" - resolved "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz" - integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== - dependencies: - define-data-property "^1.0.1" - has-property-descriptors "^1.0.0" - object-keys "^1.1.1" - -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.npmmirror.com/define-property/-/define-property-0.2.5.tgz" - integrity sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA== - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/define-property/-/define-property-1.0.0.tgz" - integrity sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA== - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.npmmirror.com/define-property/-/define-property-2.0.2.tgz" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - -del@^4.1.1: - version "4.1.1" - resolved "https://registry.npmmirror.com/del/-/del-4.1.1.tgz" - integrity sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ== - dependencies: - "@types/glob" "^7.1.1" - globby "^6.1.0" - is-path-cwd "^2.0.0" - is-path-in-cwd "^2.0.0" - p-map "^2.0.0" - pify "^4.0.1" - rimraf "^2.6.3" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== - -depd@2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.npmmirror.com/depd/-/depd-1.1.2.tgz" - integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== - -des.js@^1.0.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/des.js/-/des.js-1.1.0.tgz" - integrity sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg== - dependencies: - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -destroy@1.2.0: - version "1.2.0" - resolved "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - -detect-node@^2.0.4: - version "2.1.0" - resolved "https://registry.npmmirror.com/detect-node/-/detect-node-2.1.0.tgz" - integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== - -diff-match-patch@^1.0.0: - version "1.0.5" - resolved "https://registry.npmmirror.com/diff-match-patch/-/diff-match-patch-1.0.5.tgz" - integrity sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw== - -diffie-hellman@^5.0.0: - version "5.0.3" - resolved "https://registry.npmmirror.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz" - integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== - dependencies: - bn.js "^4.1.0" - miller-rabin "^4.0.0" - randombytes "^2.0.0" - -dir-glob@^2.0.0, dir-glob@^2.2.2: - version "2.2.2" - resolved "https://registry.npmmirror.com/dir-glob/-/dir-glob-2.2.2.tgz" - integrity sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw== - dependencies: - path-type "^3.0.0" - -dns-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/dns-equal/-/dns-equal-1.0.0.tgz" - integrity sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg== - -dns-packet@^1.3.1: - version "1.3.4" - resolved "https://registry.npmmirror.com/dns-packet/-/dns-packet-1.3.4.tgz" - integrity sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA== - dependencies: - ip "^1.1.0" - safe-buffer "^5.0.1" - -dns-txt@^2.0.2: - version "2.0.2" - resolved "https://registry.npmmirror.com/dns-txt/-/dns-txt-2.0.2.tgz" - integrity sha512-Ix5PrWjphuSoUXV/Zv5gaFHjnaJtb02F2+Si3Ht9dyJ87+Z/lMmy+dpNHtTGraNK958ndXq2i+GLkWsWHcKaBQ== - dependencies: - buffer-indexof "^1.0.0" - -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/doctrine/-/doctrine-3.0.0.tgz" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - -dom-converter@^0.2.0: - version "0.2.0" - resolved "https://registry.npmmirror.com/dom-converter/-/dom-converter-0.2.0.tgz" - integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== - dependencies: - utila "~0.4" - -dom-serializer@0: - version "0.2.2" - resolved "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-0.2.2.tgz" - integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== - dependencies: - domelementtype "^2.0.1" - entities "^2.0.0" - -dom-serializer@^1.0.1: - version "1.4.1" - resolved "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-1.4.1.tgz" - integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.2.0" - entities "^2.0.0" - -domain-browser@^1.1.1: - version "1.2.0" - resolved "https://registry.npmmirror.com/domain-browser/-/domain-browser-1.2.0.tgz" - integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== - -domelementtype@1: - version "1.3.1" - resolved "https://registry.npmmirror.com/domelementtype/-/domelementtype-1.3.1.tgz" - integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== - -domelementtype@^2.0.1, domelementtype@^2.2.0: - version "2.3.0" - resolved "https://registry.npmmirror.com/domelementtype/-/domelementtype-2.3.0.tgz" - integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== - -domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: - version "4.3.1" - resolved "https://registry.npmmirror.com/domhandler/-/domhandler-4.3.1.tgz" - integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== - dependencies: - domelementtype "^2.2.0" - -domutils@^1.7.0: - version "1.7.0" - resolved "https://registry.npmmirror.com/domutils/-/domutils-1.7.0.tgz" - integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== - dependencies: - dom-serializer "0" - domelementtype "1" - -domutils@^2.5.2, domutils@^2.8.0: - version "2.8.0" - resolved "https://registry.npmmirror.com/domutils/-/domutils-2.8.0.tgz" - integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== - dependencies: - dom-serializer "^1.0.1" - domelementtype "^2.2.0" - domhandler "^4.2.0" - -dot-prop@^5.2.0: - version "5.3.0" - resolved "https://registry.npmmirror.com/dot-prop/-/dot-prop-5.3.0.tgz" - integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== - dependencies: - is-obj "^2.0.0" - -dotenv-expand@^5.1.0: - version "5.1.0" - resolved "https://registry.npmmirror.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz" - integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== - -dotenv@^8.2.0: - version "8.6.0" - resolved "https://registry.npmmirror.com/dotenv/-/dotenv-8.6.0.tgz" - integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g== - -dunder-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" - integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== - dependencies: - call-bind-apply-helpers "^1.0.1" - es-errors "^1.3.0" - gopd "^1.2.0" - -duplexer@^0.1.1: - version "0.1.2" - resolved "https://registry.npmmirror.com/duplexer/-/duplexer-0.1.2.tgz" - integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== - -duplexify@^3.4.2, duplexify@^3.6.0: - version "3.7.1" - resolved "https://registry.npmmirror.com/duplexify/-/duplexify-3.7.1.tgz" - integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== - dependencies: - end-of-stream "^1.0.0" - inherits "^2.0.1" - readable-stream "^2.0.0" - stream-shift "^1.0.0" - -easy-stack@1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/easy-stack/-/easy-stack-1.0.1.tgz" - integrity sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w== - -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.npmmirror.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz" - integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw== - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - -ejs@^2.6.1: - version "2.7.4" - resolved "https://registry.npmmirror.com/ejs/-/ejs-2.7.4.tgz" - integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA== - -electron-to-chromium@^1.4.668: - version "1.4.785" - resolved "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.785.tgz" - integrity sha512-V94FSLwW6V1JYeYT/pl23zIdwiAbeod7gBjyud2uhuWTa2yV3gZ4IaWt6l0t808y4l6NwOspyR5hiENGrYQqvA== - -element-ui@^2.15.13: - version "2.15.14" - resolved "https://registry.npmmirror.com/element-ui/-/element-ui-2.15.14.tgz" - integrity sha512-2v9fHL0ZGINotOlRIAJD5YuVB8V7WKxrE9Qy7dXhRipa035+kF7WuU/z+tEmLVPBcJ0zt8mOu1DKpWcVzBK8IA== - dependencies: - async-validator "~1.8.1" - babel-helper-vue-jsx-merge-props "^2.0.0" - deepmerge "^1.2.0" - normalize-wheel "^1.0.1" - resize-observer-polyfill "^1.5.0" - throttle-debounce "^1.0.1" - -elliptic@^6.5.3, elliptic@^6.5.5: - version "6.5.5" - resolved "https://registry.npmmirror.com/elliptic/-/elliptic-6.5.5.tgz" - integrity sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw== - dependencies: - bn.js "^4.11.9" - brorand "^1.1.0" - hash.js "^1.0.0" - hmac-drbg "^1.0.1" - inherits "^2.0.4" - minimalistic-assert "^1.0.1" - minimalistic-crypto-utils "^1.0.1" - -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-7.0.3.tgz" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -emojis-list@^2.0.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/emojis-list/-/emojis-list-2.1.0.tgz" - integrity sha512-knHEZMgs8BB+MInokmNTg/OyPlAddghe1YBgNwJBc5zsJi/uyIcXoSDsL/W9ymOsBoBGdPIHXYJ9+qKFwRwDng== - -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/emojis-list/-/emojis-list-3.0.0.tgz" - integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - -end-of-stream@^1.0.0, end-of-stream@^1.1.0: - version "1.4.4" - resolved "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.4.tgz" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -enhanced-resolve@^4.5.0: - version "4.5.0" - resolved "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz" - integrity sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg== - dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.5.0" - tapable "^1.0.0" - -entities@^2.0.0: - version "2.2.0" - resolved "https://registry.npmmirror.com/entities/-/entities-2.2.0.tgz" - integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== - -entities@^4.5.0: - version "4.5.0" - resolved "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz" - integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== - -errno@^0.1.3, errno@~0.1.7: - version "0.1.8" - resolved "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz" - integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== - dependencies: - prr "~1.0.1" - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.2.tgz" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -error-stack-parser@^2.0.6: - version "2.1.4" - resolved "https://registry.npmmirror.com/error-stack-parser/-/error-stack-parser-2.1.4.tgz" - integrity sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ== - dependencies: - stackframe "^1.3.4" - -es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.2: - version "1.23.3" - resolved "https://registry.npmmirror.com/es-abstract/-/es-abstract-1.23.3.tgz" - integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== - dependencies: - array-buffer-byte-length "^1.0.1" - arraybuffer.prototype.slice "^1.0.3" - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - data-view-buffer "^1.0.1" - data-view-byte-length "^1.0.1" - data-view-byte-offset "^1.0.0" - es-define-property "^1.0.0" - es-errors "^1.3.0" - es-object-atoms "^1.0.0" - es-set-tostringtag "^2.0.3" - es-to-primitive "^1.2.1" - function.prototype.name "^1.1.6" - get-intrinsic "^1.2.4" - get-symbol-description "^1.0.2" - globalthis "^1.0.3" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - has-proto "^1.0.3" - has-symbols "^1.0.3" - hasown "^2.0.2" - internal-slot "^1.0.7" - is-array-buffer "^3.0.4" - is-callable "^1.2.7" - is-data-view "^1.0.1" - is-negative-zero "^2.0.3" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.3" - is-string "^1.0.7" - is-typed-array "^1.1.13" - is-weakref "^1.0.2" - object-inspect "^1.13.1" - object-keys "^1.1.1" - object.assign "^4.1.5" - regexp.prototype.flags "^1.5.2" - safe-array-concat "^1.1.2" - safe-regex-test "^1.0.3" - string.prototype.trim "^1.2.9" - string.prototype.trimend "^1.0.8" - string.prototype.trimstart "^1.0.8" - typed-array-buffer "^1.0.2" - typed-array-byte-length "^1.0.1" - typed-array-byte-offset "^1.0.2" - typed-array-length "^1.0.6" - unbox-primitive "^1.0.2" - which-typed-array "^1.1.15" - -es-array-method-boxes-properly@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz" - integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== - -es-define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.0.tgz" - integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== - dependencies: - get-intrinsic "^1.2.4" - -es-define-property@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" - integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== - -es-errors@^1.2.1, es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-object-atoms@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz" - integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw== - dependencies: - es-errors "^1.3.0" - -es-object-atoms@^1.1.1: - version "1.1.1" - resolved "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" - integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== - dependencies: - es-errors "^1.3.0" - -es-set-tostringtag@^2.0.3: - version "2.0.3" - resolved "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz" - integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ== - dependencies: - get-intrinsic "^1.2.4" - has-tostringtag "^1.0.2" - hasown "^2.0.1" - -es-set-tostringtag@^2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" - integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== - dependencies: - es-errors "^1.3.0" - get-intrinsic "^1.2.6" - has-tostringtag "^1.0.2" - hasown "^2.0.2" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.npmmirror.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - -escalade@^3.1.1, escalade@^3.1.2: - version "3.1.2" - resolved "https://registry.npmmirror.com/escalade/-/escalade-3.1.2.tgz" - integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - -eslint-loader@^2.2.1: - version "2.2.1" - resolved "https://registry.npmmirror.com/eslint-loader/-/eslint-loader-2.2.1.tgz" - integrity sha512-RLgV9hoCVsMLvOxCuNjdqOrUqIj9oJg8hF44vzJaYqsAHuY9G2YAeN3joQ9nxP0p5Th9iFSIpKo+SD8KISxXRg== - dependencies: - loader-fs-cache "^1.0.0" - loader-utils "^1.0.2" - object-assign "^4.0.1" - object-hash "^1.1.4" - rimraf "^2.6.1" - -eslint-plugin-vue@^6.2.2: - version "6.2.2" - resolved "https://registry.npmmirror.com/eslint-plugin-vue/-/eslint-plugin-vue-6.2.2.tgz" - integrity sha512-Nhc+oVAHm0uz/PkJAWscwIT4ijTrK5fqNqz9QB1D35SbbuMG1uB6Yr5AJpvPSWg+WOw7nYNswerYh0kOk64gqQ== - dependencies: - natural-compare "^1.4.0" - semver "^5.6.0" - vue-eslint-parser "^7.0.0" - -eslint-scope@^4.0.3: - version "4.0.3" - resolved "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-4.0.3.tgz" - integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== - dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" - -eslint-scope@^5.0.0, eslint-scope@^5.1.1: - version "5.1.1" - resolved "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-5.1.1.tgz" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -eslint-utils@^1.4.3: - version "1.4.3" - resolved "https://registry.npmmirror.com/eslint-utils/-/eslint-utils-1.4.3.tgz" - integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== - dependencies: - eslint-visitor-keys "^1.1.0" - -eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: - version "1.3.0" - resolved "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz" - integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== - -eslint@^6.7.2: - version "6.8.0" - resolved "https://registry.npmmirror.com/eslint/-/eslint-6.8.0.tgz" - integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig== - dependencies: - "@babel/code-frame" "^7.0.0" - ajv "^6.10.0" - chalk "^2.1.0" - cross-spawn "^6.0.5" - debug "^4.0.1" - doctrine "^3.0.0" - eslint-scope "^5.0.0" - eslint-utils "^1.4.3" - eslint-visitor-keys "^1.1.0" - espree "^6.1.2" - esquery "^1.0.1" - esutils "^2.0.2" - file-entry-cache "^5.0.1" - functional-red-black-tree "^1.0.1" - glob-parent "^5.0.0" - globals "^12.1.0" - ignore "^4.0.6" - import-fresh "^3.0.0" - imurmurhash "^0.1.4" - inquirer "^7.0.0" - is-glob "^4.0.0" - js-yaml "^3.13.1" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.3.0" - lodash "^4.17.14" - minimatch "^3.0.4" - mkdirp "^0.5.1" - natural-compare "^1.4.0" - optionator "^0.8.3" - progress "^2.0.0" - regexpp "^2.0.1" - semver "^6.1.2" - strip-ansi "^5.2.0" - strip-json-comments "^3.0.1" - table "^5.2.3" - text-table "^0.2.0" - v8-compile-cache "^2.0.3" - -espree@^6.1.2, espree@^6.2.1: - version "6.2.1" - resolved "https://registry.npmmirror.com/espree/-/espree-6.2.1.tgz" - integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw== - dependencies: - acorn "^7.1.1" - acorn-jsx "^5.2.0" - eslint-visitor-keys "^1.1.0" - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esquery@^1.0.1, esquery@^1.4.0: - version "1.5.0" - resolved "https://registry.npmmirror.com/esquery/-/esquery-1.5.0.tgz" - integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.1.0, esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.npmmirror.com/estraverse/-/estraverse-4.3.0.tgz" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.1.0, estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -estree-walker@^2.0.2: - version "2.0.2" - resolved "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz" - integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz" - integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - -event-pubsub@4.3.0: - version "4.3.0" - resolved "https://registry.npmmirror.com/event-pubsub/-/event-pubsub-4.3.0.tgz" - integrity sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ== - -eventemitter3@^4.0.0: - version "4.0.7" - resolved "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - -events@^3.0.0: - version "3.3.0" - resolved "https://registry.npmmirror.com/events/-/events-3.3.0.tgz" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -eventsource@^2.0.2: - version "2.0.2" - resolved "https://registry.npmmirror.com/eventsource/-/eventsource-2.0.2.tgz" - integrity sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA== - -evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: - version "1.0.3" - resolved "https://registry.npmmirror.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz" - integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== - dependencies: - md5.js "^1.3.4" - safe-buffer "^5.1.1" - -execa@^0.8.0: - version "0.8.0" - resolved "https://registry.npmmirror.com/execa/-/execa-0.8.0.tgz" - integrity sha512-zDWS+Rb1E8BlqqhALSt9kUhss8Qq4nN3iof3gsOdyINksElaPyNBtKUMTR62qhvgVWR0CqCX7sdnKe4MnUbFEA== - dependencies: - cross-spawn "^5.0.1" - get-stream "^3.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -execa@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/execa/-/execa-1.0.0.tgz" - integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== - dependencies: - cross-spawn "^6.0.0" - get-stream "^4.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -execa@^3.3.0: - version "3.4.0" - resolved "https://registry.npmmirror.com/execa/-/execa-3.4.0.tgz" - integrity sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g== - dependencies: - cross-spawn "^7.0.0" - get-stream "^5.0.0" - human-signals "^1.1.1" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.0" - onetime "^5.1.0" - p-finally "^2.0.0" - signal-exit "^3.0.2" - strip-final-newline "^2.0.0" - -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.npmmirror.com/expand-brackets/-/expand-brackets-2.1.4.tgz" - integrity sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA== - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -express@^4.16.3, express@^4.17.1: - version "4.19.2" - resolved "https://registry.npmmirror.com/express/-/express-4.19.2.tgz" - integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.20.2" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.6.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.2.0" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "2.4.1" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.7" - qs "6.11.0" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.18.0" - serve-static "1.15.0" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz" - integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug== - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-3.0.2.tgz" - integrity sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q== - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - -extend@~3.0.2: - version "3.0.2" - resolved "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -external-editor@^3.0.3: - version "3.1.0" - resolved "https://registry.npmmirror.com/external-editor/-/external-editor-3.1.0.tgz" - integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== - dependencies: - chardet "^0.7.0" - iconv-lite "^0.4.24" - tmp "^0.0.33" - -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.npmmirror.com/extglob/-/extglob-2.0.4.tgz" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -extsprintf@1.3.0, extsprintf@^1.2.0: - version "1.3.0" - resolved "https://registry.npmmirror.com/extsprintf/-/extsprintf-1.3.0.tgz" - integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== - -fast-deep-equal@^3.1.1: - version "3.1.3" - resolved "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-glob@^2.2.6: - version "2.2.7" - resolved "https://registry.npmmirror.com/fast-glob/-/fast-glob-2.2.7.tgz" - integrity sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw== - dependencies: - "@mrmlnc/readdir-enhanced" "^2.2.1" - "@nodelib/fs.stat" "^1.1.2" - glob-parent "^3.1.0" - is-glob "^4.0.0" - merge2 "^1.2.3" - micromatch "^3.1.10" - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-levenshtein@~2.0.6: - version "2.0.6" - resolved "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" - integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== - -faye-websocket@^0.11.3, faye-websocket@^0.11.4: - version "0.11.4" - resolved "https://registry.npmmirror.com/faye-websocket/-/faye-websocket-0.11.4.tgz" - integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== - dependencies: - websocket-driver ">=0.5.1" - -figgy-pudding@^3.5.1: - version "3.5.2" - resolved "https://registry.npmmirror.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz" - integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== - -figures@^3.0.0: - version "3.2.0" - resolved "https://registry.npmmirror.com/figures/-/figures-3.2.0.tgz" - integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== - dependencies: - escape-string-regexp "^1.0.5" - -file-entry-cache@^5.0.1: - version "5.0.1" - resolved "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz" - integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== - dependencies: - flat-cache "^2.0.1" - -file-loader@^4.2.0: - version "4.3.0" - resolved "https://registry.npmmirror.com/file-loader/-/file-loader-4.3.0.tgz" - integrity sha512-aKrYPYjF1yG3oX0kWRrqrSMfgftm7oJW5M+m4owoldH5C51C0RkIwB++JbRvEW3IU6/ZG5n8UvEcdgwOt2UOWA== - dependencies: - loader-utils "^1.2.3" - schema-utils "^2.5.0" - -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== - -filesize@^3.6.1: - version "3.6.1" - resolved "https://registry.npmmirror.com/filesize/-/filesize-3.6.1.tgz" - integrity sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg== - -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/fill-range/-/fill-range-4.0.0.tgz" - integrity sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ== - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - -fill-range@^7.1.1: - version "7.1.1" - resolved "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz" - integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== - dependencies: - to-regex-range "^5.0.1" - -finalhandler@1.2.0: - version "1.2.0" - resolved "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.2.0.tgz" - integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "2.4.1" - parseurl "~1.3.3" - statuses "2.0.1" - unpipe "~1.0.0" - -find-cache-dir@^0.1.1: - version "0.1.1" - resolved "https://registry.npmmirror.com/find-cache-dir/-/find-cache-dir-0.1.1.tgz" - integrity sha512-Z9XSBoNE7xQiV6MSgPuCfyMokH2K7JdpRkOYE1+mu3d4BFJtx3GW+f6Bo4q8IX6rlf5MYbLBKW0pjl2cWdkm2A== - dependencies: - commondir "^1.0.1" - mkdirp "^0.5.1" - pkg-dir "^1.0.0" - -find-cache-dir@^2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz" - integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== - dependencies: - commondir "^1.0.1" - make-dir "^2.0.0" - pkg-dir "^3.0.0" - -find-cache-dir@^3.0.0, find-cache-dir@^3.3.1: - version "3.3.2" - resolved "https://registry.npmmirror.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz" - integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== - dependencies: - commondir "^1.0.1" - make-dir "^3.0.2" - pkg-dir "^4.1.0" - -find-up@^1.0.0: - version "1.1.2" - resolved "https://registry.npmmirror.com/find-up/-/find-up-1.1.2.tgz" - integrity sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA== - dependencies: - path-exists "^2.0.0" - pinkie-promise "^2.0.0" - -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/find-up/-/find-up-3.0.0.tgz" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== - dependencies: - locate-path "^3.0.0" - -find-up@^4.0.0: - version "4.1.0" - resolved "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -flat-cache@^2.0.1: - version "2.0.1" - resolved "https://registry.npmmirror.com/flat-cache/-/flat-cache-2.0.1.tgz" - integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== - dependencies: - flatted "^2.0.0" - rimraf "2.6.3" - write "1.0.3" - -flatted@^2.0.0: - version "2.0.2" - resolved "https://registry.npmmirror.com/flatted/-/flatted-2.0.2.tgz" - integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== - -flush-write-stream@^1.0.0: - version "1.1.1" - resolved "https://registry.npmmirror.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz" - integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== - dependencies: - inherits "^2.0.3" - readable-stream "^2.3.6" - -follow-redirects@^1.0.0: - version "1.15.6" - resolved "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.6.tgz" - integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== - -follow-redirects@^1.15.4: - version "1.16.0" - resolved "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.16.0.tgz#28474a159d3b9d11ef62050a14ed60e4df6d61bc" - integrity sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw== - -font-awesome@^4.7.0: - version "4.7.0" - resolved "https://registry.npmmirror.com/font-awesome/-/font-awesome-4.7.0.tgz" - integrity sha512-U6kGnykA/6bFmg1M/oT9EkFeIYv7JlX3bozwQJWiiLz6L0w3F5vBVPxHlwyX/vtNq1ckcpRKOB9f2Qal/VtFpg== - -for-each@^0.3.3: - version "0.3.3" - resolved "https://registry.npmmirror.com/for-each/-/for-each-0.3.3.tgz" - integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== - dependencies: - is-callable "^1.1.3" - -for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/for-in/-/for-in-1.0.2.tgz" - integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ== - -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.npmmirror.com/forever-agent/-/forever-agent-0.6.1.tgz" - integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== - -form-data@^4.0.4: - version "4.0.5" - resolved "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz#b49e48858045ff4cbf6b03e1805cebcad3679053" - integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - es-set-tostringtag "^2.1.0" - hasown "^2.0.2" - mime-types "^2.1.12" - -form-data@~2.3.2: - version "2.3.3" - resolved "https://registry.npmmirror.com/form-data/-/form-data-2.3.3.tgz" - integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.npmmirror.com/fragment-cache/-/fragment-cache-0.2.1.tgz" - integrity sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA== - dependencies: - map-cache "^0.2.2" - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz" - integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== - -from2@^2.1.0: - version "2.3.0" - resolved "https://registry.npmmirror.com/from2/-/from2-2.3.0.tgz" - integrity sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g== - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.0" - -fs-extra@^7.0.1: - version "7.0.1" - resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-7.0.1.tgz" - integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== - dependencies: - graceful-fs "^4.1.2" - jsonfile "^4.0.0" - universalify "^0.1.0" - -fs-write-stream-atomic@^1.0.8: - version "1.0.10" - resolved "https://registry.npmmirror.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz" - integrity sha512-gehEzmPn2nAwr39eay+x3X34Ra+M2QlVUTLhkXPjWdeO8RF9kszk116avgBJM3ZyNHgHXBNx+VmPaFC36k0PzA== - dependencies: - graceful-fs "^4.1.2" - iferr "^0.1.5" - imurmurhash "^0.1.4" - readable-stream "1 || 2" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@^1.2.7: - version "1.2.13" - resolved "https://registry.npmmirror.com/fsevents/-/fsevents-1.2.13.tgz" - integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw== - dependencies: - bindings "^1.5.0" - nan "^2.12.1" - -fsevents@~2.3.2: - version "2.3.3" - resolved "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -function.prototype.name@^1.1.6: - version "1.1.6" - resolved "https://registry.npmmirror.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz" - integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - functions-have-names "^1.2.3" - -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz" - integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== - -functions-have-names@^1.2.3: - version "1.2.3" - resolved "https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz" - integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-caller-file@^2.0.1, get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: - version "1.2.4" - resolved "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz" - integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - has-proto "^1.0.1" - has-symbols "^1.0.3" - hasown "^2.0.0" - -get-intrinsic@^1.2.6: - version "1.3.0" - resolved "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" - integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== - dependencies: - call-bind-apply-helpers "^1.0.2" - es-define-property "^1.0.1" - es-errors "^1.3.0" - es-object-atoms "^1.1.1" - function-bind "^1.1.2" - get-proto "^1.0.1" - gopd "^1.2.0" - has-symbols "^1.1.0" - hasown "^2.0.2" - math-intrinsics "^1.1.0" - -get-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" - integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== - dependencies: - dunder-proto "^1.0.1" - es-object-atoms "^1.0.0" - -get-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/get-stream/-/get-stream-3.0.0.tgz" - integrity sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ== - -get-stream@^4.0.0: - version "4.1.0" - resolved "https://registry.npmmirror.com/get-stream/-/get-stream-4.1.0.tgz" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== - dependencies: - pump "^3.0.0" - -get-stream@^5.0.0: - version "5.2.0" - resolved "https://registry.npmmirror.com/get-stream/-/get-stream-5.2.0.tgz" - integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== - dependencies: - pump "^3.0.0" - -get-symbol-description@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz" - integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== - dependencies: - call-bind "^1.0.5" - es-errors "^1.3.0" - get-intrinsic "^1.2.4" - -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.npmmirror.com/get-value/-/get-value-2.0.6.tgz" - integrity sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA== - -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.npmmirror.com/getpass/-/getpass-0.1.7.tgz" - integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng== - dependencies: - assert-plus "^1.0.0" - -glob-parent@^3.1.0: - version "3.1.0" - resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-3.1.0.tgz" - integrity sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA== - dependencies: - is-glob "^3.1.0" - path-dirname "^1.0.0" - -glob-parent@^5.0.0, glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-to-regexp@^0.3.0: - version "0.3.0" - resolved "https://registry.npmmirror.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz" - integrity sha512-Iozmtbqv0noj0uDDqoL0zNq0VBEfK2YFoMAZoxJe4cwphvLR+JskfF30QhXHOR4m3KrE6NLRYw+U9MRXvifyig== - -glob@^7.0.3, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: - version "7.2.3" - resolved "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -globals@^12.1.0: - version "12.4.0" - resolved "https://registry.npmmirror.com/globals/-/globals-12.4.0.tgz" - integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== - dependencies: - type-fest "^0.8.1" - -globalthis@^1.0.3: - version "1.0.4" - resolved "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.4.tgz" - integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== - dependencies: - define-properties "^1.2.1" - gopd "^1.0.1" - -globby@^6.1.0: - version "6.1.0" - resolved "https://registry.npmmirror.com/globby/-/globby-6.1.0.tgz" - integrity sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw== - dependencies: - array-union "^1.0.1" - glob "^7.0.3" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" - -globby@^7.1.1: - version "7.1.1" - resolved "https://registry.npmmirror.com/globby/-/globby-7.1.1.tgz" - integrity sha512-yANWAN2DUcBtuus5Cpd+SKROzXHs2iVXFZt/Ykrfz6SAXqacLX25NZpltE+39ceMexYF4TtEadjuSTw8+3wX4g== - dependencies: - array-union "^1.0.1" - dir-glob "^2.0.0" - glob "^7.1.2" - ignore "^3.3.5" - pify "^3.0.0" - slash "^1.0.0" - -globby@^9.2.0: - version "9.2.0" - resolved "https://registry.npmmirror.com/globby/-/globby-9.2.0.tgz" - integrity sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg== - dependencies: - "@types/glob" "^7.1.1" - array-union "^1.0.2" - dir-glob "^2.2.2" - fast-glob "^2.2.6" - glob "^7.1.3" - ignore "^4.0.3" - pify "^4.0.1" - slash "^2.0.0" - -gopd@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/gopd/-/gopd-1.0.1.tgz" - integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== - dependencies: - get-intrinsic "^1.1.3" - -gopd@^1.2.0: - version "1.2.0" - resolved "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" - integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== - -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6: - version "4.2.11" - resolved "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -gzip-size@^5.0.0: - version "5.1.1" - resolved "https://registry.npmmirror.com/gzip-size/-/gzip-size-5.1.1.tgz" - integrity sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA== - dependencies: - duplexer "^0.1.1" - pify "^4.0.1" - -handle-thing@^2.0.0: - version "2.0.1" - resolved "https://registry.npmmirror.com/handle-thing/-/handle-thing-2.0.1.tgz" - integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== - -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/har-schema/-/har-schema-2.0.0.tgz" - integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q== - -har-validator@~5.1.3: - version "5.1.5" - resolved "https://registry.npmmirror.com/har-validator/-/har-validator-5.1.5.tgz" - integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== - dependencies: - ajv "^6.12.3" - har-schema "^2.0.0" - -has-bigints@^1.0.1, has-bigints@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/has-bigints/-/has-bigints-1.0.2.tgz" - integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz" - integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== - dependencies: - es-define-property "^1.0.0" - -has-proto@^1.0.1, has-proto@^1.0.3: - version "1.0.3" - resolved "https://registry.npmmirror.com/has-proto/-/has-proto-1.0.3.tgz" - integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== - -has-symbols@^1.0.2, has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.0.3.tgz" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -has-symbols@^1.1.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" - integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== - -has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz" - integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== - dependencies: - has-symbols "^1.0.3" - -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.npmmirror.com/has-value/-/has-value-0.3.1.tgz" - integrity sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q== - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/has-value/-/has-value-1.0.0.tgz" - integrity sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw== - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.npmmirror.com/has-values/-/has-values-0.1.4.tgz" - integrity sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ== - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/has-values/-/has-values-1.0.0.tgz" - integrity sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ== - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - -has@^1.0.0: - version "1.0.4" - resolved "https://registry.npmmirror.com/has/-/has-1.0.4.tgz" - integrity sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ== - -hash-base@^3.0.0, hash-base@~3.0: - version "3.0.4" - resolved "https://registry.npmmirror.com/hash-base/-/hash-base-3.0.4.tgz" - integrity sha512-EeeoJKjTyt868liAlVmcv2ZsUfGHlE3Q+BICOXcZiwN3osr5Q/zFGYmTJpoIzuaSTAwndFy+GqhEwlU4L3j4Ow== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -hash-sum@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/hash-sum/-/hash-sum-1.0.2.tgz" - integrity sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA== - -hash-sum@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/hash-sum/-/hash-sum-2.0.0.tgz" - integrity sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg== - -hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.7" - resolved "https://registry.npmmirror.com/hash.js/-/hash.js-1.1.7.tgz" - integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.1" - -hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - -he@1.2.x, he@^1.2.0: - version "1.2.0" - resolved "https://registry.npmmirror.com/he/-/he-1.2.0.tgz" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -hex-color-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz" - integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== - -highlight.js@^10.7.1: - version "10.7.3" - resolved "https://registry.npmmirror.com/highlight.js/-/highlight.js-10.7.3.tgz" - integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== - -hmac-drbg@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz" - integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== - dependencies: - hash.js "^1.0.3" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.1" - -hoopy@^0.1.4: - version "0.1.4" - resolved "https://registry.npmmirror.com/hoopy/-/hoopy-0.1.4.tgz" - integrity sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ== - -hosted-git-info@^2.1.4: - version "2.8.9" - resolved "https://registry.npmmirror.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz" - integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== - -hpack.js@^2.1.6: - version "2.1.6" - resolved "https://registry.npmmirror.com/hpack.js/-/hpack.js-2.1.6.tgz" - integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== - dependencies: - inherits "^2.0.1" - obuf "^1.0.0" - readable-stream "^2.0.1" - wbuf "^1.1.0" - -hsl-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/hsl-regex/-/hsl-regex-1.0.0.tgz" - integrity sha512-M5ezZw4LzXbBKMruP+BNANf0k+19hDQMgpzBIYnya//Al+fjNct9Wf3b1WedLqdEs2hKBvxq/jh+DsHJLj0F9A== - -hsla-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/hsla-regex/-/hsla-regex-1.0.0.tgz" - integrity sha512-7Wn5GMLuHBjZCb2bTmnDOycho0p/7UVaAeqXZGbHrBCl6Yd/xDhQJAXe6Ga9AXJH2I5zY1dEdYw2u1UptnSBJA== - -html-entities@^1.3.1: - version "1.4.0" - resolved "https://registry.npmmirror.com/html-entities/-/html-entities-1.4.0.tgz" - integrity sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA== - -html-minifier@^3.2.3: - version "3.5.21" - resolved "https://registry.npmmirror.com/html-minifier/-/html-minifier-3.5.21.tgz" - integrity sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA== - dependencies: - camel-case "3.0.x" - clean-css "4.2.x" - commander "2.17.x" - he "1.2.x" - param-case "2.1.x" - relateurl "0.2.x" - uglify-js "3.4.x" - -html-tags@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/html-tags/-/html-tags-2.0.0.tgz" - integrity sha512-+Il6N8cCo2wB/Vd3gqy/8TZhTD3QvcVeQLCnZiGkGCH3JP28IgGAY41giccp2W4R3jfyJPAP318FQTa1yU7K7g== - -html-tags@^3.3.1: - version "3.3.1" - resolved "https://registry.npmmirror.com/html-tags/-/html-tags-3.3.1.tgz" - integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ== - -html-webpack-plugin@^3.2.0: - version "3.2.0" - resolved "https://registry.npmmirror.com/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz" - integrity sha512-Br4ifmjQojUP4EmHnRBoUIYcZ9J7M4bTMcm7u6xoIAIuq2Nte4TzXX0533owvkQKQD1WeMTTTyD4Ni4QKxS0Bg== - dependencies: - html-minifier "^3.2.3" - loader-utils "^0.2.16" - lodash "^4.17.3" - pretty-error "^2.0.2" - tapable "^1.0.0" - toposort "^1.0.0" - util.promisify "1.0.0" - -htmlparser2@^6.1.0: - version "6.1.0" - resolved "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-6.1.0.tgz" - integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.0.0" - domutils "^2.5.2" - entities "^2.0.0" - -http-deceiver@^1.2.7: - version "1.2.7" - resolved "https://registry.npmmirror.com/http-deceiver/-/http-deceiver-1.2.7.tgz" - integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== - -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.npmmirror.com/http-errors/-/http-errors-1.6.3.tgz" - integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" - -http-parser-js@>=0.5.1: - version "0.5.8" - resolved "https://registry.npmmirror.com/http-parser-js/-/http-parser-js-0.5.8.tgz" - integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== - -http-proxy-middleware@0.19.1: - version "0.19.1" - resolved "https://registry.npmmirror.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz" - integrity sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q== - dependencies: - http-proxy "^1.17.0" - is-glob "^4.0.0" - lodash "^4.17.11" - micromatch "^3.1.10" - -http-proxy-middleware@^1.0.0: - version "1.3.1" - resolved "https://registry.npmmirror.com/http-proxy-middleware/-/http-proxy-middleware-1.3.1.tgz" - integrity sha512-13eVVDYS4z79w7f1+NPllJtOQFx/FdUW4btIvVRMaRlUY9VGstAbo5MOhLEuUgZFRHn3x50ufn25zkj/boZnEg== - dependencies: - "@types/http-proxy" "^1.17.5" - http-proxy "^1.18.1" - is-glob "^4.0.1" - is-plain-obj "^3.0.0" - micromatch "^4.0.2" - -http-proxy@^1.17.0, http-proxy@^1.18.1: - version "1.18.1" - resolved "https://registry.npmmirror.com/http-proxy/-/http-proxy-1.18.1.tgz" - integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== - dependencies: - eventemitter3 "^4.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" - -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.npmmirror.com/http-signature/-/http-signature-1.2.0.tgz" - integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ== - dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - -https-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/https-browserify/-/https-browserify-1.0.0.tgz" - integrity sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg== - -human-signals@^1.1.1: - version "1.1.1" - resolved "https://registry.npmmirror.com/human-signals/-/human-signals-1.1.1.tgz" - integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== - -iconv-lite@0.4.24, iconv-lite@^0.4.24: - version "0.4.24" - resolved "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -icss-utils@^4.0.0, icss-utils@^4.1.1: - version "4.1.1" - resolved "https://registry.npmmirror.com/icss-utils/-/icss-utils-4.1.1.tgz" - integrity sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA== - dependencies: - postcss "^7.0.14" - -ieee754@^1.1.4: - version "1.2.1" - resolved "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - -iferr@^0.1.5: - version "0.1.5" - resolved "https://registry.npmmirror.com/iferr/-/iferr-0.1.5.tgz" - integrity sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA== - -ignore@^3.3.5: - version "3.3.10" - resolved "https://registry.npmmirror.com/ignore/-/ignore-3.3.10.tgz" - integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== - -ignore@^4.0.3, ignore@^4.0.6: - version "4.0.6" - resolved "https://registry.npmmirror.com/ignore/-/ignore-4.0.6.tgz" - integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== - -immutable@^4.0.0: - version "4.3.6" - resolved "https://registry.npmmirror.com/immutable/-/immutable-4.3.6.tgz" - integrity sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ== - -import-cwd@^2.0.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/import-cwd/-/import-cwd-2.1.0.tgz" - integrity sha512-Ew5AZzJQFqrOV5BTW3EIoHAnoie1LojZLXKcCQ/yTRyVZosBhK1x1ViYjHGf5pAFOq8ZyChZp6m/fSN7pJyZtg== - dependencies: - import-from "^2.1.0" - -import-fresh@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/import-fresh/-/import-fresh-2.0.0.tgz" - integrity sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg== - dependencies: - caller-path "^2.0.0" - resolve-from "^3.0.0" - -import-fresh@^3.0.0: - version "3.3.0" - resolved "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.0.tgz" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -import-from@^2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/import-from/-/import-from-2.1.0.tgz" - integrity sha512-0vdnLL2wSGnhlRmzHJAg5JHjt1l2vYhzJ7tNLGbeVg0fse56tpGaH0uzH+r9Slej+BSXXEHvBKDEnVSLLE9/+w== - dependencies: - resolve-from "^3.0.0" - -import-local@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/import-local/-/import-local-2.0.0.tgz" - integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== - dependencies: - pkg-dir "^3.0.0" - resolve-cwd "^2.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - -indexes-of@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/indexes-of/-/indexes-of-1.0.1.tgz" - integrity sha512-bup+4tap3Hympa+JBJUG7XuOsdNQ6fxt0MHyXMKuLBKn0OqsTfvUxkUrroEX1+B2VsSHvCjiIcZVxRtYa4nllA== - -infer-owner@^1.0.3: - version "1.0.4" - resolved "https://registry.npmmirror.com/infer-owner/-/infer-owner-1.0.4.tgz" - integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.3.tgz" - integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== - -inquirer@^7.0.0, inquirer@^7.1.0: - version "7.3.3" - resolved "https://registry.npmmirror.com/inquirer/-/inquirer-7.3.3.tgz" - integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== - dependencies: - ansi-escapes "^4.2.1" - chalk "^4.1.0" - cli-cursor "^3.1.0" - cli-width "^3.0.0" - external-editor "^3.0.3" - figures "^3.0.0" - lodash "^4.17.19" - mute-stream "0.0.8" - run-async "^2.4.0" - rxjs "^6.6.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - through "^2.3.6" - -internal-ip@^4.3.0: - version "4.3.0" - resolved "https://registry.npmmirror.com/internal-ip/-/internal-ip-4.3.0.tgz" - integrity sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg== - dependencies: - default-gateway "^4.2.0" - ipaddr.js "^1.9.0" - -internal-slot@^1.0.7: - version "1.0.7" - resolved "https://registry.npmmirror.com/internal-slot/-/internal-slot-1.0.7.tgz" - integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== - dependencies: - es-errors "^1.3.0" - hasown "^2.0.0" - side-channel "^1.0.4" - -ip-regex@^2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/ip-regex/-/ip-regex-2.1.0.tgz" - integrity sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw== - -ip@^1.1.0, ip@^1.1.5: - version "1.1.9" - resolved "https://registry.npmmirror.com/ip/-/ip-1.1.9.tgz" - integrity sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ== - -ipaddr.js@1.9.1, ipaddr.js@^1.9.0: - version "1.9.1" - resolved "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -is-absolute-url@^2.0.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz" - integrity sha512-vOx7VprsKyllwjSkLV79NIhpyLfr3jAp7VaTCMXOJHu4m0Ew1CZ2fcjASwmV1jI3BWuWHB013M48eyeldk9gYg== - -is-absolute-url@^3.0.3: - version "3.0.3" - resolved "https://registry.npmmirror.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz" - integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== - -is-accessor-descriptor@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.1.tgz" - integrity sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA== - dependencies: - hasown "^2.0.0" - -is-arguments@^1.1.1: - version "1.1.1" - resolved "https://registry.npmmirror.com/is-arguments/-/is-arguments-1.1.1.tgz" - integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-array-buffer@^3.0.4: - version "3.0.4" - resolved "https://registry.npmmirror.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz" - integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.1" - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.3.2.tgz" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== - -is-bigint@^1.0.1: - version "1.0.4" - resolved "https://registry.npmmirror.com/is-bigint/-/is-bigint-1.0.4.tgz" - integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== - dependencies: - has-bigints "^1.0.1" - -is-binary-path@^1.0.0: - version "1.0.1" - resolved "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-1.0.1.tgz" - integrity sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q== - dependencies: - binary-extensions "^1.0.0" - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-boolean-object@^1.1.0: - version "1.1.2" - resolved "https://registry.npmmirror.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz" - integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - -is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: - version "1.2.7" - resolved "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz" - integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== - -is-ci@^1.0.10: - version "1.2.1" - resolved "https://registry.npmmirror.com/is-ci/-/is-ci-1.2.1.tgz" - integrity sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg== - dependencies: - ci-info "^1.5.0" - -is-color-stop@^1.0.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/is-color-stop/-/is-color-stop-1.1.0.tgz" - integrity sha512-H1U8Vz0cfXNujrJzEcvvwMDW9Ra+biSYA3ThdQvAnMLJkEHQXn6bWzLkxHtVYJ+Sdbx0b6finn3jZiaVe7MAHA== - dependencies: - css-color-names "^0.0.4" - hex-color-regex "^1.1.0" - hsl-regex "^1.0.0" - hsla-regex "^1.0.0" - rgb-regex "^1.0.1" - rgba-regex "^1.0.0" - -is-core-module@^2.13.0: - version "2.13.1" - resolved "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.13.1.tgz" - integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== - dependencies: - hasown "^2.0.0" - -is-data-descriptor@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/is-data-descriptor/-/is-data-descriptor-1.0.1.tgz" - integrity sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw== - dependencies: - hasown "^2.0.0" - -is-data-view@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/is-data-view/-/is-data-view-1.0.1.tgz" - integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w== - dependencies: - is-typed-array "^1.1.13" - -is-date-object@^1.0.1, is-date-object@^1.0.5: - version "1.0.5" - resolved "https://registry.npmmirror.com/is-date-object/-/is-date-object-1.0.5.tgz" - integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== - dependencies: - has-tostringtag "^1.0.0" - -is-descriptor@^0.1.0: - version "0.1.7" - resolved "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-0.1.7.tgz" - integrity sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg== - dependencies: - is-accessor-descriptor "^1.0.1" - is-data-descriptor "^1.0.1" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.3" - resolved "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-1.0.3.tgz" - integrity sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw== - dependencies: - is-accessor-descriptor "^1.0.1" - is-data-descriptor "^1.0.1" - -is-directory@^0.3.1: - version "0.3.1" - resolved "https://registry.npmmirror.com/is-directory/-/is-directory-0.3.1.tgz" - integrity sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw== - -is-docker@^2.0.0: - version "2.2.1" - resolved "https://registry.npmmirror.com/is-docker/-/is-docker-2.2.1.tgz" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.npmmirror.com/is-extendable/-/is-extendable-0.1.1.tgz" - integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/is-extendable/-/is-extendable-1.0.1.tgz" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== - dependencies: - is-plain-object "^2.0.4" - -is-extglob@^2.1.0, is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz" - integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^3.1.0: - version "3.1.0" - resolved "https://registry.npmmirror.com/is-glob/-/is-glob-3.1.0.tgz" - integrity sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw== - dependencies: - is-extglob "^2.1.0" - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-negative-zero@^2.0.3: - version "2.0.3" - resolved "https://registry.npmmirror.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz" - integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== - -is-number-object@^1.0.4: - version "1.0.7" - resolved "https://registry.npmmirror.com/is-number-object/-/is-number-object-1.0.7.tgz" - integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== - dependencies: - has-tostringtag "^1.0.0" - -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/is-number/-/is-number-3.0.0.tgz" - integrity sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg== - dependencies: - kind-of "^3.0.2" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/is-obj/-/is-obj-2.0.0.tgz" - integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== - -is-path-cwd@^2.0.0: - version "2.2.0" - resolved "https://registry.npmmirror.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz" - integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== - -is-path-in-cwd@^2.0.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz" - integrity sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ== - dependencies: - is-path-inside "^2.1.0" - -is-path-inside@^2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/is-path-inside/-/is-path-inside-2.1.0.tgz" - integrity sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg== - dependencies: - path-is-inside "^1.0.2" - -is-plain-obj@^1.0.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz" - integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== - -is-plain-obj@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz" - integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== - -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-regex@^1.1.4: - version "1.1.4" - resolved "https://registry.npmmirror.com/is-regex/-/is-regex-1.1.4.tgz" - integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-resolvable@^1.0.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/is-resolvable/-/is-resolvable-1.1.0.tgz" - integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== - -is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: - version "1.0.3" - resolved "https://registry.npmmirror.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz" - integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== - dependencies: - call-bind "^1.0.7" - -is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/is-stream/-/is-stream-1.1.0.tgz" - integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.npmmirror.com/is-stream/-/is-stream-2.0.1.tgz" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-string@^1.0.5, is-string@^1.0.7: - version "1.0.7" - resolved "https://registry.npmmirror.com/is-string/-/is-string-1.0.7.tgz" - integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== - dependencies: - has-tostringtag "^1.0.0" - -is-symbol@^1.0.2, is-symbol@^1.0.3: - version "1.0.4" - resolved "https://registry.npmmirror.com/is-symbol/-/is-symbol-1.0.4.tgz" - integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== - dependencies: - has-symbols "^1.0.2" - -is-typed-array@^1.1.13: - version "1.1.13" - resolved "https://registry.npmmirror.com/is-typed-array/-/is-typed-array-1.1.13.tgz" - integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== - dependencies: - which-typed-array "^1.1.14" - -is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/is-typedarray/-/is-typedarray-1.0.0.tgz" - integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== - -is-weakref@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/is-weakref/-/is-weakref-1.0.2.tgz" - integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== - dependencies: - call-bind "^1.0.2" - -is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/is-windows/-/is-windows-1.0.2.tgz" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - -is-wsl@^1.1.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/is-wsl/-/is-wsl-1.1.0.tgz" - integrity sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw== - -is-wsl@^2.1.1: - version "2.2.0" - resolved "https://registry.npmmirror.com/is-wsl/-/is-wsl-2.2.0.tgz" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - -isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.npmmirror.com/isarray/-/isarray-2.0.5.tgz" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/isobject/-/isobject-2.1.0.tgz" - integrity sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA== - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.npmmirror.com/isobject/-/isobject-3.0.1.tgz" - integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== - -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.npmmirror.com/isstream/-/isstream-0.1.2.tgz" - integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== - -javascript-stringify@^2.0.1: - version "2.1.0" - resolved "https://registry.npmmirror.com/javascript-stringify/-/javascript-stringify-2.1.0.tgz" - integrity sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg== - -js-message@1.0.7: - version "1.0.7" - resolved "https://registry.npmmirror.com/js-message/-/js-message-1.0.7.tgz" - integrity sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA== - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.14.1.tgz" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.npmmirror.com/jsbn/-/jsbn-0.1.1.tgz" - integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== - -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.npmmirror.com/jsesc/-/jsesc-2.5.2.tgz" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - -jsesc@~0.5.0: - version "0.5.0" - resolved "https://registry.npmmirror.com/jsesc/-/jsesc-0.5.0.tgz" - integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== - -json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz" - integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== - -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema@0.4.0: - version "0.4.0" - resolved "https://registry.npmmirror.com/json-schema/-/json-schema-0.4.0.tgz" - integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" - integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== - -json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.npmmirror.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" - integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== - -json5@^0.5.0: - version "0.5.1" - resolved "https://registry.npmmirror.com/json5/-/json5-0.5.1.tgz" - integrity sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw== - -json5@^1.0.1: - version "1.0.2" - resolved "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz" - integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== - dependencies: - minimist "^1.2.0" - -json5@^2.1.2, json5@^2.2.3: - version "2.2.3" - resolved "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - -jsonfile@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz" - integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== - optionalDependencies: - graceful-fs "^4.1.6" - -jsprim@^1.2.2: - version "1.4.2" - resolved "https://registry.npmmirror.com/jsprim/-/jsprim-1.4.2.tgz" - integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.4.0" - verror "1.10.0" - -killable@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/killable/-/killable-1.0.1.tgz" - integrity sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg== - -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz" - integrity sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ== - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/kind-of/-/kind-of-4.0.0.tgz" - integrity sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw== - dependencies: - is-buffer "^1.1.5" - -kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -klona@^2.0.4: - version "2.0.6" - resolved "https://registry.npmmirror.com/klona/-/klona-2.0.6.tgz" - integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA== - -launch-editor-middleware@^2.2.1: - version "2.6.1" - resolved "https://registry.npmmirror.com/launch-editor-middleware/-/launch-editor-middleware-2.6.1.tgz" - integrity sha512-Fg/xYhf7ARmRp40n18wIfJyuAMEjXo67Yull7uF7d0OJ3qA4EYJISt1XfPPn69IIJ5jKgQwzcg6DqHYo95LL/g== - dependencies: - launch-editor "^2.6.1" - -launch-editor@^2.2.1, launch-editor@^2.6.1: - version "2.6.1" - resolved "https://registry.npmmirror.com/launch-editor/-/launch-editor-2.6.1.tgz" - integrity sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw== - dependencies: - picocolors "^1.0.0" - shell-quote "^1.8.1" - -levn@^0.3.0, levn@~0.3.0: - version "0.3.0" - resolved "https://registry.npmmirror.com/levn/-/levn-0.3.0.tgz" - integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== - dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" - -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - -loader-fs-cache@^1.0.0: - version "1.0.3" - resolved "https://registry.npmmirror.com/loader-fs-cache/-/loader-fs-cache-1.0.3.tgz" - integrity sha512-ldcgZpjNJj71n+2Mf6yetz+c9bM4xpKtNds4LbqXzU/PTdeAX0g3ytnU1AJMEcTk2Lex4Smpe3Q/eCTsvUBxbA== - dependencies: - find-cache-dir "^0.1.1" - mkdirp "^0.5.1" - -loader-runner@^2.3.1, loader-runner@^2.4.0: - version "2.4.0" - resolved "https://registry.npmmirror.com/loader-runner/-/loader-runner-2.4.0.tgz" - integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== - -loader-utils@^0.2.16: - version "0.2.17" - resolved "https://registry.npmmirror.com/loader-utils/-/loader-utils-0.2.17.tgz" - integrity sha512-tiv66G0SmiOx+pLWMtGEkfSEejxvb6N6uRrQjfWJIT79W9GMpgKeCAmm9aVBKtd4WEgntciI8CsGqjpDoCWJug== - dependencies: - big.js "^3.1.3" - emojis-list "^2.0.0" - json5 "^0.5.0" - object-assign "^4.0.1" - -loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3: - version "1.4.2" - resolved "https://registry.npmmirror.com/loader-utils/-/loader-utils-1.4.2.tgz" - integrity sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^1.0.1" - -loader-utils@^2.0.0: - version "2.0.4" - resolved "https://registry.npmmirror.com/loader-utils/-/loader-utils-2.0.4.tgz" - integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^2.1.2" - -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/locate-path/-/locate-path-3.0.0.tgz" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== - dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -lodash.debounce@^4.0.8: - version "4.0.8" - resolved "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz" - integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== - -lodash.defaultsdeep@^4.6.1: - version "4.6.1" - resolved "https://registry.npmmirror.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz" - integrity sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA== - -lodash.kebabcase@^4.1.1: - version "4.1.1" - resolved "https://registry.npmmirror.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz" - integrity sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g== - -lodash.mapvalues@^4.6.0: - version "4.6.0" - resolved "https://registry.npmmirror.com/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz" - integrity sha512-JPFqXFeZQ7BfS00H58kClY7SPVeHertPE0lNuCyZ26/XlN8TvakYD7b9bGyNmXbT/D3BbtPAAmq90gPWqLkxlQ== - -lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.npmmirror.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz" - integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== - -lodash.transform@^4.6.0: - version "4.6.0" - resolved "https://registry.npmmirror.com/lodash.transform/-/lodash.transform-4.6.0.tgz" - integrity sha512-LO37ZnhmBVx0GvOU/caQuipEh4GN82TcWv3yHlebGDgOxbxiwwzW5Pcx2AcvpIv2WmvmSMoC492yQFNhy/l/UQ== - -lodash.uniq@^4.5.0: - version "4.5.0" - resolved "https://registry.npmmirror.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz" - integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== - -lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.3: - version "4.17.21" - resolved "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -log-symbols@^2.2.0: - version "2.2.0" - resolved "https://registry.npmmirror.com/log-symbols/-/log-symbols-2.2.0.tgz" - integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== - dependencies: - chalk "^2.0.1" - -loglevel@^1.6.8: - version "1.9.1" - resolved "https://registry.npmmirror.com/loglevel/-/loglevel-1.9.1.tgz" - integrity sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg== - -lower-case@^1.1.1: - version "1.1.4" - resolved "https://registry.npmmirror.com/lower-case/-/lower-case-1.1.4.tgz" - integrity sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA== - -lru-cache@^4.0.1, lru-cache@^4.1.2: - version "4.1.5" - resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-4.1.5.tgz" - integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== - dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -magic-string@^0.30.10: - version "0.30.10" - resolved "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.10.tgz" - integrity sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ== - dependencies: - "@jridgewell/sourcemap-codec" "^1.4.15" - -make-dir@^2.0.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz" - integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== - dependencies: - pify "^4.0.1" - semver "^5.6.0" - -make-dir@^3.0.2, make-dir@^3.1.0: - version "3.1.0" - resolved "https://registry.npmmirror.com/make-dir/-/make-dir-3.1.0.tgz" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== - dependencies: - semver "^6.0.0" - -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.npmmirror.com/map-cache/-/map-cache-0.2.2.tgz" - integrity sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg== - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/map-visit/-/map-visit-1.0.0.tgz" - integrity sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w== - dependencies: - object-visit "^1.0.0" - -math-intrinsics@^1.1.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" - integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== - -md5.js@^1.3.4: - version "1.3.5" - resolved "https://registry.npmmirror.com/md5.js/-/md5.js-1.3.5.tgz" - integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" - -mdn-data@2.0.14: - version "2.0.14" - resolved "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.14.tgz" - integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== - -mdn-data@2.0.4: - version "2.0.4" - resolved "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.4.tgz" - integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - -memory-fs@^0.4.1: - version "0.4.1" - resolved "https://registry.npmmirror.com/memory-fs/-/memory-fs-0.4.1.tgz" - integrity sha512-cda4JKCxReDXFXRqOHPQscuIYg1PvxbE2S2GP45rnwfEK+vZaXC8C1OFvdHIbgw0DLzowXGVoxLaAmlgRy14GQ== - dependencies: - errno "^0.1.3" - readable-stream "^2.0.1" - -memory-fs@^0.5.0: - version "0.5.0" - resolved "https://registry.npmmirror.com/memory-fs/-/memory-fs-0.5.0.tgz" - integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== - dependencies: - errno "^0.1.3" - readable-stream "^2.0.1" - -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz" - integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== - -merge-source-map@^1.1.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/merge-source-map/-/merge-source-map-1.1.0.tgz" - integrity sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw== - dependencies: - source-map "^0.6.1" - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -merge2@^1.2.3: - version "1.4.1" - resolved "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.npmmirror.com/methods/-/methods-1.1.2.tgz" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - -micromatch@^3.1.10, micromatch@^3.1.4: - version "3.1.10" - resolved "https://registry.npmmirror.com/micromatch/-/micromatch-3.1.10.tgz" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - -micromatch@^4.0.2: - version "4.0.7" - resolved "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.7.tgz" - integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q== - dependencies: - braces "^3.0.3" - picomatch "^2.3.1" - -miller-rabin@^4.0.0: - version "4.0.1" - resolved "https://registry.npmmirror.com/miller-rabin/-/miller-rabin-4.0.1.tgz" - integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== - dependencies: - bn.js "^4.0.0" - brorand "^1.0.1" - -mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": - version "1.52.0" - resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -mime@^2.4.4: - version "2.6.0" - resolved "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz" - integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== - -mimic-fn@^1.0.0: - version "1.2.0" - resolved "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-1.2.0.tgz" - integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -mini-css-extract-plugin@^0.9.0: - version "0.9.0" - resolved "https://registry.npmmirror.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz" - integrity sha512-lp3GeY7ygcgAmVIcRPBVhIkf8Us7FZjA+ILpal44qLdSu11wmjKQ3d9k15lfD7pO4esu9eUIAW7qiYIBppv40A== - dependencies: - loader-utils "^1.1.0" - normalize-url "1.9.1" - schema-utils "^1.0.0" - webpack-sources "^1.1.0" - -minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - -minimalistic-crypto-utils@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz" - integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== - -minimatch@^3.0.4, minimatch@^3.1.1: - version "3.1.2" - resolved "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: - version "1.2.8" - resolved "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -minipass@^3.1.1: - version "3.3.6" - resolved "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz" - integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== - dependencies: - yallist "^4.0.0" - -mississippi@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/mississippi/-/mississippi-3.0.0.tgz" - integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA== - dependencies: - concat-stream "^1.5.0" - duplexify "^3.4.2" - end-of-stream "^1.1.0" - flush-write-stream "^1.0.0" - from2 "^2.1.0" - parallel-transform "^1.1.0" - pump "^3.0.0" - pumpify "^1.3.3" - stream-each "^1.1.0" - through2 "^2.0.0" - -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.npmmirror.com/mixin-deep/-/mixin-deep-1.3.2.tgz" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - -mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.6, mkdirp@~0.5.1: - version "0.5.6" - resolved "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz" - integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== - dependencies: - minimist "^1.2.6" - -moment@^2.29.4: - version "2.30.1" - resolved "https://registry.npmmirror.com/moment/-/moment-2.30.1.tgz" - integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== - -move-concurrently@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/move-concurrently/-/move-concurrently-1.0.1.tgz" - integrity sha512-hdrFxZOycD/g6A6SoI2bB5NA/5NEqD0569+S47WZhPvm46sD50ZHdYaFmnua5lndde9rCHGjmfK7Z8BuCt/PcQ== - dependencies: - aproba "^1.1.1" - copy-concurrently "^1.0.0" - fs-write-stream-atomic "^1.0.8" - mkdirp "^0.5.1" - rimraf "^2.5.4" - run-queue "^1.0.3" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - -ms@2.1.2, ms@^2.1.1: - version "2.1.2" - resolved "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@2.1.3: - version "2.1.3" - resolved "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -multicast-dns-service-types@^1.1.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz" - integrity sha512-cnAsSVxIDsYt0v7HmC0hWZFwwXSh+E6PgCrREDuN/EsjgLwA5XRmlMHhSiDPrt6HxY1gTivEa/Zh7GtODoLevQ== - -multicast-dns@^6.0.1: - version "6.2.3" - resolved "https://registry.npmmirror.com/multicast-dns/-/multicast-dns-6.2.3.tgz" - integrity sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g== - dependencies: - dns-packet "^1.3.1" - thunky "^1.0.2" - -mute-stream@0.0.8: - version "0.0.8" - resolved "https://registry.npmmirror.com/mute-stream/-/mute-stream-0.0.8.tgz" - integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== - -mz@^2.4.0: - version "2.7.0" - resolved "https://registry.npmmirror.com/mz/-/mz-2.7.0.tgz" - integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== - dependencies: - any-promise "^1.0.0" - object-assign "^4.0.1" - thenify-all "^1.0.0" - -nan@^2.12.1: - version "2.19.0" - resolved "https://registry.npmmirror.com/nan/-/nan-2.19.0.tgz" - integrity sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw== - -nanoid@^3.3.7: - version "3.3.7" - resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz" - integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== - -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.npmmirror.com/nanomatch/-/nanomatch-1.2.13.tgz" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz" - integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== - -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - -neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1, neo-async@^2.6.2: - version "2.6.2" - resolved "https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.npmmirror.com/nice-try/-/nice-try-1.0.5.tgz" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== - -no-case@^2.2.0: - version "2.3.2" - resolved "https://registry.npmmirror.com/no-case/-/no-case-2.3.2.tgz" - integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ== - dependencies: - lower-case "^1.1.1" - -node-forge@^0.10.0: - version "0.10.0" - resolved "https://registry.npmmirror.com/node-forge/-/node-forge-0.10.0.tgz" - integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== - -node-libs-browser@^2.2.1: - version "2.2.1" - resolved "https://registry.npmmirror.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz" - integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== - dependencies: - assert "^1.1.1" - browserify-zlib "^0.2.0" - buffer "^4.3.0" - console-browserify "^1.1.0" - constants-browserify "^1.0.0" - crypto-browserify "^3.11.0" - domain-browser "^1.1.1" - events "^3.0.0" - https-browserify "^1.0.0" - os-browserify "^0.3.0" - path-browserify "0.0.1" - process "^0.11.10" - punycode "^1.2.4" - querystring-es3 "^0.2.0" - readable-stream "^2.3.3" - stream-browserify "^2.0.1" - stream-http "^2.7.2" - string_decoder "^1.0.0" - timers-browserify "^2.0.4" - tty-browserify "0.0.0" - url "^0.11.0" - util "^0.11.0" - vm-browserify "^1.0.1" - -node-releases@^2.0.14: - version "2.0.14" - resolved "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.14.tgz" - integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== - -normalize-package-data@^2.5.0: - version "2.5.0" - resolved "https://registry.npmmirror.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz" - integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - -normalize-path@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/normalize-path/-/normalize-path-1.0.0.tgz" - integrity sha512-7WyT0w8jhpDStXRq5836AMmihQwq2nrUVQrgjvUo/p/NZf9uy/MeJ246lBJVmWuYXMlJuG9BNZHF0hWjfTbQUA== - -normalize-path@^2.1.1: - version "2.1.1" - resolved "https://registry.npmmirror.com/normalize-path/-/normalize-path-2.1.1.tgz" - integrity sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w== - dependencies: - remove-trailing-separator "^1.0.1" - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -normalize-range@^0.1.2: - version "0.1.2" - resolved "https://registry.npmmirror.com/normalize-range/-/normalize-range-0.1.2.tgz" - integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== - -normalize-url@1.9.1: - version "1.9.1" - resolved "https://registry.npmmirror.com/normalize-url/-/normalize-url-1.9.1.tgz" - integrity sha512-A48My/mtCklowHBlI8Fq2jFWK4tX4lJ5E6ytFsSOq1fzpvT0SQSgKhSg7lN5c2uYFOrUAOQp6zhhJnpp1eMloQ== - dependencies: - object-assign "^4.0.1" - prepend-http "^1.0.0" - query-string "^4.1.0" - sort-keys "^1.0.0" - -normalize-url@^3.0.0: - version "3.3.0" - resolved "https://registry.npmmirror.com/normalize-url/-/normalize-url-3.3.0.tgz" - integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== - -normalize-wheel@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/normalize-wheel/-/normalize-wheel-1.0.1.tgz" - integrity sha512-1OnlAPZ3zgrk8B91HyRj+eVv+kS5u+Z0SCsak6Xil/kmgEia50ga7zfkumayonZrImffAxPU/5WcyGhzetHNPA== - -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-2.0.2.tgz" - integrity sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw== - dependencies: - path-key "^2.0.0" - -npm-run-path@^4.0.0: - version "4.0.1" - resolved "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-4.0.1.tgz" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -nth-check@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/nth-check/-/nth-check-1.0.2.tgz" - integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== - dependencies: - boolbase "~1.0.0" - -nth-check@^2.0.1: - version "2.1.1" - resolved "https://registry.npmmirror.com/nth-check/-/nth-check-2.1.1.tgz" - integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== - dependencies: - boolbase "^1.0.0" - -num2fraction@^1.2.2: - version "1.2.2" - resolved "https://registry.npmmirror.com/num2fraction/-/num2fraction-1.2.2.tgz" - integrity sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg== - -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.npmmirror.com/oauth-sign/-/oauth-sign-0.9.0.tgz" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== - -object-assign@^4.0.1, object-assign@^4.1.0: - version "4.1.1" - resolved "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.npmmirror.com/object-copy/-/object-copy-0.1.0.tgz" - integrity sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ== - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - -object-hash@^1.1.4: - version "1.3.1" - resolved "https://registry.npmmirror.com/object-hash/-/object-hash-1.3.1.tgz" - integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA== - -object-inspect@^1.13.1: - version "1.13.1" - resolved "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.1.tgz" - integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== - -object-is@^1.1.5: - version "1.1.6" - resolved "https://registry.npmmirror.com/object-is/-/object-is-1.1.6.tgz" - integrity sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - -object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.npmmirror.com/object-visit/-/object-visit-1.0.1.tgz" - integrity sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA== - dependencies: - isobject "^3.0.0" - -object.assign@^4.1.0, object.assign@^4.1.4, object.assign@^4.1.5: - version "4.1.5" - resolved "https://registry.npmmirror.com/object.assign/-/object.assign-4.1.5.tgz" - integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== - dependencies: - call-bind "^1.0.5" - define-properties "^1.2.1" - has-symbols "^1.0.3" - object-keys "^1.1.1" - -object.getownpropertydescriptors@^2.0.3: - version "2.1.8" - resolved "https://registry.npmmirror.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.8.tgz" - integrity sha512-qkHIGe4q0lSYMv0XI4SsBTJz3WaURhLvd0lKSgtVuOsJ2krg4SgMw3PIRQFMp07yi++UR3se2mkcLqsBNpBb/A== - dependencies: - array.prototype.reduce "^1.0.6" - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - es-object-atoms "^1.0.0" - gopd "^1.0.1" - safe-array-concat "^1.1.2" - -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.npmmirror.com/object.pick/-/object.pick-1.3.0.tgz" - integrity sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ== - dependencies: - isobject "^3.0.1" - -object.values@^1.1.0: - version "1.2.0" - resolved "https://registry.npmmirror.com/object.values/-/object.values-1.2.0.tgz" - integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - -obuf@^1.0.0, obuf@^1.1.2: - version "1.1.2" - resolved "https://registry.npmmirror.com/obuf/-/obuf-1.1.2.tgz" - integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== - -on-finished@2.4.1: - version "2.4.1" - resolved "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/on-headers/-/on-headers-1.0.2.tgz" - integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== - -once@^1.3.0, once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.npmmirror.com/once/-/once-1.4.0.tgz" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -onetime@^2.0.0: - version "2.0.1" - resolved "https://registry.npmmirror.com/onetime/-/onetime-2.0.1.tgz" - integrity sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ== - dependencies: - mimic-fn "^1.0.0" - -onetime@^5.1.0: - version "5.1.2" - resolved "https://registry.npmmirror.com/onetime/-/onetime-5.1.2.tgz" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -open@^6.3.0: - version "6.4.0" - resolved "https://registry.npmmirror.com/open/-/open-6.4.0.tgz" - integrity sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg== - dependencies: - is-wsl "^1.1.0" - -opener@^1.5.1: - version "1.5.2" - resolved "https://registry.npmmirror.com/opener/-/opener-1.5.2.tgz" - integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== - -opn@^5.5.0: - version "5.5.0" - resolved "https://registry.npmmirror.com/opn/-/opn-5.5.0.tgz" - integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA== - dependencies: - is-wsl "^1.1.0" - -optionator@^0.8.3: - version "0.8.3" - resolved "https://registry.npmmirror.com/optionator/-/optionator-0.8.3.tgz" - integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== - dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.6" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - word-wrap "~1.2.3" - -ora@^3.4.0: - version "3.4.0" - resolved "https://registry.npmmirror.com/ora/-/ora-3.4.0.tgz" - integrity sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg== - dependencies: - chalk "^2.4.2" - cli-cursor "^2.1.0" - cli-spinners "^2.0.0" - log-symbols "^2.2.0" - strip-ansi "^5.2.0" - wcwidth "^1.0.1" - -os-browserify@^0.3.0: - version "0.3.0" - resolved "https://registry.npmmirror.com/os-browserify/-/os-browserify-0.3.0.tgz" - integrity sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A== - -os-tmpdir@~1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz" - integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/p-finally/-/p-finally-1.0.0.tgz" - integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== - -p-finally@^2.0.0: - version "2.0.1" - resolved "https://registry.npmmirror.com/p-finally/-/p-finally-2.0.1.tgz" - integrity sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw== - -p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.2.1: - version "2.3.0" - resolved "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/p-locate/-/p-locate-3.0.0.tgz" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== - dependencies: - p-limit "^2.0.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-map@^2.0.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/p-map/-/p-map-2.1.0.tgz" - integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== - -p-retry@^3.0.1: - version "3.0.1" - resolved "https://registry.npmmirror.com/p-retry/-/p-retry-3.0.1.tgz" - integrity sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w== - dependencies: - retry "^0.12.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -pako@~1.0.5: - version "1.0.11" - resolved "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz" - integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== - -parallel-transform@^1.1.0: - version "1.2.0" - resolved "https://registry.npmmirror.com/parallel-transform/-/parallel-transform-1.2.0.tgz" - integrity sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg== - dependencies: - cyclist "^1.0.1" - inherits "^2.0.3" - readable-stream "^2.1.5" - -param-case@2.1.x: - version "2.1.1" - resolved "https://registry.npmmirror.com/param-case/-/param-case-2.1.1.tgz" - integrity sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w== - dependencies: - no-case "^2.2.0" - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parse-asn1@^5.0.0, parse-asn1@^5.1.7: - version "5.1.7" - resolved "https://registry.npmmirror.com/parse-asn1/-/parse-asn1-5.1.7.tgz" - integrity sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg== - dependencies: - asn1.js "^4.10.1" - browserify-aes "^1.2.0" - evp_bytestokey "^1.0.3" - hash-base "~3.0" - pbkdf2 "^3.1.2" - safe-buffer "^5.2.1" - -parse-json@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/parse-json/-/parse-json-4.0.0.tgz" - integrity sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw== - dependencies: - error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" - -parse-json@^5.0.0: - version "5.2.0" - resolved "https://registry.npmmirror.com/parse-json/-/parse-json-5.2.0.tgz" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - -parse5-htmlparser2-tree-adapter@^6.0.0: - version "6.0.1" - resolved "https://registry.npmmirror.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz" - integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== - dependencies: - parse5 "^6.0.1" - -parse5@^5.1.1: - version "5.1.1" - resolved "https://registry.npmmirror.com/parse5/-/parse5-5.1.1.tgz" - integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== - -parse5@^6.0.1: - version "6.0.1" - resolved "https://registry.npmmirror.com/parse5/-/parse5-6.0.1.tgz" - integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== - -parseurl@~1.3.2, parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.npmmirror.com/pascalcase/-/pascalcase-0.1.1.tgz" - integrity sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw== - -path-browserify@0.0.1: - version "0.0.1" - resolved "https://registry.npmmirror.com/path-browserify/-/path-browserify-0.0.1.tgz" - integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== - -path-dirname@^1.0.0: - version "1.0.2" - resolved "https://registry.npmmirror.com/path-dirname/-/path-dirname-1.0.2.tgz" - integrity sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q== - -path-exists@^2.0.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/path-exists/-/path-exists-2.1.0.tgz" - integrity sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ== - dependencies: - pinkie-promise "^2.0.0" - -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/path-exists/-/path-exists-3.0.0.tgz" - integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-is-inside@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/path-is-inside/-/path-is-inside-1.0.2.tgz" - integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w== - -path-key@^2.0.0, path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.npmmirror.com/path-key/-/path-key-2.0.1.tgz" - integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz" - integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== - -path-type@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/path-type/-/path-type-3.0.0.tgz" - integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== - dependencies: - pify "^3.0.0" - -pbkdf2@^3.0.3, pbkdf2@^3.1.2: - version "3.1.2" - resolved "https://registry.npmmirror.com/pbkdf2/-/pbkdf2-3.1.2.tgz" - integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== - dependencies: - create-hash "^1.1.2" - create-hmac "^1.1.4" - ripemd160 "^2.0.1" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/performance-now/-/performance-now-2.1.0.tgz" - integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== - -picocolors@^0.2.1: - version "0.2.1" - resolved "https://registry.npmmirror.com/picocolors/-/picocolors-0.2.1.tgz" - integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA== - -picocolors@^1.0.0, picocolors@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.1.tgz" - integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== - -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pify@^2.0.0: - version "2.3.0" - resolved "https://registry.npmmirror.com/pify/-/pify-2.3.0.tgz" - integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== - -pify@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/pify/-/pify-3.0.0.tgz" - integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg== - -pify@^4.0.1: - version "4.0.1" - resolved "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz" - integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== - -pinkie-promise@^2.0.0: - version "2.0.1" - resolved "https://registry.npmmirror.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz" - integrity sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw== - dependencies: - pinkie "^2.0.0" - -pinkie@^2.0.0: - version "2.0.4" - resolved "https://registry.npmmirror.com/pinkie/-/pinkie-2.0.4.tgz" - integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg== - -pkg-dir@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-1.0.0.tgz" - integrity sha512-c6pv3OE78mcZ92ckebVDqg0aWSoKhOTbwCV6qbCWMk546mAL9pZln0+QsN/yQ7fkucd4+yJPLrCBXNt8Ruk+Eg== - dependencies: - find-up "^1.0.0" - -pkg-dir@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-3.0.0.tgz" - integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== - dependencies: - find-up "^3.0.0" - -pkg-dir@^4.1.0: - version "4.2.0" - resolved "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-4.2.0.tgz" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -pnp-webpack-plugin@^1.6.4: - version "1.7.0" - resolved "https://registry.npmmirror.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.7.0.tgz" - integrity sha512-2Rb3vm+EXble/sMXNSu6eoBx8e79gKqhNq9F5ZWW6ERNCTE/Q0wQNne5541tE5vKjfM8hpNCYL+LGc1YTfI0dg== - dependencies: - ts-pnp "^1.1.6" - -portfinder@^1.0.26: - version "1.0.32" - resolved "https://registry.npmmirror.com/portfinder/-/portfinder-1.0.32.tgz" - integrity sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg== - dependencies: - async "^2.6.4" - debug "^3.2.7" - mkdirp "^0.5.6" - -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.npmmirror.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz" - integrity sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg== - -possible-typed-array-names@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz" - integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== - -postcss-calc@^7.0.1: - version "7.0.5" - resolved "https://registry.npmmirror.com/postcss-calc/-/postcss-calc-7.0.5.tgz" - integrity sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg== - dependencies: - postcss "^7.0.27" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.0.2" - -postcss-colormin@^4.0.3: - version "4.0.3" - resolved "https://registry.npmmirror.com/postcss-colormin/-/postcss-colormin-4.0.3.tgz" - integrity sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw== - dependencies: - browserslist "^4.0.0" - color "^3.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-convert-values@^4.0.1: - version "4.0.1" - resolved "https://registry.npmmirror.com/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz" - integrity sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ== - dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-discard-comments@^4.0.2: - version "4.0.2" - resolved "https://registry.npmmirror.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz" - integrity sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg== - dependencies: - postcss "^7.0.0" - -postcss-discard-duplicates@^4.0.2: - version "4.0.2" - resolved "https://registry.npmmirror.com/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz" - integrity sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ== - dependencies: - postcss "^7.0.0" - -postcss-discard-empty@^4.0.1: - version "4.0.1" - resolved "https://registry.npmmirror.com/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz" - integrity sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w== - dependencies: - postcss "^7.0.0" - -postcss-discard-overridden@^4.0.1: - version "4.0.1" - resolved "https://registry.npmmirror.com/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz" - integrity sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg== - dependencies: - postcss "^7.0.0" - -postcss-load-config@^2.0.0: - version "2.1.2" - resolved "https://registry.npmmirror.com/postcss-load-config/-/postcss-load-config-2.1.2.tgz" - integrity sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw== - dependencies: - cosmiconfig "^5.0.0" - import-cwd "^2.0.0" - -postcss-loader@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/postcss-loader/-/postcss-loader-3.0.0.tgz" - integrity sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA== - dependencies: - loader-utils "^1.1.0" - postcss "^7.0.0" - postcss-load-config "^2.0.0" - schema-utils "^1.0.0" - -postcss-merge-longhand@^4.0.11: - version "4.0.11" - resolved "https://registry.npmmirror.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz" - integrity sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw== - dependencies: - css-color-names "0.0.4" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - stylehacks "^4.0.0" - -postcss-merge-rules@^4.0.3: - version "4.0.3" - resolved "https://registry.npmmirror.com/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz" - integrity sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ== - dependencies: - browserslist "^4.0.0" - caniuse-api "^3.0.0" - cssnano-util-same-parent "^4.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" - vendors "^1.0.0" - -postcss-minify-font-values@^4.0.2: - version "4.0.2" - resolved "https://registry.npmmirror.com/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz" - integrity sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg== - dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-minify-gradients@^4.0.2: - version "4.0.2" - resolved "https://registry.npmmirror.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz" - integrity sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q== - dependencies: - cssnano-util-get-arguments "^4.0.0" - is-color-stop "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-minify-params@^4.0.2: - version "4.0.2" - resolved "https://registry.npmmirror.com/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz" - integrity sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg== - dependencies: - alphanum-sort "^1.0.0" - browserslist "^4.0.0" - cssnano-util-get-arguments "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - uniqs "^2.0.0" - -postcss-minify-selectors@^4.0.2: - version "4.0.2" - resolved "https://registry.npmmirror.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz" - integrity sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g== - dependencies: - alphanum-sort "^1.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" - -postcss-modules-extract-imports@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz" - integrity sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ== - dependencies: - postcss "^7.0.5" - -postcss-modules-local-by-default@^3.0.2: - version "3.0.3" - resolved "https://registry.npmmirror.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz" - integrity sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw== - dependencies: - icss-utils "^4.1.1" - postcss "^7.0.32" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.1.0" - -postcss-modules-scope@^2.2.0: - version "2.2.0" - resolved "https://registry.npmmirror.com/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz" - integrity sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ== - dependencies: - postcss "^7.0.6" - postcss-selector-parser "^6.0.0" - -postcss-modules-values@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz" - integrity sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg== - dependencies: - icss-utils "^4.0.0" - postcss "^7.0.6" - -postcss-normalize-charset@^4.0.1: - version "4.0.1" - resolved "https://registry.npmmirror.com/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz" - integrity sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g== - dependencies: - postcss "^7.0.0" - -postcss-normalize-display-values@^4.0.2: - version "4.0.2" - resolved "https://registry.npmmirror.com/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz" - integrity sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ== - dependencies: - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-positions@^4.0.2: - version "4.0.2" - resolved "https://registry.npmmirror.com/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz" - integrity sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA== - dependencies: - cssnano-util-get-arguments "^4.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-repeat-style@^4.0.2: - version "4.0.2" - resolved "https://registry.npmmirror.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz" - integrity sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q== - dependencies: - cssnano-util-get-arguments "^4.0.0" - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-string@^4.0.2: - version "4.0.2" - resolved "https://registry.npmmirror.com/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz" - integrity sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA== - dependencies: - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-timing-functions@^4.0.2: - version "4.0.2" - resolved "https://registry.npmmirror.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz" - integrity sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A== - dependencies: - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-unicode@^4.0.1: - version "4.0.1" - resolved "https://registry.npmmirror.com/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz" - integrity sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg== - dependencies: - browserslist "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-url@^4.0.1: - version "4.0.1" - resolved "https://registry.npmmirror.com/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz" - integrity sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA== - dependencies: - is-absolute-url "^2.0.0" - normalize-url "^3.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-whitespace@^4.0.2: - version "4.0.2" - resolved "https://registry.npmmirror.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz" - integrity sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA== - dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-ordered-values@^4.1.2: - version "4.1.2" - resolved "https://registry.npmmirror.com/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz" - integrity sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw== - dependencies: - cssnano-util-get-arguments "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-reduce-initial@^4.0.3: - version "4.0.3" - resolved "https://registry.npmmirror.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz" - integrity sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA== - dependencies: - browserslist "^4.0.0" - caniuse-api "^3.0.0" - has "^1.0.0" - postcss "^7.0.0" - -postcss-reduce-transforms@^4.0.2: - version "4.0.2" - resolved "https://registry.npmmirror.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz" - integrity sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg== - dependencies: - cssnano-util-get-match "^4.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-selector-parser@^3.0.0: - version "3.1.2" - resolved "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz" - integrity sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA== - dependencies: - dot-prop "^5.2.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - -postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: - version "6.1.0" - resolved "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz" - integrity sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ== - dependencies: - cssesc "^3.0.0" - util-deprecate "^1.0.2" - -postcss-svgo@^4.0.3: - version "4.0.3" - resolved "https://registry.npmmirror.com/postcss-svgo/-/postcss-svgo-4.0.3.tgz" - integrity sha512-NoRbrcMWTtUghzuKSoIm6XV+sJdvZ7GZSc3wdBN0W19FTtp2ko8NqLsgoh/m9CzNhU3KLPvQmjIwtaNFkaFTvw== - dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - svgo "^1.0.0" - -postcss-unique-selectors@^4.0.1: - version "4.0.1" - resolved "https://registry.npmmirror.com/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz" - integrity sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg== - dependencies: - alphanum-sort "^1.0.0" - postcss "^7.0.0" - uniqs "^2.0.0" - -postcss-value-parser@^3.0.0: - version "3.3.1" - resolved "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz" - integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== - -postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: - version "4.2.0" - resolved "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" - integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== - -postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.36, postcss@^7.0.5, postcss@^7.0.6: - version "7.0.39" - resolved "https://registry.npmmirror.com/postcss/-/postcss-7.0.39.tgz" - integrity sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA== - dependencies: - picocolors "^0.2.1" - source-map "^0.6.1" - -postcss@^8.4.14, postcss@^8.4.38: - version "8.4.38" - resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.38.tgz" - integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== - dependencies: - nanoid "^3.3.7" - picocolors "^1.0.0" - source-map-js "^1.2.0" - -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.1.2.tgz" - integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== - -prepend-http@^1.0.0: - version "1.0.4" - resolved "https://registry.npmmirror.com/prepend-http/-/prepend-http-1.0.4.tgz" - integrity sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg== - -"prettier@^1.18.2 || ^2.0.0": - version "2.8.8" - resolved "https://registry.npmmirror.com/prettier/-/prettier-2.8.8.tgz" - integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== - -pretty-error@^2.0.2: - version "2.1.2" - resolved "https://registry.npmmirror.com/pretty-error/-/pretty-error-2.1.2.tgz" - integrity sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw== - dependencies: - lodash "^4.17.20" - renderkid "^2.0.4" - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -process@^0.11.10: - version "0.11.10" - resolved "https://registry.npmmirror.com/process/-/process-0.11.10.tgz" - integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== - -progress@^2.0.0: - version "2.0.3" - resolved "https://registry.npmmirror.com/progress/-/progress-2.0.3.tgz" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - -promise-inflight@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/promise-inflight/-/promise-inflight-1.0.1.tgz" - integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== - -proxy-addr@~2.0.7: - version "2.0.7" - resolved "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - -proxy-from-env@^1.1.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== - -prr@~1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz" - integrity sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw== - -pseudomap@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/pseudomap/-/pseudomap-1.0.2.tgz" - integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ== - -psl@^1.1.28: - version "1.9.0" - resolved "https://registry.npmmirror.com/psl/-/psl-1.9.0.tgz" - integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== - -public-encrypt@^4.0.0: - version "4.0.3" - resolved "https://registry.npmmirror.com/public-encrypt/-/public-encrypt-4.0.3.tgz" - integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== - dependencies: - bn.js "^4.1.0" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - parse-asn1 "^5.0.0" - randombytes "^2.0.1" - safe-buffer "^5.1.2" - -pump@^2.0.0: - version "2.0.1" - resolved "https://registry.npmmirror.com/pump/-/pump-2.0.1.tgz" - integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/pump/-/pump-3.0.0.tgz" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -pumpify@^1.3.3: - version "1.5.1" - resolved "https://registry.npmmirror.com/pumpify/-/pumpify-1.5.1.tgz" - integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== - dependencies: - duplexify "^3.6.0" - inherits "^2.0.3" - pump "^2.0.0" - -punycode@^1.2.4, punycode@^1.4.1: - version "1.4.1" - resolved "https://registry.npmmirror.com/punycode/-/punycode-1.4.1.tgz" - integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== - -punycode@^2.1.0, punycode@^2.1.1: - version "2.3.1" - resolved "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz" - integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== - -q@^1.1.2: - version "1.5.1" - resolved "https://registry.npmmirror.com/q/-/q-1.5.1.tgz" - integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== - -qs@6.11.0: - version "6.11.0" - resolved "https://registry.npmmirror.com/qs/-/qs-6.11.0.tgz" - integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== - dependencies: - side-channel "^1.0.4" - -qs@^6.11.2: - version "6.12.1" - resolved "https://registry.npmmirror.com/qs/-/qs-6.12.1.tgz" - integrity sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ== - dependencies: - side-channel "^1.0.6" - -qs@~6.5.2: - version "6.5.3" - resolved "https://registry.npmmirror.com/qs/-/qs-6.5.3.tgz" - integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== - -query-string@^4.1.0: - version "4.3.4" - resolved "https://registry.npmmirror.com/query-string/-/query-string-4.3.4.tgz" - integrity sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q== - dependencies: - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" - -querystring-es3@^0.2.0: - version "0.2.1" - resolved "https://registry.npmmirror.com/querystring-es3/-/querystring-es3-0.2.1.tgz" - integrity sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA== - -querystringify@^2.1.1: - version "2.2.0" - resolved "https://registry.npmmirror.com/querystringify/-/querystringify-2.2.0.tgz" - integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== - -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -randomfill@^1.0.3: - version "1.0.4" - resolved "https://registry.npmmirror.com/randomfill/-/randomfill-1.0.4.tgz" - integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== - dependencies: - randombytes "^2.0.5" - safe-buffer "^5.1.0" - -range-parser@^1.2.1, range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.5.2: - version "2.5.2" - resolved "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.2.tgz" - integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - -read-pkg@^5.1.1: - version "5.2.0" - resolved "https://registry.npmmirror.com/read-pkg/-/read-pkg-5.2.0.tgz" - integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== - dependencies: - "@types/normalize-package-data" "^2.4.0" - normalize-package-data "^2.5.0" - parse-json "^5.0.0" - type-fest "^0.6.0" - -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@^2.3.8, readable-stream@~2.3.6: - version "2.3.8" - resolved "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz" - integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^3.0.6: - version "3.6.2" - resolved "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readdirp@^2.2.1: - version "2.2.1" - resolved "https://registry.npmmirror.com/readdirp/-/readdirp-2.2.1.tgz" - integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== - dependencies: - graceful-fs "^4.1.11" - micromatch "^3.1.10" - readable-stream "^2.0.2" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -regenerate-unicode-properties@^10.1.0: - version "10.1.1" - resolved "https://registry.npmmirror.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz" - integrity sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q== - dependencies: - regenerate "^1.4.2" - -regenerate@^1.4.2: - version "1.4.2" - resolved "https://registry.npmmirror.com/regenerate/-/regenerate-1.4.2.tgz" - integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== - -regenerator-runtime@^0.11.0: - version "0.11.1" - resolved "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz" - integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== - -regenerator-runtime@^0.14.0: - version "0.14.1" - resolved "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz" - integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== - -regenerator-transform@^0.15.2: - version "0.15.2" - resolved "https://registry.npmmirror.com/regenerator-transform/-/regenerator-transform-0.15.2.tgz" - integrity sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg== - dependencies: - "@babel/runtime" "^7.8.4" - -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/regex-not/-/regex-not-1.0.2.tgz" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - -regexp.prototype.flags@^1.5.1, regexp.prototype.flags@^1.5.2: - version "1.5.2" - resolved "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz" - integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== - dependencies: - call-bind "^1.0.6" - define-properties "^1.2.1" - es-errors "^1.3.0" - set-function-name "^2.0.1" - -regexpp@^2.0.1: - version "2.0.1" - resolved "https://registry.npmmirror.com/regexpp/-/regexpp-2.0.1.tgz" - integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== - -regexpu-core@^5.3.1: - version "5.3.2" - resolved "https://registry.npmmirror.com/regexpu-core/-/regexpu-core-5.3.2.tgz" - integrity sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ== - dependencies: - "@babel/regjsgen" "^0.8.0" - regenerate "^1.4.2" - regenerate-unicode-properties "^10.1.0" - regjsparser "^0.9.1" - unicode-match-property-ecmascript "^2.0.0" - unicode-match-property-value-ecmascript "^2.1.0" - -regjsparser@^0.9.1: - version "0.9.1" - resolved "https://registry.npmmirror.com/regjsparser/-/regjsparser-0.9.1.tgz" - integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ== - dependencies: - jsesc "~0.5.0" - -relateurl@0.2.x: - version "0.2.7" - resolved "https://registry.npmmirror.com/relateurl/-/relateurl-0.2.7.tgz" - integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog== - -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.npmmirror.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz" - integrity sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw== - -renderkid@^2.0.4: - version "2.0.7" - resolved "https://registry.npmmirror.com/renderkid/-/renderkid-2.0.7.tgz" - integrity sha512-oCcFyxaMrKsKcTY59qnCAtmDVSLfPbrv6A3tVbPdFMMrv5jaK10V6m40cKsoPNhAqN6rmHW9sswW4o3ruSrwUQ== - dependencies: - css-select "^4.1.3" - dom-converter "^0.2.0" - htmlparser2 "^6.1.0" - lodash "^4.17.21" - strip-ansi "^3.0.1" - -repeat-element@^1.1.2: - version "1.1.4" - resolved "https://registry.npmmirror.com/repeat-element/-/repeat-element-1.1.4.tgz" - integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== - -repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.npmmirror.com/repeat-string/-/repeat-string-1.6.1.tgz" - integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== - -request@^2.88.2: - version "2.88.2" - resolved "https://registry.npmmirror.com/request/-/request-2.88.2.tgz" - integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.3" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.5.0" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/require-main-filename/-/require-main-filename-2.0.0.tgz" - integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== - -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/requires-port/-/requires-port-1.0.0.tgz" - integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== - -resize-observer-polyfill@^1.5.0: - version "1.5.1" - resolved "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz" - integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== - -resolve-cwd@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz" - integrity sha512-ccu8zQTrzVr954472aUVPLEcB3YpKSYR3cg/3lo1okzobPBM+1INXBbBZlDbnI/hbEocnf8j0QVo43hQKrbchg== - dependencies: - resolve-from "^3.0.0" - -resolve-from@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/resolve-from/-/resolve-from-3.0.0.tgz" - integrity sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw== - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.npmmirror.com/resolve-url/-/resolve-url-0.2.1.tgz" - integrity sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg== - -resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14.2: - version "1.22.8" - resolved "https://registry.npmmirror.com/resolve/-/resolve-1.22.8.tgz" - integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -restore-cursor@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-2.0.0.tgz" - integrity sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q== - dependencies: - onetime "^2.0.0" - signal-exit "^3.0.2" - -restore-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-3.1.0.tgz" - integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== - dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" - -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.npmmirror.com/ret/-/ret-0.1.15.tgz" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - -retry@^0.12.0: - version "0.12.0" - resolved "https://registry.npmmirror.com/retry/-/retry-0.12.0.tgz" - integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== - -rgb-regex@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/rgb-regex/-/rgb-regex-1.0.1.tgz" - integrity sha512-gDK5mkALDFER2YLqH6imYvK6g02gpNGM4ILDZ472EwWfXZnC2ZEpoB2ECXTyOVUKuk/bPJZMzwQPBYICzP+D3w== - -rgba-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/rgba-regex/-/rgba-regex-1.0.0.tgz" - integrity sha512-zgn5OjNQXLUTdq8m17KdaicF6w89TZs8ZU8y0AYENIU6wG8GG6LLm0yLSiPY8DmaYmHdgRW8rnApjoT0fQRfMg== - -rimraf@2.6.3: - version "2.6.3" - resolved "https://registry.npmmirror.com/rimraf/-/rimraf-2.6.3.tgz" - integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== - dependencies: - glob "^7.1.3" - -rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3: - version "2.7.1" - resolved "https://registry.npmmirror.com/rimraf/-/rimraf-2.7.1.tgz" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - -ripemd160@^2.0.0, ripemd160@^2.0.1: - version "2.0.2" - resolved "https://registry.npmmirror.com/ripemd160/-/ripemd160-2.0.2.tgz" - integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - -run-async@^2.4.0: - version "2.4.1" - resolved "https://registry.npmmirror.com/run-async/-/run-async-2.4.1.tgz" - integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== - -run-queue@^1.0.0, run-queue@^1.0.3: - version "1.0.3" - resolved "https://registry.npmmirror.com/run-queue/-/run-queue-1.0.3.tgz" - integrity sha512-ntymy489o0/QQplUDnpYAYUsO50K9SBrIVaKCWDOJzYJts0f9WH9RFJkyagebkw5+y1oi00R7ynNW/d12GBumg== - dependencies: - aproba "^1.1.1" - -rxjs@^6.6.0, rxjs@^6.6.7: - version "6.6.7" - resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz" - integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== - dependencies: - tslib "^1.9.0" - -safe-array-concat@^1.1.2: - version "1.1.2" - resolved "https://registry.npmmirror.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz" - integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== - dependencies: - call-bind "^1.0.7" - get-intrinsic "^1.2.4" - has-symbols "^1.0.3" - isarray "^2.0.5" - -safe-buffer@5.1.2, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@5.2.1, safe-buffer@^5.2.1: - version "5.2.1" - resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-regex-test@^1.0.3: - version "1.0.3" - resolved "https://registry.npmmirror.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz" - integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== - dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-regex "^1.1.4" - -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/safe-regex/-/safe-regex-1.1.0.tgz" - integrity sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg== - dependencies: - ret "~0.1.10" - -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: - version "2.1.2" - resolved "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sass-loader@^10.1.1: - version "10.5.2" - resolved "https://registry.npmmirror.com/sass-loader/-/sass-loader-10.5.2.tgz" - integrity sha512-vMUoSNOUKJILHpcNCCyD23X34gve1TS7Rjd9uXHeKqhvBG39x6XbswFDtpbTElj6XdMFezoWhkh5vtKudf2cgQ== - dependencies: - klona "^2.0.4" - loader-utils "^2.0.0" - neo-async "^2.6.2" - schema-utils "^3.0.0" - semver "^7.3.2" - -sass@^1.49.0: - version "1.77.3" - resolved "https://registry.npmmirror.com/sass/-/sass-1.77.3.tgz" - integrity sha512-WJHo+jmFp0dwRuymPmIovuxHaBntcCyja5hCB0yYY9wWrViEp4kF5Cdai98P72v6FzroPuABqu+ddLMbQWmwzA== - dependencies: - chokidar ">=3.0.0 <4.0.0" - immutable "^4.0.0" - source-map-js ">=0.6.2 <2.0.0" - -sax@~1.2.4: - version "1.2.4" - resolved "https://registry.npmmirror.com/sax/-/sax-1.2.4.tgz" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -schema-utils@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/schema-utils/-/schema-utils-1.0.0.tgz" - integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== - dependencies: - ajv "^6.1.0" - ajv-errors "^1.0.0" - ajv-keywords "^3.1.0" - -schema-utils@^2.0.0, schema-utils@^2.5.0, schema-utils@^2.6.5, schema-utils@^2.7.0: - version "2.7.1" - resolved "https://registry.npmmirror.com/schema-utils/-/schema-utils-2.7.1.tgz" - integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== - dependencies: - "@types/json-schema" "^7.0.5" - ajv "^6.12.4" - ajv-keywords "^3.5.2" - -schema-utils@^3.0.0: - version "3.3.0" - resolved "https://registry.npmmirror.com/schema-utils/-/schema-utils-3.3.0.tgz" - integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -select-hose@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/select-hose/-/select-hose-2.0.0.tgz" - integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== - -selfsigned@^1.10.8: - version "1.10.14" - resolved "https://registry.npmmirror.com/selfsigned/-/selfsigned-1.10.14.tgz" - integrity sha512-lkjaiAye+wBZDCBsu5BGi0XiLRxeUlsGod5ZP924CRSEoGuZAw/f7y9RKu28rwTfiHVhdavhB0qH0INV6P1lEA== - dependencies: - node-forge "^0.10.0" - -"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0: - version "5.7.2" - resolved "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz" - integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== - -semver@^6.0.0, semver@^6.1.0, semver@^6.1.2, semver@^6.3.0, semver@^6.3.1: - version "6.3.1" - resolved "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.3.2: - version "7.6.2" - resolved "https://registry.npmmirror.com/semver/-/semver-7.6.2.tgz" - integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== - -send@0.18.0: - version "0.18.0" - resolved "https://registry.npmmirror.com/send/-/send-0.18.0.tgz" - integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - -serialize-javascript@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz" - integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw== - dependencies: - randombytes "^2.1.0" - -serve-index@^1.9.1: - version "1.9.1" - resolved "https://registry.npmmirror.com/serve-index/-/serve-index-1.9.1.tgz" - integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== - dependencies: - accepts "~1.3.4" - batch "0.6.1" - debug "2.6.9" - escape-html "~1.0.3" - http-errors "~1.6.2" - mime-types "~2.1.17" - parseurl "~1.3.2" - -serve-static@1.15.0: - version "1.15.0" - resolved "https://registry.npmmirror.com/serve-static/-/serve-static-1.15.0.tgz" - integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.18.0" - -set-blocking@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz" - integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== - -set-function-length@^1.2.1: - version "1.2.2" - resolved "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz" - integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - -set-function-name@^2.0.1: - version "2.0.2" - resolved "https://registry.npmmirror.com/set-function-name/-/set-function-name-2.0.2.tgz" - integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - functions-have-names "^1.2.3" - has-property-descriptors "^1.0.2" - -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.npmmirror.com/set-value/-/set-value-2.0.1.tgz" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - -setimmediate@^1.0.4: - version "1.0.5" - resolved "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz" - integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== - -setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.1.0.tgz" - integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -sha.js@^2.4.0, sha.js@^2.4.8: - version "2.4.11" - resolved "https://registry.npmmirror.com/sha.js/-/sha.js-2.4.11.tgz" - integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.npmmirror.com/shebang-command/-/shebang-command-1.2.0.tgz" - integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg== - dependencies: - shebang-regex "^1.0.0" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-1.0.0.tgz" - integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ== - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -shell-quote@^1.8.1: - version "1.8.1" - resolved "https://registry.npmmirror.com/shell-quote/-/shell-quote-1.8.1.tgz" - integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== - -side-channel@^1.0.4, side-channel@^1.0.6: - version "1.0.6" - resolved "https://registry.npmmirror.com/side-channel/-/side-channel-1.0.6.tgz" - integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - get-intrinsic "^1.2.4" - object-inspect "^1.13.1" - -signal-exit@^3.0.0, signal-exit@^3.0.2: - version "3.0.7" - resolved "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.npmmirror.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz" - integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== - dependencies: - is-arrayish "^0.3.1" - -slash@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/slash/-/slash-1.0.0.tgz" - integrity sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg== - -slash@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/slash/-/slash-2.0.0.tgz" - integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== - -slice-ansi@^2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-2.1.0.tgz" - integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== - dependencies: - ansi-styles "^3.2.0" - astral-regex "^1.0.0" - is-fullwidth-code-point "^2.0.0" - -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.npmmirror.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.npmmirror.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.npmmirror.com/snapdragon/-/snapdragon-0.8.2.tgz" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - -sockjs-client@^1.5.0: - version "1.6.1" - resolved "https://registry.npmmirror.com/sockjs-client/-/sockjs-client-1.6.1.tgz" - integrity sha512-2g0tjOR+fRs0amxENLi/q5TiJTqY+WXFOzb5UwXndlK6TO3U/mirZznpx6w34HVMoc3g7cY24yC/ZMIYnDlfkw== - dependencies: - debug "^3.2.7" - eventsource "^2.0.2" - faye-websocket "^0.11.4" - inherits "^2.0.4" - url-parse "^1.5.10" - -sockjs@^0.3.21: - version "0.3.24" - resolved "https://registry.npmmirror.com/sockjs/-/sockjs-0.3.24.tgz" - integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== - dependencies: - faye-websocket "^0.11.3" - uuid "^8.3.2" - websocket-driver "^0.7.4" - -sort-keys@^1.0.0: - version "1.1.2" - resolved "https://registry.npmmirror.com/sort-keys/-/sort-keys-1.1.2.tgz" - integrity sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg== - dependencies: - is-plain-obj "^1.0.0" - -source-list-map@^2.0.0: - version "2.0.1" - resolved "https://registry.npmmirror.com/source-list-map/-/source-list-map-2.0.1.tgz" - integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== - -"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.0: - version "1.2.0" - resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.0.tgz" - integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== - -source-map-resolve@^0.5.0: - version "0.5.3" - resolved "https://registry.npmmirror.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz" - integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - -source-map-support@~0.5.12: - version "0.5.21" - resolved "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map-url@^0.4.0: - version "0.4.1" - resolved "https://registry.npmmirror.com/source-map-url/-/source-map-url-0.4.1.tgz" - integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== - -source-map@^0.5.6: - version "0.5.7" - resolved "https://registry.npmmirror.com/source-map/-/source-map-0.5.7.tgz" - integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== - -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: - version "0.6.1" - resolved "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -source-map@^0.7.3: - version "0.7.4" - resolved "https://registry.npmmirror.com/source-map/-/source-map-0.7.4.tgz" - integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== - -spdx-correct@^3.0.0: - version "3.2.0" - resolved "https://registry.npmmirror.com/spdx-correct/-/spdx-correct-3.2.0.tgz" - integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA== - dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - -spdx-exceptions@^2.1.0: - version "2.5.0" - resolved "https://registry.npmmirror.com/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz" - integrity sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w== - -spdx-expression-parse@^3.0.0: - version "3.0.1" - resolved "https://registry.npmmirror.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz" - integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - -spdx-license-ids@^3.0.0: - version "3.0.18" - resolved "https://registry.npmmirror.com/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz" - integrity sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ== - -spdy-transport@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/spdy-transport/-/spdy-transport-3.0.0.tgz" - integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== - dependencies: - debug "^4.1.0" - detect-node "^2.0.4" - hpack.js "^2.1.6" - obuf "^1.1.2" - readable-stream "^3.0.6" - wbuf "^1.7.3" - -spdy@^4.0.2: - version "4.0.2" - resolved "https://registry.npmmirror.com/spdy/-/spdy-4.0.2.tgz" - integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== - dependencies: - debug "^4.1.0" - handle-thing "^2.0.0" - http-deceiver "^1.2.7" - select-hose "^2.0.0" - spdy-transport "^3.0.0" - -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.npmmirror.com/split-string/-/split-string-3.1.0.tgz" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz" - integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== - -sql-formatter@^9.0.0: - version "9.2.0" - resolved "https://registry.npmmirror.com/sql-formatter/-/sql-formatter-9.2.0.tgz" - integrity sha512-Dn4lEpUeAhfNDR2LnEs9Uaq92TSHjhcNrzhllPuMnp188P4sLU7UcdcB9UqIfMfcN62gWXJlJ3KocaAf/SOzXQ== - dependencies: - argparse "^2.0.1" - -sshpk@^1.7.0: - version "1.18.0" - resolved "https://registry.npmmirror.com/sshpk/-/sshpk-1.18.0.tgz" - integrity sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ== - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" - -ssri@^6.0.1: - version "6.0.2" - resolved "https://registry.npmmirror.com/ssri/-/ssri-6.0.2.tgz" - integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q== - dependencies: - figgy-pudding "^3.5.1" - -ssri@^8.0.1: - version "8.0.1" - resolved "https://registry.npmmirror.com/ssri/-/ssri-8.0.1.tgz" - integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== - dependencies: - minipass "^3.1.1" - -stable@^0.1.8: - version "0.1.8" - resolved "https://registry.npmmirror.com/stable/-/stable-0.1.8.tgz" - integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== - -stackframe@^1.3.4: - version "1.3.4" - resolved "https://registry.npmmirror.com/stackframe/-/stackframe-1.3.4.tgz" - integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw== - -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.npmmirror.com/static-extend/-/static-extend-0.1.2.tgz" - integrity sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g== - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -"statuses@>= 1.4.0 < 2": - version "1.5.0" - resolved "https://registry.npmmirror.com/statuses/-/statuses-1.5.0.tgz" - integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== - -stream-browserify@^2.0.1: - version "2.0.2" - resolved "https://registry.npmmirror.com/stream-browserify/-/stream-browserify-2.0.2.tgz" - integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== - dependencies: - inherits "~2.0.1" - readable-stream "^2.0.2" - -stream-each@^1.1.0: - version "1.2.3" - resolved "https://registry.npmmirror.com/stream-each/-/stream-each-1.2.3.tgz" - integrity sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw== - dependencies: - end-of-stream "^1.1.0" - stream-shift "^1.0.0" - -stream-http@^2.7.2: - version "2.8.3" - resolved "https://registry.npmmirror.com/stream-http/-/stream-http-2.8.3.tgz" - integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== - dependencies: - builtin-status-codes "^3.0.0" - inherits "^2.0.1" - readable-stream "^2.3.6" - to-arraybuffer "^1.0.0" - xtend "^4.0.0" - -stream-shift@^1.0.0: - version "1.0.3" - resolved "https://registry.npmmirror.com/stream-shift/-/stream-shift-1.0.3.tgz" - integrity sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ== - -strict-uri-encode@^1.0.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz" - integrity sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ== - -string-width@^3.0.0, string-width@^3.1.0: - version "3.1.0" - resolved "https://registry.npmmirror.com/string-width/-/string-width-3.1.0.tgz" - integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== - dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string.prototype.trim@^1.2.9: - version "1.2.9" - resolved "https://registry.npmmirror.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz" - integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.0" - es-object-atoms "^1.0.0" - -string.prototype.trimend@^1.0.8: - version "1.0.8" - resolved "https://registry.npmmirror.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz" - integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - -string.prototype.trimstart@^1.0.8: - version "1.0.8" - resolved "https://registry.npmmirror.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz" - integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - -string_decoder@^1.0.0, string_decoder@^1.1.1, string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-3.0.1.tgz" - integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg== - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-5.2.0.tgz" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/strip-eof/-/strip-eof-1.0.0.tgz" - integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q== - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -strip-indent@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/strip-indent/-/strip-indent-2.0.0.tgz" - integrity sha512-RsSNPLpq6YUL7QYy44RnPVTn/lcVZtb48Uof3X5JLbF4zD/Gs7ZFDv2HWol+leoQN2mT86LAzSshGfkTlSOpsA== - -strip-json-comments@^3.0.1: - version "3.1.1" - resolved "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -stylehacks@^4.0.0: - version "4.0.3" - resolved "https://registry.npmmirror.com/stylehacks/-/stylehacks-4.0.3.tgz" - integrity sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g== - dependencies: - browserslist "^4.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^6.1.0: - version "6.1.0" - resolved "https://registry.npmmirror.com/supports-color/-/supports-color-6.1.0.tgz" - integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -svg-tags@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/svg-tags/-/svg-tags-1.0.0.tgz" - integrity sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA== - -svgo@^1.0.0: - version "1.3.2" - resolved "https://registry.npmmirror.com/svgo/-/svgo-1.3.2.tgz" - integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== - dependencies: - chalk "^2.4.1" - coa "^2.0.2" - css-select "^2.0.0" - css-select-base-adapter "^0.1.1" - css-tree "1.0.0-alpha.37" - csso "^4.0.2" - js-yaml "^3.13.1" - mkdirp "~0.5.1" - object.values "^1.1.0" - sax "~1.2.4" - stable "^0.1.8" - unquote "~1.1.1" - util.promisify "~1.0.0" - -table@^5.2.3: - version "5.4.6" - resolved "https://registry.npmmirror.com/table/-/table-5.4.6.tgz" - integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== - dependencies: - ajv "^6.10.2" - lodash "^4.17.14" - slice-ansi "^2.1.0" - string-width "^3.0.0" - -tapable@^1.0.0, tapable@^1.1.3: - version "1.1.3" - resolved "https://registry.npmmirror.com/tapable/-/tapable-1.1.3.tgz" - integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== - -terser-webpack-plugin@^1.4.3, terser-webpack-plugin@^1.4.4: - version "1.4.5" - resolved "https://registry.npmmirror.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz" - integrity sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw== - dependencies: - cacache "^12.0.2" - find-cache-dir "^2.1.0" - is-wsl "^1.1.0" - schema-utils "^1.0.0" - serialize-javascript "^4.0.0" - source-map "^0.6.1" - terser "^4.1.2" - webpack-sources "^1.4.0" - worker-farm "^1.7.0" - -terser@^4.1.2: - version "4.8.1" - resolved "https://registry.npmmirror.com/terser/-/terser-4.8.1.tgz" - integrity sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw== - dependencies: - commander "^2.20.0" - source-map "~0.6.1" - source-map-support "~0.5.12" - -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz" - integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== - -thenify-all@^1.0.0: - version "1.6.0" - resolved "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz" - integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== - dependencies: - thenify ">= 3.1.0 < 4" - -"thenify@>= 3.1.0 < 4": - version "3.3.1" - resolved "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz" - integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== - dependencies: - any-promise "^1.0.0" - -thread-loader@^2.1.3: - version "2.1.3" - resolved "https://registry.npmmirror.com/thread-loader/-/thread-loader-2.1.3.tgz" - integrity sha512-wNrVKH2Lcf8ZrWxDF/khdlLlsTMczdcwPA9VEK4c2exlEPynYWxi9op3nPTo5lAnDIkE0rQEB3VBP+4Zncc9Hg== - dependencies: - loader-runner "^2.3.1" - loader-utils "^1.1.0" - neo-async "^2.6.0" - -throttle-debounce@^1.0.1: - version "1.1.0" - resolved "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-1.1.0.tgz" - integrity sha512-XH8UiPCQcWNuk2LYePibW/4qL97+ZQ1AN3FNXwZRBNPPowo/NRU5fAlDCSNBJIYCKbioZfuYtMhG4quqoJhVzg== - -through2@^2.0.0: - version "2.0.5" - resolved "https://registry.npmmirror.com/through2/-/through2-2.0.5.tgz" - integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== - dependencies: - readable-stream "~2.3.6" - xtend "~4.0.1" - -through@^2.3.6: - version "2.3.8" - resolved "https://registry.npmmirror.com/through/-/through-2.3.8.tgz" - integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== - -thunky@^1.0.2: - version "1.1.0" - resolved "https://registry.npmmirror.com/thunky/-/thunky-1.1.0.tgz" - integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== - -timers-browserify@^2.0.4: - version "2.0.12" - resolved "https://registry.npmmirror.com/timers-browserify/-/timers-browserify-2.0.12.tgz" - integrity sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ== - dependencies: - setimmediate "^1.0.4" - -timsort@^0.3.0: - version "0.3.0" - resolved "https://registry.npmmirror.com/timsort/-/timsort-0.3.0.tgz" - integrity sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A== - -tiny-cookie@^1.0: - version "1.0.1" - resolved "https://registry.npmmirror.com/tiny-cookie/-/tiny-cookie-1.0.1.tgz" - integrity sha512-4KJ21HYTNumxnHvCbLrtrrhSwtWXzpmaRJIUg4biBii5AiSe2izod5bauXiiQxVuf2Fta57U2DTW1Fa4/6Cmgw== - -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.npmmirror.com/tmp/-/tmp-0.0.33.tgz" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== - dependencies: - os-tmpdir "~1.0.2" - -to-arraybuffer@^1.0.0: - version "1.0.1" - resolved "https://registry.npmmirror.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz" - integrity sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA== - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz" - integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== - -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.npmmirror.com/to-object-path/-/to-object-path-0.3.0.tgz" - integrity sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg== - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-2.1.1.tgz" - integrity sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg== - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.npmmirror.com/to-regex/-/to-regex-3.0.2.tgz" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -toposort@^1.0.0: - version "1.0.7" - resolved "https://registry.npmmirror.com/toposort/-/toposort-1.0.7.tgz" - integrity sha512-FclLrw8b9bMWf4QlCJuHBEVhSRsqDj6u3nIjAzPeJvgl//1hBlffdlk0MALceL14+koWEdU4ofRAXofbODxQzg== - -tough-cookie@~2.5.0: - version "2.5.0" - resolved "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-2.5.0.tgz" - integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== - dependencies: - psl "^1.1.28" - punycode "^2.1.1" - -tryer@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/tryer/-/tryer-1.0.1.tgz" - integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA== - -ts-pnp@^1.1.6: - version "1.2.0" - resolved "https://registry.npmmirror.com/ts-pnp/-/ts-pnp-1.2.0.tgz" - integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw== - -tslib@^1.9.0: - version "1.14.1" - resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - -tty-browserify@0.0.0: - version "0.0.0" - resolved "https://registry.npmmirror.com/tty-browserify/-/tty-browserify-0.0.0.tgz" - integrity sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw== - -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.npmmirror.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz" - integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== - dependencies: - safe-buffer "^5.0.1" - -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.npmmirror.com/tweetnacl/-/tweetnacl-0.14.5.tgz" - integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== - -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.npmmirror.com/type-check/-/type-check-0.3.2.tgz" - integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg== - dependencies: - prelude-ls "~1.1.2" - -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.21.3.tgz" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - -type-fest@^0.6.0: - version "0.6.0" - resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.6.0.tgz" - integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== - -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.8.1.tgz" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== - -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -typed-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz" - integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - is-typed-array "^1.1.13" - -typed-array-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz" - integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== - dependencies: - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - -typed-array-byte-offset@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz" - integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - -typed-array-length@^1.0.6: - version "1.0.6" - resolved "https://registry.npmmirror.com/typed-array-length/-/typed-array-length-1.0.6.tgz" - integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== - dependencies: - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - possible-typed-array-names "^1.0.0" - -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.npmmirror.com/typedarray/-/typedarray-0.0.6.tgz" - integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== - -uglify-js@3.4.x: - version "3.4.10" - resolved "https://registry.npmmirror.com/uglify-js/-/uglify-js-3.4.10.tgz" - integrity sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw== - dependencies: - commander "~2.19.0" - source-map "~0.6.1" - -unbox-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz" - integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== - dependencies: - call-bind "^1.0.2" - has-bigints "^1.0.2" - has-symbols "^1.0.3" - which-boxed-primitive "^1.0.2" - -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.npmmirror.com/undici-types/-/undici-types-5.26.5.tgz" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== - -unicode-canonical-property-names-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz" - integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== - -unicode-match-property-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz" - integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== - dependencies: - unicode-canonical-property-names-ecmascript "^2.0.0" - unicode-property-aliases-ecmascript "^2.0.0" - -unicode-match-property-value-ecmascript@^2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz" - integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA== - -unicode-property-aliases-ecmascript@^2.0.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz" - integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== - -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.npmmirror.com/union-value/-/union-value-1.0.1.tgz" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - -uniq@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/uniq/-/uniq-1.0.1.tgz" - integrity sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA== - -uniqs@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/uniqs/-/uniqs-2.0.0.tgz" - integrity sha512-mZdDpf3vBV5Efh29kMw5tXoup/buMgxLzOt/XKFKcVmi+15ManNQWr6HfZ2aiZTYlYixbdNJ0KFmIZIv52tHSQ== - -unique-filename@^1.1.1: - version "1.1.1" - resolved "https://registry.npmmirror.com/unique-filename/-/unique-filename-1.1.1.tgz" - integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== - dependencies: - unique-slug "^2.0.0" - -unique-slug@^2.0.0: - version "2.0.2" - resolved "https://registry.npmmirror.com/unique-slug/-/unique-slug-2.0.2.tgz" - integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== - dependencies: - imurmurhash "^0.1.4" - -universalify@^0.1.0: - version "0.1.2" - resolved "https://registry.npmmirror.com/universalify/-/universalify-0.1.2.tgz" - integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== - -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - -unquote@~1.1.1: - version "1.1.1" - resolved "https://registry.npmmirror.com/unquote/-/unquote-1.1.1.tgz" - integrity sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg== - -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/unset-value/-/unset-value-1.0.0.tgz" - integrity sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ== - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -upath@^1.1.1: - version "1.2.0" - resolved "https://registry.npmmirror.com/upath/-/upath-1.2.0.tgz" - integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== - -update-browserslist-db@^1.0.13: - version "1.0.16" - resolved "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz" - integrity sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ== - dependencies: - escalade "^3.1.2" - picocolors "^1.0.1" - -upper-case@^1.1.1: - version "1.1.3" - resolved "https://registry.npmmirror.com/upper-case/-/upper-case-1.1.3.tgz" - integrity sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA== - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.npmmirror.com/urix/-/urix-0.1.0.tgz" - integrity sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg== - -url-loader@^2.2.0: - version "2.3.0" - resolved "https://registry.npmmirror.com/url-loader/-/url-loader-2.3.0.tgz" - integrity sha512-goSdg8VY+7nPZKUEChZSEtW5gjbS66USIGCeSJ1OVOJ7Yfuh/36YxCwMi5HVEJh6mqUYOoy3NJ0vlOMrWsSHog== - dependencies: - loader-utils "^1.2.3" - mime "^2.4.4" - schema-utils "^2.5.0" - -url-parse@^1.5.10: - version "1.5.10" - resolved "https://registry.npmmirror.com/url-parse/-/url-parse-1.5.10.tgz" - integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== - dependencies: - querystringify "^2.1.1" - requires-port "^1.0.0" - -url@^0.11.0: - version "0.11.3" - resolved "https://registry.npmmirror.com/url/-/url-0.11.3.tgz" - integrity sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw== - dependencies: - punycode "^1.4.1" - qs "^6.11.2" - -use@^3.1.0: - version "3.1.1" - resolved "https://registry.npmmirror.com/use/-/use-3.1.1.tgz" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== - -util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -util.promisify@1.0.0, util.promisify@~1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/util.promisify/-/util.promisify-1.0.0.tgz" - integrity sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA== - dependencies: - define-properties "^1.1.2" - object.getownpropertydescriptors "^2.0.3" - -util@^0.10.4: - version "0.10.4" - resolved "https://registry.npmmirror.com/util/-/util-0.10.4.tgz" - integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== - dependencies: - inherits "2.0.3" - -util@^0.11.0: - version "0.11.1" - resolved "https://registry.npmmirror.com/util/-/util-0.11.1.tgz" - integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== - dependencies: - inherits "2.0.3" - -utila@~0.4: - version "0.4.0" - resolved "https://registry.npmmirror.com/utila/-/utila-0.4.0.tgz" - integrity sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA== - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz" - integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - -uuid@^3.3.2: - version "3.4.0" - resolved "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - -uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -v8-compile-cache@^2.0.3: - version "2.4.0" - resolved "https://registry.npmmirror.com/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz" - integrity sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw== - -validate-npm-package-license@^3.0.1: - version "3.0.4" - resolved "https://registry.npmmirror.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz" - integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== - dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" - -vary@~1.1.2: - version "1.1.2" - resolved "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - -vendors@^1.0.0: - version "1.0.4" - resolved "https://registry.npmmirror.com/vendors/-/vendors-1.0.4.tgz" - integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== - -verror@1.10.0: - version "1.10.0" - resolved "https://registry.npmmirror.com/verror/-/verror-1.10.0.tgz" - integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw== - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - -vm-browserify@^1.0.1: - version "1.1.2" - resolved "https://registry.npmmirror.com/vm-browserify/-/vm-browserify-1.1.2.tgz" - integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== - -vue-class-component@^7.1.0: - version "7.2.6" - resolved "https://registry.npmmirror.com/vue-class-component/-/vue-class-component-7.2.6.tgz" - integrity sha512-+eaQXVrAm/LldalI272PpDe3+i4mPis0ORiMYxF6Ae4hyuCh15W8Idet7wPUEs4N4YptgFHGys4UrgNQOMyO6w== - -vue-codemirror@^4.0.6: - version "4.0.6" - resolved "https://registry.npmmirror.com/vue-codemirror/-/vue-codemirror-4.0.6.tgz" - integrity sha512-ilU7Uf0mqBNSSV3KT7FNEeRIxH4s1fmpG4TfHlzvXn0QiQAbkXS9lLfwuZpaBVEnpP5CSE62iGJjoliTuA8poQ== - dependencies: - codemirror "^5.41.0" - diff-match-patch "^1.0.0" - -vue-contextmenujs@^1.4.9: - version "1.4.11" - resolved "https://registry.npmmirror.com/vue-contextmenujs/-/vue-contextmenujs-1.4.11.tgz" - integrity sha512-k8y0ExOL5yn7fRRCo6UDT81ekq/Ls1JBz70eBBpIgcf1p++84amDL6uCMmlt6D6WK5a9f0NhW2kc2pkCUb2bHA== - -vue-cookie@^1.1.4: - version "1.1.4" - resolved "https://registry.npmmirror.com/vue-cookie/-/vue-cookie-1.1.4.tgz" - integrity sha512-lxWIzmUTCVra0H7YPbNpYx0wbTQThV+n7ueUOcqtK8dlckmfB8fcyf4OEiRuQUd7iiVub2T3IDlWiisEM5Ku0Q== - dependencies: - tiny-cookie "^1.0" - -vue-eslint-parser@^7.0.0: - version "7.11.0" - resolved "https://registry.npmmirror.com/vue-eslint-parser/-/vue-eslint-parser-7.11.0.tgz" - integrity sha512-qh3VhDLeh773wjgNTl7ss0VejY9bMMa0GoDG2fQVyDzRFdiU3L7fw74tWZDHNQXdZqxO3EveQroa9ct39D2nqg== - dependencies: - debug "^4.1.1" - eslint-scope "^5.1.1" - eslint-visitor-keys "^1.1.0" - espree "^6.2.1" - esquery "^1.4.0" - lodash "^4.17.21" - semver "^6.3.0" - -vue-hot-reload-api@^2.3.0: - version "2.3.4" - resolved "https://registry.npmmirror.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz" - integrity sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog== - -vue-i18n@^8.22.4: - version "8.28.2" - resolved "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-8.28.2.tgz" - integrity sha512-C5GZjs1tYlAqjwymaaCPDjCyGo10ajUphiwA922jKt9n7KPpqR7oM1PCwYzhB/E7+nT3wfdG3oRre5raIT1rKA== - -"vue-loader-v16@npm:vue-loader@^16.1.0": - version "16.8.3" - resolved "https://registry.npmmirror.com/vue-loader/-/vue-loader-16.8.3.tgz" - integrity sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA== - dependencies: - chalk "^4.1.0" - hash-sum "^2.0.0" - loader-utils "^2.0.0" - -vue-loader@^15.9.2: - version "15.11.1" - resolved "https://registry.npmmirror.com/vue-loader/-/vue-loader-15.11.1.tgz" - integrity sha512-0iw4VchYLePqJfJu9s62ACWUXeSqM30SQqlIftbYWM3C+jpPcEHKSPUZBLjSF9au4HTHQ/naF6OGnO3Q/qGR3Q== - dependencies: - "@vue/component-compiler-utils" "^3.1.0" - hash-sum "^1.0.2" - loader-utils "^1.1.0" - vue-hot-reload-api "^2.3.0" - vue-style-loader "^4.1.0" - -vue-property-decorator@^8.5.1: - version "8.5.1" - resolved "https://registry.npmmirror.com/vue-property-decorator/-/vue-property-decorator-8.5.1.tgz" - integrity sha512-O6OUN2OMsYTGPvgFtXeBU3jPnX5ffQ9V4I1WfxFQ6dqz6cOUbR3Usou7kgFpfiXDvV7dJQSFcJ5yUPgOtPPm1Q== - dependencies: - vue-class-component "^7.1.0" - -vue-rx@^6.2.0: - version "6.2.0" - resolved "https://registry.npmmirror.com/vue-rx/-/vue-rx-6.2.0.tgz" - integrity sha512-tpKUcqS5IUYS+HsdbR5TlE5LL9PK4zwlplEtmMeydnbqaUTa9ciLMplJXAtFSiQw1vuURoyEJmCXqMxaVEIloQ== - -vue-splitpane@^1.0.6: - version "1.0.6" - resolved "https://registry.npmmirror.com/vue-splitpane/-/vue-splitpane-1.0.6.tgz" - integrity sha512-6sneVSEeF1VsCD025HP93nRxwzYhv3rotCeYi2Ah1FmGxwtfqbCZEIzaHGberdGNCpMlEzGD/1OIE1kK5QukLA== - -vue-style-loader@^4.1.0, vue-style-loader@^4.1.2: - version "4.1.3" - resolved "https://registry.npmmirror.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz" - integrity sha512-sFuh0xfbtpRlKfm39ss/ikqs9AbKCoXZBpHeVZ8Tx650o0k0q/YCM7FRvigtxpACezfq6af+a7JeqVTWvncqDg== - dependencies: - hash-sum "^1.0.2" - loader-utils "^1.0.2" - -vue-template-compiler@^2.6.11: - version "2.7.16" - resolved "https://registry.npmmirror.com/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz" - integrity sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ== - dependencies: - de-indent "^1.0.2" - he "^1.2.0" - -vue-template-es2015-compiler@^1.9.0: - version "1.9.1" - resolved "https://registry.npmmirror.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz" - integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw== - -vue@^2.6.11: - version "2.7.16" - resolved "https://registry.npmmirror.com/vue/-/vue-2.7.16.tgz" - integrity sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw== - dependencies: - "@vue/compiler-sfc" "2.7.16" - csstype "^3.1.0" - -vuex@^3.6.0: - version "3.6.2" - resolved "https://registry.npmmirror.com/vuex/-/vuex-3.6.2.tgz" - integrity sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw== - -watchpack-chokidar2@^2.0.1: - version "2.0.1" - resolved "https://registry.npmmirror.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz" - integrity sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww== - dependencies: - chokidar "^2.1.8" - -watchpack@^1.7.4: - version "1.7.5" - resolved "https://registry.npmmirror.com/watchpack/-/watchpack-1.7.5.tgz" - integrity sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ== - dependencies: - graceful-fs "^4.1.2" - neo-async "^2.5.0" - optionalDependencies: - chokidar "^3.4.1" - watchpack-chokidar2 "^2.0.1" - -wbuf@^1.1.0, wbuf@^1.7.3: - version "1.7.3" - resolved "https://registry.npmmirror.com/wbuf/-/wbuf-1.7.3.tgz" - integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== - dependencies: - minimalistic-assert "^1.0.0" - -wcwidth@^1.0.1: - version "1.0.1" - resolved "https://registry.npmmirror.com/wcwidth/-/wcwidth-1.0.1.tgz" - integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== - dependencies: - defaults "^1.0.3" - -webpack-bundle-analyzer@^3.8.0: - version "3.9.0" - resolved "https://registry.npmmirror.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.9.0.tgz" - integrity sha512-Ob8amZfCm3rMB1ScjQVlbYYUEJyEjdEtQ92jqiFUYt5VkEeO2v5UMbv49P/gnmCZm3A6yaFQzCBvpZqN4MUsdA== - dependencies: - acorn "^7.1.1" - acorn-walk "^7.1.1" - bfj "^6.1.1" - chalk "^2.4.1" - commander "^2.18.0" - ejs "^2.6.1" - express "^4.16.3" - filesize "^3.6.1" - gzip-size "^5.0.0" - lodash "^4.17.19" - mkdirp "^0.5.1" - opener "^1.5.1" - ws "^6.0.0" - -webpack-chain@^6.4.0: - version "6.5.1" - resolved "https://registry.npmmirror.com/webpack-chain/-/webpack-chain-6.5.1.tgz" - integrity sha512-7doO/SRtLu8q5WM0s7vPKPWX580qhi0/yBHkOxNkv50f6qB76Zy9o2wRTrrPULqYTvQlVHuvbA8v+G5ayuUDsA== - dependencies: - deepmerge "^1.5.2" - javascript-stringify "^2.0.1" - -webpack-dev-middleware@^3.7.2: - version "3.7.3" - resolved "https://registry.npmmirror.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz" - integrity sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ== - dependencies: - memory-fs "^0.4.1" - mime "^2.4.4" - mkdirp "^0.5.1" - range-parser "^1.2.1" - webpack-log "^2.0.0" - -webpack-dev-server@^3.11.0: - version "3.11.3" - resolved "https://registry.npmmirror.com/webpack-dev-server/-/webpack-dev-server-3.11.3.tgz" - integrity sha512-3x31rjbEQWKMNzacUZRE6wXvUFuGpH7vr0lIEbYpMAG9BOxi0928QU1BBswOAP3kg3H1O4hiS+sq4YyAn6ANnA== - dependencies: - ansi-html-community "0.0.8" - bonjour "^3.5.0" - chokidar "^2.1.8" - compression "^1.7.4" - connect-history-api-fallback "^1.6.0" - debug "^4.1.1" - del "^4.1.1" - express "^4.17.1" - html-entities "^1.3.1" - http-proxy-middleware "0.19.1" - import-local "^2.0.0" - internal-ip "^4.3.0" - ip "^1.1.5" - is-absolute-url "^3.0.3" - killable "^1.0.1" - loglevel "^1.6.8" - opn "^5.5.0" - p-retry "^3.0.1" - portfinder "^1.0.26" - schema-utils "^1.0.0" - selfsigned "^1.10.8" - semver "^6.3.0" - serve-index "^1.9.1" - sockjs "^0.3.21" - sockjs-client "^1.5.0" - spdy "^4.0.2" - strip-ansi "^3.0.1" - supports-color "^6.1.0" - url "^0.11.0" - webpack-dev-middleware "^3.7.2" - webpack-log "^2.0.0" - ws "^6.2.1" - yargs "^13.3.2" - -webpack-log@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/webpack-log/-/webpack-log-2.0.0.tgz" - integrity sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg== - dependencies: - ansi-colors "^3.0.0" - uuid "^3.3.2" - -webpack-merge@^4.2.2: - version "4.2.2" - resolved "https://registry.npmmirror.com/webpack-merge/-/webpack-merge-4.2.2.tgz" - integrity sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g== - dependencies: - lodash "^4.17.15" - -webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1: - version "1.4.3" - resolved "https://registry.npmmirror.com/webpack-sources/-/webpack-sources-1.4.3.tgz" - integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== - dependencies: - source-list-map "^2.0.0" - source-map "~0.6.1" - -webpack@^4.0.0: - version "4.47.0" - resolved "https://registry.npmmirror.com/webpack/-/webpack-4.47.0.tgz" - integrity sha512-td7fYwgLSrky3fI1EuU5cneU4+pbH6GgOfuKNS1tNPcfdGinGELAqsb/BP4nnvZyKSG2i/xFGU7+n2PvZA8HJQ== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-module-context" "1.9.0" - "@webassemblyjs/wasm-edit" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - acorn "^6.4.1" - ajv "^6.10.2" - ajv-keywords "^3.4.1" - chrome-trace-event "^1.0.2" - enhanced-resolve "^4.5.0" - eslint-scope "^4.0.3" - json-parse-better-errors "^1.0.2" - loader-runner "^2.4.0" - loader-utils "^1.2.3" - memory-fs "^0.4.1" - micromatch "^3.1.10" - mkdirp "^0.5.3" - neo-async "^2.6.1" - node-libs-browser "^2.2.1" - schema-utils "^1.0.0" - tapable "^1.1.3" - terser-webpack-plugin "^1.4.3" - watchpack "^1.7.4" - webpack-sources "^1.4.1" - -websocket-driver@>=0.5.1, websocket-driver@^0.7.4: - version "0.7.4" - resolved "https://registry.npmmirror.com/websocket-driver/-/websocket-driver-0.7.4.tgz" - integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== - dependencies: - http-parser-js ">=0.5.1" - safe-buffer ">=5.1.0" - websocket-extensions ">=0.1.1" - -websocket-extensions@>=0.1.1: - version "0.1.4" - resolved "https://registry.npmmirror.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz" - integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== - -which-boxed-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz" - integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== - dependencies: - is-bigint "^1.0.1" - is-boolean-object "^1.1.0" - is-number-object "^1.0.4" - is-string "^1.0.5" - is-symbol "^1.0.3" - -which-module@^2.0.0: - version "2.0.1" - resolved "https://registry.npmmirror.com/which-module/-/which-module-2.0.1.tgz" - integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== - -which-typed-array@^1.1.14, which-typed-array@^1.1.15: - version "1.1.15" - resolved "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.15.tgz" - integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.2" - -which@^1.2.9: - version "1.3.1" - resolved "https://registry.npmmirror.com/which/-/which-1.3.1.tgz" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.npmmirror.com/which/-/which-2.0.2.tgz" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -word-wrap@~1.2.3: - version "1.2.5" - resolved "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz" - integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== - -worker-farm@^1.7.0: - version "1.7.0" - resolved "https://registry.npmmirror.com/worker-farm/-/worker-farm-1.7.0.tgz" - integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw== - dependencies: - errno "~0.1.7" - -wrap-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz" - integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== - dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" - -wrap-ansi@^6.2.0: - version "6.2.0" - resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz" - integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -write@1.0.3: - version "1.0.3" - resolved "https://registry.npmmirror.com/write/-/write-1.0.3.tgz" - integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== - dependencies: - mkdirp "^0.5.1" - -ws@^6.0.0, ws@^6.2.1: - version "6.2.2" - resolved "https://registry.npmmirror.com/ws/-/ws-6.2.2.tgz" - integrity sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw== - dependencies: - async-limiter "~1.0.0" - -xtend@^4.0.0, xtend@~4.0.1: - version "4.0.2" - resolved "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -y18n@^4.0.0: - version "4.0.3" - resolved "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz" - integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yallist@^2.1.2: - version "2.1.2" - resolved "https://registry.npmmirror.com/yallist/-/yallist-2.1.2.tgz" - integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A== - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - -yargs-parser@^13.1.2: - version "13.1.2" - resolved "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-13.1.2.tgz" - integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs-parser@^20.2.2: - version "20.2.9" - resolved "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-20.2.9.tgz" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - -yargs@^13.3.2: - version "13.3.2" - resolved "https://registry.npmmirror.com/yargs/-/yargs-13.3.2.tgz" - integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== - dependencies: - cliui "^5.0.0" - find-up "^3.0.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^3.0.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^13.1.2" - -yargs@^16.0.0: - version "16.2.0" - resolved "https://registry.npmmirror.com/yargs/-/yargs-16.2.0.tgz" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - -yorkie@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/yorkie/-/yorkie-2.0.0.tgz" - integrity sha512-jcKpkthap6x63MB4TxwCyuIGkV0oYP/YRyuQU5UO0Yz/E/ZAu+653/uov+phdmO54n6BcvFRyyt0RRrWdN2mpw== - dependencies: - execa "^0.8.0" - is-ci "^1.0.10" - normalize-path "^1.0.0" - strip-indent "^2.0.0" diff --git a/pom.xml b/pom.xml index 9fa1a974..bf8bbaae 100644 --- a/pom.xml +++ b/pom.xml @@ -21,15 +21,13 @@ 0.0.1 + 1.2.28-jms-chen.1 21 3.9.0 21 21 UTF-8 - 1.12.1 - v14.18.0 - 6.14.15 10.1.55 1.75.0 4.1.135.Final diff --git a/scripts/resolve-wisp-tag.sh b/scripts/resolve-wisp-tag.sh new file mode 100755 index 00000000..5dd5c61c --- /dev/null +++ b/scripts/resolve-wisp-tag.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env sh + +set -eu + +script_dir=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) +chen_root=$(CDPATH='' cd -- "${script_dir}/.." && pwd) + +branch=${WISP_BRANCH:-} + +if [ -z "${branch}" ]; then + current_branch=$(git -C "${chen_root}" symbolic-ref --quiet --short HEAD 2>/dev/null || true) + + if [ -n "${current_branch}" ]; then + # Prefer the configured tracking branch. This lets a locally named + # worktree branch still select the canonical Chen/Wisp branch. + branch=$(git -C "${chen_root}" config --get "branch.${current_branch}.merge" 2>/dev/null || true) + branch=${branch#refs/heads/} + branch=${branch:-${current_branch}} + fi +fi + +case "${branch}" in + refs/heads/*) + branch=${branch#refs/heads/} + ;; + refs/remotes/*/*) + branch=${branch#refs/remotes/} + branch=${branch#*/} + ;; +esac + +# JumpServer feature branches use pr@@ (and occasionally repr@). +# Wisp images are shared at the base-branch level, not per component PR. +case "${branch}" in + pr@*@* | repr@*@*) + branch=${branch#*@} + branch=${branch%%@*} + ;; +esac + +# Match Docker tag rules and docker/metadata-action's replacement behavior. +tag=$(printf '%s' "${branch}" \ + | sed 's/[^A-Za-z0-9_.-][^A-Za-z0-9_.-]*/-/g; s/^[.-][.-]*//' \ + | cut -c 1-128) + +printf '%s\n' "${tag:-latest}" diff --git a/scripts/sync-wisp-proto.sh b/scripts/sync-wisp-proto.sh new file mode 100755 index 00000000..ad568960 --- /dev/null +++ b/scripts/sync-wisp-proto.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +set -euo pipefail + +script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +chen_root=$(cd -- "${script_dir}/.." && pwd) +wisp_root=${WISP_SOURCE:-"${chen_root}/../wisp"} +source_dir="${wisp_root}/protobuf-java/org/jumpserver/wisp" +target_dir="${chen_root}/backend/wisp/src/main/java/org/jumpserver/wisp" + +if [[ ! -d "${source_dir}" ]]; then + echo "Wisp Java protobuf output was not found: ${source_dir}" >&2 + echo "Generate it in Wisp first (make proto-java), or set WISP_SOURCE." >&2 + exit 1 +fi + +shopt -s nullglob +generated_files=("${source_dir}"/*.java) +if (( ${#generated_files[@]} == 0 )); then + echo "No generated Java files were found in ${source_dir}" >&2 + exit 1 +fi + +mkdir -p "${target_dir}" +for generated_file in "${generated_files[@]}"; do + cp -- "${generated_file}" "${target_dir}/" +done + +echo "Synchronized ${#generated_files[@]} Wisp Java protobuf file(s) into ${target_dir}" diff --git a/start.sh b/start.sh new file mode 100755 index 00000000..c93030cd --- /dev/null +++ b/start.sh @@ -0,0 +1,133 @@ +#!/usr/bin/env bash + +set -euo pipefail +chen_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +wisp_root=${WISP_SOURCE:-"${chen_root}/../wisp"} +wisp_bin="${chen_root}/data/bin/wisp-local" +wisp_config=${WISP_CONFIG:-"${chen_root}/data/wisp-local.yml"} + +if [[ ! -f "${wisp_root}/main.go" ]]; then + echo "Wisp source was not found: ${wisp_root}" >&2 + exit 1 +fi + +for command in go java mvn; do + if ! command -v "${command}" >/dev/null 2>&1; then + echo "Required command was not found: ${command}" >&2 + exit 1 + fi +done + +cd "${chen_root}" +mkdir -p "$(dirname -- "${wisp_bin}")" + +echo "Building Wisp from ${wisp_root}" +( + cd "${wisp_root}" + go build -o "${wisp_bin}" . +) + +echo "Building Chen" +mvn -pl backend/web -am -DskipTests package + +shopt -s nullglob +chen_jars=("${chen_root}"/backend/web/target/web-*.jar) +if (( ${#chen_jars[@]} != 1 )); then + echo "Expected one Chen executable jar, found ${#chen_jars[@]}" >&2 + exit 1 +fi +chen_jar=${chen_jars[0]} + +export COMPONENT_NAME=chen +export CORE_HOST=${CORE_HOST:-http://127.0.0.1:8080} +export BOOTSTRAP_TOKEN=${BOOTSTRAP_TOKEN:-PleaseChangeMe} +export BIND_HOST=${WISP_BIND_HOST:-127.0.0.1} +export BIND_PORT=${WISP_BIND_PORT:-9090} +export LOG_LEVEL=${LOG_LEVEL:-INFO} +export WORK_DIR=${WISP_WORK_DIR:-${chen_root}} +export EXECUTE_PROGRAM="" +wisp_connect_host=${WISP_CONNECT_HOST:-127.0.0.1} +export GRPC_CLIENT_WISP_ADDRESS=${GRPC_CLIENT_WISP_ADDRESS:-static://${wisp_connect_host}:${BIND_PORT}} + +wisp_pid="" +chen_pid="" + +cleanup() { + status=$? + trap - EXIT INT TERM + for pid in "${chen_pid}" "${wisp_pid}"; do + if [[ -n "${pid}" ]] && kill -0 "${pid}" 2>/dev/null; then + kill -TERM "${pid}" 2>/dev/null || true + fi + done + for pid in "${chen_pid}" "${wisp_pid}"; do + if [[ -n "${pid}" ]]; then + wait "${pid}" 2>/dev/null || true + fi + done + exit "${status}" +} + +trap cleanup EXIT +trap 'exit 130' INT +trap 'exit 143' TERM + +wait_for_wisp() { + timeout=${WISP_START_TIMEOUT:-60} + deadline=$((SECONDS + timeout)) + while (( SECONDS < deadline )); do + if ! kill -0 "${wisp_pid}" 2>/dev/null; then + status=0 + wait "${wisp_pid}" || status=$? + echo "Wisp exited before becoming ready (status ${status})." >&2 + tail -n 20 "${WORK_DIR}/data/logs/wisp.log" 2>/dev/null || true + return 1 + fi + if (echo >/dev/tcp/"${wisp_connect_host}"/"${BIND_PORT}") 2>/dev/null; then + return 0 + fi + sleep 1 + done + echo "Timed out waiting ${timeout}s for Wisp on ${wisp_connect_host}:${BIND_PORT}." >&2 + tail -n 20 "${WORK_DIR}/data/logs/wisp.log" 2>/dev/null || true + return 1 +} + +echo "Starting Wisp on ${BIND_HOST}:${BIND_PORT}" +"${wisp_bin}" --config "${wisp_config}" & +wisp_pid=$! + +if ! wait_for_wisp; then + echo "If Core reports 401, enable service-account registration or restart a Core configured with registration=auto, then retry." >&2 + exit 1 +fi +echo "Wisp is ready." + +chen_port=${CHEN_PORT:-8082} +echo "Starting Chen on http://127.0.0.1:${chen_port}/chen/" +java \ + -Dfile.encoding=utf-8 \ + -DsocksProxyHost= \ + -DsocksProxyPort=0 \ + -XX:+ExitOnOutOfMemoryError \ + -jar "${chen_jar}" \ + --server.port="${chen_port}" \ + --spring.config.additional-location=optional:file:"${chen_root}/config/" \ + --spring.profiles.active=dev & +chen_pid=$! + +echo "Wisp PID: ${wisp_pid}; Chen PID: ${chen_pid}. Press Ctrl-C to stop both." + +while kill -0 "${wisp_pid}" 2>/dev/null && kill -0 "${chen_pid}" 2>/dev/null; do + sleep 1 +done + +status=0 +if ! kill -0 "${wisp_pid}" 2>/dev/null; then + wait "${wisp_pid}" || status=$? + echo "Wisp stopped with status ${status}" >&2 +else + wait "${chen_pid}" || status=$? + echo "Chen stopped with status ${status}" >&2 +fi +exit "${status}"