diff --git a/.dockerignore b/.dockerignore index ac6cec25..aa27c76c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,35 +1,25 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* +# The production Dockerfile only needs the application sources and entrypoint. +# Start by excluding everything so local metadata, secrets and generated files +# can never leak into the remote BuildKit context. +** -/**/node_modules -/**/dist -/**/dist-ssr -*.local +!.dockerignore +!pkg/ +!pkg/docker/ +!pkg/docker/Dockerfile +!pkg/docker/docker-entrypoint.sh -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? +!core/ +!core/** -/**/.idea -/**/bin -/**/.dockman.yaml -compose/* -/**/test-compose -/dock/ -dock/* -/backend/**/*compose.yml -install/stacks/ -/backend/**/gitTest \ No newline at end of file +!ui/ +!ui/** + +# Never reuse host-generated dependency or build output trees. +core/**/.build/ +core/**/bin/ +ui/node_modules/ +ui/dist/ +ui/release/ +**/*.log +**/.DS_Store diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..e21d948f --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,52 @@ +version: 2 + +updates: + - package-ecosystem: gomod + directory: /core + target-branch: integration + schedule: + interval: weekly + day: monday + time: "06:00" + timezone: Europe/Paris + groups: + go-minor-patch: + update-types: + - minor + - patch + + - package-ecosystem: npm + directory: /ui + target-branch: integration + schedule: + interval: weekly + day: monday + time: "06:15" + timezone: Europe/Paris + groups: + npm-minor-patch: + update-types: + - minor + - patch + + - package-ecosystem: docker + directory: /pkg/docker + target-branch: integration + schedule: + interval: weekly + day: monday + time: "06:30" + timezone: Europe/Paris + + - package-ecosystem: github-actions + directory: / + target-branch: integration + schedule: + interval: weekly + day: monday + time: "06:45" + timezone: Europe/Paris + groups: + github-actions: + patterns: + - "*" diff --git a/.github/workflows/fork-checks.yml b/.github/workflows/fork-checks.yml new file mode 100644 index 00000000..49b27e15 --- /dev/null +++ b/.github/workflows/fork-checks.yml @@ -0,0 +1,136 @@ +name: Fork Checks + +# Go build / vet / test for THIS fork, without touching contribution branches. +# +# - push to `integration` -> checks the stacked code (all merged subjects) +# - manual dispatch (`ref`) -> check any single branch in isolation, e.g. to +# gate a subject before opening its upstream PR. +# +# Like the build workflow, this lives ONLY on `integration` so that fix/* and +# feat/* branches stay pristine for zero-conflict PRs. + +on: + push: + branches: + - integration + workflow_dispatch: + inputs: + ref: + description: 'Optional branch or ref to check (defaults to the workflow ref)' + required: false + type: string + test_path: + description: 'Go package path passed to `go test` (owned/passing packages)' + required: false + default: './internal/auth/...' + type: string + +permissions: + contents: read + +jobs: + go: + name: Go build / vet / test + runs-on: ubuntu-latest + defaults: + run: + working-directory: core + steps: + - name: Checkout target ref + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.event.inputs.ref || github.ref_name }} + + - name: Set up Go + uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 + with: + go-version-file: core/go.mod + cache-dependency-path: core/go.sum + + - name: Verify module graph + run: | + go mod tidy + git diff --exit-code -- go.mod go.sum + + # Scope to library code (./internal, ./pkg). The cmd/* main packages use + # //go:embed dist and need the frontend built first, which is out of scope + # for a Go check; the Docker image build already validates the full binary. + # + # Upstream has some rough edges we route around so checks reflect real, + # shipping code (extend these lists if we adopt one of them as a subject): + # DEAD_PKGS - not wired into any binary; `notifications` doesn't even + # compile. Excluded from everything. + # STALE_TESTS - package code compiles and ships, but its *_test.go files + # are stale (drifted from the API) and fail to compile. + # Excluded from vet/test only; still built. + - name: Resolve package lists + id: pkgs + run: | + DEAD_PKGS='/internal/(notifications|lsp)$' + STALE_TESTS='/internal/(host|ssh)$' + ALL="$(go list -e ./internal/... ./pkg/...)" + BUILD="$(echo "$ALL" | grep -vE "$DEAD_PKGS" | tr '\n' ' ')" + VET="$(echo "$ALL" | grep -vE "$DEAD_PKGS|$STALE_TESTS" | tr '\n' ' ')" + echo "build=$BUILD" >> "$GITHUB_OUTPUT" + echo "vet=$VET" >> "$GITHUB_OUTPUT" + echo "Build scope:"; echo "$BUILD" | tr ' ' '\n' + + # `go build` across all shipping packages is the real anti-regression net + # for merges into `integration`: it catches any compile break introduced by + # a stacked subject, everywhere. + - name: Build + run: go build ${{ steps.pkgs.outputs.build }} + + - name: Vet + run: go vet ${{ steps.pkgs.outputs.vet }} + + # Only run tests we own and that pass in a plain CI runner. Upstream tests + # for docker/*, git, host and ssh require a live Docker daemon / SSH host + # (or are broken) and are intentionally not run here. This list grows as we + # add subjects; a dispatch can override it with `test_path`. + - name: Test + env: + CGO_ENABLED: "1" + run: go test ${{ github.event.inputs.test_path || './internal/auth/...' }} + + - name: Build and test Docker target + env: + CGO_ENABLED: "1" + run: | + go test ./cmd/docker + go build -trimpath -buildvcs=false -o "${RUNNER_TEMP}/dockman" ./cmd/docker + + - name: Install govulncheck + run: go install golang.org/x/vuln/cmd/govulncheck@v1.6.0 + + - name: Check reachable Go vulnerabilities + working-directory: . + run: bash scripts/check-govuln.sh + + frontend: + name: Frontend audit / build + runs-on: ubuntu-latest + defaults: + run: + working-directory: ui + steps: + - name: Checkout target ref + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.event.inputs.ref || github.ref_name }} + + - name: Set up Node + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 + with: + node-version: "24.18.0" + cache: npm + cache-dependency-path: ui/package-lock.json + + - name: Install locked dependencies + run: npm ci + + - name: Audit dependencies + run: npm audit --audit-level=low + + - name: Build frontend + run: npm run build diff --git a/.github/workflows/fork-integration-build.yml b/.github/workflows/fork-integration-build.yml new file mode 100644 index 00000000..72b8faaf --- /dev/null +++ b/.github/workflows/fork-integration-build.yml @@ -0,0 +1,204 @@ +name: Fork Integration Build + +# Builds and pushes Dockman images to GHCR for THIS fork (not upstream). +# +# - push to `integration` -> image tag `integration` +# = the stacked image containing every merged subject, +# for end-to-end testing on the homelab. +# - manual dispatch (`ref`) -> build any single branch in isolation, +# tag = sanitized branch name (or custom `tag`). +# +# This workflow lives ONLY on the `integration` branch, on purpose: +# contribution branches (fix/*, feat/*) stay clean so their PRs to the +# upstream repo never carry any of our CI files. Zero-conflict by design. + +on: + push: + branches: + - integration + paths: + - 'core/**' + - 'ui/**' + - 'pkg/docker/**' + - '.dockerignore' + - '.github/workflows/fork-integration-build.yml' + workflow_dispatch: + inputs: + ref: + description: 'Optional branch or ref to build (defaults to the workflow ref)' + required: false + type: string + tag: + description: 'Optional image tag override (defaults to sanitized ref)' + required: false + type: string + +permissions: + contents: read + packages: write + +concurrency: + group: fork-build-${{ github.event.inputs.ref || github.ref_name }} + cancel-in-progress: true + +jobs: + prepare: + runs-on: ubuntu-latest + outputs: + ref: ${{ steps.p.outputs.ref }} + tag: ${{ steps.p.outputs.tag }} + image: ${{ steps.p.outputs.image }} + steps: + - id: p + run: | + REF="${{ github.event.inputs.ref || github.ref_name }}" + RAW="${{ github.event.inputs.tag || github.event.inputs.ref || github.ref_name }}" + TAG="$(echo "$RAW" | tr '[:upper:]' '[:lower:]' | tr '/' '-' | sed 's/[^a-z0-9._-]/-/g')" + IMAGE="$(echo "ghcr.io/${{ github.repository_owner }}/dockman" | tr '[:upper:]' '[:lower:]')" + echo "ref=$REF" >> "$GITHUB_OUTPUT" + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + echo "image=$IMAGE" >> "$GITHUB_OUTPUT" + echo "Building ref='$REF' -> '$IMAGE:$TAG'" + + build: + needs: prepare + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + machine: ubuntu-latest + arch: amd64 + - platform: linux/arm64 + machine: ubuntu-24.04-arm + arch: arm64 + name: Build (${{ matrix.arch }}) + runs-on: ${{ matrix.machine }} + permissions: + contents: read + packages: write + steps: + - name: Checkout target ref + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ needs.prepare.outputs.ref }} + fetch-depth: 0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 + + - name: Resolve source metadata + id: source + run: | + echo "commit=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + echo "build_date=$(git show -s --format=%cI HEAD)" >> "$GITHUB_OUTPUT" + + - name: Log in to GHCR + uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push (${{ matrix.arch }}) + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 + with: + context: . + file: pkg/docker/Dockerfile + push: true + sbom: true + provenance: mode=max + platforms: ${{ matrix.platform }} + tags: ${{ needs.prepare.outputs.image }}:${{ needs.prepare.outputs.tag }}-${{ matrix.arch }} + cache-to: type=gha,mode=max,scope=${{ needs.prepare.outputs.tag }}-${{ matrix.arch }} + cache-from: | + type=gha,scope=${{ needs.prepare.outputs.tag }}-${{ matrix.arch }} + type=registry,ref=${{ needs.prepare.outputs.image }}:${{ needs.prepare.outputs.tag }}-${{ matrix.arch }} + build-args: | + VERSION=${{ needs.prepare.outputs.tag }} + COMMIT_INFO=${{ steps.source.outputs.commit }} + BUILD_DATE=${{ steps.source.outputs.build_date }} + BRANCH=${{ needs.prepare.outputs.ref }} + + - name: Report all vulnerabilities (${{ matrix.arch }}) + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + image-ref: ${{ needs.prepare.outputs.image }}:${{ needs.prepare.outputs.tag }}-${{ matrix.arch }} + version: v0.72.0 + format: json + output: trivy-${{ matrix.arch }}.json + vuln-type: os,library + severity: UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL + ignore-unfixed: false + exit-code: "0" + scanners: vuln + env: + TRIVY_PLATFORM: ${{ matrix.platform }} + + - name: Upload vulnerability report (${{ matrix.arch }}) + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: trivy-${{ matrix.arch }} + path: trivy-${{ matrix.arch }}.json + if-no-files-found: error + retention-days: 30 + + - name: Gate fixable high and critical vulnerabilities (${{ matrix.arch }}) + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + image-ref: ${{ needs.prepare.outputs.image }}:${{ needs.prepare.outputs.tag }}-${{ matrix.arch }} + version: v0.72.0 + format: table + vuln-type: os,library + severity: CRITICAL,HIGH + ignore-unfixed: true + exit-code: "1" + scanners: vuln + trivyignores: .trivyignore.yaml + env: + TRIVY_PLATFORM: ${{ matrix.platform }} + + manifest: + needs: [prepare, build] + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write + steps: + - name: Log in to GHCR + uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Create and push multi-arch manifest + id: image + run: | + IMAGE="${{ needs.prepare.outputs.image }}" + TAG="${{ needs.prepare.outputs.tag }}" + docker buildx imagetools create -t "$IMAGE:$TAG" \ + "$IMAGE:$TAG-amd64" \ + "$IMAGE:$TAG-arm64" + INSPECT="$(docker buildx imagetools inspect "$IMAGE:$TAG")" + echo "$INSPECT" + DIGEST="$(awk '$1 == "Digest:" {print $2; exit}' <<< "$INSPECT")" + if [[ ! "$DIGEST" =~ ^sha256:[a-f0-9]{64}$ ]]; then + echo "Unable to resolve the multi-arch image digest" >&2 + exit 1 + fi + echo "digest=$DIGEST" >> "$GITHUB_OUTPUT" + echo "### Image \`$IMAGE:$TAG\`" >> "$GITHUB_STEP_SUMMARY" + echo "Digest: \`$DIGEST\`" >> "$GITHUB_STEP_SUMMARY" + + - name: Install Cosign + uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 + with: + cosign-release: v3.1.2 + + - name: Sign multi-arch image + env: + IMAGE: ${{ needs.prepare.outputs.image }} + DIGEST: ${{ steps.image.outputs.digest }} + run: cosign sign --yes "$IMAGE@$DIGEST" diff --git a/.github/workflows/fork-proto-gen.yml b/.github/workflows/fork-proto-gen.yml new file mode 100644 index 00000000..779fa487 --- /dev/null +++ b/.github/workflows/fork-proto-gen.yml @@ -0,0 +1,84 @@ +name: Fork Proto Gen + +# Fork-only helper: regenerate the protobuf/Connect stubs for a contribution +# branch and commit them back, since buf + the codegen toolchain are not run +# locally. Plugin versions are pinned to what produced the committed stubs so +# the diff stays limited to the messages that actually changed. + +on: + # A push that only touches this file registers the workflow with the Actions + # API (so it becomes dispatchable) without doing any work — the job is gated + # to workflow_dispatch below. + push: + branches: + - integration + paths: + - ".github/workflows/fork-proto-gen.yml" + workflow_dispatch: + inputs: + ref: + description: "Branch to regenerate stubs on (checked out and pushed back)" + required: true + type: string + pkg: + description: "Proto package dir to commit (e.g. files, dockyaml)" + required: true + type: string + +permissions: + contents: write + +jobs: + gen: + if: github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref }} + + - uses: actions/setup-go@v5 + with: + go-version: "1.25" + + - uses: actions/setup-node@v4 + with: + node-version: "22" + + - name: Install buf + pinned plugins + run: | + set -euxo pipefail + go install github.com/bufbuild/buf/cmd/buf@latest + go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.11 + go install connectrpc.com/connect/cmd/protoc-gen-connect-go@v1.19.1 + npm install -g @bufbuild/protoc-gen-es@2.11.0 + echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" + + - name: Generate stubs + run: | + set -euxo pipefail + cd spec + buf generate + # mirror spec/Taskfile.yml copy targets + rm -rf ../core/generated/* ../ui/src/gen/* + mkdir -p ../core/generated ../ui/src/gen + cp -r generated/go/* ../core/generated/ + cp -r generated/web/* ../ui/src/gen/ + + - name: Commit regenerated stubs + run: | + set -euxo pipefail + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + PKG="${{ inputs.pkg }}" + # Only commit the requested package's stubs so any incidental version + # drift in unrelated generated files (left unstaged) is discarded, + # keeping the contribution branch's diff minimal. + git add "core/generated/${PKG}" "ui/src/gen/${PKG}" + if git diff --cached --quiet; then + echo "No ${PKG} stub changes to commit." + exit 0 + fi + git status --short "core/generated/${PKG}" "ui/src/gen/${PKG}" + git commit -m "chore(gen): regenerate ${PKG} stubs [skip ci]" + git push origin HEAD:${{ inputs.ref }} diff --git a/.trivyignore.yaml b/.trivyignore.yaml new file mode 100644 index 00000000..288e8cf7 --- /dev/null +++ b/.trivyignore.yaml @@ -0,0 +1,8 @@ +vulnerabilities: + - id: CVE-2026-34040 + statement: >- + Not affected: Dockman and its bundled Compose executable use Moby only + as Docker API clients. The vulnerable authorization middleware belongs + to the Docker daemon, which is neither embedded nor executed in this + image; deployments connect to an external socket proxy. + expired_at: 2026-10-31 diff --git a/core/cmd/develop/main.go b/core/cmd/develop/main.go index fd94961c..8e9f3f4e 100644 --- a/core/cmd/develop/main.go +++ b/core/cmd/develop/main.go @@ -34,6 +34,7 @@ func main() { //"PRIV_KEY_PATH": "./key.pem", "AUTH_ENABLE": "false", + "ORIGINS": "http://localhost:5173", //"AUTH_OIDC_ENABLE": "true", //"AUTH_OIDC_AUTO_REDIRECT": "false", "AUTH_OIDC_ISSUER": "https://localhost", diff --git a/core/generated/docker/v1/docker.pb.go b/core/generated/docker/v1/docker.pb.go index f0353d5b..7adefc2f 100644 --- a/core/generated/docker/v1/docker.pb.go +++ b/core/generated/docker/v1/docker.pb.go @@ -31,6 +31,7 @@ const ( SORT_FIELD_NETWORK_TX SORT_FIELD = 4 SORT_FIELD_DISK_R SORT_FIELD = 5 SORT_FIELD_DISK_W SORT_FIELD = 6 + SORT_FIELD_STARTED SORT_FIELD = 7 ) // Enum value maps for SORT_FIELD. @@ -43,6 +44,7 @@ var ( 4: "NETWORK_TX", 5: "DISK_R", 6: "DISK_W", + 7: "STARTED", } SORT_FIELD_value = map[string]int32{ "NAME": 0, @@ -52,6 +54,7 @@ var ( "NETWORK_TX": 4, "DISK_R": 5, "DISK_W": 6, + "STARTED": 7, } ) @@ -469,15 +472,18 @@ func (x *Top) GetTitles() []string { } type ContainerInspectMessage struct { - state protoimpl.MessageState `protogen:"open.v1"` - Name string `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"` - ID string `protobuf:"bytes,2,opt,name=ID,proto3" json:"ID,omitempty"` - Path string `protobuf:"bytes,3,opt,name=Path,proto3" json:"Path,omitempty"` - Created string `protobuf:"bytes,7,opt,name=Created,proto3" json:"Created,omitempty"` - Image string `protobuf:"bytes,4,opt,name=Image,proto3" json:"Image,omitempty"` - HostsPath string `protobuf:"bytes,5,opt,name=HostsPath,proto3" json:"HostsPath,omitempty"` - Mounts []*ContainerMount `protobuf:"bytes,6,rep,name=mounts,proto3" json:"mounts,omitempty"` - Config *ContainerConfig `protobuf:"bytes,8,opt,name=config,proto3" json:"config,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"` + ID string `protobuf:"bytes,2,opt,name=ID,proto3" json:"ID,omitempty"` + Path string `protobuf:"bytes,3,opt,name=Path,proto3" json:"Path,omitempty"` + Created string `protobuf:"bytes,7,opt,name=Created,proto3" json:"Created,omitempty"` + Image string `protobuf:"bytes,4,opt,name=Image,proto3" json:"Image,omitempty"` + HostsPath string `protobuf:"bytes,5,opt,name=HostsPath,proto3" json:"HostsPath,omitempty"` + Mounts []*ContainerMount `protobuf:"bytes,6,rep,name=mounts,proto3" json:"mounts,omitempty"` + Config *ContainerConfig `protobuf:"bytes,8,opt,name=config,proto3" json:"config,omitempty"` + // Complete daemon inspect response. This deliberately stays JSON so newer + // daemon fields remain visible without forcing a Dockman protocol release. + RawJson string `protobuf:"bytes,9,opt,name=raw_json,json=rawJson,proto3" json:"raw_json,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -568,6 +574,13 @@ func (x *ContainerInspectMessage) GetConfig() *ContainerConfig { return nil } +func (x *ContainerInspectMessage) GetRawJson() string { + if x != nil { + return x.RawJson + } + return "" +} + type ContainerConfig struct { state protoimpl.MessageState `protogen:"open.v1"` Hostname string `protobuf:"bytes,1,opt,name=Hostname,proto3" json:"Hostname,omitempty"` @@ -1181,13 +1194,14 @@ func (x *ImageInspectResponse) GetInspect() *ImageInspect { } type ImageInspect struct { - state protoimpl.MessageState `protogen:"open.v1"` - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Id string `protobuf:"bytes,6,opt,name=id,proto3" json:"id,omitempty"` - Size string `protobuf:"bytes,3,opt,name=size,proto3" json:"size,omitempty"` - Arch string `protobuf:"bytes,5,opt,name=arch,proto3" json:"arch,omitempty"` - CreatedIso string `protobuf:"bytes,4,opt,name=createdIso,proto3" json:"createdIso,omitempty"` - Layers []*ImageLayer `protobuf:"bytes,2,rep,name=layers,proto3" json:"layers,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Id string `protobuf:"bytes,6,opt,name=id,proto3" json:"id,omitempty"` + Size string `protobuf:"bytes,3,opt,name=size,proto3" json:"size,omitempty"` + Arch string `protobuf:"bytes,5,opt,name=arch,proto3" json:"arch,omitempty"` + CreatedIso string `protobuf:"bytes,4,opt,name=createdIso,proto3" json:"createdIso,omitempty"` + Layers []*ImageLayer `protobuf:"bytes,2,rep,name=layers,proto3" json:"layers,omitempty"` + Containers []*ImageContainerInspect `protobuf:"bytes,7,rep,name=containers,proto3" json:"containers,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1264,6 +1278,13 @@ func (x *ImageInspect) GetLayers() []*ImageLayer { return nil } +func (x *ImageInspect) GetContainers() []*ImageContainerInspect { + if x != nil { + return x.Containers + } + return nil +} + type ImageLayer struct { state protoimpl.MessageState `protogen:"open.v1"` LayerId string `protobuf:"bytes,3,opt,name=LayerId,proto3" json:"LayerId,omitempty"` @@ -1332,6 +1353,74 @@ func (x *ImageLayer) GetTotalSizeAtLayer() string { return "" } +type ImageContainerInspect struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + State string `protobuf:"bytes,3,opt,name=state,proto3" json:"state,omitempty"` + ComposeProject string `protobuf:"bytes,4,opt,name=composeProject,proto3" json:"composeProject,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ImageContainerInspect) Reset() { + *x = ImageContainerInspect{} + mi := &file_docker_v1_docker_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ImageContainerInspect) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ImageContainerInspect) ProtoMessage() {} + +func (x *ImageContainerInspect) ProtoReflect() protoreflect.Message { + mi := &file_docker_v1_docker_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ImageContainerInspect.ProtoReflect.Descriptor instead. +func (*ImageContainerInspect) Descriptor() ([]byte, []int) { + return file_docker_v1_docker_proto_rawDescGZIP(), []int{19} +} + +func (x *ImageContainerInspect) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ImageContainerInspect) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ImageContainerInspect) GetState() string { + if x != nil { + return x.State + } + return "" +} + +func (x *ImageContainerInspect) GetComposeProject() string { + if x != nil { + return x.ComposeProject + } + return "" +} + type ComposeValidateResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Errs []string `protobuf:"bytes,1,rep,name=errs,proto3" json:"errs,omitempty"` @@ -1341,7 +1430,7 @@ type ComposeValidateResponse struct { func (x *ComposeValidateResponse) Reset() { *x = ComposeValidateResponse{} - mi := &file_docker_v1_docker_proto_msgTypes[19] + mi := &file_docker_v1_docker_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1353,7 +1442,7 @@ func (x *ComposeValidateResponse) String() string { func (*ComposeValidateResponse) ProtoMessage() {} func (x *ComposeValidateResponse) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[19] + mi := &file_docker_v1_docker_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1366,7 +1455,7 @@ func (x *ComposeValidateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ComposeValidateResponse.ProtoReflect.Descriptor instead. func (*ComposeValidateResponse) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{19} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{20} } func (x *ComposeValidateResponse) GetErrs() []string { @@ -1387,7 +1476,7 @@ type ContainerExecCmdInput struct { func (x *ContainerExecCmdInput) Reset() { *x = ContainerExecCmdInput{} - mi := &file_docker_v1_docker_proto_msgTypes[20] + mi := &file_docker_v1_docker_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1399,7 +1488,7 @@ func (x *ContainerExecCmdInput) String() string { func (*ContainerExecCmdInput) ProtoMessage() {} func (x *ContainerExecCmdInput) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[20] + mi := &file_docker_v1_docker_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1412,7 +1501,7 @@ func (x *ContainerExecCmdInput) ProtoReflect() protoreflect.Message { // Deprecated: Use ContainerExecCmdInput.ProtoReflect.Descriptor instead. func (*ContainerExecCmdInput) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{20} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{21} } func (x *ContainerExecCmdInput) GetUserCmd() string { @@ -1440,7 +1529,7 @@ type ContainerExecRequest struct { func (x *ContainerExecRequest) Reset() { *x = ContainerExecRequest{} - mi := &file_docker_v1_docker_proto_msgTypes[21] + mi := &file_docker_v1_docker_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1452,7 +1541,7 @@ func (x *ContainerExecRequest) String() string { func (*ContainerExecRequest) ProtoMessage() {} func (x *ContainerExecRequest) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[21] + mi := &file_docker_v1_docker_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1465,7 +1554,7 @@ func (x *ContainerExecRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ContainerExecRequest.ProtoReflect.Descriptor instead. func (*ContainerExecRequest) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{21} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{22} } func (x *ContainerExecRequest) GetContainerID() string { @@ -1502,7 +1591,7 @@ type Image struct { func (x *Image) Reset() { *x = Image{} - mi := &file_docker_v1_docker_proto_msgTypes[22] + mi := &file_docker_v1_docker_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1514,7 +1603,7 @@ func (x *Image) String() string { func (*Image) ProtoMessage() {} func (x *Image) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[22] + mi := &file_docker_v1_docker_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1527,7 +1616,7 @@ func (x *Image) ProtoReflect() protoreflect.Message { // Deprecated: Use Image.ProtoReflect.Descriptor instead. func (*Image) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{22} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{23} } func (x *Image) GetContainers() int64 { @@ -1618,7 +1707,7 @@ type ManifestSummary struct { func (x *ManifestSummary) Reset() { *x = ManifestSummary{} - mi := &file_docker_v1_docker_proto_msgTypes[23] + mi := &file_docker_v1_docker_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1630,7 +1719,7 @@ func (x *ManifestSummary) String() string { func (*ManifestSummary) ProtoMessage() {} func (x *ManifestSummary) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[23] + mi := &file_docker_v1_docker_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1643,7 +1732,7 @@ func (x *ManifestSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use ManifestSummary.ProtoReflect.Descriptor instead. func (*ManifestSummary) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{23} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{24} } func (x *ManifestSummary) GetDigest() string { @@ -1675,7 +1764,7 @@ type ListImagesRequest struct { func (x *ListImagesRequest) Reset() { *x = ListImagesRequest{} - mi := &file_docker_v1_docker_proto_msgTypes[24] + mi := &file_docker_v1_docker_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1687,7 +1776,7 @@ func (x *ListImagesRequest) String() string { func (*ListImagesRequest) ProtoMessage() {} func (x *ListImagesRequest) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[24] + mi := &file_docker_v1_docker_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1700,7 +1789,7 @@ func (x *ListImagesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListImagesRequest.ProtoReflect.Descriptor instead. func (*ListImagesRequest) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{24} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{25} } type ListImagesResponse struct { @@ -1715,7 +1804,7 @@ type ListImagesResponse struct { func (x *ListImagesResponse) Reset() { *x = ListImagesResponse{} - mi := &file_docker_v1_docker_proto_msgTypes[25] + mi := &file_docker_v1_docker_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1727,7 +1816,7 @@ func (x *ListImagesResponse) String() string { func (*ListImagesResponse) ProtoMessage() {} func (x *ListImagesResponse) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[25] + mi := &file_docker_v1_docker_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1740,7 +1829,7 @@ func (x *ListImagesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListImagesResponse.ProtoReflect.Descriptor instead. func (*ListImagesResponse) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{25} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{26} } func (x *ListImagesResponse) GetTotalDiskUsage() int64 { @@ -1781,7 +1870,7 @@ type RemoveImageRequest struct { func (x *RemoveImageRequest) Reset() { *x = RemoveImageRequest{} - mi := &file_docker_v1_docker_proto_msgTypes[26] + mi := &file_docker_v1_docker_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1793,7 +1882,7 @@ func (x *RemoveImageRequest) String() string { func (*RemoveImageRequest) ProtoMessage() {} func (x *RemoveImageRequest) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[26] + mi := &file_docker_v1_docker_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1806,7 +1895,7 @@ func (x *RemoveImageRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveImageRequest.ProtoReflect.Descriptor instead. func (*RemoveImageRequest) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{26} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{27} } func (x *RemoveImageRequest) GetHost() string { @@ -1831,7 +1920,7 @@ type RemoveImageResponse struct { func (x *RemoveImageResponse) Reset() { *x = RemoveImageResponse{} - mi := &file_docker_v1_docker_proto_msgTypes[27] + mi := &file_docker_v1_docker_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1843,7 +1932,7 @@ func (x *RemoveImageResponse) String() string { func (*RemoveImageResponse) ProtoMessage() {} func (x *RemoveImageResponse) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[27] + mi := &file_docker_v1_docker_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1856,7 +1945,7 @@ func (x *RemoveImageResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveImageResponse.ProtoReflect.Descriptor instead. func (*RemoveImageResponse) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{27} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{28} } type ImagePruneResponse struct { @@ -1869,7 +1958,7 @@ type ImagePruneResponse struct { func (x *ImagePruneResponse) Reset() { *x = ImagePruneResponse{} - mi := &file_docker_v1_docker_proto_msgTypes[28] + mi := &file_docker_v1_docker_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1881,7 +1970,7 @@ func (x *ImagePruneResponse) String() string { func (*ImagePruneResponse) ProtoMessage() {} func (x *ImagePruneResponse) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[28] + mi := &file_docker_v1_docker_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1894,7 +1983,7 @@ func (x *ImagePruneResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ImagePruneResponse.ProtoReflect.Descriptor instead. func (*ImagePruneResponse) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{28} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{29} } func (x *ImagePruneResponse) GetSpaceReclaimed() uint64 { @@ -1921,7 +2010,7 @@ type ImagePruneRequest struct { func (x *ImagePruneRequest) Reset() { *x = ImagePruneRequest{} - mi := &file_docker_v1_docker_proto_msgTypes[29] + mi := &file_docker_v1_docker_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1933,7 +2022,7 @@ func (x *ImagePruneRequest) String() string { func (*ImagePruneRequest) ProtoMessage() {} func (x *ImagePruneRequest) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[29] + mi := &file_docker_v1_docker_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1946,7 +2035,7 @@ func (x *ImagePruneRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ImagePruneRequest.ProtoReflect.Descriptor instead. func (*ImagePruneRequest) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{29} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{30} } func (x *ImagePruneRequest) GetHost() string { @@ -1973,7 +2062,7 @@ type ImagesDeleted struct { func (x *ImagesDeleted) Reset() { *x = ImagesDeleted{} - mi := &file_docker_v1_docker_proto_msgTypes[30] + mi := &file_docker_v1_docker_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1985,7 +2074,7 @@ func (x *ImagesDeleted) String() string { func (*ImagesDeleted) ProtoMessage() {} func (x *ImagesDeleted) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[30] + mi := &file_docker_v1_docker_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1998,7 +2087,7 @@ func (x *ImagesDeleted) ProtoReflect() protoreflect.Message { // Deprecated: Use ImagesDeleted.ProtoReflect.Descriptor instead. func (*ImagesDeleted) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{30} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{31} } func (x *ImagesDeleted) GetDeleted() string { @@ -2032,7 +2121,7 @@ type Volume struct { func (x *Volume) Reset() { *x = Volume{} - mi := &file_docker_v1_docker_proto_msgTypes[31] + mi := &file_docker_v1_docker_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2044,7 +2133,7 @@ func (x *Volume) String() string { func (*Volume) ProtoMessage() {} func (x *Volume) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[31] + mi := &file_docker_v1_docker_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2057,7 +2146,7 @@ func (x *Volume) ProtoReflect() protoreflect.Message { // Deprecated: Use Volume.ProtoReflect.Descriptor instead. func (*Volume) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{31} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{32} } func (x *Volume) GetName() string { @@ -2124,7 +2213,7 @@ type ListVolumesRequest struct { func (x *ListVolumesRequest) Reset() { *x = ListVolumesRequest{} - mi := &file_docker_v1_docker_proto_msgTypes[32] + mi := &file_docker_v1_docker_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2136,7 +2225,7 @@ func (x *ListVolumesRequest) String() string { func (*ListVolumesRequest) ProtoMessage() {} func (x *ListVolumesRequest) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[32] + mi := &file_docker_v1_docker_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2149,7 +2238,7 @@ func (x *ListVolumesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListVolumesRequest.ProtoReflect.Descriptor instead. func (*ListVolumesRequest) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{32} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{33} } type ListVolumesResponse struct { @@ -2161,7 +2250,7 @@ type ListVolumesResponse struct { func (x *ListVolumesResponse) Reset() { *x = ListVolumesResponse{} - mi := &file_docker_v1_docker_proto_msgTypes[33] + mi := &file_docker_v1_docker_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2173,7 +2262,7 @@ func (x *ListVolumesResponse) String() string { func (*ListVolumesResponse) ProtoMessage() {} func (x *ListVolumesResponse) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[33] + mi := &file_docker_v1_docker_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2186,7 +2275,7 @@ func (x *ListVolumesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListVolumesResponse.ProtoReflect.Descriptor instead. func (*ListVolumesResponse) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{33} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{34} } func (x *ListVolumesResponse) GetVolumes() []*Volume { @@ -2204,7 +2293,7 @@ type CreateVolumeRequest struct { func (x *CreateVolumeRequest) Reset() { *x = CreateVolumeRequest{} - mi := &file_docker_v1_docker_proto_msgTypes[34] + mi := &file_docker_v1_docker_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2216,7 +2305,7 @@ func (x *CreateVolumeRequest) String() string { func (*CreateVolumeRequest) ProtoMessage() {} func (x *CreateVolumeRequest) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[34] + mi := &file_docker_v1_docker_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2229,7 +2318,7 @@ func (x *CreateVolumeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateVolumeRequest.ProtoReflect.Descriptor instead. func (*CreateVolumeRequest) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{34} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{35} } type CreateVolumeResponse struct { @@ -2240,7 +2329,7 @@ type CreateVolumeResponse struct { func (x *CreateVolumeResponse) Reset() { *x = CreateVolumeResponse{} - mi := &file_docker_v1_docker_proto_msgTypes[35] + mi := &file_docker_v1_docker_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2252,7 +2341,7 @@ func (x *CreateVolumeResponse) String() string { func (*CreateVolumeResponse) ProtoMessage() {} func (x *CreateVolumeResponse) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[35] + mi := &file_docker_v1_docker_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2265,7 +2354,7 @@ func (x *CreateVolumeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateVolumeResponse.ProtoReflect.Descriptor instead. func (*CreateVolumeResponse) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{35} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{36} } type DeleteVolumeRequest struct { @@ -2280,7 +2369,7 @@ type DeleteVolumeRequest struct { func (x *DeleteVolumeRequest) Reset() { *x = DeleteVolumeRequest{} - mi := &file_docker_v1_docker_proto_msgTypes[36] + mi := &file_docker_v1_docker_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2292,7 +2381,7 @@ func (x *DeleteVolumeRequest) String() string { func (*DeleteVolumeRequest) ProtoMessage() {} func (x *DeleteVolumeRequest) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[36] + mi := &file_docker_v1_docker_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2305,7 +2394,7 @@ func (x *DeleteVolumeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteVolumeRequest.ProtoReflect.Descriptor instead. func (*DeleteVolumeRequest) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{36} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{37} } func (x *DeleteVolumeRequest) GetHost() string { @@ -2344,7 +2433,7 @@ type DeleteVolumeResponse struct { func (x *DeleteVolumeResponse) Reset() { *x = DeleteVolumeResponse{} - mi := &file_docker_v1_docker_proto_msgTypes[37] + mi := &file_docker_v1_docker_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2356,7 +2445,7 @@ func (x *DeleteVolumeResponse) String() string { func (*DeleteVolumeResponse) ProtoMessage() {} func (x *DeleteVolumeResponse) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[37] + mi := &file_docker_v1_docker_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2369,43 +2458,31 @@ func (x *DeleteVolumeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteVolumeResponse.ProtoReflect.Descriptor instead. func (*DeleteVolumeResponse) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{37} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{38} } -// Network-related messages -type Network struct { - state protoimpl.MessageState `protogen:"open.v1"` - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` - Subnet string `protobuf:"bytes,3,opt,name=subnet,proto3" json:"subnet,omitempty"` - Scope string `protobuf:"bytes,4,opt,name=scope,proto3" json:"scope,omitempty"` - Driver string `protobuf:"bytes,5,opt,name=driver,proto3" json:"driver,omitempty"` - EnableIpv4 bool `protobuf:"varint,6,opt,name=enable_ipv4,json=enableIpv4,proto3" json:"enable_ipv4,omitempty"` - EnableIpv6 bool `protobuf:"varint,7,opt,name=enable_ipv6,json=enableIpv6,proto3" json:"enable_ipv6,omitempty"` - Internal bool `protobuf:"varint,9,opt,name=internal,proto3" json:"internal,omitempty"` - Attachable bool `protobuf:"varint,10,opt,name=attachable,proto3" json:"attachable,omitempty"` - CreatedAt string `protobuf:"bytes,11,opt,name=createdAt,proto3" json:"createdAt,omitempty"` - ComposeProject string `protobuf:"bytes,12,opt,name=composeProject,proto3" json:"composeProject,omitempty"` - ContainerIds []string `protobuf:"bytes,13,rep,name=containerIds,proto3" json:"containerIds,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +type VolumeInspectRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + VolumeName string `protobuf:"bytes,1,opt,name=volumeName,proto3" json:"volumeName,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *Network) Reset() { - *x = Network{} - mi := &file_docker_v1_docker_proto_msgTypes[38] +func (x *VolumeInspectRequest) Reset() { + *x = VolumeInspectRequest{} + mi := &file_docker_v1_docker_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *Network) String() string { +func (x *VolumeInspectRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*Network) ProtoMessage() {} +func (*VolumeInspectRequest) ProtoMessage() {} -func (x *Network) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[38] +func (x *VolumeInspectRequest) ProtoReflect() protoreflect.Message { + mi := &file_docker_v1_docker_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2416,44 +2493,272 @@ func (x *Network) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use Network.ProtoReflect.Descriptor instead. -func (*Network) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{38} +// Deprecated: Use VolumeInspectRequest.ProtoReflect.Descriptor instead. +func (*VolumeInspectRequest) Descriptor() ([]byte, []int) { + return file_docker_v1_docker_proto_rawDescGZIP(), []int{39} } -func (x *Network) GetName() string { +func (x *VolumeInspectRequest) GetVolumeName() string { if x != nil { - return x.Name + return x.VolumeName } return "" } -func (x *Network) GetId() string { - if x != nil { - return x.Id - } - return "" +type VolumeInspectResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Inspect *VolumeInspectInfo `protobuf:"bytes,1,opt,name=inspect,proto3" json:"inspect,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *Network) GetSubnet() string { - if x != nil { - return x.Subnet - } - return "" +func (x *VolumeInspectResponse) Reset() { + *x = VolumeInspectResponse{} + mi := &file_docker_v1_docker_proto_msgTypes[40] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } -func (x *Network) GetScope() string { +func (x *VolumeInspectResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VolumeInspectResponse) ProtoMessage() {} + +func (x *VolumeInspectResponse) ProtoReflect() protoreflect.Message { + mi := &file_docker_v1_docker_proto_msgTypes[40] if x != nil { - return x.Scope + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return "" + return mi.MessageOf(x) } -func (x *Network) GetDriver() string { +// Deprecated: Use VolumeInspectResponse.ProtoReflect.Descriptor instead. +func (*VolumeInspectResponse) Descriptor() ([]byte, []int) { + return file_docker_v1_docker_proto_rawDescGZIP(), []int{40} +} + +func (x *VolumeInspectResponse) GetInspect() *VolumeInspectInfo { if x != nil { - return x.Driver + return x.Inspect } - return "" + return nil +} + +type VolumeInspectInfo struct { + state protoimpl.MessageState `protogen:"open.v1"` + Vol *Volume `protobuf:"bytes,1,opt,name=vol,proto3" json:"vol,omitempty"` + Containers []*VolumeContainerInspect `protobuf:"bytes,2,rep,name=containers,proto3" json:"containers,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *VolumeInspectInfo) Reset() { + *x = VolumeInspectInfo{} + mi := &file_docker_v1_docker_proto_msgTypes[41] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VolumeInspectInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VolumeInspectInfo) ProtoMessage() {} + +func (x *VolumeInspectInfo) ProtoReflect() protoreflect.Message { + mi := &file_docker_v1_docker_proto_msgTypes[41] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VolumeInspectInfo.ProtoReflect.Descriptor instead. +func (*VolumeInspectInfo) Descriptor() ([]byte, []int) { + return file_docker_v1_docker_proto_rawDescGZIP(), []int{41} +} + +func (x *VolumeInspectInfo) GetVol() *Volume { + if x != nil { + return x.Vol + } + return nil +} + +func (x *VolumeInspectInfo) GetContainers() []*VolumeContainerInspect { + if x != nil { + return x.Containers + } + return nil +} + +type VolumeContainerInspect struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + Destination string `protobuf:"bytes,3,opt,name=destination,proto3" json:"destination,omitempty"` + Rw bool `protobuf:"varint,4,opt,name=rw,proto3" json:"rw,omitempty"` + ComposeProject string `protobuf:"bytes,5,opt,name=composeProject,proto3" json:"composeProject,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *VolumeContainerInspect) Reset() { + *x = VolumeContainerInspect{} + mi := &file_docker_v1_docker_proto_msgTypes[42] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VolumeContainerInspect) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VolumeContainerInspect) ProtoMessage() {} + +func (x *VolumeContainerInspect) ProtoReflect() protoreflect.Message { + mi := &file_docker_v1_docker_proto_msgTypes[42] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VolumeContainerInspect.ProtoReflect.Descriptor instead. +func (*VolumeContainerInspect) Descriptor() ([]byte, []int) { + return file_docker_v1_docker_proto_rawDescGZIP(), []int{42} +} + +func (x *VolumeContainerInspect) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *VolumeContainerInspect) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *VolumeContainerInspect) GetDestination() string { + if x != nil { + return x.Destination + } + return "" +} + +func (x *VolumeContainerInspect) GetRw() bool { + if x != nil { + return x.Rw + } + return false +} + +func (x *VolumeContainerInspect) GetComposeProject() string { + if x != nil { + return x.ComposeProject + } + return "" +} + +// Network-related messages +type Network struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + Subnet string `protobuf:"bytes,3,opt,name=subnet,proto3" json:"subnet,omitempty"` + Scope string `protobuf:"bytes,4,opt,name=scope,proto3" json:"scope,omitempty"` + Driver string `protobuf:"bytes,5,opt,name=driver,proto3" json:"driver,omitempty"` + EnableIpv4 bool `protobuf:"varint,6,opt,name=enable_ipv4,json=enableIpv4,proto3" json:"enable_ipv4,omitempty"` + EnableIpv6 bool `protobuf:"varint,7,opt,name=enable_ipv6,json=enableIpv6,proto3" json:"enable_ipv6,omitempty"` + Internal bool `protobuf:"varint,9,opt,name=internal,proto3" json:"internal,omitempty"` + Attachable bool `protobuf:"varint,10,opt,name=attachable,proto3" json:"attachable,omitempty"` + CreatedAt string `protobuf:"bytes,11,opt,name=createdAt,proto3" json:"createdAt,omitempty"` + ComposeProject string `protobuf:"bytes,12,opt,name=composeProject,proto3" json:"composeProject,omitempty"` + ContainerIds []string `protobuf:"bytes,13,rep,name=containerIds,proto3" json:"containerIds,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Network) Reset() { + *x = Network{} + mi := &file_docker_v1_docker_proto_msgTypes[43] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Network) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Network) ProtoMessage() {} + +func (x *Network) ProtoReflect() protoreflect.Message { + mi := &file_docker_v1_docker_proto_msgTypes[43] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Network.ProtoReflect.Descriptor instead. +func (*Network) Descriptor() ([]byte, []int) { + return file_docker_v1_docker_proto_rawDescGZIP(), []int{43} +} + +func (x *Network) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Network) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Network) GetSubnet() string { + if x != nil { + return x.Subnet + } + return "" +} + +func (x *Network) GetScope() string { + if x != nil { + return x.Scope + } + return "" +} + +func (x *Network) GetDriver() string { + if x != nil { + return x.Driver + } + return "" } func (x *Network) GetEnableIpv4() bool { @@ -2513,7 +2818,7 @@ type ListNetworksRequest struct { func (x *ListNetworksRequest) Reset() { *x = ListNetworksRequest{} - mi := &file_docker_v1_docker_proto_msgTypes[39] + mi := &file_docker_v1_docker_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2525,7 +2830,7 @@ func (x *ListNetworksRequest) String() string { func (*ListNetworksRequest) ProtoMessage() {} func (x *ListNetworksRequest) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[39] + mi := &file_docker_v1_docker_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2538,7 +2843,7 @@ func (x *ListNetworksRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListNetworksRequest.ProtoReflect.Descriptor instead. func (*ListNetworksRequest) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{39} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{44} } type ListNetworksResponse struct { @@ -2550,7 +2855,7 @@ type ListNetworksResponse struct { func (x *ListNetworksResponse) Reset() { *x = ListNetworksResponse{} - mi := &file_docker_v1_docker_proto_msgTypes[40] + mi := &file_docker_v1_docker_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2562,7 +2867,7 @@ func (x *ListNetworksResponse) String() string { func (*ListNetworksResponse) ProtoMessage() {} func (x *ListNetworksResponse) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[40] + mi := &file_docker_v1_docker_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2575,7 +2880,7 @@ func (x *ListNetworksResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListNetworksResponse.ProtoReflect.Descriptor instead. func (*ListNetworksResponse) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{40} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{45} } func (x *ListNetworksResponse) GetNetworks() []*Network { @@ -2593,7 +2898,7 @@ type CreateNetworkRequest struct { func (x *CreateNetworkRequest) Reset() { *x = CreateNetworkRequest{} - mi := &file_docker_v1_docker_proto_msgTypes[41] + mi := &file_docker_v1_docker_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2605,7 +2910,7 @@ func (x *CreateNetworkRequest) String() string { func (*CreateNetworkRequest) ProtoMessage() {} func (x *CreateNetworkRequest) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[41] + mi := &file_docker_v1_docker_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2618,7 +2923,7 @@ func (x *CreateNetworkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateNetworkRequest.ProtoReflect.Descriptor instead. func (*CreateNetworkRequest) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{41} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{46} } type CreateNetworkResponse struct { @@ -2629,7 +2934,7 @@ type CreateNetworkResponse struct { func (x *CreateNetworkResponse) Reset() { *x = CreateNetworkResponse{} - mi := &file_docker_v1_docker_proto_msgTypes[42] + mi := &file_docker_v1_docker_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2640,8 +2945,485 @@ func (x *CreateNetworkResponse) String() string { func (*CreateNetworkResponse) ProtoMessage() {} -func (x *CreateNetworkResponse) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[42] +func (x *CreateNetworkResponse) ProtoReflect() protoreflect.Message { + mi := &file_docker_v1_docker_proto_msgTypes[47] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateNetworkResponse.ProtoReflect.Descriptor instead. +func (*CreateNetworkResponse) Descriptor() ([]byte, []int) { + return file_docker_v1_docker_proto_rawDescGZIP(), []int{47} +} + +type DeleteNetworkRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + NetworkIds []string `protobuf:"bytes,3,rep,name=networkIds,proto3" json:"networkIds,omitempty"` + Prune bool `protobuf:"varint,2,opt,name=prune,proto3" json:"prune,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteNetworkRequest) Reset() { + *x = DeleteNetworkRequest{} + mi := &file_docker_v1_docker_proto_msgTypes[48] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteNetworkRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteNetworkRequest) ProtoMessage() {} + +func (x *DeleteNetworkRequest) ProtoReflect() protoreflect.Message { + mi := &file_docker_v1_docker_proto_msgTypes[48] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteNetworkRequest.ProtoReflect.Descriptor instead. +func (*DeleteNetworkRequest) Descriptor() ([]byte, []int) { + return file_docker_v1_docker_proto_rawDescGZIP(), []int{48} +} + +func (x *DeleteNetworkRequest) GetNetworkIds() []string { + if x != nil { + return x.NetworkIds + } + return nil +} + +func (x *DeleteNetworkRequest) GetPrune() bool { + if x != nil { + return x.Prune + } + return false +} + +type DeleteNetworkResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteNetworkResponse) Reset() { + *x = DeleteNetworkResponse{} + mi := &file_docker_v1_docker_proto_msgTypes[49] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteNetworkResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteNetworkResponse) ProtoMessage() {} + +func (x *DeleteNetworkResponse) ProtoReflect() protoreflect.Message { + mi := &file_docker_v1_docker_proto_msgTypes[49] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteNetworkResponse.ProtoReflect.Descriptor instead. +func (*DeleteNetworkResponse) Descriptor() ([]byte, []int) { + return file_docker_v1_docker_proto_rawDescGZIP(), []int{49} +} + +type NetworkConnectContainerRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + NetworkId string `protobuf:"bytes,1,opt,name=network_id,json=networkId,proto3" json:"network_id,omitempty"` + ContainerId string `protobuf:"bytes,2,opt,name=container_id,json=containerId,proto3" json:"container_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetworkConnectContainerRequest) Reset() { + *x = NetworkConnectContainerRequest{} + mi := &file_docker_v1_docker_proto_msgTypes[50] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetworkConnectContainerRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetworkConnectContainerRequest) ProtoMessage() {} + +func (x *NetworkConnectContainerRequest) ProtoReflect() protoreflect.Message { + mi := &file_docker_v1_docker_proto_msgTypes[50] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NetworkConnectContainerRequest.ProtoReflect.Descriptor instead. +func (*NetworkConnectContainerRequest) Descriptor() ([]byte, []int) { + return file_docker_v1_docker_proto_rawDescGZIP(), []int{50} +} + +func (x *NetworkConnectContainerRequest) GetNetworkId() string { + if x != nil { + return x.NetworkId + } + return "" +} + +func (x *NetworkConnectContainerRequest) GetContainerId() string { + if x != nil { + return x.ContainerId + } + return "" +} + +type NetworkConnectContainerResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetworkConnectContainerResponse) Reset() { + *x = NetworkConnectContainerResponse{} + mi := &file_docker_v1_docker_proto_msgTypes[51] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetworkConnectContainerResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetworkConnectContainerResponse) ProtoMessage() {} + +func (x *NetworkConnectContainerResponse) ProtoReflect() protoreflect.Message { + mi := &file_docker_v1_docker_proto_msgTypes[51] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NetworkConnectContainerResponse.ProtoReflect.Descriptor instead. +func (*NetworkConnectContainerResponse) Descriptor() ([]byte, []int) { + return file_docker_v1_docker_proto_rawDescGZIP(), []int{51} +} + +type NetworkDisconnectContainerRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + NetworkId string `protobuf:"bytes,1,opt,name=network_id,json=networkId,proto3" json:"network_id,omitempty"` + ContainerId string `protobuf:"bytes,2,opt,name=container_id,json=containerId,proto3" json:"container_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetworkDisconnectContainerRequest) Reset() { + *x = NetworkDisconnectContainerRequest{} + mi := &file_docker_v1_docker_proto_msgTypes[52] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetworkDisconnectContainerRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetworkDisconnectContainerRequest) ProtoMessage() {} + +func (x *NetworkDisconnectContainerRequest) ProtoReflect() protoreflect.Message { + mi := &file_docker_v1_docker_proto_msgTypes[52] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NetworkDisconnectContainerRequest.ProtoReflect.Descriptor instead. +func (*NetworkDisconnectContainerRequest) Descriptor() ([]byte, []int) { + return file_docker_v1_docker_proto_rawDescGZIP(), []int{52} +} + +func (x *NetworkDisconnectContainerRequest) GetNetworkId() string { + if x != nil { + return x.NetworkId + } + return "" +} + +func (x *NetworkDisconnectContainerRequest) GetContainerId() string { + if x != nil { + return x.ContainerId + } + return "" +} + +type NetworkDisconnectContainerResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetworkDisconnectContainerResponse) Reset() { + *x = NetworkDisconnectContainerResponse{} + mi := &file_docker_v1_docker_proto_msgTypes[53] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetworkDisconnectContainerResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetworkDisconnectContainerResponse) ProtoMessage() {} + +func (x *NetworkDisconnectContainerResponse) ProtoReflect() protoreflect.Message { + mi := &file_docker_v1_docker_proto_msgTypes[53] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NetworkDisconnectContainerResponse.ProtoReflect.Descriptor instead. +func (*NetworkDisconnectContainerResponse) Descriptor() ([]byte, []int) { + return file_docker_v1_docker_proto_rawDescGZIP(), []int{53} +} + +type EventsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EventsRequest) Reset() { + *x = EventsRequest{} + mi := &file_docker_v1_docker_proto_msgTypes[54] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EventsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EventsRequest) ProtoMessage() {} + +func (x *EventsRequest) ProtoReflect() protoreflect.Message { + mi := &file_docker_v1_docker_proto_msgTypes[54] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EventsRequest.ProtoReflect.Descriptor instead. +func (*EventsRequest) Descriptor() ([]byte, []int) { + return file_docker_v1_docker_proto_rawDescGZIP(), []int{54} +} + +func (x *EventsRequest) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +type ContainerEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + // create / start / stop / die / kill / restart / pause / unpause / + // destroy / rename / update / oom / health_status. + // Empty for keepalive frames. + Action string `protobuf:"bytes,1,opt,name=action,proto3" json:"action,omitempty"` + // health_status only: healthy / unhealthy / ... + Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` + ContainerId string `protobuf:"bytes,3,opt,name=containerId,proto3" json:"containerId,omitempty"` + ContainerName string `protobuf:"bytes,4,opt,name=containerName,proto3" json:"containerName,omitempty"` + Image string `protobuf:"bytes,5,opt,name=image,proto3" json:"image,omitempty"` + TimeNano int64 `protobuf:"varint,6,opt,name=timeNano,proto3" json:"timeNano,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ContainerEvent) Reset() { + *x = ContainerEvent{} + mi := &file_docker_v1_docker_proto_msgTypes[55] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ContainerEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ContainerEvent) ProtoMessage() {} + +func (x *ContainerEvent) ProtoReflect() protoreflect.Message { + mi := &file_docker_v1_docker_proto_msgTypes[55] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ContainerEvent.ProtoReflect.Descriptor instead. +func (*ContainerEvent) Descriptor() ([]byte, []int) { + return file_docker_v1_docker_proto_rawDescGZIP(), []int{55} +} + +func (x *ContainerEvent) GetAction() string { + if x != nil { + return x.Action + } + return "" +} + +func (x *ContainerEvent) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *ContainerEvent) GetContainerId() string { + if x != nil { + return x.ContainerId + } + return "" +} + +func (x *ContainerEvent) GetContainerName() string { + if x != nil { + return x.ContainerName + } + return "" +} + +func (x *ContainerEvent) GetImage() string { + if x != nil { + return x.Image + } + return "" +} + +func (x *ContainerEvent) GetTimeNano() int64 { + if x != nil { + return x.TimeNano + } + return 0 +} + +type ContainerLogsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ContainerID string `protobuf:"bytes,1,opt,name=containerID,proto3" json:"containerID,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ContainerLogsRequest) Reset() { + *x = ContainerLogsRequest{} + mi := &file_docker_v1_docker_proto_msgTypes[56] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ContainerLogsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ContainerLogsRequest) ProtoMessage() {} + +func (x *ContainerLogsRequest) ProtoReflect() protoreflect.Message { + mi := &file_docker_v1_docker_proto_msgTypes[56] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ContainerLogsRequest.ProtoReflect.Descriptor instead. +func (*ContainerLogsRequest) Descriptor() ([]byte, []int) { + return file_docker_v1_docker_proto_rawDescGZIP(), []int{56} +} + +func (x *ContainerLogsRequest) GetContainerID() string { + if x != nil { + return x.ContainerID + } + return "" +} + +type LogsMessage struct { + state protoimpl.MessageState `protogen:"open.v1"` + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LogsMessage) Reset() { + *x = LogsMessage{} + mi := &file_docker_v1_docker_proto_msgTypes[57] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LogsMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LogsMessage) ProtoMessage() {} + +func (x *LogsMessage) ProtoReflect() protoreflect.Message { + mi := &file_docker_v1_docker_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2652,34 +3434,48 @@ func (x *CreateNetworkResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CreateNetworkResponse.ProtoReflect.Descriptor instead. -func (*CreateNetworkResponse) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{42} +// Deprecated: Use LogsMessage.ProtoReflect.Descriptor instead. +func (*LogsMessage) Descriptor() ([]byte, []int) { + return file_docker_v1_docker_proto_rawDescGZIP(), []int{57} } -type DeleteNetworkRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - NetworkIds []string `protobuf:"bytes,3,rep,name=networkIds,proto3" json:"networkIds,omitempty"` - Prune bool `protobuf:"varint,2,opt,name=prune,proto3" json:"prune,omitempty"` +func (x *LogsMessage) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +type LogsStreamRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // one id = single container view, several = merged stack view + ContainerIds []string `protobuf:"bytes,1,rep,name=containerIds,proto3" json:"containerIds,omitempty"` + // number of trailing lines per container, <= 0 means the server default + Tail int32 `protobuf:"varint,2,opt,name=tail,proto3" json:"tail,omitempty"` + // unix seconds bounds, 0 means unbounded + Since int64 `protobuf:"varint,3,opt,name=since,proto3" json:"since,omitempty"` + Until int64 `protobuf:"varint,4,opt,name=until,proto3" json:"until,omitempty"` + // keep the stream open for new lines; false ends it once history is sent + Follow bool `protobuf:"varint,5,opt,name=follow,proto3" json:"follow,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *DeleteNetworkRequest) Reset() { - *x = DeleteNetworkRequest{} - mi := &file_docker_v1_docker_proto_msgTypes[43] +func (x *LogsStreamRequest) Reset() { + *x = LogsStreamRequest{} + mi := &file_docker_v1_docker_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *DeleteNetworkRequest) String() string { +func (x *LogsStreamRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*DeleteNetworkRequest) ProtoMessage() {} +func (*LogsStreamRequest) ProtoMessage() {} -func (x *DeleteNetworkRequest) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[43] +func (x *LogsStreamRequest) ProtoReflect() protoreflect.Message { + mi := &file_docker_v1_docker_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2690,46 +3486,75 @@ func (x *DeleteNetworkRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use DeleteNetworkRequest.ProtoReflect.Descriptor instead. -func (*DeleteNetworkRequest) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{43} +// Deprecated: Use LogsStreamRequest.ProtoReflect.Descriptor instead. +func (*LogsStreamRequest) Descriptor() ([]byte, []int) { + return file_docker_v1_docker_proto_rawDescGZIP(), []int{58} } -func (x *DeleteNetworkRequest) GetNetworkIds() []string { +func (x *LogsStreamRequest) GetContainerIds() []string { if x != nil { - return x.NetworkIds + return x.ContainerIds } return nil } -func (x *DeleteNetworkRequest) GetPrune() bool { +func (x *LogsStreamRequest) GetTail() int32 { if x != nil { - return x.Prune + return x.Tail + } + return 0 +} + +func (x *LogsStreamRequest) GetSince() int64 { + if x != nil { + return x.Since + } + return 0 +} + +func (x *LogsStreamRequest) GetUntil() int64 { + if x != nil { + return x.Until + } + return 0 +} + +func (x *LogsStreamRequest) GetFollow() bool { + if x != nil { + return x.Follow } return false } -type DeleteNetworkResponse struct { +// a frame with an empty containerId and text is a keepalive +type LogLine struct { state protoimpl.MessageState `protogen:"open.v1"` + ContainerId string `protobuf:"bytes,1,opt,name=containerId,proto3" json:"containerId,omitempty"` + ContainerName string `protobuf:"bytes,2,opt,name=containerName,proto3" json:"containerName,omitempty"` + // line content without the daemon timestamp prefix + Text string `protobuf:"bytes,3,opt,name=text,proto3" json:"text,omitempty"` + TimeNano int64 `protobuf:"varint,4,opt,name=timeNano,proto3" json:"timeNano,omitempty"` + // 1 = stdout, 2 = stderr + Stream int32 `protobuf:"varint,5,opt,name=stream,proto3" json:"stream,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *DeleteNetworkResponse) Reset() { - *x = DeleteNetworkResponse{} - mi := &file_docker_v1_docker_proto_msgTypes[44] +func (x *LogLine) Reset() { + *x = LogLine{} + mi := &file_docker_v1_docker_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *DeleteNetworkResponse) String() string { +func (x *LogLine) String() string { return protoimpl.X.MessageStringOf(x) } -func (*DeleteNetworkResponse) ProtoMessage() {} +func (*LogLine) ProtoMessage() {} -func (x *DeleteNetworkResponse) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[44] +func (x *LogLine) ProtoReflect() protoreflect.Message { + mi := &file_docker_v1_docker_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2740,33 +3565,69 @@ func (x *DeleteNetworkResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use DeleteNetworkResponse.ProtoReflect.Descriptor instead. -func (*DeleteNetworkResponse) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{44} +// Deprecated: Use LogLine.ProtoReflect.Descriptor instead. +func (*LogLine) Descriptor() ([]byte, []int) { + return file_docker_v1_docker_proto_rawDescGZIP(), []int{59} } -type ContainerLogsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - ContainerID string `protobuf:"bytes,1,opt,name=containerID,proto3" json:"containerID,omitempty"` +func (x *LogLine) GetContainerId() string { + if x != nil { + return x.ContainerId + } + return "" +} + +func (x *LogLine) GetContainerName() string { + if x != nil { + return x.ContainerName + } + return "" +} + +func (x *LogLine) GetText() string { + if x != nil { + return x.Text + } + return "" +} + +func (x *LogLine) GetTimeNano() int64 { + if x != nil { + return x.TimeNano + } + return 0 +} + +func (x *LogLine) GetStream() int32 { + if x != nil { + return x.Stream + } + return 0 +} + +type DockerCommandRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // full command line, e.g. "docker run --rm -p 8080:80 nginx:alpine" + Command string `protobuf:"bytes,1,opt,name=command,proto3" json:"command,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ContainerLogsRequest) Reset() { - *x = ContainerLogsRequest{} - mi := &file_docker_v1_docker_proto_msgTypes[45] +func (x *DockerCommandRequest) Reset() { + *x = DockerCommandRequest{} + mi := &file_docker_v1_docker_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ContainerLogsRequest) String() string { +func (x *DockerCommandRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ContainerLogsRequest) ProtoMessage() {} +func (*DockerCommandRequest) ProtoMessage() {} -func (x *ContainerLogsRequest) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[45] +func (x *DockerCommandRequest) ProtoReflect() protoreflect.Message { + mi := &file_docker_v1_docker_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2777,40 +3638,44 @@ func (x *ContainerLogsRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ContainerLogsRequest.ProtoReflect.Descriptor instead. -func (*ContainerLogsRequest) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{45} +// Deprecated: Use DockerCommandRequest.ProtoReflect.Descriptor instead. +func (*DockerCommandRequest) Descriptor() ([]byte, []int) { + return file_docker_v1_docker_proto_rawDescGZIP(), []int{60} } -func (x *ContainerLogsRequest) GetContainerID() string { +func (x *DockerCommandRequest) GetCommand() string { if x != nil { - return x.ContainerID + return x.Command } return "" } -type LogsMessage struct { - state protoimpl.MessageState `protogen:"open.v1"` - Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` +type HostStatsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // whole-host cpu usage in percent (0-100), 0 until two samples exist + CpuPercent float64 `protobuf:"fixed64,1,opt,name=cpuPercent,proto3" json:"cpuPercent,omitempty"` + MemUsed int64 `protobuf:"varint,2,opt,name=memUsed,proto3" json:"memUsed,omitempty"` + MemTotal int64 `protobuf:"varint,3,opt,name=memTotal,proto3" json:"memTotal,omitempty"` + Cpus int32 `protobuf:"varint,4,opt,name=cpus,proto3" json:"cpus,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *LogsMessage) Reset() { - *x = LogsMessage{} - mi := &file_docker_v1_docker_proto_msgTypes[46] +func (x *HostStatsResponse) Reset() { + *x = HostStatsResponse{} + mi := &file_docker_v1_docker_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *LogsMessage) String() string { +func (x *HostStatsResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*LogsMessage) ProtoMessage() {} +func (*HostStatsResponse) ProtoMessage() {} -func (x *LogsMessage) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[46] +func (x *HostStatsResponse) ProtoReflect() protoreflect.Message { + mi := &file_docker_v1_docker_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2821,16 +3686,37 @@ func (x *LogsMessage) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use LogsMessage.ProtoReflect.Descriptor instead. -func (*LogsMessage) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{46} +// Deprecated: Use HostStatsResponse.ProtoReflect.Descriptor instead. +func (*HostStatsResponse) Descriptor() ([]byte, []int) { + return file_docker_v1_docker_proto_rawDescGZIP(), []int{61} } -func (x *LogsMessage) GetMessage() string { +func (x *HostStatsResponse) GetCpuPercent() float64 { if x != nil { - return x.Message + return x.CpuPercent } - return "" + return 0 +} + +func (x *HostStatsResponse) GetMemUsed() int64 { + if x != nil { + return x.MemUsed + } + return 0 +} + +func (x *HostStatsResponse) GetMemTotal() int64 { + if x != nil { + return x.MemTotal + } + return 0 +} + +func (x *HostStatsResponse) GetCpus() int32 { + if x != nil { + return x.Cpus + } + return 0 } type StatsResponse struct { @@ -2843,7 +3729,7 @@ type StatsResponse struct { func (x *StatsResponse) Reset() { *x = StatsResponse{} - mi := &file_docker_v1_docker_proto_msgTypes[47] + mi := &file_docker_v1_docker_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2855,7 +3741,7 @@ func (x *StatsResponse) String() string { func (*StatsResponse) ProtoMessage() {} func (x *StatsResponse) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[47] + mi := &file_docker_v1_docker_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2868,7 +3754,7 @@ func (x *StatsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use StatsResponse.ProtoReflect.Descriptor instead. func (*StatsResponse) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{47} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{62} } func (x *StatsResponse) GetSystem() *SystemInfo { @@ -2897,7 +3783,7 @@ type StatsRequest struct { func (x *StatsRequest) Reset() { *x = StatsRequest{} - mi := &file_docker_v1_docker_proto_msgTypes[48] + mi := &file_docker_v1_docker_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2909,7 +3795,7 @@ func (x *StatsRequest) String() string { func (*StatsRequest) ProtoMessage() {} func (x *StatsRequest) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[48] + mi := &file_docker_v1_docker_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2922,7 +3808,7 @@ func (x *StatsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use StatsRequest.ProtoReflect.Descriptor instead. func (*StatsRequest) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{48} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{63} } func (x *StatsRequest) GetHost() string { @@ -2963,7 +3849,7 @@ type SystemInfo struct { func (x *SystemInfo) Reset() { *x = SystemInfo{} - mi := &file_docker_v1_docker_proto_msgTypes[49] + mi := &file_docker_v1_docker_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2975,7 +3861,7 @@ func (x *SystemInfo) String() string { func (*SystemInfo) ProtoMessage() {} func (x *SystemInfo) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[49] + mi := &file_docker_v1_docker_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2988,7 +3874,7 @@ func (x *SystemInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use SystemInfo.ProtoReflect.Descriptor instead. func (*SystemInfo) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{49} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{64} } func (x *SystemInfo) GetCPU() float64 { @@ -3015,7 +3901,7 @@ type ListResponse struct { func (x *ListResponse) Reset() { *x = ListResponse{} - mi := &file_docker_v1_docker_proto_msgTypes[50] + mi := &file_docker_v1_docker_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3027,7 +3913,7 @@ func (x *ListResponse) String() string { func (*ListResponse) ProtoMessage() {} func (x *ListResponse) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[50] + mi := &file_docker_v1_docker_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3040,7 +3926,7 @@ func (x *ListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListResponse.ProtoReflect.Descriptor instead. func (*ListResponse) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{50} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{65} } func (x *ListResponse) GetStatusCount() map[string]int32 { @@ -3079,7 +3965,7 @@ type ContainerList struct { func (x *ContainerList) Reset() { *x = ContainerList{} - mi := &file_docker_v1_docker_proto_msgTypes[51] + mi := &file_docker_v1_docker_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3091,7 +3977,7 @@ func (x *ContainerList) String() string { func (*ContainerList) ProtoMessage() {} func (x *ContainerList) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[51] + mi := &file_docker_v1_docker_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3104,7 +3990,7 @@ func (x *ContainerList) ProtoReflect() protoreflect.Message { // Deprecated: Use ContainerList.ProtoReflect.Descriptor instead. func (*ContainerList) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{51} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{66} } func (x *ContainerList) GetId() string { @@ -3218,14 +4104,27 @@ type ContainerStats struct { // Total bytes read from block devices. BlockRead uint64 `protobuf:"varint,8,opt,name=block_read,json=blockRead,proto3" json:"block_read,omitempty"` // Total bytes written to block devices. - BlockWrite uint64 `protobuf:"varint,9,opt,name=block_write,json=blockWrite,proto3" json:"block_write,omitempty"` + BlockWrite uint64 `protobuf:"varint,9,opt,name=block_write,json=blockWrite,proto3" json:"block_write,omitempty"` + // Container start time (RFC3339). Empty if unknown / not running. + StartedAt string `protobuf:"bytes,10,opt,name=started_at,json=startedAt,proto3" json:"started_at,omitempty"` + // Image reference the container was created from. + Image string `protobuf:"bytes,11,opt,name=image,proto3" json:"image,omitempty"` + // Container state: running, exited, paused, restarting... + State string `protobuf:"bytes,12,opt,name=state,proto3" json:"state,omitempty"` + // Health status: healthy / unhealthy / starting. Empty when the container + // has no healthcheck. + Health string `protobuf:"bytes,13,opt,name=health,proto3" json:"health,omitempty"` + // Container network IP addresses. + IpAddress []string `protobuf:"bytes,14,rep,name=ip_address,json=ipAddress,proto3" json:"ip_address,omitempty"` + // How many times the container restarted. + RestartCount int32 `protobuf:"varint,15,opt,name=restart_count,json=restartCount,proto3" json:"restart_count,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ContainerStats) Reset() { *x = ContainerStats{} - mi := &file_docker_v1_docker_proto_msgTypes[52] + mi := &file_docker_v1_docker_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3237,7 +4136,7 @@ func (x *ContainerStats) String() string { func (*ContainerStats) ProtoMessage() {} func (x *ContainerStats) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[52] + mi := &file_docker_v1_docker_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3250,7 +4149,7 @@ func (x *ContainerStats) ProtoReflect() protoreflect.Message { // Deprecated: Use ContainerStats.ProtoReflect.Descriptor instead. func (*ContainerStats) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{52} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{67} } func (x *ContainerStats) GetId() string { @@ -3316,6 +4215,48 @@ func (x *ContainerStats) GetBlockWrite() uint64 { return 0 } +func (x *ContainerStats) GetStartedAt() string { + if x != nil { + return x.StartedAt + } + return "" +} + +func (x *ContainerStats) GetImage() string { + if x != nil { + return x.Image + } + return "" +} + +func (x *ContainerStats) GetState() string { + if x != nil { + return x.State + } + return "" +} + +func (x *ContainerStats) GetHealth() string { + if x != nil { + return x.Health + } + return "" +} + +func (x *ContainerStats) GetIpAddress() []string { + if x != nil { + return x.IpAddress + } + return nil +} + +func (x *ContainerStats) GetRestartCount() int32 { + if x != nil { + return x.RestartCount + } + return 0 +} + type Port struct { state protoimpl.MessageState `protogen:"open.v1"` Public int32 `protobuf:"varint,1,opt,name=public,proto3" json:"public,omitempty"` @@ -3328,7 +4269,7 @@ type Port struct { func (x *Port) Reset() { *x = Port{} - mi := &file_docker_v1_docker_proto_msgTypes[53] + mi := &file_docker_v1_docker_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3340,7 +4281,7 @@ func (x *Port) String() string { func (*Port) ProtoMessage() {} func (x *Port) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[53] + mi := &file_docker_v1_docker_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3353,7 +4294,7 @@ func (x *Port) ProtoReflect() protoreflect.Message { // Deprecated: Use Port.ProtoReflect.Descriptor instead. func (*Port) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{53} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{68} } func (x *Port) GetPublic() int32 { @@ -3392,7 +4333,7 @@ type Empty struct { func (x *Empty) Reset() { *x = Empty{} - mi := &file_docker_v1_docker_proto_msgTypes[54] + mi := &file_docker_v1_docker_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3404,7 +4345,7 @@ func (x *Empty) String() string { func (*Empty) ProtoMessage() {} func (x *Empty) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[54] + mi := &file_docker_v1_docker_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3417,7 +4358,7 @@ func (x *Empty) ProtoReflect() protoreflect.Message { // Deprecated: Use Empty.ProtoReflect.Descriptor instead. func (*Empty) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{54} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{69} } type ContainerRequest struct { @@ -3429,7 +4370,7 @@ type ContainerRequest struct { func (x *ContainerRequest) Reset() { *x = ContainerRequest{} - mi := &file_docker_v1_docker_proto_msgTypes[55] + mi := &file_docker_v1_docker_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3441,7 +4382,7 @@ func (x *ContainerRequest) String() string { func (*ContainerRequest) ProtoMessage() {} func (x *ContainerRequest) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[55] + mi := &file_docker_v1_docker_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3454,7 +4395,7 @@ func (x *ContainerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ContainerRequest.ProtoReflect.Descriptor instead. func (*ContainerRequest) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{55} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{70} } func (x *ContainerRequest) GetContainerIds() []string { @@ -3474,7 +4415,7 @@ type ComposeFile struct { func (x *ComposeFile) Reset() { *x = ComposeFile{} - mi := &file_docker_v1_docker_proto_msgTypes[56] + mi := &file_docker_v1_docker_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3486,7 +4427,7 @@ func (x *ComposeFile) String() string { func (*ComposeFile) ProtoMessage() {} func (x *ComposeFile) ProtoReflect() protoreflect.Message { - mi := &file_docker_v1_docker_proto_msgTypes[56] + mi := &file_docker_v1_docker_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3499,7 +4440,7 @@ func (x *ComposeFile) ProtoReflect() protoreflect.Message { // Deprecated: Use ComposeFile.ProtoReflect.Descriptor instead. func (*ComposeFile) Descriptor() ([]byte, []int) { - return file_docker_v1_docker_proto_rawDescGZIP(), []int{56} + return file_docker_v1_docker_proto_rawDescGZIP(), []int{71} } func (x *ComposeFile) GetFilename() string { @@ -3516,6 +4457,77 @@ func (x *ComposeFile) GetSelectedServices() []string { return nil } +type ComposeRedeployRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + File *ComposeFile `protobuf:"bytes,1,opt,name=file,proto3" json:"file,omitempty"` + // force-pull images (--pull always) + Pull bool `protobuf:"varint,2,opt,name=pull,proto3" json:"pull,omitempty"` + // force-build images (--build) + Build bool `protobuf:"varint,3,opt,name=build,proto3" json:"build,omitempty"` + // recreate containers even when nothing changed (--force-recreate) + Recreate bool `protobuf:"varint,4,opt,name=recreate,proto3" json:"recreate,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ComposeRedeployRequest) Reset() { + *x = ComposeRedeployRequest{} + mi := &file_docker_v1_docker_proto_msgTypes[72] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ComposeRedeployRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ComposeRedeployRequest) ProtoMessage() {} + +func (x *ComposeRedeployRequest) ProtoReflect() protoreflect.Message { + mi := &file_docker_v1_docker_proto_msgTypes[72] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ComposeRedeployRequest.ProtoReflect.Descriptor instead. +func (*ComposeRedeployRequest) Descriptor() ([]byte, []int) { + return file_docker_v1_docker_proto_rawDescGZIP(), []int{72} +} + +func (x *ComposeRedeployRequest) GetFile() *ComposeFile { + if x != nil { + return x.File + } + return nil +} + +func (x *ComposeRedeployRequest) GetPull() bool { + if x != nil { + return x.Pull + } + return false +} + +func (x *ComposeRedeployRequest) GetBuild() bool { + if x != nil { + return x.Build + } + return false +} + +func (x *ComposeRedeployRequest) GetRecreate() bool { + if x != nil { + return x.Recreate + } + return false +} + var File_docker_v1_docker_proto protoreflect.FileDescriptor const file_docker_v1_docker_proto_rawDesc = "" + @@ -3543,7 +4555,7 @@ const file_docker_v1_docker_proto_rawDesc = "" + "\tProcesses\x18\x01 \x03(\tR\tProcesses\"E\n" + "\x03Top\x12&\n" + "\x04proc\x18\x01 \x03(\v2\x12.docker.v1.ProcessR\x04proc\x12\x16\n" + - "\x06Titles\x18\x02 \x03(\tR\x06Titles\"\x86\x02\n" + + "\x06Titles\x18\x02 \x03(\tR\x06Titles\"\xa1\x02\n" + "\x17ContainerInspectMessage\x12\x12\n" + "\x04Name\x18\x01 \x01(\tR\x04Name\x12\x0e\n" + "\x02ID\x18\x02 \x01(\tR\x02ID\x12\x12\n" + @@ -3552,7 +4564,8 @@ const file_docker_v1_docker_proto_rawDesc = "" + "\x05Image\x18\x04 \x01(\tR\x05Image\x12\x1c\n" + "\tHostsPath\x18\x05 \x01(\tR\tHostsPath\x121\n" + "\x06mounts\x18\x06 \x03(\v2\x19.docker.v1.ContainerMountR\x06mounts\x122\n" + - "\x06config\x18\b \x01(\v2\x1a.docker.v1.ContainerConfigR\x06config\"\xee\x04\n" + + "\x06config\x18\b \x01(\v2\x1a.docker.v1.ContainerConfigR\x06config\x12\x19\n" + + "\braw_json\x18\t \x01(\tR\arawJson\"\xee\x04\n" + "\x0fContainerConfig\x12\x1a\n" + "\bHostname\x18\x01 \x01(\tR\bHostname\x12\x1e\n" + "\n" + @@ -3607,7 +4620,7 @@ const file_docker_v1_docker_proto_rawDesc = "" + "\x13ImageInspectRequest\x12\x18\n" + "\aimageId\x18\x01 \x01(\tR\aimageId\"I\n" + "\x14ImageInspectResponse\x121\n" + - "\ainspect\x18\x01 \x01(\v2\x17.docker.v1.ImageInspectR\ainspect\"\xa9\x01\n" + + "\ainspect\x18\x01 \x01(\v2\x17.docker.v1.ImageInspectR\ainspect\"\xeb\x01\n" + "\fImageInspect\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x0e\n" + "\x02id\x18\x06 \x01(\tR\x02id\x12\x12\n" + @@ -3616,13 +4629,21 @@ const file_docker_v1_docker_proto_rawDesc = "" + "\n" + "createdIso\x18\x04 \x01(\tR\n" + "createdIso\x12-\n" + - "\x06layers\x18\x02 \x03(\v2\x15.docker.v1.ImageLayerR\x06layers\"x\n" + + "\x06layers\x18\x02 \x03(\v2\x15.docker.v1.ImageLayerR\x06layers\x12@\n" + + "\n" + + "containers\x18\a \x03(\v2 .docker.v1.ImageContainerInspectR\n" + + "containers\"x\n" + "\n" + "ImageLayer\x12\x18\n" + "\aLayerId\x18\x03 \x01(\tR\aLayerId\x12\x10\n" + "\x03cmd\x18\x01 \x01(\tR\x03cmd\x12\x12\n" + "\x04size\x18\x02 \x01(\tR\x04size\x12*\n" + - "\x10totalSizeAtLayer\x18\x04 \x01(\tR\x10totalSizeAtLayer\"-\n" + + "\x10totalSizeAtLayer\x18\x04 \x01(\tR\x10totalSizeAtLayer\"y\n" + + "\x15ImageContainerInspect\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x0e\n" + + "\x02id\x18\x02 \x01(\tR\x02id\x12\x14\n" + + "\x05state\x18\x03 \x01(\tR\x05state\x12&\n" + + "\x0ecomposeProject\x18\x04 \x01(\tR\x0ecomposeProject\"-\n" + "\x17ComposeValidateResponse\x12\x12\n" + "\x04errs\x18\x01 \x03(\tR\x04errs\"S\n" + "\x15ContainerExecCmdInput\x12\x18\n" + @@ -3695,7 +4716,24 @@ const file_docker_v1_docker_proto_rawDesc = "" + "\tvolumeIds\x18\x01 \x03(\tR\tvolumeIds\x12\x12\n" + "\x04anon\x18\x02 \x01(\bR\x04anon\x12\x16\n" + "\x06unused\x18\x03 \x01(\bR\x06unused\"\x16\n" + - "\x14DeleteVolumeResponse\"\xdb\x02\n" + + "\x14DeleteVolumeResponse\"6\n" + + "\x14VolumeInspectRequest\x12\x1e\n" + + "\n" + + "volumeName\x18\x01 \x01(\tR\n" + + "volumeName\"O\n" + + "\x15VolumeInspectResponse\x126\n" + + "\ainspect\x18\x01 \x01(\v2\x1c.docker.v1.VolumeInspectInfoR\ainspect\"{\n" + + "\x11VolumeInspectInfo\x12#\n" + + "\x03vol\x18\x01 \x01(\v2\x11.docker.v1.VolumeR\x03vol\x12A\n" + + "\n" + + "containers\x18\x02 \x03(\v2!.docker.v1.VolumeContainerInspectR\n" + + "containers\"\x96\x01\n" + + "\x16VolumeContainerInspect\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x0e\n" + + "\x02id\x18\x02 \x01(\tR\x02id\x12 \n" + + "\vdestination\x18\x03 \x01(\tR\vdestination\x12\x0e\n" + + "\x02rw\x18\x04 \x01(\bR\x02rw\x12&\n" + + "\x0ecomposeProject\x18\x05 \x01(\tR\x0ecomposeProject\"\xdb\x02\n" + "\aNetwork\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x0e\n" + "\x02id\x18\x02 \x01(\tR\x02id\x12\x16\n" + @@ -3724,11 +4762,51 @@ const file_docker_v1_docker_proto_rawDesc = "" + "networkIds\x18\x03 \x03(\tR\n" + "networkIds\x12\x14\n" + "\x05prune\x18\x02 \x01(\bR\x05prune\"\x17\n" + - "\x15DeleteNetworkResponse\"8\n" + + "\x15DeleteNetworkResponse\"b\n" + + "\x1eNetworkConnectContainerRequest\x12\x1d\n" + + "\n" + + "network_id\x18\x01 \x01(\tR\tnetworkId\x12!\n" + + "\fcontainer_id\x18\x02 \x01(\tR\vcontainerId\"!\n" + + "\x1fNetworkConnectContainerResponse\"e\n" + + "!NetworkDisconnectContainerRequest\x12\x1d\n" + + "\n" + + "network_id\x18\x01 \x01(\tR\tnetworkId\x12!\n" + + "\fcontainer_id\x18\x02 \x01(\tR\vcontainerId\"$\n" + + "\"NetworkDisconnectContainerResponse\"#\n" + + "\rEventsRequest\x12\x12\n" + + "\x04host\x18\x01 \x01(\tR\x04host\"\xba\x01\n" + + "\x0eContainerEvent\x12\x16\n" + + "\x06action\x18\x01 \x01(\tR\x06action\x12\x16\n" + + "\x06status\x18\x02 \x01(\tR\x06status\x12 \n" + + "\vcontainerId\x18\x03 \x01(\tR\vcontainerId\x12$\n" + + "\rcontainerName\x18\x04 \x01(\tR\rcontainerName\x12\x14\n" + + "\x05image\x18\x05 \x01(\tR\x05image\x12\x1a\n" + + "\btimeNano\x18\x06 \x01(\x03R\btimeNano\"8\n" + "\x14ContainerLogsRequest\x12 \n" + "\vcontainerID\x18\x01 \x01(\tR\vcontainerID\"'\n" + "\vLogsMessage\x12\x18\n" + - "\amessage\x18\x01 \x01(\tR\amessage\"y\n" + + "\amessage\x18\x01 \x01(\tR\amessage\"\x8f\x01\n" + + "\x11LogsStreamRequest\x12\"\n" + + "\fcontainerIds\x18\x01 \x03(\tR\fcontainerIds\x12\x12\n" + + "\x04tail\x18\x02 \x01(\x05R\x04tail\x12\x14\n" + + "\x05since\x18\x03 \x01(\x03R\x05since\x12\x14\n" + + "\x05until\x18\x04 \x01(\x03R\x05until\x12\x16\n" + + "\x06follow\x18\x05 \x01(\bR\x06follow\"\x99\x01\n" + + "\aLogLine\x12 \n" + + "\vcontainerId\x18\x01 \x01(\tR\vcontainerId\x12$\n" + + "\rcontainerName\x18\x02 \x01(\tR\rcontainerName\x12\x12\n" + + "\x04text\x18\x03 \x01(\tR\x04text\x12\x1a\n" + + "\btimeNano\x18\x04 \x01(\x03R\btimeNano\x12\x16\n" + + "\x06stream\x18\x05 \x01(\x05R\x06stream\"0\n" + + "\x14DockerCommandRequest\x12\x18\n" + + "\acommand\x18\x01 \x01(\tR\acommand\"}\n" + + "\x11HostStatsResponse\x12\x1e\n" + + "\n" + + "cpuPercent\x18\x01 \x01(\x01R\n" + + "cpuPercent\x12\x18\n" + + "\amemUsed\x18\x02 \x01(\x03R\amemUsed\x12\x1a\n" + + "\bmemTotal\x18\x03 \x01(\x03R\bmemTotal\x12\x12\n" + + "\x04cpus\x18\x04 \x01(\x05R\x04cpus\"y\n" + "\rStatsResponse\x12-\n" + "\x06system\x18\x01 \x01(\v2\x15.docker.v1.SystemInfoR\x06system\x129\n" + "\n" + @@ -3765,7 +4843,7 @@ const file_docker_v1_docker_proto_rawDesc = "" + "\tstackName\x18\n" + " \x01(\tR\tstackName\x12(\n" + "\x0fupdateAvailable\x18\v \x01(\tR\x0fupdateAvailable\x12\x1c\n" + - "\tIPAddress\x18\f \x03(\tR\tIPAddress\"\x95\x02\n" + + "\tIPAddress\x18\f \x03(\tR\tIPAddress\"\xbc\x03\n" + "\x0eContainerStats\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12\x1b\n" + @@ -3779,7 +4857,16 @@ const file_docker_v1_docker_proto_rawDesc = "" + "\n" + "block_read\x18\b \x01(\x04R\tblockRead\x12\x1f\n" + "\vblock_write\x18\t \x01(\x04R\n" + - "blockWrite\"`\n" + + "blockWrite\x12\x1d\n" + + "\n" + + "started_at\x18\n" + + " \x01(\tR\tstartedAt\x12\x14\n" + + "\x05image\x18\v \x01(\tR\x05image\x12\x14\n" + + "\x05state\x18\f \x01(\tR\x05state\x12\x16\n" + + "\x06health\x18\r \x01(\tR\x06health\x12\x1d\n" + + "\n" + + "ip_address\x18\x0e \x03(\tR\tipAddress\x12#\n" + + "\rrestart_count\x18\x0f \x01(\x05R\frestartCount\"`\n" + "\x04Port\x12\x16\n" + "\x06public\x18\x01 \x01(\x05R\x06public\x12\x18\n" + "\aprivate\x18\x02 \x01(\x05R\aprivate\x12\x12\n" + @@ -3790,7 +4877,12 @@ const file_docker_v1_docker_proto_rawDesc = "" + "\fcontainerIds\x18\x01 \x03(\tR\fcontainerIds\"U\n" + "\vComposeFile\x12\x1a\n" + "\bfilename\x18\x01 \x01(\tR\bfilename\x12*\n" + - "\x10selectedServices\x18\x03 \x03(\tR\x10selectedServices*`\n" + + "\x10selectedServices\x18\x03 \x03(\tR\x10selectedServices\"\x8a\x01\n" + + "\x16ComposeRedeployRequest\x12*\n" + + "\x04file\x18\x01 \x01(\v2\x16.docker.v1.ComposeFileR\x04file\x12\x12\n" + + "\x04pull\x18\x02 \x01(\bR\x04pull\x12\x14\n" + + "\x05build\x18\x03 \x01(\bR\x05build\x12\x1a\n" + + "\brecreate\x18\x04 \x01(\bR\brecreate*m\n" + "\n" + "SORT_FIELD\x12\b\n" + "\x04NAME\x10\x00\x12\a\n" + @@ -3803,30 +4895,39 @@ const file_docker_v1_docker_proto_rawDesc = "" + "\n" + "\x06DISK_R\x10\x05\x12\n" + "\n" + - "\x06DISK_W\x10\x06*\x19\n" + + "\x06DISK_W\x10\x06\x12\v\n" + + "\aSTARTED\x10\a*\x19\n" + "\x05ORDER\x12\a\n" + "\x03DSC\x10\x00\x12\a\n" + - "\x03ASC\x10\x012\xa2\x12\n" + + "\x03ASC\x10\x012\xcd\x19\n" + "\rDockerService\x12G\n" + "\x0eContainerStart\x12\x1b.docker.v1.ContainerRequest\x1a\x16.docker.v1.LogsMessage\"\x00\x12F\n" + "\rContainerStop\x12\x1b.docker.v1.ContainerRequest\x1a\x16.docker.v1.LogsMessage\"\x00\x12H\n" + "\x0fContainerRemove\x12\x1b.docker.v1.ContainerRequest\x1a\x16.docker.v1.LogsMessage\"\x00\x12I\n" + - "\x10ContainerRestart\x12\x1b.docker.v1.ContainerRequest\x1a\x16.docker.v1.LogsMessage\"\x00\x12B\n" + - "\x0fContainerUpdate\x12\x1b.docker.v1.ContainerRequest\x1a\x10.docker.v1.Empty\"\x00\x12Q\n" + + "\x10ContainerRestart\x12\x1b.docker.v1.ContainerRequest\x1a\x16.docker.v1.LogsMessage\"\x00\x12G\n" + + "\x0eContainerPause\x12\x1b.docker.v1.ContainerRequest\x1a\x16.docker.v1.LogsMessage\"\x00\x12I\n" + + "\x10ContainerUnpause\x12\x1b.docker.v1.ContainerRequest\x1a\x16.docker.v1.LogsMessage\"\x00\x12J\n" + + "\x0fContainerUpdate\x12\x1b.docker.v1.ContainerRequest\x1a\x16.docker.v1.LogsMessage\"\x000\x01\x12Q\n" + "\fContainerTop\x12\x1e.docker.v1.ContainerTopRequest\x1a\x1f.docker.v1.ContainerTopResponse\"\x00\x12K\n" + "\rContainerList\x12\x1f.docker.v1.ContainerListRequest\x1a\x17.docker.v1.ListResponse\"\x00\x12E\n" + - "\x0eContainerStats\x12\x17.docker.v1.StatsRequest\x1a\x18.docker.v1.StatsResponse\"\x00\x12L\n" + - "\rContainerLogs\x12\x1f.docker.v1.ContainerLogsRequest\x1a\x16.docker.v1.LogsMessage\"\x000\x01\x12Y\n" + + "\x0eContainerStats\x12\x17.docker.v1.StatsRequest\x1a\x18.docker.v1.StatsResponse\"\x00\x12N\n" + + "\x14ContainerStatsStream\x12\x17.docker.v1.StatsRequest\x1a\x19.docker.v1.ContainerStats\"\x000\x01\x12=\n" + + "\tHostStats\x12\x10.docker.v1.Empty\x1a\x1c.docker.v1.HostStatsResponse\"\x00\x12L\n" + + "\rContainerLogs\x12\x1f.docker.v1.ContainerLogsRequest\x1a\x16.docker.v1.LogsMessage\"\x000\x01\x12J\n" + + "\x0fContainerEvents\x12\x18.docker.v1.EventsRequest\x1a\x19.docker.v1.ContainerEvent\"\x000\x01\x12K\n" + + "\x13ContainerLogsStream\x12\x1c.docker.v1.LogsStreamRequest\x1a\x12.docker.v1.LogLine\"\x000\x01\x12Y\n" + "\x10ContainerInspect\x12\x1f.docker.v1.ContainerLogsRequest\x1a\".docker.v1.ContainerInspectMessage\"\x00\x12?\n" + "\tComposeUp\x12\x16.docker.v1.ComposeFile\x1a\x16.docker.v1.LogsMessage\"\x000\x01\x12A\n" + "\vComposeDown\x12\x16.docker.v1.ComposeFile\x1a\x16.docker.v1.LogsMessage\"\x000\x01\x12B\n" + "\fComposeStart\x12\x16.docker.v1.ComposeFile\x1a\x16.docker.v1.LogsMessage\"\x000\x01\x12A\n" + "\vComposeStop\x12\x16.docker.v1.ComposeFile\x1a\x16.docker.v1.LogsMessage\"\x000\x01\x12D\n" + "\x0eComposeRestart\x12\x16.docker.v1.ComposeFile\x1a\x16.docker.v1.LogsMessage\"\x000\x01\x12C\n" + - "\rComposeUpdate\x12\x16.docker.v1.ComposeFile\x1a\x16.docker.v1.LogsMessage\"\x000\x01\x12@\n" + + "\rComposeUpdate\x12\x16.docker.v1.ComposeFile\x1a\x16.docker.v1.LogsMessage\"\x000\x01\x12P\n" + + "\x0fComposeRedeploy\x12!.docker.v1.ComposeRedeployRequest\x1a\x16.docker.v1.LogsMessage\"\x000\x01\x12@\n" + "\vComposeList\x12\x16.docker.v1.ComposeFile\x1a\x17.docker.v1.ListResponse\"\x00\x12O\n" + "\x0fComposeValidate\x12\x16.docker.v1.ComposeFile\x1a\".docker.v1.ComposeValidateResponse\"\x00\x12`\n" + - "\x11ComposeFileStatus\x12#.docker.v1.ComposeFileStatusRequest\x1a$.docker.v1.ComposeFileStatusResponse\"\x00\x12J\n" + + "\x11ComposeFileStatus\x12#.docker.v1.ComposeFileStatusRequest\x1a$.docker.v1.ComposeFileStatusResponse\"\x00\x12L\n" + + "\rDockerCommand\x12\x1f.docker.v1.DockerCommandRequest\x1a\x16.docker.v1.LogsMessage\"\x000\x01\x12J\n" + "\tImageList\x12\x1c.docker.v1.ListImagesRequest\x1a\x1d.docker.v1.ListImagesResponse\"\x00\x12N\n" + "\vImageRemove\x12\x1d.docker.v1.RemoveImageRequest\x1a\x1e.docker.v1.RemoveImageResponse\"\x00\x12Q\n" + "\x10ImagePruneUnused\x12\x1c.docker.v1.ImagePruneRequest\x1a\x1d.docker.v1.ImagePruneResponse\"\x00\x12Q\n" + @@ -3834,11 +4935,14 @@ const file_docker_v1_docker_proto_rawDesc = "" + "\n" + "VolumeList\x12\x1d.docker.v1.ListVolumesRequest\x1a\x1e.docker.v1.ListVolumesResponse\"\x00\x12Q\n" + "\fVolumeCreate\x12\x1e.docker.v1.CreateVolumeRequest\x1a\x1f.docker.v1.CreateVolumeResponse\"\x00\x12Q\n" + - "\fVolumeDelete\x12\x1e.docker.v1.DeleteVolumeRequest\x1a\x1f.docker.v1.DeleteVolumeResponse\"\x00\x12P\n" + + "\fVolumeDelete\x12\x1e.docker.v1.DeleteVolumeRequest\x1a\x1f.docker.v1.DeleteVolumeResponse\"\x00\x12T\n" + + "\rVolumeInspect\x12\x1f.docker.v1.VolumeInspectRequest\x1a .docker.v1.VolumeInspectResponse\"\x00\x12P\n" + "\vNetworkList\x12\x1e.docker.v1.ListNetworksRequest\x1a\x1f.docker.v1.ListNetworksResponse\"\x00\x12T\n" + "\rNetworkCreate\x12\x1f.docker.v1.CreateNetworkRequest\x1a .docker.v1.CreateNetworkResponse\"\x00\x12T\n" + "\rNetworkDelete\x12\x1f.docker.v1.DeleteNetworkRequest\x1a .docker.v1.DeleteNetworkResponse\"\x00\x12W\n" + - "\x0eNetworkInspect\x12 .docker.v1.NetworkInspectRequest\x1a!.docker.v1.NetworkInspectResponse\"\x00B\x8f\x01\n" + + "\x0eNetworkInspect\x12 .docker.v1.NetworkInspectRequest\x1a!.docker.v1.NetworkInspectResponse\"\x00\x12r\n" + + "\x17NetworkConnectContainer\x12).docker.v1.NetworkConnectContainerRequest\x1a*.docker.v1.NetworkConnectContainerResponse\"\x00\x12{\n" + + "\x1aNetworkDisconnectContainer\x12,.docker.v1.NetworkDisconnectContainerRequest\x1a-.docker.v1.NetworkDisconnectContainerResponse\"\x00B\x8f\x01\n" + "\rcom.docker.v1B\vDockerProtoP\x01Z,github.com/RA341/dockman/generated/docker/v1\xa2\x02\x03DXX\xaa\x02\tDocker.V1\xca\x02\tDocker\\V1\xe2\x02\x15Docker\\V1\\GPBMetadata\xea\x02\n" + "Docker::V1b\x06proto3" @@ -3855,164 +4959,207 @@ func file_docker_v1_docker_proto_rawDescGZIP() []byte { } var file_docker_v1_docker_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_docker_v1_docker_proto_msgTypes = make([]protoimpl.MessageInfo, 61) +var file_docker_v1_docker_proto_msgTypes = make([]protoimpl.MessageInfo, 77) var file_docker_v1_docker_proto_goTypes = []any{ - (SORT_FIELD)(0), // 0: docker.v1.SORT_FIELD - (ORDER)(0), // 1: docker.v1.ORDER - (*ComposeFileStatusRequest)(nil), // 2: docker.v1.ComposeFileStatusRequest - (*Status)(nil), // 3: docker.v1.Status - (*ComposeFileStatusResponse)(nil), // 4: docker.v1.ComposeFileStatusResponse - (*ContainerTopRequest)(nil), // 5: docker.v1.ContainerTopRequest - (*ContainerTopResponse)(nil), // 6: docker.v1.ContainerTopResponse - (*Process)(nil), // 7: docker.v1.Process - (*Top)(nil), // 8: docker.v1.Top - (*ContainerInspectMessage)(nil), // 9: docker.v1.ContainerInspectMessage - (*ContainerConfig)(nil), // 10: docker.v1.ContainerConfig - (*ContainerMount)(nil), // 11: docker.v1.ContainerMount - (*ContainerListRequest)(nil), // 12: docker.v1.ContainerListRequest - (*NetworkInspectRequest)(nil), // 13: docker.v1.NetworkInspectRequest - (*NetworkInspectResponse)(nil), // 14: docker.v1.NetworkInspectResponse - (*NetworkInspectInfo)(nil), // 15: docker.v1.NetworkInspectInfo - (*NetworkContainerInspect)(nil), // 16: docker.v1.NetworkContainerInspect - (*ImageInspectRequest)(nil), // 17: docker.v1.ImageInspectRequest - (*ImageInspectResponse)(nil), // 18: docker.v1.ImageInspectResponse - (*ImageInspect)(nil), // 19: docker.v1.ImageInspect - (*ImageLayer)(nil), // 20: docker.v1.ImageLayer - (*ComposeValidateResponse)(nil), // 21: docker.v1.ComposeValidateResponse - (*ContainerExecCmdInput)(nil), // 22: docker.v1.ContainerExecCmdInput - (*ContainerExecRequest)(nil), // 23: docker.v1.ContainerExecRequest - (*Image)(nil), // 24: docker.v1.Image - (*ManifestSummary)(nil), // 25: docker.v1.ManifestSummary - (*ListImagesRequest)(nil), // 26: docker.v1.ListImagesRequest - (*ListImagesResponse)(nil), // 27: docker.v1.ListImagesResponse - (*RemoveImageRequest)(nil), // 28: docker.v1.RemoveImageRequest - (*RemoveImageResponse)(nil), // 29: docker.v1.RemoveImageResponse - (*ImagePruneResponse)(nil), // 30: docker.v1.ImagePruneResponse - (*ImagePruneRequest)(nil), // 31: docker.v1.ImagePruneRequest - (*ImagesDeleted)(nil), // 32: docker.v1.ImagesDeleted - (*Volume)(nil), // 33: docker.v1.Volume - (*ListVolumesRequest)(nil), // 34: docker.v1.ListVolumesRequest - (*ListVolumesResponse)(nil), // 35: docker.v1.ListVolumesResponse - (*CreateVolumeRequest)(nil), // 36: docker.v1.CreateVolumeRequest - (*CreateVolumeResponse)(nil), // 37: docker.v1.CreateVolumeResponse - (*DeleteVolumeRequest)(nil), // 38: docker.v1.DeleteVolumeRequest - (*DeleteVolumeResponse)(nil), // 39: docker.v1.DeleteVolumeResponse - (*Network)(nil), // 40: docker.v1.Network - (*ListNetworksRequest)(nil), // 41: docker.v1.ListNetworksRequest - (*ListNetworksResponse)(nil), // 42: docker.v1.ListNetworksResponse - (*CreateNetworkRequest)(nil), // 43: docker.v1.CreateNetworkRequest - (*CreateNetworkResponse)(nil), // 44: docker.v1.CreateNetworkResponse - (*DeleteNetworkRequest)(nil), // 45: docker.v1.DeleteNetworkRequest - (*DeleteNetworkResponse)(nil), // 46: docker.v1.DeleteNetworkResponse - (*ContainerLogsRequest)(nil), // 47: docker.v1.ContainerLogsRequest - (*LogsMessage)(nil), // 48: docker.v1.LogsMessage - (*StatsResponse)(nil), // 49: docker.v1.StatsResponse - (*StatsRequest)(nil), // 50: docker.v1.StatsRequest - (*SystemInfo)(nil), // 51: docker.v1.SystemInfo - (*ListResponse)(nil), // 52: docker.v1.ListResponse - (*ContainerList)(nil), // 53: docker.v1.ContainerList - (*ContainerStats)(nil), // 54: docker.v1.ContainerStats - (*Port)(nil), // 55: docker.v1.Port - (*Empty)(nil), // 56: docker.v1.Empty - (*ContainerRequest)(nil), // 57: docker.v1.ContainerRequest - (*ComposeFile)(nil), // 58: docker.v1.ComposeFile - nil, // 59: docker.v1.ComposeFileStatusResponse.StatusEntry - nil, // 60: docker.v1.ContainerConfig.LabelsEntry - nil, // 61: docker.v1.Image.LabelsEntry - nil, // 62: docker.v1.ListResponse.StatusCountEntry + (SORT_FIELD)(0), // 0: docker.v1.SORT_FIELD + (ORDER)(0), // 1: docker.v1.ORDER + (*ComposeFileStatusRequest)(nil), // 2: docker.v1.ComposeFileStatusRequest + (*Status)(nil), // 3: docker.v1.Status + (*ComposeFileStatusResponse)(nil), // 4: docker.v1.ComposeFileStatusResponse + (*ContainerTopRequest)(nil), // 5: docker.v1.ContainerTopRequest + (*ContainerTopResponse)(nil), // 6: docker.v1.ContainerTopResponse + (*Process)(nil), // 7: docker.v1.Process + (*Top)(nil), // 8: docker.v1.Top + (*ContainerInspectMessage)(nil), // 9: docker.v1.ContainerInspectMessage + (*ContainerConfig)(nil), // 10: docker.v1.ContainerConfig + (*ContainerMount)(nil), // 11: docker.v1.ContainerMount + (*ContainerListRequest)(nil), // 12: docker.v1.ContainerListRequest + (*NetworkInspectRequest)(nil), // 13: docker.v1.NetworkInspectRequest + (*NetworkInspectResponse)(nil), // 14: docker.v1.NetworkInspectResponse + (*NetworkInspectInfo)(nil), // 15: docker.v1.NetworkInspectInfo + (*NetworkContainerInspect)(nil), // 16: docker.v1.NetworkContainerInspect + (*ImageInspectRequest)(nil), // 17: docker.v1.ImageInspectRequest + (*ImageInspectResponse)(nil), // 18: docker.v1.ImageInspectResponse + (*ImageInspect)(nil), // 19: docker.v1.ImageInspect + (*ImageLayer)(nil), // 20: docker.v1.ImageLayer + (*ImageContainerInspect)(nil), // 21: docker.v1.ImageContainerInspect + (*ComposeValidateResponse)(nil), // 22: docker.v1.ComposeValidateResponse + (*ContainerExecCmdInput)(nil), // 23: docker.v1.ContainerExecCmdInput + (*ContainerExecRequest)(nil), // 24: docker.v1.ContainerExecRequest + (*Image)(nil), // 25: docker.v1.Image + (*ManifestSummary)(nil), // 26: docker.v1.ManifestSummary + (*ListImagesRequest)(nil), // 27: docker.v1.ListImagesRequest + (*ListImagesResponse)(nil), // 28: docker.v1.ListImagesResponse + (*RemoveImageRequest)(nil), // 29: docker.v1.RemoveImageRequest + (*RemoveImageResponse)(nil), // 30: docker.v1.RemoveImageResponse + (*ImagePruneResponse)(nil), // 31: docker.v1.ImagePruneResponse + (*ImagePruneRequest)(nil), // 32: docker.v1.ImagePruneRequest + (*ImagesDeleted)(nil), // 33: docker.v1.ImagesDeleted + (*Volume)(nil), // 34: docker.v1.Volume + (*ListVolumesRequest)(nil), // 35: docker.v1.ListVolumesRequest + (*ListVolumesResponse)(nil), // 36: docker.v1.ListVolumesResponse + (*CreateVolumeRequest)(nil), // 37: docker.v1.CreateVolumeRequest + (*CreateVolumeResponse)(nil), // 38: docker.v1.CreateVolumeResponse + (*DeleteVolumeRequest)(nil), // 39: docker.v1.DeleteVolumeRequest + (*DeleteVolumeResponse)(nil), // 40: docker.v1.DeleteVolumeResponse + (*VolumeInspectRequest)(nil), // 41: docker.v1.VolumeInspectRequest + (*VolumeInspectResponse)(nil), // 42: docker.v1.VolumeInspectResponse + (*VolumeInspectInfo)(nil), // 43: docker.v1.VolumeInspectInfo + (*VolumeContainerInspect)(nil), // 44: docker.v1.VolumeContainerInspect + (*Network)(nil), // 45: docker.v1.Network + (*ListNetworksRequest)(nil), // 46: docker.v1.ListNetworksRequest + (*ListNetworksResponse)(nil), // 47: docker.v1.ListNetworksResponse + (*CreateNetworkRequest)(nil), // 48: docker.v1.CreateNetworkRequest + (*CreateNetworkResponse)(nil), // 49: docker.v1.CreateNetworkResponse + (*DeleteNetworkRequest)(nil), // 50: docker.v1.DeleteNetworkRequest + (*DeleteNetworkResponse)(nil), // 51: docker.v1.DeleteNetworkResponse + (*NetworkConnectContainerRequest)(nil), // 52: docker.v1.NetworkConnectContainerRequest + (*NetworkConnectContainerResponse)(nil), // 53: docker.v1.NetworkConnectContainerResponse + (*NetworkDisconnectContainerRequest)(nil), // 54: docker.v1.NetworkDisconnectContainerRequest + (*NetworkDisconnectContainerResponse)(nil), // 55: docker.v1.NetworkDisconnectContainerResponse + (*EventsRequest)(nil), // 56: docker.v1.EventsRequest + (*ContainerEvent)(nil), // 57: docker.v1.ContainerEvent + (*ContainerLogsRequest)(nil), // 58: docker.v1.ContainerLogsRequest + (*LogsMessage)(nil), // 59: docker.v1.LogsMessage + (*LogsStreamRequest)(nil), // 60: docker.v1.LogsStreamRequest + (*LogLine)(nil), // 61: docker.v1.LogLine + (*DockerCommandRequest)(nil), // 62: docker.v1.DockerCommandRequest + (*HostStatsResponse)(nil), // 63: docker.v1.HostStatsResponse + (*StatsResponse)(nil), // 64: docker.v1.StatsResponse + (*StatsRequest)(nil), // 65: docker.v1.StatsRequest + (*SystemInfo)(nil), // 66: docker.v1.SystemInfo + (*ListResponse)(nil), // 67: docker.v1.ListResponse + (*ContainerList)(nil), // 68: docker.v1.ContainerList + (*ContainerStats)(nil), // 69: docker.v1.ContainerStats + (*Port)(nil), // 70: docker.v1.Port + (*Empty)(nil), // 71: docker.v1.Empty + (*ContainerRequest)(nil), // 72: docker.v1.ContainerRequest + (*ComposeFile)(nil), // 73: docker.v1.ComposeFile + (*ComposeRedeployRequest)(nil), // 74: docker.v1.ComposeRedeployRequest + nil, // 75: docker.v1.ComposeFileStatusResponse.StatusEntry + nil, // 76: docker.v1.ContainerConfig.LabelsEntry + nil, // 77: docker.v1.Image.LabelsEntry + nil, // 78: docker.v1.ListResponse.StatusCountEntry } var file_docker_v1_docker_proto_depIdxs = []int32{ - 59, // 0: docker.v1.ComposeFileStatusResponse.status:type_name -> docker.v1.ComposeFileStatusResponse.StatusEntry + 75, // 0: docker.v1.ComposeFileStatusResponse.status:type_name -> docker.v1.ComposeFileStatusResponse.StatusEntry 8, // 1: docker.v1.ContainerTopResponse.top:type_name -> docker.v1.Top 7, // 2: docker.v1.Top.proc:type_name -> docker.v1.Process 11, // 3: docker.v1.ContainerInspectMessage.mounts:type_name -> docker.v1.ContainerMount 10, // 4: docker.v1.ContainerInspectMessage.config:type_name -> docker.v1.ContainerConfig - 60, // 5: docker.v1.ContainerConfig.Labels:type_name -> docker.v1.ContainerConfig.LabelsEntry + 76, // 5: docker.v1.ContainerConfig.Labels:type_name -> docker.v1.ContainerConfig.LabelsEntry 15, // 6: docker.v1.NetworkInspectResponse.inspect:type_name -> docker.v1.NetworkInspectInfo - 40, // 7: docker.v1.NetworkInspectInfo.net:type_name -> docker.v1.Network + 45, // 7: docker.v1.NetworkInspectInfo.net:type_name -> docker.v1.Network 16, // 8: docker.v1.NetworkInspectInfo.container:type_name -> docker.v1.NetworkContainerInspect 19, // 9: docker.v1.ImageInspectResponse.inspect:type_name -> docker.v1.ImageInspect 20, // 10: docker.v1.ImageInspect.layers:type_name -> docker.v1.ImageLayer - 61, // 11: docker.v1.Image.labels:type_name -> docker.v1.Image.LabelsEntry - 25, // 12: docker.v1.Image.manifests:type_name -> docker.v1.ManifestSummary - 24, // 13: docker.v1.ListImagesResponse.images:type_name -> docker.v1.Image - 32, // 14: docker.v1.ImagePruneResponse.deleted:type_name -> docker.v1.ImagesDeleted - 33, // 15: docker.v1.ListVolumesResponse.volumes:type_name -> docker.v1.Volume - 40, // 16: docker.v1.ListNetworksResponse.networks:type_name -> docker.v1.Network - 51, // 17: docker.v1.StatsResponse.system:type_name -> docker.v1.SystemInfo - 54, // 18: docker.v1.StatsResponse.containers:type_name -> docker.v1.ContainerStats - 58, // 19: docker.v1.StatsRequest.file:type_name -> docker.v1.ComposeFile - 0, // 20: docker.v1.StatsRequest.sortBy:type_name -> docker.v1.SORT_FIELD - 1, // 21: docker.v1.StatsRequest.order:type_name -> docker.v1.ORDER - 62, // 22: docker.v1.ListResponse.statusCount:type_name -> docker.v1.ListResponse.StatusCountEntry - 53, // 23: docker.v1.ListResponse.list:type_name -> docker.v1.ContainerList - 55, // 24: docker.v1.ContainerList.ports:type_name -> docker.v1.Port - 3, // 25: docker.v1.ComposeFileStatusResponse.StatusEntry.value:type_name -> docker.v1.Status - 57, // 26: docker.v1.DockerService.ContainerStart:input_type -> docker.v1.ContainerRequest - 57, // 27: docker.v1.DockerService.ContainerStop:input_type -> docker.v1.ContainerRequest - 57, // 28: docker.v1.DockerService.ContainerRemove:input_type -> docker.v1.ContainerRequest - 57, // 29: docker.v1.DockerService.ContainerRestart:input_type -> docker.v1.ContainerRequest - 57, // 30: docker.v1.DockerService.ContainerUpdate:input_type -> docker.v1.ContainerRequest - 5, // 31: docker.v1.DockerService.ContainerTop:input_type -> docker.v1.ContainerTopRequest - 12, // 32: docker.v1.DockerService.ContainerList:input_type -> docker.v1.ContainerListRequest - 50, // 33: docker.v1.DockerService.ContainerStats:input_type -> docker.v1.StatsRequest - 47, // 34: docker.v1.DockerService.ContainerLogs:input_type -> docker.v1.ContainerLogsRequest - 47, // 35: docker.v1.DockerService.ContainerInspect:input_type -> docker.v1.ContainerLogsRequest - 58, // 36: docker.v1.DockerService.ComposeUp:input_type -> docker.v1.ComposeFile - 58, // 37: docker.v1.DockerService.ComposeDown:input_type -> docker.v1.ComposeFile - 58, // 38: docker.v1.DockerService.ComposeStart:input_type -> docker.v1.ComposeFile - 58, // 39: docker.v1.DockerService.ComposeStop:input_type -> docker.v1.ComposeFile - 58, // 40: docker.v1.DockerService.ComposeRestart:input_type -> docker.v1.ComposeFile - 58, // 41: docker.v1.DockerService.ComposeUpdate:input_type -> docker.v1.ComposeFile - 58, // 42: docker.v1.DockerService.ComposeList:input_type -> docker.v1.ComposeFile - 58, // 43: docker.v1.DockerService.ComposeValidate:input_type -> docker.v1.ComposeFile - 2, // 44: docker.v1.DockerService.ComposeFileStatus:input_type -> docker.v1.ComposeFileStatusRequest - 26, // 45: docker.v1.DockerService.ImageList:input_type -> docker.v1.ListImagesRequest - 28, // 46: docker.v1.DockerService.ImageRemove:input_type -> docker.v1.RemoveImageRequest - 31, // 47: docker.v1.DockerService.ImagePruneUnused:input_type -> docker.v1.ImagePruneRequest - 17, // 48: docker.v1.DockerService.ImageInspect:input_type -> docker.v1.ImageInspectRequest - 34, // 49: docker.v1.DockerService.VolumeList:input_type -> docker.v1.ListVolumesRequest - 36, // 50: docker.v1.DockerService.VolumeCreate:input_type -> docker.v1.CreateVolumeRequest - 38, // 51: docker.v1.DockerService.VolumeDelete:input_type -> docker.v1.DeleteVolumeRequest - 41, // 52: docker.v1.DockerService.NetworkList:input_type -> docker.v1.ListNetworksRequest - 43, // 53: docker.v1.DockerService.NetworkCreate:input_type -> docker.v1.CreateNetworkRequest - 45, // 54: docker.v1.DockerService.NetworkDelete:input_type -> docker.v1.DeleteNetworkRequest - 13, // 55: docker.v1.DockerService.NetworkInspect:input_type -> docker.v1.NetworkInspectRequest - 48, // 56: docker.v1.DockerService.ContainerStart:output_type -> docker.v1.LogsMessage - 48, // 57: docker.v1.DockerService.ContainerStop:output_type -> docker.v1.LogsMessage - 48, // 58: docker.v1.DockerService.ContainerRemove:output_type -> docker.v1.LogsMessage - 48, // 59: docker.v1.DockerService.ContainerRestart:output_type -> docker.v1.LogsMessage - 56, // 60: docker.v1.DockerService.ContainerUpdate:output_type -> docker.v1.Empty - 6, // 61: docker.v1.DockerService.ContainerTop:output_type -> docker.v1.ContainerTopResponse - 52, // 62: docker.v1.DockerService.ContainerList:output_type -> docker.v1.ListResponse - 49, // 63: docker.v1.DockerService.ContainerStats:output_type -> docker.v1.StatsResponse - 48, // 64: docker.v1.DockerService.ContainerLogs:output_type -> docker.v1.LogsMessage - 9, // 65: docker.v1.DockerService.ContainerInspect:output_type -> docker.v1.ContainerInspectMessage - 48, // 66: docker.v1.DockerService.ComposeUp:output_type -> docker.v1.LogsMessage - 48, // 67: docker.v1.DockerService.ComposeDown:output_type -> docker.v1.LogsMessage - 48, // 68: docker.v1.DockerService.ComposeStart:output_type -> docker.v1.LogsMessage - 48, // 69: docker.v1.DockerService.ComposeStop:output_type -> docker.v1.LogsMessage - 48, // 70: docker.v1.DockerService.ComposeRestart:output_type -> docker.v1.LogsMessage - 48, // 71: docker.v1.DockerService.ComposeUpdate:output_type -> docker.v1.LogsMessage - 52, // 72: docker.v1.DockerService.ComposeList:output_type -> docker.v1.ListResponse - 21, // 73: docker.v1.DockerService.ComposeValidate:output_type -> docker.v1.ComposeValidateResponse - 4, // 74: docker.v1.DockerService.ComposeFileStatus:output_type -> docker.v1.ComposeFileStatusResponse - 27, // 75: docker.v1.DockerService.ImageList:output_type -> docker.v1.ListImagesResponse - 29, // 76: docker.v1.DockerService.ImageRemove:output_type -> docker.v1.RemoveImageResponse - 30, // 77: docker.v1.DockerService.ImagePruneUnused:output_type -> docker.v1.ImagePruneResponse - 18, // 78: docker.v1.DockerService.ImageInspect:output_type -> docker.v1.ImageInspectResponse - 35, // 79: docker.v1.DockerService.VolumeList:output_type -> docker.v1.ListVolumesResponse - 37, // 80: docker.v1.DockerService.VolumeCreate:output_type -> docker.v1.CreateVolumeResponse - 39, // 81: docker.v1.DockerService.VolumeDelete:output_type -> docker.v1.DeleteVolumeResponse - 42, // 82: docker.v1.DockerService.NetworkList:output_type -> docker.v1.ListNetworksResponse - 44, // 83: docker.v1.DockerService.NetworkCreate:output_type -> docker.v1.CreateNetworkResponse - 46, // 84: docker.v1.DockerService.NetworkDelete:output_type -> docker.v1.DeleteNetworkResponse - 14, // 85: docker.v1.DockerService.NetworkInspect:output_type -> docker.v1.NetworkInspectResponse - 56, // [56:86] is the sub-list for method output_type - 26, // [26:56] is the sub-list for method input_type - 26, // [26:26] is the sub-list for extension type_name - 26, // [26:26] is the sub-list for extension extendee - 0, // [0:26] is the sub-list for field type_name + 21, // 11: docker.v1.ImageInspect.containers:type_name -> docker.v1.ImageContainerInspect + 77, // 12: docker.v1.Image.labels:type_name -> docker.v1.Image.LabelsEntry + 26, // 13: docker.v1.Image.manifests:type_name -> docker.v1.ManifestSummary + 25, // 14: docker.v1.ListImagesResponse.images:type_name -> docker.v1.Image + 33, // 15: docker.v1.ImagePruneResponse.deleted:type_name -> docker.v1.ImagesDeleted + 34, // 16: docker.v1.ListVolumesResponse.volumes:type_name -> docker.v1.Volume + 43, // 17: docker.v1.VolumeInspectResponse.inspect:type_name -> docker.v1.VolumeInspectInfo + 34, // 18: docker.v1.VolumeInspectInfo.vol:type_name -> docker.v1.Volume + 44, // 19: docker.v1.VolumeInspectInfo.containers:type_name -> docker.v1.VolumeContainerInspect + 45, // 20: docker.v1.ListNetworksResponse.networks:type_name -> docker.v1.Network + 66, // 21: docker.v1.StatsResponse.system:type_name -> docker.v1.SystemInfo + 69, // 22: docker.v1.StatsResponse.containers:type_name -> docker.v1.ContainerStats + 73, // 23: docker.v1.StatsRequest.file:type_name -> docker.v1.ComposeFile + 0, // 24: docker.v1.StatsRequest.sortBy:type_name -> docker.v1.SORT_FIELD + 1, // 25: docker.v1.StatsRequest.order:type_name -> docker.v1.ORDER + 78, // 26: docker.v1.ListResponse.statusCount:type_name -> docker.v1.ListResponse.StatusCountEntry + 68, // 27: docker.v1.ListResponse.list:type_name -> docker.v1.ContainerList + 70, // 28: docker.v1.ContainerList.ports:type_name -> docker.v1.Port + 73, // 29: docker.v1.ComposeRedeployRequest.file:type_name -> docker.v1.ComposeFile + 3, // 30: docker.v1.ComposeFileStatusResponse.StatusEntry.value:type_name -> docker.v1.Status + 72, // 31: docker.v1.DockerService.ContainerStart:input_type -> docker.v1.ContainerRequest + 72, // 32: docker.v1.DockerService.ContainerStop:input_type -> docker.v1.ContainerRequest + 72, // 33: docker.v1.DockerService.ContainerRemove:input_type -> docker.v1.ContainerRequest + 72, // 34: docker.v1.DockerService.ContainerRestart:input_type -> docker.v1.ContainerRequest + 72, // 35: docker.v1.DockerService.ContainerPause:input_type -> docker.v1.ContainerRequest + 72, // 36: docker.v1.DockerService.ContainerUnpause:input_type -> docker.v1.ContainerRequest + 72, // 37: docker.v1.DockerService.ContainerUpdate:input_type -> docker.v1.ContainerRequest + 5, // 38: docker.v1.DockerService.ContainerTop:input_type -> docker.v1.ContainerTopRequest + 12, // 39: docker.v1.DockerService.ContainerList:input_type -> docker.v1.ContainerListRequest + 65, // 40: docker.v1.DockerService.ContainerStats:input_type -> docker.v1.StatsRequest + 65, // 41: docker.v1.DockerService.ContainerStatsStream:input_type -> docker.v1.StatsRequest + 71, // 42: docker.v1.DockerService.HostStats:input_type -> docker.v1.Empty + 58, // 43: docker.v1.DockerService.ContainerLogs:input_type -> docker.v1.ContainerLogsRequest + 56, // 44: docker.v1.DockerService.ContainerEvents:input_type -> docker.v1.EventsRequest + 60, // 45: docker.v1.DockerService.ContainerLogsStream:input_type -> docker.v1.LogsStreamRequest + 58, // 46: docker.v1.DockerService.ContainerInspect:input_type -> docker.v1.ContainerLogsRequest + 73, // 47: docker.v1.DockerService.ComposeUp:input_type -> docker.v1.ComposeFile + 73, // 48: docker.v1.DockerService.ComposeDown:input_type -> docker.v1.ComposeFile + 73, // 49: docker.v1.DockerService.ComposeStart:input_type -> docker.v1.ComposeFile + 73, // 50: docker.v1.DockerService.ComposeStop:input_type -> docker.v1.ComposeFile + 73, // 51: docker.v1.DockerService.ComposeRestart:input_type -> docker.v1.ComposeFile + 73, // 52: docker.v1.DockerService.ComposeUpdate:input_type -> docker.v1.ComposeFile + 74, // 53: docker.v1.DockerService.ComposeRedeploy:input_type -> docker.v1.ComposeRedeployRequest + 73, // 54: docker.v1.DockerService.ComposeList:input_type -> docker.v1.ComposeFile + 73, // 55: docker.v1.DockerService.ComposeValidate:input_type -> docker.v1.ComposeFile + 2, // 56: docker.v1.DockerService.ComposeFileStatus:input_type -> docker.v1.ComposeFileStatusRequest + 62, // 57: docker.v1.DockerService.DockerCommand:input_type -> docker.v1.DockerCommandRequest + 27, // 58: docker.v1.DockerService.ImageList:input_type -> docker.v1.ListImagesRequest + 29, // 59: docker.v1.DockerService.ImageRemove:input_type -> docker.v1.RemoveImageRequest + 32, // 60: docker.v1.DockerService.ImagePruneUnused:input_type -> docker.v1.ImagePruneRequest + 17, // 61: docker.v1.DockerService.ImageInspect:input_type -> docker.v1.ImageInspectRequest + 35, // 62: docker.v1.DockerService.VolumeList:input_type -> docker.v1.ListVolumesRequest + 37, // 63: docker.v1.DockerService.VolumeCreate:input_type -> docker.v1.CreateVolumeRequest + 39, // 64: docker.v1.DockerService.VolumeDelete:input_type -> docker.v1.DeleteVolumeRequest + 41, // 65: docker.v1.DockerService.VolumeInspect:input_type -> docker.v1.VolumeInspectRequest + 46, // 66: docker.v1.DockerService.NetworkList:input_type -> docker.v1.ListNetworksRequest + 48, // 67: docker.v1.DockerService.NetworkCreate:input_type -> docker.v1.CreateNetworkRequest + 50, // 68: docker.v1.DockerService.NetworkDelete:input_type -> docker.v1.DeleteNetworkRequest + 13, // 69: docker.v1.DockerService.NetworkInspect:input_type -> docker.v1.NetworkInspectRequest + 52, // 70: docker.v1.DockerService.NetworkConnectContainer:input_type -> docker.v1.NetworkConnectContainerRequest + 54, // 71: docker.v1.DockerService.NetworkDisconnectContainer:input_type -> docker.v1.NetworkDisconnectContainerRequest + 59, // 72: docker.v1.DockerService.ContainerStart:output_type -> docker.v1.LogsMessage + 59, // 73: docker.v1.DockerService.ContainerStop:output_type -> docker.v1.LogsMessage + 59, // 74: docker.v1.DockerService.ContainerRemove:output_type -> docker.v1.LogsMessage + 59, // 75: docker.v1.DockerService.ContainerRestart:output_type -> docker.v1.LogsMessage + 59, // 76: docker.v1.DockerService.ContainerPause:output_type -> docker.v1.LogsMessage + 59, // 77: docker.v1.DockerService.ContainerUnpause:output_type -> docker.v1.LogsMessage + 59, // 78: docker.v1.DockerService.ContainerUpdate:output_type -> docker.v1.LogsMessage + 6, // 79: docker.v1.DockerService.ContainerTop:output_type -> docker.v1.ContainerTopResponse + 67, // 80: docker.v1.DockerService.ContainerList:output_type -> docker.v1.ListResponse + 64, // 81: docker.v1.DockerService.ContainerStats:output_type -> docker.v1.StatsResponse + 69, // 82: docker.v1.DockerService.ContainerStatsStream:output_type -> docker.v1.ContainerStats + 63, // 83: docker.v1.DockerService.HostStats:output_type -> docker.v1.HostStatsResponse + 59, // 84: docker.v1.DockerService.ContainerLogs:output_type -> docker.v1.LogsMessage + 57, // 85: docker.v1.DockerService.ContainerEvents:output_type -> docker.v1.ContainerEvent + 61, // 86: docker.v1.DockerService.ContainerLogsStream:output_type -> docker.v1.LogLine + 9, // 87: docker.v1.DockerService.ContainerInspect:output_type -> docker.v1.ContainerInspectMessage + 59, // 88: docker.v1.DockerService.ComposeUp:output_type -> docker.v1.LogsMessage + 59, // 89: docker.v1.DockerService.ComposeDown:output_type -> docker.v1.LogsMessage + 59, // 90: docker.v1.DockerService.ComposeStart:output_type -> docker.v1.LogsMessage + 59, // 91: docker.v1.DockerService.ComposeStop:output_type -> docker.v1.LogsMessage + 59, // 92: docker.v1.DockerService.ComposeRestart:output_type -> docker.v1.LogsMessage + 59, // 93: docker.v1.DockerService.ComposeUpdate:output_type -> docker.v1.LogsMessage + 59, // 94: docker.v1.DockerService.ComposeRedeploy:output_type -> docker.v1.LogsMessage + 67, // 95: docker.v1.DockerService.ComposeList:output_type -> docker.v1.ListResponse + 22, // 96: docker.v1.DockerService.ComposeValidate:output_type -> docker.v1.ComposeValidateResponse + 4, // 97: docker.v1.DockerService.ComposeFileStatus:output_type -> docker.v1.ComposeFileStatusResponse + 59, // 98: docker.v1.DockerService.DockerCommand:output_type -> docker.v1.LogsMessage + 28, // 99: docker.v1.DockerService.ImageList:output_type -> docker.v1.ListImagesResponse + 30, // 100: docker.v1.DockerService.ImageRemove:output_type -> docker.v1.RemoveImageResponse + 31, // 101: docker.v1.DockerService.ImagePruneUnused:output_type -> docker.v1.ImagePruneResponse + 18, // 102: docker.v1.DockerService.ImageInspect:output_type -> docker.v1.ImageInspectResponse + 36, // 103: docker.v1.DockerService.VolumeList:output_type -> docker.v1.ListVolumesResponse + 38, // 104: docker.v1.DockerService.VolumeCreate:output_type -> docker.v1.CreateVolumeResponse + 40, // 105: docker.v1.DockerService.VolumeDelete:output_type -> docker.v1.DeleteVolumeResponse + 42, // 106: docker.v1.DockerService.VolumeInspect:output_type -> docker.v1.VolumeInspectResponse + 47, // 107: docker.v1.DockerService.NetworkList:output_type -> docker.v1.ListNetworksResponse + 49, // 108: docker.v1.DockerService.NetworkCreate:output_type -> docker.v1.CreateNetworkResponse + 51, // 109: docker.v1.DockerService.NetworkDelete:output_type -> docker.v1.DeleteNetworkResponse + 14, // 110: docker.v1.DockerService.NetworkInspect:output_type -> docker.v1.NetworkInspectResponse + 53, // 111: docker.v1.DockerService.NetworkConnectContainer:output_type -> docker.v1.NetworkConnectContainerResponse + 55, // 112: docker.v1.DockerService.NetworkDisconnectContainer:output_type -> docker.v1.NetworkDisconnectContainerResponse + 72, // [72:113] is the sub-list for method output_type + 31, // [31:72] is the sub-list for method input_type + 31, // [31:31] is the sub-list for extension type_name + 31, // [31:31] is the sub-list for extension extendee + 0, // [0:31] is the sub-list for field type_name } func init() { file_docker_v1_docker_proto_init() } @@ -4026,7 +5173,7 @@ func file_docker_v1_docker_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_docker_v1_docker_proto_rawDesc), len(file_docker_v1_docker_proto_rawDesc)), NumEnums: 2, - NumMessages: 61, + NumMessages: 77, NumExtensions: 0, NumServices: 1, }, diff --git a/core/generated/docker/v1/v1connect/docker.connect.go b/core/generated/docker/v1/v1connect/docker.connect.go index bc315e4a..690833f9 100644 --- a/core/generated/docker/v1/v1connect/docker.connect.go +++ b/core/generated/docker/v1/v1connect/docker.connect.go @@ -45,6 +45,12 @@ const ( // DockerServiceContainerRestartProcedure is the fully-qualified name of the DockerService's // ContainerRestart RPC. DockerServiceContainerRestartProcedure = "/docker.v1.DockerService/ContainerRestart" + // DockerServiceContainerPauseProcedure is the fully-qualified name of the DockerService's + // ContainerPause RPC. + DockerServiceContainerPauseProcedure = "/docker.v1.DockerService/ContainerPause" + // DockerServiceContainerUnpauseProcedure is the fully-qualified name of the DockerService's + // ContainerUnpause RPC. + DockerServiceContainerUnpauseProcedure = "/docker.v1.DockerService/ContainerUnpause" // DockerServiceContainerUpdateProcedure is the fully-qualified name of the DockerService's // ContainerUpdate RPC. DockerServiceContainerUpdateProcedure = "/docker.v1.DockerService/ContainerUpdate" @@ -57,9 +63,20 @@ const ( // DockerServiceContainerStatsProcedure is the fully-qualified name of the DockerService's // ContainerStats RPC. DockerServiceContainerStatsProcedure = "/docker.v1.DockerService/ContainerStats" + // DockerServiceContainerStatsStreamProcedure is the fully-qualified name of the DockerService's + // ContainerStatsStream RPC. + DockerServiceContainerStatsStreamProcedure = "/docker.v1.DockerService/ContainerStatsStream" + // DockerServiceHostStatsProcedure is the fully-qualified name of the DockerService's HostStats RPC. + DockerServiceHostStatsProcedure = "/docker.v1.DockerService/HostStats" // DockerServiceContainerLogsProcedure is the fully-qualified name of the DockerService's // ContainerLogs RPC. DockerServiceContainerLogsProcedure = "/docker.v1.DockerService/ContainerLogs" + // DockerServiceContainerEventsProcedure is the fully-qualified name of the DockerService's + // ContainerEvents RPC. + DockerServiceContainerEventsProcedure = "/docker.v1.DockerService/ContainerEvents" + // DockerServiceContainerLogsStreamProcedure is the fully-qualified name of the DockerService's + // ContainerLogsStream RPC. + DockerServiceContainerLogsStreamProcedure = "/docker.v1.DockerService/ContainerLogsStream" // DockerServiceContainerInspectProcedure is the fully-qualified name of the DockerService's // ContainerInspect RPC. DockerServiceContainerInspectProcedure = "/docker.v1.DockerService/ContainerInspect" @@ -80,6 +97,9 @@ const ( // DockerServiceComposeUpdateProcedure is the fully-qualified name of the DockerService's // ComposeUpdate RPC. DockerServiceComposeUpdateProcedure = "/docker.v1.DockerService/ComposeUpdate" + // DockerServiceComposeRedeployProcedure is the fully-qualified name of the DockerService's + // ComposeRedeploy RPC. + DockerServiceComposeRedeployProcedure = "/docker.v1.DockerService/ComposeRedeploy" // DockerServiceComposeListProcedure is the fully-qualified name of the DockerService's ComposeList // RPC. DockerServiceComposeListProcedure = "/docker.v1.DockerService/ComposeList" @@ -89,6 +109,9 @@ const ( // DockerServiceComposeFileStatusProcedure is the fully-qualified name of the DockerService's // ComposeFileStatus RPC. DockerServiceComposeFileStatusProcedure = "/docker.v1.DockerService/ComposeFileStatus" + // DockerServiceDockerCommandProcedure is the fully-qualified name of the DockerService's + // DockerCommand RPC. + DockerServiceDockerCommandProcedure = "/docker.v1.DockerService/DockerCommand" // DockerServiceImageListProcedure is the fully-qualified name of the DockerService's ImageList RPC. DockerServiceImageListProcedure = "/docker.v1.DockerService/ImageList" // DockerServiceImageRemoveProcedure is the fully-qualified name of the DockerService's ImageRemove @@ -109,6 +132,9 @@ const ( // DockerServiceVolumeDeleteProcedure is the fully-qualified name of the DockerService's // VolumeDelete RPC. DockerServiceVolumeDeleteProcedure = "/docker.v1.DockerService/VolumeDelete" + // DockerServiceVolumeInspectProcedure is the fully-qualified name of the DockerService's + // VolumeInspect RPC. + DockerServiceVolumeInspectProcedure = "/docker.v1.DockerService/VolumeInspect" // DockerServiceNetworkListProcedure is the fully-qualified name of the DockerService's NetworkList // RPC. DockerServiceNetworkListProcedure = "/docker.v1.DockerService/NetworkList" @@ -121,6 +147,12 @@ const ( // DockerServiceNetworkInspectProcedure is the fully-qualified name of the DockerService's // NetworkInspect RPC. DockerServiceNetworkInspectProcedure = "/docker.v1.DockerService/NetworkInspect" + // DockerServiceNetworkConnectContainerProcedure is the fully-qualified name of the DockerService's + // NetworkConnectContainer RPC. + DockerServiceNetworkConnectContainerProcedure = "/docker.v1.DockerService/NetworkConnectContainer" + // DockerServiceNetworkDisconnectContainerProcedure is the fully-qualified name of the + // DockerService's NetworkDisconnectContainer RPC. + DockerServiceNetworkDisconnectContainerProcedure = "/docker.v1.DockerService/NetworkDisconnectContainer" ) // DockerServiceClient is a client for the docker.v1.DockerService service. @@ -130,11 +162,29 @@ type DockerServiceClient interface { ContainerStop(context.Context, *connect.Request[v1.ContainerRequest]) (*connect.Response[v1.LogsMessage], error) ContainerRemove(context.Context, *connect.Request[v1.ContainerRequest]) (*connect.Response[v1.LogsMessage], error) ContainerRestart(context.Context, *connect.Request[v1.ContainerRequest]) (*connect.Response[v1.LogsMessage], error) - ContainerUpdate(context.Context, *connect.Request[v1.ContainerRequest]) (*connect.Response[v1.Empty], error) + ContainerPause(context.Context, *connect.Request[v1.ContainerRequest]) (*connect.Response[v1.LogsMessage], error) + ContainerUnpause(context.Context, *connect.Request[v1.ContainerRequest]) (*connect.Response[v1.LogsMessage], error) + // force-updates the containers' images (pull, recreate when the image + // changed, rollback on failure), streaming per-step progress + ContainerUpdate(context.Context, *connect.Request[v1.ContainerRequest]) (*connect.ServerStreamForClient[v1.LogsMessage], error) ContainerTop(context.Context, *connect.Request[v1.ContainerTopRequest]) (*connect.Response[v1.ContainerTopResponse], error) ContainerList(context.Context, *connect.Request[v1.ContainerListRequest]) (*connect.Response[v1.ListResponse], error) ContainerStats(context.Context, *connect.Request[v1.StatsRequest]) (*connect.Response[v1.StatsResponse], error) + // streams each container's stats as soon as its read completes, so the UI + // fills in progressively instead of waiting for the slowest container + // (fully qualified return type: the sibling ContainerStats rpc otherwise + // shadows the message name inside the service scope) + ContainerStatsStream(context.Context, *connect.Request[v1.StatsRequest]) (*connect.ServerStreamForClient[v1.ContainerStats], error) + // real host-level usage (from /proc via the host's runner, so it works for + // ssh hosts too) — the general stats view shows this instead of summing + // per-container numbers + HostStats(context.Context, *connect.Request[v1.Empty]) (*connect.Response[v1.HostStatsResponse], error) ContainerLogs(context.Context, *connect.Request[v1.ContainerLogsRequest]) (*connect.ServerStreamForClient[v1.LogsMessage], error) + // pushes filtered container lifecycle events (start/stop/die/health + // transitions...) so views can refresh reactively instead of polling; + // empty-action messages are keepalives + ContainerEvents(context.Context, *connect.Request[v1.EventsRequest]) (*connect.ServerStreamForClient[v1.ContainerEvent], error) + ContainerLogsStream(context.Context, *connect.Request[v1.LogsStreamRequest]) (*connect.ServerStreamForClient[v1.LogLine], error) ContainerInspect(context.Context, *connect.Request[v1.ContainerLogsRequest]) (*connect.Response[v1.ContainerInspectMessage], error) // compose ComposeUp(context.Context, *connect.Request[v1.ComposeFile]) (*connect.ServerStreamForClient[v1.LogsMessage], error) @@ -143,9 +193,15 @@ type DockerServiceClient interface { ComposeStop(context.Context, *connect.Request[v1.ComposeFile]) (*connect.ServerStreamForClient[v1.LogsMessage], error) ComposeRestart(context.Context, *connect.Request[v1.ComposeFile]) (*connect.ServerStreamForClient[v1.LogsMessage], error) ComposeUpdate(context.Context, *connect.Request[v1.ComposeFile]) (*connect.ServerStreamForClient[v1.LogsMessage], error) + // compose up -d with explicit force flags (pull / build / recreate), + // so a stack can be redeployed in one action + ComposeRedeploy(context.Context, *connect.Request[v1.ComposeRedeployRequest]) (*connect.ServerStreamForClient[v1.LogsMessage], error) ComposeList(context.Context, *connect.Request[v1.ComposeFile]) (*connect.Response[v1.ListResponse], error) ComposeValidate(context.Context, *connect.Request[v1.ComposeFile]) (*connect.Response[v1.ComposeValidateResponse], error) ComposeFileStatus(context.Context, *connect.Request[v1.ComposeFileStatusRequest]) (*connect.Response[v1.ComposeFileStatusResponse], error) + // runs a user-provided docker CLI command on the selected host and streams + // its combined output; only the docker binary is allowed + DockerCommand(context.Context, *connect.Request[v1.DockerCommandRequest]) (*connect.ServerStreamForClient[v1.LogsMessage], error) // images ImageList(context.Context, *connect.Request[v1.ListImagesRequest]) (*connect.Response[v1.ListImagesResponse], error) ImageRemove(context.Context, *connect.Request[v1.RemoveImageRequest]) (*connect.Response[v1.RemoveImageResponse], error) @@ -155,11 +211,14 @@ type DockerServiceClient interface { VolumeList(context.Context, *connect.Request[v1.ListVolumesRequest]) (*connect.Response[v1.ListVolumesResponse], error) VolumeCreate(context.Context, *connect.Request[v1.CreateVolumeRequest]) (*connect.Response[v1.CreateVolumeResponse], error) VolumeDelete(context.Context, *connect.Request[v1.DeleteVolumeRequest]) (*connect.Response[v1.DeleteVolumeResponse], error) + VolumeInspect(context.Context, *connect.Request[v1.VolumeInspectRequest]) (*connect.Response[v1.VolumeInspectResponse], error) // networks NetworkList(context.Context, *connect.Request[v1.ListNetworksRequest]) (*connect.Response[v1.ListNetworksResponse], error) NetworkCreate(context.Context, *connect.Request[v1.CreateNetworkRequest]) (*connect.Response[v1.CreateNetworkResponse], error) NetworkDelete(context.Context, *connect.Request[v1.DeleteNetworkRequest]) (*connect.Response[v1.DeleteNetworkResponse], error) NetworkInspect(context.Context, *connect.Request[v1.NetworkInspectRequest]) (*connect.Response[v1.NetworkInspectResponse], error) + NetworkConnectContainer(context.Context, *connect.Request[v1.NetworkConnectContainerRequest]) (*connect.Response[v1.NetworkConnectContainerResponse], error) + NetworkDisconnectContainer(context.Context, *connect.Request[v1.NetworkDisconnectContainerRequest]) (*connect.Response[v1.NetworkDisconnectContainerResponse], error) } // NewDockerServiceClient constructs a client for the docker.v1.DockerService service. By default, @@ -197,7 +256,19 @@ func NewDockerServiceClient(httpClient connect.HTTPClient, baseURL string, opts connect.WithSchema(dockerServiceMethods.ByName("ContainerRestart")), connect.WithClientOptions(opts...), ), - containerUpdate: connect.NewClient[v1.ContainerRequest, v1.Empty]( + containerPause: connect.NewClient[v1.ContainerRequest, v1.LogsMessage]( + httpClient, + baseURL+DockerServiceContainerPauseProcedure, + connect.WithSchema(dockerServiceMethods.ByName("ContainerPause")), + connect.WithClientOptions(opts...), + ), + containerUnpause: connect.NewClient[v1.ContainerRequest, v1.LogsMessage]( + httpClient, + baseURL+DockerServiceContainerUnpauseProcedure, + connect.WithSchema(dockerServiceMethods.ByName("ContainerUnpause")), + connect.WithClientOptions(opts...), + ), + containerUpdate: connect.NewClient[v1.ContainerRequest, v1.LogsMessage]( httpClient, baseURL+DockerServiceContainerUpdateProcedure, connect.WithSchema(dockerServiceMethods.ByName("ContainerUpdate")), @@ -221,12 +292,36 @@ func NewDockerServiceClient(httpClient connect.HTTPClient, baseURL string, opts connect.WithSchema(dockerServiceMethods.ByName("ContainerStats")), connect.WithClientOptions(opts...), ), + containerStatsStream: connect.NewClient[v1.StatsRequest, v1.ContainerStats]( + httpClient, + baseURL+DockerServiceContainerStatsStreamProcedure, + connect.WithSchema(dockerServiceMethods.ByName("ContainerStatsStream")), + connect.WithClientOptions(opts...), + ), + hostStats: connect.NewClient[v1.Empty, v1.HostStatsResponse]( + httpClient, + baseURL+DockerServiceHostStatsProcedure, + connect.WithSchema(dockerServiceMethods.ByName("HostStats")), + connect.WithClientOptions(opts...), + ), containerLogs: connect.NewClient[v1.ContainerLogsRequest, v1.LogsMessage]( httpClient, baseURL+DockerServiceContainerLogsProcedure, connect.WithSchema(dockerServiceMethods.ByName("ContainerLogs")), connect.WithClientOptions(opts...), ), + containerEvents: connect.NewClient[v1.EventsRequest, v1.ContainerEvent]( + httpClient, + baseURL+DockerServiceContainerEventsProcedure, + connect.WithSchema(dockerServiceMethods.ByName("ContainerEvents")), + connect.WithClientOptions(opts...), + ), + containerLogsStream: connect.NewClient[v1.LogsStreamRequest, v1.LogLine]( + httpClient, + baseURL+DockerServiceContainerLogsStreamProcedure, + connect.WithSchema(dockerServiceMethods.ByName("ContainerLogsStream")), + connect.WithClientOptions(opts...), + ), containerInspect: connect.NewClient[v1.ContainerLogsRequest, v1.ContainerInspectMessage]( httpClient, baseURL+DockerServiceContainerInspectProcedure, @@ -269,6 +364,12 @@ func NewDockerServiceClient(httpClient connect.HTTPClient, baseURL string, opts connect.WithSchema(dockerServiceMethods.ByName("ComposeUpdate")), connect.WithClientOptions(opts...), ), + composeRedeploy: connect.NewClient[v1.ComposeRedeployRequest, v1.LogsMessage]( + httpClient, + baseURL+DockerServiceComposeRedeployProcedure, + connect.WithSchema(dockerServiceMethods.ByName("ComposeRedeploy")), + connect.WithClientOptions(opts...), + ), composeList: connect.NewClient[v1.ComposeFile, v1.ListResponse]( httpClient, baseURL+DockerServiceComposeListProcedure, @@ -287,6 +388,12 @@ func NewDockerServiceClient(httpClient connect.HTTPClient, baseURL string, opts connect.WithSchema(dockerServiceMethods.ByName("ComposeFileStatus")), connect.WithClientOptions(opts...), ), + dockerCommand: connect.NewClient[v1.DockerCommandRequest, v1.LogsMessage]( + httpClient, + baseURL+DockerServiceDockerCommandProcedure, + connect.WithSchema(dockerServiceMethods.ByName("DockerCommand")), + connect.WithClientOptions(opts...), + ), imageList: connect.NewClient[v1.ListImagesRequest, v1.ListImagesResponse]( httpClient, baseURL+DockerServiceImageListProcedure, @@ -329,6 +436,12 @@ func NewDockerServiceClient(httpClient connect.HTTPClient, baseURL string, opts connect.WithSchema(dockerServiceMethods.ByName("VolumeDelete")), connect.WithClientOptions(opts...), ), + volumeInspect: connect.NewClient[v1.VolumeInspectRequest, v1.VolumeInspectResponse]( + httpClient, + baseURL+DockerServiceVolumeInspectProcedure, + connect.WithSchema(dockerServiceMethods.ByName("VolumeInspect")), + connect.WithClientOptions(opts...), + ), networkList: connect.NewClient[v1.ListNetworksRequest, v1.ListNetworksResponse]( httpClient, baseURL+DockerServiceNetworkListProcedure, @@ -353,41 +466,64 @@ func NewDockerServiceClient(httpClient connect.HTTPClient, baseURL string, opts connect.WithSchema(dockerServiceMethods.ByName("NetworkInspect")), connect.WithClientOptions(opts...), ), + networkConnectContainer: connect.NewClient[v1.NetworkConnectContainerRequest, v1.NetworkConnectContainerResponse]( + httpClient, + baseURL+DockerServiceNetworkConnectContainerProcedure, + connect.WithSchema(dockerServiceMethods.ByName("NetworkConnectContainer")), + connect.WithClientOptions(opts...), + ), + networkDisconnectContainer: connect.NewClient[v1.NetworkDisconnectContainerRequest, v1.NetworkDisconnectContainerResponse]( + httpClient, + baseURL+DockerServiceNetworkDisconnectContainerProcedure, + connect.WithSchema(dockerServiceMethods.ByName("NetworkDisconnectContainer")), + connect.WithClientOptions(opts...), + ), } } // dockerServiceClient implements DockerServiceClient. type dockerServiceClient struct { - containerStart *connect.Client[v1.ContainerRequest, v1.LogsMessage] - containerStop *connect.Client[v1.ContainerRequest, v1.LogsMessage] - containerRemove *connect.Client[v1.ContainerRequest, v1.LogsMessage] - containerRestart *connect.Client[v1.ContainerRequest, v1.LogsMessage] - containerUpdate *connect.Client[v1.ContainerRequest, v1.Empty] - containerTop *connect.Client[v1.ContainerTopRequest, v1.ContainerTopResponse] - containerList *connect.Client[v1.ContainerListRequest, v1.ListResponse] - containerStats *connect.Client[v1.StatsRequest, v1.StatsResponse] - containerLogs *connect.Client[v1.ContainerLogsRequest, v1.LogsMessage] - containerInspect *connect.Client[v1.ContainerLogsRequest, v1.ContainerInspectMessage] - composeUp *connect.Client[v1.ComposeFile, v1.LogsMessage] - composeDown *connect.Client[v1.ComposeFile, v1.LogsMessage] - composeStart *connect.Client[v1.ComposeFile, v1.LogsMessage] - composeStop *connect.Client[v1.ComposeFile, v1.LogsMessage] - composeRestart *connect.Client[v1.ComposeFile, v1.LogsMessage] - composeUpdate *connect.Client[v1.ComposeFile, v1.LogsMessage] - composeList *connect.Client[v1.ComposeFile, v1.ListResponse] - composeValidate *connect.Client[v1.ComposeFile, v1.ComposeValidateResponse] - composeFileStatus *connect.Client[v1.ComposeFileStatusRequest, v1.ComposeFileStatusResponse] - imageList *connect.Client[v1.ListImagesRequest, v1.ListImagesResponse] - imageRemove *connect.Client[v1.RemoveImageRequest, v1.RemoveImageResponse] - imagePruneUnused *connect.Client[v1.ImagePruneRequest, v1.ImagePruneResponse] - imageInspect *connect.Client[v1.ImageInspectRequest, v1.ImageInspectResponse] - volumeList *connect.Client[v1.ListVolumesRequest, v1.ListVolumesResponse] - volumeCreate *connect.Client[v1.CreateVolumeRequest, v1.CreateVolumeResponse] - volumeDelete *connect.Client[v1.DeleteVolumeRequest, v1.DeleteVolumeResponse] - networkList *connect.Client[v1.ListNetworksRequest, v1.ListNetworksResponse] - networkCreate *connect.Client[v1.CreateNetworkRequest, v1.CreateNetworkResponse] - networkDelete *connect.Client[v1.DeleteNetworkRequest, v1.DeleteNetworkResponse] - networkInspect *connect.Client[v1.NetworkInspectRequest, v1.NetworkInspectResponse] + containerStart *connect.Client[v1.ContainerRequest, v1.LogsMessage] + containerStop *connect.Client[v1.ContainerRequest, v1.LogsMessage] + containerRemove *connect.Client[v1.ContainerRequest, v1.LogsMessage] + containerRestart *connect.Client[v1.ContainerRequest, v1.LogsMessage] + containerPause *connect.Client[v1.ContainerRequest, v1.LogsMessage] + containerUnpause *connect.Client[v1.ContainerRequest, v1.LogsMessage] + containerUpdate *connect.Client[v1.ContainerRequest, v1.LogsMessage] + containerTop *connect.Client[v1.ContainerTopRequest, v1.ContainerTopResponse] + containerList *connect.Client[v1.ContainerListRequest, v1.ListResponse] + containerStats *connect.Client[v1.StatsRequest, v1.StatsResponse] + containerStatsStream *connect.Client[v1.StatsRequest, v1.ContainerStats] + hostStats *connect.Client[v1.Empty, v1.HostStatsResponse] + containerLogs *connect.Client[v1.ContainerLogsRequest, v1.LogsMessage] + containerEvents *connect.Client[v1.EventsRequest, v1.ContainerEvent] + containerLogsStream *connect.Client[v1.LogsStreamRequest, v1.LogLine] + containerInspect *connect.Client[v1.ContainerLogsRequest, v1.ContainerInspectMessage] + composeUp *connect.Client[v1.ComposeFile, v1.LogsMessage] + composeDown *connect.Client[v1.ComposeFile, v1.LogsMessage] + composeStart *connect.Client[v1.ComposeFile, v1.LogsMessage] + composeStop *connect.Client[v1.ComposeFile, v1.LogsMessage] + composeRestart *connect.Client[v1.ComposeFile, v1.LogsMessage] + composeUpdate *connect.Client[v1.ComposeFile, v1.LogsMessage] + composeRedeploy *connect.Client[v1.ComposeRedeployRequest, v1.LogsMessage] + composeList *connect.Client[v1.ComposeFile, v1.ListResponse] + composeValidate *connect.Client[v1.ComposeFile, v1.ComposeValidateResponse] + composeFileStatus *connect.Client[v1.ComposeFileStatusRequest, v1.ComposeFileStatusResponse] + dockerCommand *connect.Client[v1.DockerCommandRequest, v1.LogsMessage] + imageList *connect.Client[v1.ListImagesRequest, v1.ListImagesResponse] + imageRemove *connect.Client[v1.RemoveImageRequest, v1.RemoveImageResponse] + imagePruneUnused *connect.Client[v1.ImagePruneRequest, v1.ImagePruneResponse] + imageInspect *connect.Client[v1.ImageInspectRequest, v1.ImageInspectResponse] + volumeList *connect.Client[v1.ListVolumesRequest, v1.ListVolumesResponse] + volumeCreate *connect.Client[v1.CreateVolumeRequest, v1.CreateVolumeResponse] + volumeDelete *connect.Client[v1.DeleteVolumeRequest, v1.DeleteVolumeResponse] + volumeInspect *connect.Client[v1.VolumeInspectRequest, v1.VolumeInspectResponse] + networkList *connect.Client[v1.ListNetworksRequest, v1.ListNetworksResponse] + networkCreate *connect.Client[v1.CreateNetworkRequest, v1.CreateNetworkResponse] + networkDelete *connect.Client[v1.DeleteNetworkRequest, v1.DeleteNetworkResponse] + networkInspect *connect.Client[v1.NetworkInspectRequest, v1.NetworkInspectResponse] + networkConnectContainer *connect.Client[v1.NetworkConnectContainerRequest, v1.NetworkConnectContainerResponse] + networkDisconnectContainer *connect.Client[v1.NetworkDisconnectContainerRequest, v1.NetworkDisconnectContainerResponse] } // ContainerStart calls docker.v1.DockerService.ContainerStart. @@ -410,9 +546,19 @@ func (c *dockerServiceClient) ContainerRestart(ctx context.Context, req *connect return c.containerRestart.CallUnary(ctx, req) } +// ContainerPause calls docker.v1.DockerService.ContainerPause. +func (c *dockerServiceClient) ContainerPause(ctx context.Context, req *connect.Request[v1.ContainerRequest]) (*connect.Response[v1.LogsMessage], error) { + return c.containerPause.CallUnary(ctx, req) +} + +// ContainerUnpause calls docker.v1.DockerService.ContainerUnpause. +func (c *dockerServiceClient) ContainerUnpause(ctx context.Context, req *connect.Request[v1.ContainerRequest]) (*connect.Response[v1.LogsMessage], error) { + return c.containerUnpause.CallUnary(ctx, req) +} + // ContainerUpdate calls docker.v1.DockerService.ContainerUpdate. -func (c *dockerServiceClient) ContainerUpdate(ctx context.Context, req *connect.Request[v1.ContainerRequest]) (*connect.Response[v1.Empty], error) { - return c.containerUpdate.CallUnary(ctx, req) +func (c *dockerServiceClient) ContainerUpdate(ctx context.Context, req *connect.Request[v1.ContainerRequest]) (*connect.ServerStreamForClient[v1.LogsMessage], error) { + return c.containerUpdate.CallServerStream(ctx, req) } // ContainerTop calls docker.v1.DockerService.ContainerTop. @@ -430,11 +576,31 @@ func (c *dockerServiceClient) ContainerStats(ctx context.Context, req *connect.R return c.containerStats.CallUnary(ctx, req) } +// ContainerStatsStream calls docker.v1.DockerService.ContainerStatsStream. +func (c *dockerServiceClient) ContainerStatsStream(ctx context.Context, req *connect.Request[v1.StatsRequest]) (*connect.ServerStreamForClient[v1.ContainerStats], error) { + return c.containerStatsStream.CallServerStream(ctx, req) +} + +// HostStats calls docker.v1.DockerService.HostStats. +func (c *dockerServiceClient) HostStats(ctx context.Context, req *connect.Request[v1.Empty]) (*connect.Response[v1.HostStatsResponse], error) { + return c.hostStats.CallUnary(ctx, req) +} + // ContainerLogs calls docker.v1.DockerService.ContainerLogs. func (c *dockerServiceClient) ContainerLogs(ctx context.Context, req *connect.Request[v1.ContainerLogsRequest]) (*connect.ServerStreamForClient[v1.LogsMessage], error) { return c.containerLogs.CallServerStream(ctx, req) } +// ContainerEvents calls docker.v1.DockerService.ContainerEvents. +func (c *dockerServiceClient) ContainerEvents(ctx context.Context, req *connect.Request[v1.EventsRequest]) (*connect.ServerStreamForClient[v1.ContainerEvent], error) { + return c.containerEvents.CallServerStream(ctx, req) +} + +// ContainerLogsStream calls docker.v1.DockerService.ContainerLogsStream. +func (c *dockerServiceClient) ContainerLogsStream(ctx context.Context, req *connect.Request[v1.LogsStreamRequest]) (*connect.ServerStreamForClient[v1.LogLine], error) { + return c.containerLogsStream.CallServerStream(ctx, req) +} + // ContainerInspect calls docker.v1.DockerService.ContainerInspect. func (c *dockerServiceClient) ContainerInspect(ctx context.Context, req *connect.Request[v1.ContainerLogsRequest]) (*connect.Response[v1.ContainerInspectMessage], error) { return c.containerInspect.CallUnary(ctx, req) @@ -470,6 +636,11 @@ func (c *dockerServiceClient) ComposeUpdate(ctx context.Context, req *connect.Re return c.composeUpdate.CallServerStream(ctx, req) } +// ComposeRedeploy calls docker.v1.DockerService.ComposeRedeploy. +func (c *dockerServiceClient) ComposeRedeploy(ctx context.Context, req *connect.Request[v1.ComposeRedeployRequest]) (*connect.ServerStreamForClient[v1.LogsMessage], error) { + return c.composeRedeploy.CallServerStream(ctx, req) +} + // ComposeList calls docker.v1.DockerService.ComposeList. func (c *dockerServiceClient) ComposeList(ctx context.Context, req *connect.Request[v1.ComposeFile]) (*connect.Response[v1.ListResponse], error) { return c.composeList.CallUnary(ctx, req) @@ -485,6 +656,11 @@ func (c *dockerServiceClient) ComposeFileStatus(ctx context.Context, req *connec return c.composeFileStatus.CallUnary(ctx, req) } +// DockerCommand calls docker.v1.DockerService.DockerCommand. +func (c *dockerServiceClient) DockerCommand(ctx context.Context, req *connect.Request[v1.DockerCommandRequest]) (*connect.ServerStreamForClient[v1.LogsMessage], error) { + return c.dockerCommand.CallServerStream(ctx, req) +} + // ImageList calls docker.v1.DockerService.ImageList. func (c *dockerServiceClient) ImageList(ctx context.Context, req *connect.Request[v1.ListImagesRequest]) (*connect.Response[v1.ListImagesResponse], error) { return c.imageList.CallUnary(ctx, req) @@ -520,6 +696,11 @@ func (c *dockerServiceClient) VolumeDelete(ctx context.Context, req *connect.Req return c.volumeDelete.CallUnary(ctx, req) } +// VolumeInspect calls docker.v1.DockerService.VolumeInspect. +func (c *dockerServiceClient) VolumeInspect(ctx context.Context, req *connect.Request[v1.VolumeInspectRequest]) (*connect.Response[v1.VolumeInspectResponse], error) { + return c.volumeInspect.CallUnary(ctx, req) +} + // NetworkList calls docker.v1.DockerService.NetworkList. func (c *dockerServiceClient) NetworkList(ctx context.Context, req *connect.Request[v1.ListNetworksRequest]) (*connect.Response[v1.ListNetworksResponse], error) { return c.networkList.CallUnary(ctx, req) @@ -540,6 +721,16 @@ func (c *dockerServiceClient) NetworkInspect(ctx context.Context, req *connect.R return c.networkInspect.CallUnary(ctx, req) } +// NetworkConnectContainer calls docker.v1.DockerService.NetworkConnectContainer. +func (c *dockerServiceClient) NetworkConnectContainer(ctx context.Context, req *connect.Request[v1.NetworkConnectContainerRequest]) (*connect.Response[v1.NetworkConnectContainerResponse], error) { + return c.networkConnectContainer.CallUnary(ctx, req) +} + +// NetworkDisconnectContainer calls docker.v1.DockerService.NetworkDisconnectContainer. +func (c *dockerServiceClient) NetworkDisconnectContainer(ctx context.Context, req *connect.Request[v1.NetworkDisconnectContainerRequest]) (*connect.Response[v1.NetworkDisconnectContainerResponse], error) { + return c.networkDisconnectContainer.CallUnary(ctx, req) +} + // DockerServiceHandler is an implementation of the docker.v1.DockerService service. type DockerServiceHandler interface { // container @@ -547,11 +738,29 @@ type DockerServiceHandler interface { ContainerStop(context.Context, *connect.Request[v1.ContainerRequest]) (*connect.Response[v1.LogsMessage], error) ContainerRemove(context.Context, *connect.Request[v1.ContainerRequest]) (*connect.Response[v1.LogsMessage], error) ContainerRestart(context.Context, *connect.Request[v1.ContainerRequest]) (*connect.Response[v1.LogsMessage], error) - ContainerUpdate(context.Context, *connect.Request[v1.ContainerRequest]) (*connect.Response[v1.Empty], error) + ContainerPause(context.Context, *connect.Request[v1.ContainerRequest]) (*connect.Response[v1.LogsMessage], error) + ContainerUnpause(context.Context, *connect.Request[v1.ContainerRequest]) (*connect.Response[v1.LogsMessage], error) + // force-updates the containers' images (pull, recreate when the image + // changed, rollback on failure), streaming per-step progress + ContainerUpdate(context.Context, *connect.Request[v1.ContainerRequest], *connect.ServerStream[v1.LogsMessage]) error ContainerTop(context.Context, *connect.Request[v1.ContainerTopRequest]) (*connect.Response[v1.ContainerTopResponse], error) ContainerList(context.Context, *connect.Request[v1.ContainerListRequest]) (*connect.Response[v1.ListResponse], error) ContainerStats(context.Context, *connect.Request[v1.StatsRequest]) (*connect.Response[v1.StatsResponse], error) + // streams each container's stats as soon as its read completes, so the UI + // fills in progressively instead of waiting for the slowest container + // (fully qualified return type: the sibling ContainerStats rpc otherwise + // shadows the message name inside the service scope) + ContainerStatsStream(context.Context, *connect.Request[v1.StatsRequest], *connect.ServerStream[v1.ContainerStats]) error + // real host-level usage (from /proc via the host's runner, so it works for + // ssh hosts too) — the general stats view shows this instead of summing + // per-container numbers + HostStats(context.Context, *connect.Request[v1.Empty]) (*connect.Response[v1.HostStatsResponse], error) ContainerLogs(context.Context, *connect.Request[v1.ContainerLogsRequest], *connect.ServerStream[v1.LogsMessage]) error + // pushes filtered container lifecycle events (start/stop/die/health + // transitions...) so views can refresh reactively instead of polling; + // empty-action messages are keepalives + ContainerEvents(context.Context, *connect.Request[v1.EventsRequest], *connect.ServerStream[v1.ContainerEvent]) error + ContainerLogsStream(context.Context, *connect.Request[v1.LogsStreamRequest], *connect.ServerStream[v1.LogLine]) error ContainerInspect(context.Context, *connect.Request[v1.ContainerLogsRequest]) (*connect.Response[v1.ContainerInspectMessage], error) // compose ComposeUp(context.Context, *connect.Request[v1.ComposeFile], *connect.ServerStream[v1.LogsMessage]) error @@ -560,9 +769,15 @@ type DockerServiceHandler interface { ComposeStop(context.Context, *connect.Request[v1.ComposeFile], *connect.ServerStream[v1.LogsMessage]) error ComposeRestart(context.Context, *connect.Request[v1.ComposeFile], *connect.ServerStream[v1.LogsMessage]) error ComposeUpdate(context.Context, *connect.Request[v1.ComposeFile], *connect.ServerStream[v1.LogsMessage]) error + // compose up -d with explicit force flags (pull / build / recreate), + // so a stack can be redeployed in one action + ComposeRedeploy(context.Context, *connect.Request[v1.ComposeRedeployRequest], *connect.ServerStream[v1.LogsMessage]) error ComposeList(context.Context, *connect.Request[v1.ComposeFile]) (*connect.Response[v1.ListResponse], error) ComposeValidate(context.Context, *connect.Request[v1.ComposeFile]) (*connect.Response[v1.ComposeValidateResponse], error) ComposeFileStatus(context.Context, *connect.Request[v1.ComposeFileStatusRequest]) (*connect.Response[v1.ComposeFileStatusResponse], error) + // runs a user-provided docker CLI command on the selected host and streams + // its combined output; only the docker binary is allowed + DockerCommand(context.Context, *connect.Request[v1.DockerCommandRequest], *connect.ServerStream[v1.LogsMessage]) error // images ImageList(context.Context, *connect.Request[v1.ListImagesRequest]) (*connect.Response[v1.ListImagesResponse], error) ImageRemove(context.Context, *connect.Request[v1.RemoveImageRequest]) (*connect.Response[v1.RemoveImageResponse], error) @@ -572,11 +787,14 @@ type DockerServiceHandler interface { VolumeList(context.Context, *connect.Request[v1.ListVolumesRequest]) (*connect.Response[v1.ListVolumesResponse], error) VolumeCreate(context.Context, *connect.Request[v1.CreateVolumeRequest]) (*connect.Response[v1.CreateVolumeResponse], error) VolumeDelete(context.Context, *connect.Request[v1.DeleteVolumeRequest]) (*connect.Response[v1.DeleteVolumeResponse], error) + VolumeInspect(context.Context, *connect.Request[v1.VolumeInspectRequest]) (*connect.Response[v1.VolumeInspectResponse], error) // networks NetworkList(context.Context, *connect.Request[v1.ListNetworksRequest]) (*connect.Response[v1.ListNetworksResponse], error) NetworkCreate(context.Context, *connect.Request[v1.CreateNetworkRequest]) (*connect.Response[v1.CreateNetworkResponse], error) NetworkDelete(context.Context, *connect.Request[v1.DeleteNetworkRequest]) (*connect.Response[v1.DeleteNetworkResponse], error) NetworkInspect(context.Context, *connect.Request[v1.NetworkInspectRequest]) (*connect.Response[v1.NetworkInspectResponse], error) + NetworkConnectContainer(context.Context, *connect.Request[v1.NetworkConnectContainerRequest]) (*connect.Response[v1.NetworkConnectContainerResponse], error) + NetworkDisconnectContainer(context.Context, *connect.Request[v1.NetworkDisconnectContainerRequest]) (*connect.Response[v1.NetworkDisconnectContainerResponse], error) } // NewDockerServiceHandler builds an HTTP handler from the service implementation. It returns the @@ -610,7 +828,19 @@ func NewDockerServiceHandler(svc DockerServiceHandler, opts ...connect.HandlerOp connect.WithSchema(dockerServiceMethods.ByName("ContainerRestart")), connect.WithHandlerOptions(opts...), ) - dockerServiceContainerUpdateHandler := connect.NewUnaryHandler( + dockerServiceContainerPauseHandler := connect.NewUnaryHandler( + DockerServiceContainerPauseProcedure, + svc.ContainerPause, + connect.WithSchema(dockerServiceMethods.ByName("ContainerPause")), + connect.WithHandlerOptions(opts...), + ) + dockerServiceContainerUnpauseHandler := connect.NewUnaryHandler( + DockerServiceContainerUnpauseProcedure, + svc.ContainerUnpause, + connect.WithSchema(dockerServiceMethods.ByName("ContainerUnpause")), + connect.WithHandlerOptions(opts...), + ) + dockerServiceContainerUpdateHandler := connect.NewServerStreamHandler( DockerServiceContainerUpdateProcedure, svc.ContainerUpdate, connect.WithSchema(dockerServiceMethods.ByName("ContainerUpdate")), @@ -634,12 +864,36 @@ func NewDockerServiceHandler(svc DockerServiceHandler, opts ...connect.HandlerOp connect.WithSchema(dockerServiceMethods.ByName("ContainerStats")), connect.WithHandlerOptions(opts...), ) + dockerServiceContainerStatsStreamHandler := connect.NewServerStreamHandler( + DockerServiceContainerStatsStreamProcedure, + svc.ContainerStatsStream, + connect.WithSchema(dockerServiceMethods.ByName("ContainerStatsStream")), + connect.WithHandlerOptions(opts...), + ) + dockerServiceHostStatsHandler := connect.NewUnaryHandler( + DockerServiceHostStatsProcedure, + svc.HostStats, + connect.WithSchema(dockerServiceMethods.ByName("HostStats")), + connect.WithHandlerOptions(opts...), + ) dockerServiceContainerLogsHandler := connect.NewServerStreamHandler( DockerServiceContainerLogsProcedure, svc.ContainerLogs, connect.WithSchema(dockerServiceMethods.ByName("ContainerLogs")), connect.WithHandlerOptions(opts...), ) + dockerServiceContainerEventsHandler := connect.NewServerStreamHandler( + DockerServiceContainerEventsProcedure, + svc.ContainerEvents, + connect.WithSchema(dockerServiceMethods.ByName("ContainerEvents")), + connect.WithHandlerOptions(opts...), + ) + dockerServiceContainerLogsStreamHandler := connect.NewServerStreamHandler( + DockerServiceContainerLogsStreamProcedure, + svc.ContainerLogsStream, + connect.WithSchema(dockerServiceMethods.ByName("ContainerLogsStream")), + connect.WithHandlerOptions(opts...), + ) dockerServiceContainerInspectHandler := connect.NewUnaryHandler( DockerServiceContainerInspectProcedure, svc.ContainerInspect, @@ -682,6 +936,12 @@ func NewDockerServiceHandler(svc DockerServiceHandler, opts ...connect.HandlerOp connect.WithSchema(dockerServiceMethods.ByName("ComposeUpdate")), connect.WithHandlerOptions(opts...), ) + dockerServiceComposeRedeployHandler := connect.NewServerStreamHandler( + DockerServiceComposeRedeployProcedure, + svc.ComposeRedeploy, + connect.WithSchema(dockerServiceMethods.ByName("ComposeRedeploy")), + connect.WithHandlerOptions(opts...), + ) dockerServiceComposeListHandler := connect.NewUnaryHandler( DockerServiceComposeListProcedure, svc.ComposeList, @@ -700,6 +960,12 @@ func NewDockerServiceHandler(svc DockerServiceHandler, opts ...connect.HandlerOp connect.WithSchema(dockerServiceMethods.ByName("ComposeFileStatus")), connect.WithHandlerOptions(opts...), ) + dockerServiceDockerCommandHandler := connect.NewServerStreamHandler( + DockerServiceDockerCommandProcedure, + svc.DockerCommand, + connect.WithSchema(dockerServiceMethods.ByName("DockerCommand")), + connect.WithHandlerOptions(opts...), + ) dockerServiceImageListHandler := connect.NewUnaryHandler( DockerServiceImageListProcedure, svc.ImageList, @@ -742,6 +1008,12 @@ func NewDockerServiceHandler(svc DockerServiceHandler, opts ...connect.HandlerOp connect.WithSchema(dockerServiceMethods.ByName("VolumeDelete")), connect.WithHandlerOptions(opts...), ) + dockerServiceVolumeInspectHandler := connect.NewUnaryHandler( + DockerServiceVolumeInspectProcedure, + svc.VolumeInspect, + connect.WithSchema(dockerServiceMethods.ByName("VolumeInspect")), + connect.WithHandlerOptions(opts...), + ) dockerServiceNetworkListHandler := connect.NewUnaryHandler( DockerServiceNetworkListProcedure, svc.NetworkList, @@ -766,6 +1038,18 @@ func NewDockerServiceHandler(svc DockerServiceHandler, opts ...connect.HandlerOp connect.WithSchema(dockerServiceMethods.ByName("NetworkInspect")), connect.WithHandlerOptions(opts...), ) + dockerServiceNetworkConnectContainerHandler := connect.NewUnaryHandler( + DockerServiceNetworkConnectContainerProcedure, + svc.NetworkConnectContainer, + connect.WithSchema(dockerServiceMethods.ByName("NetworkConnectContainer")), + connect.WithHandlerOptions(opts...), + ) + dockerServiceNetworkDisconnectContainerHandler := connect.NewUnaryHandler( + DockerServiceNetworkDisconnectContainerProcedure, + svc.NetworkDisconnectContainer, + connect.WithSchema(dockerServiceMethods.ByName("NetworkDisconnectContainer")), + connect.WithHandlerOptions(opts...), + ) return "/docker.v1.DockerService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case DockerServiceContainerStartProcedure: @@ -776,6 +1060,10 @@ func NewDockerServiceHandler(svc DockerServiceHandler, opts ...connect.HandlerOp dockerServiceContainerRemoveHandler.ServeHTTP(w, r) case DockerServiceContainerRestartProcedure: dockerServiceContainerRestartHandler.ServeHTTP(w, r) + case DockerServiceContainerPauseProcedure: + dockerServiceContainerPauseHandler.ServeHTTP(w, r) + case DockerServiceContainerUnpauseProcedure: + dockerServiceContainerUnpauseHandler.ServeHTTP(w, r) case DockerServiceContainerUpdateProcedure: dockerServiceContainerUpdateHandler.ServeHTTP(w, r) case DockerServiceContainerTopProcedure: @@ -784,8 +1072,16 @@ func NewDockerServiceHandler(svc DockerServiceHandler, opts ...connect.HandlerOp dockerServiceContainerListHandler.ServeHTTP(w, r) case DockerServiceContainerStatsProcedure: dockerServiceContainerStatsHandler.ServeHTTP(w, r) + case DockerServiceContainerStatsStreamProcedure: + dockerServiceContainerStatsStreamHandler.ServeHTTP(w, r) + case DockerServiceHostStatsProcedure: + dockerServiceHostStatsHandler.ServeHTTP(w, r) case DockerServiceContainerLogsProcedure: dockerServiceContainerLogsHandler.ServeHTTP(w, r) + case DockerServiceContainerEventsProcedure: + dockerServiceContainerEventsHandler.ServeHTTP(w, r) + case DockerServiceContainerLogsStreamProcedure: + dockerServiceContainerLogsStreamHandler.ServeHTTP(w, r) case DockerServiceContainerInspectProcedure: dockerServiceContainerInspectHandler.ServeHTTP(w, r) case DockerServiceComposeUpProcedure: @@ -800,12 +1096,16 @@ func NewDockerServiceHandler(svc DockerServiceHandler, opts ...connect.HandlerOp dockerServiceComposeRestartHandler.ServeHTTP(w, r) case DockerServiceComposeUpdateProcedure: dockerServiceComposeUpdateHandler.ServeHTTP(w, r) + case DockerServiceComposeRedeployProcedure: + dockerServiceComposeRedeployHandler.ServeHTTP(w, r) case DockerServiceComposeListProcedure: dockerServiceComposeListHandler.ServeHTTP(w, r) case DockerServiceComposeValidateProcedure: dockerServiceComposeValidateHandler.ServeHTTP(w, r) case DockerServiceComposeFileStatusProcedure: dockerServiceComposeFileStatusHandler.ServeHTTP(w, r) + case DockerServiceDockerCommandProcedure: + dockerServiceDockerCommandHandler.ServeHTTP(w, r) case DockerServiceImageListProcedure: dockerServiceImageListHandler.ServeHTTP(w, r) case DockerServiceImageRemoveProcedure: @@ -820,6 +1120,8 @@ func NewDockerServiceHandler(svc DockerServiceHandler, opts ...connect.HandlerOp dockerServiceVolumeCreateHandler.ServeHTTP(w, r) case DockerServiceVolumeDeleteProcedure: dockerServiceVolumeDeleteHandler.ServeHTTP(w, r) + case DockerServiceVolumeInspectProcedure: + dockerServiceVolumeInspectHandler.ServeHTTP(w, r) case DockerServiceNetworkListProcedure: dockerServiceNetworkListHandler.ServeHTTP(w, r) case DockerServiceNetworkCreateProcedure: @@ -828,6 +1130,10 @@ func NewDockerServiceHandler(svc DockerServiceHandler, opts ...connect.HandlerOp dockerServiceNetworkDeleteHandler.ServeHTTP(w, r) case DockerServiceNetworkInspectProcedure: dockerServiceNetworkInspectHandler.ServeHTTP(w, r) + case DockerServiceNetworkConnectContainerProcedure: + dockerServiceNetworkConnectContainerHandler.ServeHTTP(w, r) + case DockerServiceNetworkDisconnectContainerProcedure: + dockerServiceNetworkDisconnectContainerHandler.ServeHTTP(w, r) default: http.NotFound(w, r) } @@ -853,8 +1159,16 @@ func (UnimplementedDockerServiceHandler) ContainerRestart(context.Context, *conn return nil, connect.NewError(connect.CodeUnimplemented, errors.New("docker.v1.DockerService.ContainerRestart is not implemented")) } -func (UnimplementedDockerServiceHandler) ContainerUpdate(context.Context, *connect.Request[v1.ContainerRequest]) (*connect.Response[v1.Empty], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("docker.v1.DockerService.ContainerUpdate is not implemented")) +func (UnimplementedDockerServiceHandler) ContainerPause(context.Context, *connect.Request[v1.ContainerRequest]) (*connect.Response[v1.LogsMessage], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("docker.v1.DockerService.ContainerPause is not implemented")) +} + +func (UnimplementedDockerServiceHandler) ContainerUnpause(context.Context, *connect.Request[v1.ContainerRequest]) (*connect.Response[v1.LogsMessage], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("docker.v1.DockerService.ContainerUnpause is not implemented")) +} + +func (UnimplementedDockerServiceHandler) ContainerUpdate(context.Context, *connect.Request[v1.ContainerRequest], *connect.ServerStream[v1.LogsMessage]) error { + return connect.NewError(connect.CodeUnimplemented, errors.New("docker.v1.DockerService.ContainerUpdate is not implemented")) } func (UnimplementedDockerServiceHandler) ContainerTop(context.Context, *connect.Request[v1.ContainerTopRequest]) (*connect.Response[v1.ContainerTopResponse], error) { @@ -869,10 +1183,26 @@ func (UnimplementedDockerServiceHandler) ContainerStats(context.Context, *connec return nil, connect.NewError(connect.CodeUnimplemented, errors.New("docker.v1.DockerService.ContainerStats is not implemented")) } +func (UnimplementedDockerServiceHandler) ContainerStatsStream(context.Context, *connect.Request[v1.StatsRequest], *connect.ServerStream[v1.ContainerStats]) error { + return connect.NewError(connect.CodeUnimplemented, errors.New("docker.v1.DockerService.ContainerStatsStream is not implemented")) +} + +func (UnimplementedDockerServiceHandler) HostStats(context.Context, *connect.Request[v1.Empty]) (*connect.Response[v1.HostStatsResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("docker.v1.DockerService.HostStats is not implemented")) +} + func (UnimplementedDockerServiceHandler) ContainerLogs(context.Context, *connect.Request[v1.ContainerLogsRequest], *connect.ServerStream[v1.LogsMessage]) error { return connect.NewError(connect.CodeUnimplemented, errors.New("docker.v1.DockerService.ContainerLogs is not implemented")) } +func (UnimplementedDockerServiceHandler) ContainerEvents(context.Context, *connect.Request[v1.EventsRequest], *connect.ServerStream[v1.ContainerEvent]) error { + return connect.NewError(connect.CodeUnimplemented, errors.New("docker.v1.DockerService.ContainerEvents is not implemented")) +} + +func (UnimplementedDockerServiceHandler) ContainerLogsStream(context.Context, *connect.Request[v1.LogsStreamRequest], *connect.ServerStream[v1.LogLine]) error { + return connect.NewError(connect.CodeUnimplemented, errors.New("docker.v1.DockerService.ContainerLogsStream is not implemented")) +} + func (UnimplementedDockerServiceHandler) ContainerInspect(context.Context, *connect.Request[v1.ContainerLogsRequest]) (*connect.Response[v1.ContainerInspectMessage], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("docker.v1.DockerService.ContainerInspect is not implemented")) } @@ -901,6 +1231,10 @@ func (UnimplementedDockerServiceHandler) ComposeUpdate(context.Context, *connect return connect.NewError(connect.CodeUnimplemented, errors.New("docker.v1.DockerService.ComposeUpdate is not implemented")) } +func (UnimplementedDockerServiceHandler) ComposeRedeploy(context.Context, *connect.Request[v1.ComposeRedeployRequest], *connect.ServerStream[v1.LogsMessage]) error { + return connect.NewError(connect.CodeUnimplemented, errors.New("docker.v1.DockerService.ComposeRedeploy is not implemented")) +} + func (UnimplementedDockerServiceHandler) ComposeList(context.Context, *connect.Request[v1.ComposeFile]) (*connect.Response[v1.ListResponse], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("docker.v1.DockerService.ComposeList is not implemented")) } @@ -913,6 +1247,10 @@ func (UnimplementedDockerServiceHandler) ComposeFileStatus(context.Context, *con return nil, connect.NewError(connect.CodeUnimplemented, errors.New("docker.v1.DockerService.ComposeFileStatus is not implemented")) } +func (UnimplementedDockerServiceHandler) DockerCommand(context.Context, *connect.Request[v1.DockerCommandRequest], *connect.ServerStream[v1.LogsMessage]) error { + return connect.NewError(connect.CodeUnimplemented, errors.New("docker.v1.DockerService.DockerCommand is not implemented")) +} + func (UnimplementedDockerServiceHandler) ImageList(context.Context, *connect.Request[v1.ListImagesRequest]) (*connect.Response[v1.ListImagesResponse], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("docker.v1.DockerService.ImageList is not implemented")) } @@ -941,6 +1279,10 @@ func (UnimplementedDockerServiceHandler) VolumeDelete(context.Context, *connect. return nil, connect.NewError(connect.CodeUnimplemented, errors.New("docker.v1.DockerService.VolumeDelete is not implemented")) } +func (UnimplementedDockerServiceHandler) VolumeInspect(context.Context, *connect.Request[v1.VolumeInspectRequest]) (*connect.Response[v1.VolumeInspectResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("docker.v1.DockerService.VolumeInspect is not implemented")) +} + func (UnimplementedDockerServiceHandler) NetworkList(context.Context, *connect.Request[v1.ListNetworksRequest]) (*connect.Response[v1.ListNetworksResponse], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("docker.v1.DockerService.NetworkList is not implemented")) } @@ -956,3 +1298,11 @@ func (UnimplementedDockerServiceHandler) NetworkDelete(context.Context, *connect func (UnimplementedDockerServiceHandler) NetworkInspect(context.Context, *connect.Request[v1.NetworkInspectRequest]) (*connect.Response[v1.NetworkInspectResponse], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("docker.v1.DockerService.NetworkInspect is not implemented")) } + +func (UnimplementedDockerServiceHandler) NetworkConnectContainer(context.Context, *connect.Request[v1.NetworkConnectContainerRequest]) (*connect.Response[v1.NetworkConnectContainerResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("docker.v1.DockerService.NetworkConnectContainer is not implemented")) +} + +func (UnimplementedDockerServiceHandler) NetworkDisconnectContainer(context.Context, *connect.Request[v1.NetworkDisconnectContainerRequest]) (*connect.Response[v1.NetworkDisconnectContainerResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("docker.v1.DockerService.NetworkDisconnectContainer is not implemented")) +} diff --git a/core/generated/dockyaml/v1/dockyaml.pb.go b/core/generated/dockyaml/v1/dockyaml.pb.go index 499e16be..bacb13c5 100644 --- a/core/generated/dockyaml/v1/dockyaml.pb.go +++ b/core/generated/dockyaml/v1/dockyaml.pb.go @@ -272,8 +272,15 @@ type DockmanYaml struct { NetworkPage *NetworkConfig `protobuf:"bytes,3,opt,name=networkPage,proto3" json:"networkPage,omitempty"` ImagePage *ImageConfig `protobuf:"bytes,4,opt,name=imagePage,proto3" json:"imagePage,omitempty"` ContainerPage *ContainerConfig `protobuf:"bytes,5,opt,name=containerPage,proto3" json:"containerPage,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + StatsPage *StatsConfig `protobuf:"bytes,10,opt,name=statsPage,proto3" json:"statsPage,omitempty"` + ComposePage *ComposeConfig `protobuf:"bytes,11,opt,name=composePage,proto3" json:"composePage,omitempty"` + EditorPage *EditorConfig `protobuf:"bytes,12,opt,name=editorPage,proto3" json:"editorPage,omitempty"` + MonitorPage *MonitorConfig `protobuf:"bytes,13,opt,name=monitorPage,proto3" json:"monitorPage,omitempty"` + // view opened when landing on a host: files (default), monitor, stats, + // containers, images, volumes, networks or cleaner + DefaultView string `protobuf:"bytes,14,opt,name=defaultView,proto3" json:"defaultView,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *DockmanYaml) Reset() { @@ -369,6 +376,178 @@ func (x *DockmanYaml) GetContainerPage() *ContainerConfig { return nil } +func (x *DockmanYaml) GetStatsPage() *StatsConfig { + if x != nil { + return x.StatsPage + } + return nil +} + +func (x *DockmanYaml) GetComposePage() *ComposeConfig { + if x != nil { + return x.ComposePage + } + return nil +} + +func (x *DockmanYaml) GetEditorPage() *EditorConfig { + if x != nil { + return x.EditorPage + } + return nil +} + +func (x *DockmanYaml) GetMonitorPage() *MonitorConfig { + if x != nil { + return x.MonitorPage + } + return nil +} + +func (x *DockmanYaml) GetDefaultView() string { + if x != nil { + return x.DefaultView + } + return "" +} + +type MonitorConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + // stack row density in the monitor view: "full" (default) shows CPU/RAM + // values with their charts, "compact" shows the values only + StackRows string `protobuf:"bytes,1,opt,name=stackRows,proto3" json:"stackRows,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MonitorConfig) Reset() { + *x = MonitorConfig{} + mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MonitorConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MonitorConfig) ProtoMessage() {} + +func (x *MonitorConfig) ProtoReflect() protoreflect.Message { + mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MonitorConfig.ProtoReflect.Descriptor instead. +func (*MonitorConfig) Descriptor() ([]byte, []int) { + return file_dockyaml_v1_dockyaml_proto_rawDescGZIP(), []int{7} +} + +func (x *MonitorConfig) GetStackRows() string { + if x != nil { + return x.StackRows + } + return "" +} + +type ComposeConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + // tab shown when opening a compose stack: editor (default), deploy or stats + DefaultTab string `protobuf:"bytes,1,opt,name=defaultTab,proto3" json:"defaultTab,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ComposeConfig) Reset() { + *x = ComposeConfig{} + mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ComposeConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ComposeConfig) ProtoMessage() {} + +func (x *ComposeConfig) ProtoReflect() protoreflect.Message { + mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ComposeConfig.ProtoReflect.Descriptor instead. +func (*ComposeConfig) Descriptor() ([]byte, []int) { + return file_dockyaml_v1_dockyaml_proto_rawDescGZIP(), []int{8} +} + +func (x *ComposeConfig) GetDefaultTab() string { + if x != nil { + return x.DefaultTab + } + return "" +} + +type EditorConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + // allow scrolling half a viewport past the last line (it stops at + // mid-view), for files taller than the viewport + ScrollPastEnd bool `protobuf:"varint,1,opt,name=scrollPastEnd,proto3" json:"scrollPastEnd,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EditorConfig) Reset() { + *x = EditorConfig{} + mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EditorConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EditorConfig) ProtoMessage() {} + +func (x *EditorConfig) ProtoReflect() protoreflect.Message { + mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EditorConfig.ProtoReflect.Descriptor instead. +func (*EditorConfig) Descriptor() ([]byte, []int) { + return file_dockyaml_v1_dockyaml_proto_rawDescGZIP(), []int{9} +} + +func (x *EditorConfig) GetScrollPastEnd() bool { + if x != nil { + return x.ScrollPastEnd + } + return false +} + type VolumesConfig struct { state protoimpl.MessageState `protogen:"open.v1"` Sort *Sort `protobuf:"bytes,1,opt,name=sort,proto3" json:"sort,omitempty"` @@ -378,7 +557,7 @@ type VolumesConfig struct { func (x *VolumesConfig) Reset() { *x = VolumesConfig{} - mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[7] + mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -390,7 +569,7 @@ func (x *VolumesConfig) String() string { func (*VolumesConfig) ProtoMessage() {} func (x *VolumesConfig) ProtoReflect() protoreflect.Message { - mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[7] + mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -403,7 +582,7 @@ func (x *VolumesConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use VolumesConfig.ProtoReflect.Descriptor instead. func (*VolumesConfig) Descriptor() ([]byte, []int) { - return file_dockyaml_v1_dockyaml_proto_rawDescGZIP(), []int{7} + return file_dockyaml_v1_dockyaml_proto_rawDescGZIP(), []int{10} } func (x *VolumesConfig) GetSort() *Sort { @@ -422,7 +601,7 @@ type NetworkConfig struct { func (x *NetworkConfig) Reset() { *x = NetworkConfig{} - mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[8] + mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -434,7 +613,7 @@ func (x *NetworkConfig) String() string { func (*NetworkConfig) ProtoMessage() {} func (x *NetworkConfig) ProtoReflect() protoreflect.Message { - mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[8] + mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -447,7 +626,7 @@ func (x *NetworkConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use NetworkConfig.ProtoReflect.Descriptor instead. func (*NetworkConfig) Descriptor() ([]byte, []int) { - return file_dockyaml_v1_dockyaml_proto_rawDescGZIP(), []int{8} + return file_dockyaml_v1_dockyaml_proto_rawDescGZIP(), []int{11} } func (x *NetworkConfig) GetSort() *Sort { @@ -466,7 +645,7 @@ type ImageConfig struct { func (x *ImageConfig) Reset() { *x = ImageConfig{} - mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[9] + mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -478,7 +657,7 @@ func (x *ImageConfig) String() string { func (*ImageConfig) ProtoMessage() {} func (x *ImageConfig) ProtoReflect() protoreflect.Message { - mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[9] + mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -491,7 +670,7 @@ func (x *ImageConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use ImageConfig.ProtoReflect.Descriptor instead. func (*ImageConfig) Descriptor() ([]byte, []int) { - return file_dockyaml_v1_dockyaml_proto_rawDescGZIP(), []int{9} + return file_dockyaml_v1_dockyaml_proto_rawDescGZIP(), []int{12} } func (x *ImageConfig) GetSort() *Sort { @@ -510,7 +689,7 @@ type ContainerConfig struct { func (x *ContainerConfig) Reset() { *x = ContainerConfig{} - mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[10] + mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -522,7 +701,7 @@ func (x *ContainerConfig) String() string { func (*ContainerConfig) ProtoMessage() {} func (x *ContainerConfig) ProtoReflect() protoreflect.Message { - mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[10] + mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -535,7 +714,7 @@ func (x *ContainerConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use ContainerConfig.ProtoReflect.Descriptor instead. func (*ContainerConfig) Descriptor() ([]byte, []int) { - return file_dockyaml_v1_dockyaml_proto_rawDescGZIP(), []int{10} + return file_dockyaml_v1_dockyaml_proto_rawDescGZIP(), []int{13} } func (x *ContainerConfig) GetSort() *Sort { @@ -545,6 +724,50 @@ func (x *ContainerConfig) GetSort() *Sort { return nil } +type StatsConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + Sort *Sort `protobuf:"bytes,1,opt,name=sort,proto3" json:"sort,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StatsConfig) Reset() { + *x = StatsConfig{} + mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StatsConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StatsConfig) ProtoMessage() {} + +func (x *StatsConfig) ProtoReflect() protoreflect.Message { + mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StatsConfig.ProtoReflect.Descriptor instead. +func (*StatsConfig) Descriptor() ([]byte, []int) { + return file_dockyaml_v1_dockyaml_proto_rawDescGZIP(), []int{14} +} + +func (x *StatsConfig) GetSort() *Sort { + if x != nil { + return x.Sort + } + return nil +} + type Sort struct { state protoimpl.MessageState `protogen:"open.v1"` SortOrder string `protobuf:"bytes,1,opt,name=sortOrder,proto3" json:"sortOrder,omitempty"` @@ -555,7 +778,7 @@ type Sort struct { func (x *Sort) Reset() { *x = Sort{} - mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[11] + mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -567,7 +790,7 @@ func (x *Sort) String() string { func (*Sort) ProtoMessage() {} func (x *Sort) ProtoReflect() protoreflect.Message { - mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[11] + mi := &file_dockyaml_v1_dockyaml_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -580,7 +803,7 @@ func (x *Sort) ProtoReflect() protoreflect.Message { // Deprecated: Use Sort.ProtoReflect.Descriptor instead. func (*Sort) Descriptor() ([]byte, []int) { - return file_dockyaml_v1_dockyaml_proto_rawDescGZIP(), []int{11} + return file_dockyaml_v1_dockyaml_proto_rawDescGZIP(), []int{15} } func (x *Sort) GetSortOrder() string { @@ -611,7 +834,7 @@ const file_dockyaml_v1_dockyaml_proto_rawDesc = "" + "\bcontents\x18\x01 \x01(\fR\bcontents\"\x10\n" + "\x0eGetYamlRequest\"?\n" + "\x0fGetYamlResponse\x12,\n" + - "\x04dock\x18\x01 \x01(\v2\x18.dockyaml.v1.DockmanYamlR\x04dock\"\xbe\x04\n" + + "\x04dock\x18\x01 \x01(\v2\x18.dockyaml.v1.DockmanYamlR\x04dock\"\xcf\x06\n" + "\vDockmanYaml\x12K\n" + "\vcustomTools\x18\t \x03(\v2).dockyaml.v1.DockmanYaml.CustomToolsEntryR\vcustomTools\x12,\n" + "\x11useComposeFolders\x18\x01 \x01(\bR\x11useComposeFolders\x12>\n" + @@ -621,10 +844,26 @@ const file_dockyaml_v1_dockyaml_proto_rawDesc = "" + "\vvolumesPage\x18\x02 \x01(\v2\x1a.dockyaml.v1.VolumesConfigR\vvolumesPage\x12<\n" + "\vnetworkPage\x18\x03 \x01(\v2\x1a.dockyaml.v1.NetworkConfigR\vnetworkPage\x126\n" + "\timagePage\x18\x04 \x01(\v2\x18.dockyaml.v1.ImageConfigR\timagePage\x12B\n" + - "\rcontainerPage\x18\x05 \x01(\v2\x1c.dockyaml.v1.ContainerConfigR\rcontainerPage\x1a>\n" + + "\rcontainerPage\x18\x05 \x01(\v2\x1c.dockyaml.v1.ContainerConfigR\rcontainerPage\x126\n" + + "\tstatsPage\x18\n" + + " \x01(\v2\x18.dockyaml.v1.StatsConfigR\tstatsPage\x12<\n" + + "\vcomposePage\x18\v \x01(\v2\x1a.dockyaml.v1.ComposeConfigR\vcomposePage\x129\n" + + "\n" + + "editorPage\x18\f \x01(\v2\x19.dockyaml.v1.EditorConfigR\n" + + "editorPage\x12<\n" + + "\vmonitorPage\x18\r \x01(\v2\x1a.dockyaml.v1.MonitorConfigR\vmonitorPage\x12 \n" + + "\vdefaultView\x18\x0e \x01(\tR\vdefaultView\x1a>\n" + "\x10CustomToolsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"6\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"-\n" + + "\rMonitorConfig\x12\x1c\n" + + "\tstackRows\x18\x01 \x01(\tR\tstackRows\"/\n" + + "\rComposeConfig\x12\x1e\n" + + "\n" + + "defaultTab\x18\x01 \x01(\tR\n" + + "defaultTab\"4\n" + + "\fEditorConfig\x12$\n" + + "\rscrollPastEnd\x18\x01 \x01(\bR\rscrollPastEnd\"6\n" + "\rVolumesConfig\x12%\n" + "\x04sort\x18\x01 \x01(\v2\x11.dockyaml.v1.SortR\x04sort\"6\n" + "\rNetworkConfig\x12%\n" + @@ -632,6 +871,8 @@ const file_dockyaml_v1_dockyaml_proto_rawDesc = "" + "\vImageConfig\x12%\n" + "\x04sort\x18\x01 \x01(\v2\x11.dockyaml.v1.SortR\x04sort\"8\n" + "\x0fContainerConfig\x12%\n" + + "\x04sort\x18\x01 \x01(\v2\x11.dockyaml.v1.SortR\x04sort\"4\n" + + "\vStatsConfig\x12%\n" + "\x04sort\x18\x01 \x01(\v2\x11.dockyaml.v1.SortR\x04sort\"B\n" + "\x04Sort\x12\x1c\n" + "\tsortOrder\x18\x01 \x01(\tR\tsortOrder\x12\x1c\n" + @@ -654,7 +895,7 @@ func file_dockyaml_v1_dockyaml_proto_rawDescGZIP() []byte { return file_dockyaml_v1_dockyaml_proto_rawDescData } -var file_dockyaml_v1_dockyaml_proto_msgTypes = make([]protoimpl.MessageInfo, 13) +var file_dockyaml_v1_dockyaml_proto_msgTypes = make([]protoimpl.MessageInfo, 17) var file_dockyaml_v1_dockyaml_proto_goTypes = []any{ (*SaveRequest)(nil), // 0: dockyaml.v1.SaveRequest (*SaveResponse)(nil), // 1: dockyaml.v1.SaveResponse @@ -663,35 +904,44 @@ var file_dockyaml_v1_dockyaml_proto_goTypes = []any{ (*GetYamlRequest)(nil), // 4: dockyaml.v1.GetYamlRequest (*GetYamlResponse)(nil), // 5: dockyaml.v1.GetYamlResponse (*DockmanYaml)(nil), // 6: dockyaml.v1.DockmanYaml - (*VolumesConfig)(nil), // 7: dockyaml.v1.VolumesConfig - (*NetworkConfig)(nil), // 8: dockyaml.v1.NetworkConfig - (*ImageConfig)(nil), // 9: dockyaml.v1.ImageConfig - (*ContainerConfig)(nil), // 10: dockyaml.v1.ContainerConfig - (*Sort)(nil), // 11: dockyaml.v1.Sort - nil, // 12: dockyaml.v1.DockmanYaml.CustomToolsEntry + (*MonitorConfig)(nil), // 7: dockyaml.v1.MonitorConfig + (*ComposeConfig)(nil), // 8: dockyaml.v1.ComposeConfig + (*EditorConfig)(nil), // 9: dockyaml.v1.EditorConfig + (*VolumesConfig)(nil), // 10: dockyaml.v1.VolumesConfig + (*NetworkConfig)(nil), // 11: dockyaml.v1.NetworkConfig + (*ImageConfig)(nil), // 12: dockyaml.v1.ImageConfig + (*ContainerConfig)(nil), // 13: dockyaml.v1.ContainerConfig + (*StatsConfig)(nil), // 14: dockyaml.v1.StatsConfig + (*Sort)(nil), // 15: dockyaml.v1.Sort + nil, // 16: dockyaml.v1.DockmanYaml.CustomToolsEntry } var file_dockyaml_v1_dockyaml_proto_depIdxs = []int32{ 6, // 0: dockyaml.v1.GetYamlResponse.dock:type_name -> dockyaml.v1.DockmanYaml - 12, // 1: dockyaml.v1.DockmanYaml.customTools:type_name -> dockyaml.v1.DockmanYaml.CustomToolsEntry - 7, // 2: dockyaml.v1.DockmanYaml.volumesPage:type_name -> dockyaml.v1.VolumesConfig - 8, // 3: dockyaml.v1.DockmanYaml.networkPage:type_name -> dockyaml.v1.NetworkConfig - 9, // 4: dockyaml.v1.DockmanYaml.imagePage:type_name -> dockyaml.v1.ImageConfig - 10, // 5: dockyaml.v1.DockmanYaml.containerPage:type_name -> dockyaml.v1.ContainerConfig - 11, // 6: dockyaml.v1.VolumesConfig.sort:type_name -> dockyaml.v1.Sort - 11, // 7: dockyaml.v1.NetworkConfig.sort:type_name -> dockyaml.v1.Sort - 11, // 8: dockyaml.v1.ImageConfig.sort:type_name -> dockyaml.v1.Sort - 11, // 9: dockyaml.v1.ContainerConfig.sort:type_name -> dockyaml.v1.Sort - 2, // 10: dockyaml.v1.DockyamlService.Get:input_type -> dockyaml.v1.GetRequest - 0, // 11: dockyaml.v1.DockyamlService.Save:input_type -> dockyaml.v1.SaveRequest - 4, // 12: dockyaml.v1.DockyamlService.GetYaml:input_type -> dockyaml.v1.GetYamlRequest - 3, // 13: dockyaml.v1.DockyamlService.Get:output_type -> dockyaml.v1.GetResponse - 1, // 14: dockyaml.v1.DockyamlService.Save:output_type -> dockyaml.v1.SaveResponse - 5, // 15: dockyaml.v1.DockyamlService.GetYaml:output_type -> dockyaml.v1.GetYamlResponse - 13, // [13:16] is the sub-list for method output_type - 10, // [10:13] is the sub-list for method input_type - 10, // [10:10] is the sub-list for extension type_name - 10, // [10:10] is the sub-list for extension extendee - 0, // [0:10] is the sub-list for field type_name + 16, // 1: dockyaml.v1.DockmanYaml.customTools:type_name -> dockyaml.v1.DockmanYaml.CustomToolsEntry + 10, // 2: dockyaml.v1.DockmanYaml.volumesPage:type_name -> dockyaml.v1.VolumesConfig + 11, // 3: dockyaml.v1.DockmanYaml.networkPage:type_name -> dockyaml.v1.NetworkConfig + 12, // 4: dockyaml.v1.DockmanYaml.imagePage:type_name -> dockyaml.v1.ImageConfig + 13, // 5: dockyaml.v1.DockmanYaml.containerPage:type_name -> dockyaml.v1.ContainerConfig + 14, // 6: dockyaml.v1.DockmanYaml.statsPage:type_name -> dockyaml.v1.StatsConfig + 8, // 7: dockyaml.v1.DockmanYaml.composePage:type_name -> dockyaml.v1.ComposeConfig + 9, // 8: dockyaml.v1.DockmanYaml.editorPage:type_name -> dockyaml.v1.EditorConfig + 7, // 9: dockyaml.v1.DockmanYaml.monitorPage:type_name -> dockyaml.v1.MonitorConfig + 15, // 10: dockyaml.v1.VolumesConfig.sort:type_name -> dockyaml.v1.Sort + 15, // 11: dockyaml.v1.NetworkConfig.sort:type_name -> dockyaml.v1.Sort + 15, // 12: dockyaml.v1.ImageConfig.sort:type_name -> dockyaml.v1.Sort + 15, // 13: dockyaml.v1.ContainerConfig.sort:type_name -> dockyaml.v1.Sort + 15, // 14: dockyaml.v1.StatsConfig.sort:type_name -> dockyaml.v1.Sort + 2, // 15: dockyaml.v1.DockyamlService.Get:input_type -> dockyaml.v1.GetRequest + 0, // 16: dockyaml.v1.DockyamlService.Save:input_type -> dockyaml.v1.SaveRequest + 4, // 17: dockyaml.v1.DockyamlService.GetYaml:input_type -> dockyaml.v1.GetYamlRequest + 3, // 18: dockyaml.v1.DockyamlService.Get:output_type -> dockyaml.v1.GetResponse + 1, // 19: dockyaml.v1.DockyamlService.Save:output_type -> dockyaml.v1.SaveResponse + 5, // 20: dockyaml.v1.DockyamlService.GetYaml:output_type -> dockyaml.v1.GetYamlResponse + 18, // [18:21] is the sub-list for method output_type + 15, // [15:18] is the sub-list for method input_type + 15, // [15:15] is the sub-list for extension type_name + 15, // [15:15] is the sub-list for extension extendee + 0, // [0:15] is the sub-list for field type_name } func init() { file_dockyaml_v1_dockyaml_proto_init() } @@ -705,7 +955,7 @@ func file_dockyaml_v1_dockyaml_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_dockyaml_v1_dockyaml_proto_rawDesc), len(file_dockyaml_v1_dockyaml_proto_rawDesc)), NumEnums: 0, - NumMessages: 13, + NumMessages: 17, NumExtensions: 0, NumServices: 1, }, diff --git a/core/generated/files/v1/files.pb.go b/core/generated/files/v1/files.pb.go index 687b82df..65738c22 100644 --- a/core/generated/files/v1/files.pb.go +++ b/core/generated/files/v1/files.pb.go @@ -521,8 +521,10 @@ type FsEntry struct { // flag for lazy loading IsFetched bool `protobuf:"varint,5,opt,name=isFetched,proto3" json:"isFetched,omitempty"` IsComposeFolder string `protobuf:"bytes,6,opt,name=isComposeFolder,proto3" json:"isComposeFolder,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // set when the entry's name is pinned in dockman.yml (pinnedFiles) + Pinned bool `protobuf:"varint,7,opt,name=pinned,proto3" json:"pinned,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *FsEntry) Reset() { @@ -590,6 +592,13 @@ func (x *FsEntry) GetIsComposeFolder() string { return "" } +func (x *FsEntry) GetPinned() bool { + if x != nil { + return x.Pinned + } + return false +} + type RenameFile struct { state protoimpl.MessageState `protogen:"open.v1"` OldFilePath string `protobuf:"bytes,1,opt,name=oldFilePath,proto3" json:"oldFilePath,omitempty"` @@ -760,13 +769,14 @@ const file_files_v1_files_proto_rawDesc = "" + "\rFormatRequest\x12\x1a\n" + "\bfilename\x18\x01 \x01(\tR\bfilename\",\n" + "\x0eFormatResponse\x12\x1a\n" + - "\bcontents\x18\x01 \x01(\tR\bcontents\"\xb2\x01\n" + + "\bcontents\x18\x01 \x01(\tR\bcontents\"\xca\x01\n" + "\aFsEntry\x12\x1a\n" + "\bfilename\x18\x02 \x01(\tR\bfilename\x12\x14\n" + "\x05isDir\x18\x03 \x01(\bR\x05isDir\x12-\n" + "\bsubFiles\x18\x04 \x03(\v2\x11.files.v1.FsEntryR\bsubFiles\x12\x1c\n" + "\tisFetched\x18\x05 \x01(\bR\tisFetched\x12(\n" + - "\x0fisComposeFolder\x18\x06 \x01(\tR\x0fisComposeFolder\"P\n" + + "\x0fisComposeFolder\x18\x06 \x01(\tR\x0fisComposeFolder\x12\x16\n" + + "\x06pinned\x18\a \x01(\bR\x06pinned\"P\n" + "\n" + "RenameFile\x12 \n" + "\voldFilePath\x18\x01 \x01(\tR\voldFilePath\x12 \n" + diff --git a/core/go.mod b/core/go.mod index 020d9d7c..8714658c 100644 --- a/core/go.mod +++ b/core/go.mod @@ -3,45 +3,46 @@ module github.com/RA341/dockman go 1.26 require ( - ariga.io/atlas-provider-gorm v0.6.0 - connectrpc.com/connect v1.19.1 + ariga.io/atlas-provider-gorm v0.6.1 + connectrpc.com/connect v1.20.0 connectrpc.com/cors v0.1.0 dario.cat/mergo v1.0.2 - fyne.io/systray v1.12.0 - github.com/coreos/go-oidc/v3 v3.17.0 - github.com/docker/compose/v5 v5.1.0 + fyne.io/systray v1.12.2 + github.com/coreos/go-oidc/v3 v3.20.0 + github.com/creack/pty v1.1.24 + github.com/docker/compose/v5 v5.3.1 github.com/dustin/go-humanize v1.0.1 - github.com/fatih/color v1.18.0 + github.com/fatih/color v1.19.0 github.com/gabriel-vasile/mimetype v1.4.13 github.com/gliderlabs/ssh v0.3.8 - github.com/go-co-op/gocron/v2 v2.19.1 - github.com/go-git/go-git/v5 v5.17.0 + github.com/go-co-op/gocron/v2 v2.22.0 + github.com/go-git/go-git/v5 v5.19.1 github.com/goccy/go-yaml v1.19.2 github.com/google/uuid v1.6.0 github.com/google/yamlfmt v0.21.0 github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 github.com/joho/godotenv v1.5.1 - github.com/moby/moby/api v1.54.0 - github.com/moby/moby/client v0.3.0 + github.com/moby/moby/api v1.55.0 + github.com/moby/moby/client v0.5.0 github.com/ncruces/zenity v0.10.14 github.com/nikoksr/notify v1.5.0 - github.com/pkg/sftp v1.13.10 - github.com/pressly/goose/v3 v3.27.0 + github.com/pkg/sftp v1.13.11 + github.com/pressly/goose/v3 v3.27.2 github.com/rs/cors v1.11.1 - github.com/rs/zerolog v1.34.0 - github.com/sahilm/fuzzy v0.1.1 + github.com/rs/zerolog v1.35.1 + github.com/sahilm/fuzzy v0.1.3 github.com/stretchr/testify v1.11.1 github.com/wagoodman/dive v0.13.1 go.lsp.dev/jsonrpc2 v0.10.0 go.lsp.dev/protocol v0.12.0 - go.uber.org/zap v1.27.1 - golang.org/x/crypto v0.49.0 - golang.org/x/net v0.52.0 + go.uber.org/zap v1.28.0 + golang.org/x/crypto v0.54.0 + golang.org/x/net v0.57.0 golang.org/x/oauth2 v0.36.0 - golang.org/x/sync v0.20.0 + golang.org/x/sync v0.22.0 google.golang.org/protobuf v1.36.11 gorm.io/driver/sqlite v1.6.0 - gorm.io/gorm v1.31.1 + gorm.io/gorm v1.31.2 ) require ( @@ -57,41 +58,33 @@ require ( cloud.google.com/go/spanner v1.88.0 // indirect filippo.io/edwards25519 v1.2.0 // indirect github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.6.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProtonMail/go-crypto v1.3.0 // indirect - github.com/agext/levenshtein v1.2.3 // indirect github.com/akavel/rsrc v0.10.2 // indirect - github.com/alecthomas/chroma v0.10.0 // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect - github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect - github.com/aquasecurity/iamgo v0.0.10 // indirect - github.com/aquasecurity/jfather v0.0.8 // indirect - github.com/aquasecurity/trivy-checks v1.12.2-0.20251219190323-79d27547baf5 // indirect - github.com/aquasecurity/trivy-db v0.0.0-20260224070823-8ee75f8f4fff // indirect github.com/awesome-gocui/gocui v1.1.0 // indirect github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/clipperhouse/uax29/v2 v2.2.0 // indirect github.com/cloudflare/circl v1.6.3 // indirect github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect - github.com/compose-spec/compose-go/v2 v2.10.1 // indirect + github.com/compose-spec/compose-go/v2 v2.13.0 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/containerd/platforms v1.0.0-rc.2 // indirect + github.com/containerd/platforms v1.0.0-rc.4 // indirect github.com/cyphar/filepath-securejoin v0.6.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dchest/jsmin v1.0.0 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/dlclark/regexp2 v1.11.5 // indirect - github.com/docker/cli v29.2.1+incompatible // indirect + github.com/docker/cli v29.6.1+incompatible // indirect github.com/docker/docker v28.5.2+incompatible // indirect - github.com/docker/docker-credential-helpers v0.9.5 // indirect - github.com/docker/go-connections v0.6.0 // indirect + github.com/docker/docker-credential-helpers v0.9.8 // indirect + github.com/docker/go-connections v0.7.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect - github.com/envoyproxy/go-control-plane v0.14.0 // indirect github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -99,11 +92,11 @@ require ( github.com/gdamore/encoding v1.0.1 // indirect github.com/gdamore/tcell/v2 v2.13.8 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.8.0 // indirect - github.com/go-jose/go-jose/v4 v4.1.3 // indirect + github.com/go-git/go-billy/v5 v5.9.0 // indirect + github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-sql-driver/mysql v1.9.3 // indirect + github.com/go-sql-driver/mysql v1.10.0 // indirect github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect @@ -111,21 +104,16 @@ require ( github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/go-containerregistry v0.20.7 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect github.com/googleapis/gax-go/v2 v2.17.0 // indirect github.com/googleapis/go-gorm-spanner v1.8.6 // indirect github.com/googleapis/go-sql-spanner v1.17.0 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-version v1.8.0 // indirect + github.com/hashicorp/go-version v1.9.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect - github.com/hashicorp/hcl/v2 v2.24.0 // indirect - github.com/iancoleman/strcase v0.3.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.8.0 // indirect + github.com/jackc/pgx/v5 v5.10.0 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jinzhu/inflection v1.0.0 // indirect @@ -133,30 +121,27 @@ require ( github.com/jonboulle/clockwork v0.5.0 // indirect github.com/josephspurrier/goversioninfo v1.4.1 // indirect github.com/kevinburke/ssh_config v1.4.0 // indirect - github.com/klauspost/compress v1.18.4 // indirect + github.com/klauspost/compress v1.18.6 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/kr/fs v0.1.0 // indirect github.com/logrusorgru/aurora/v4 v4.0.0 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect - github.com/lyft/protoc-gen-star/v2 v2.0.4 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/mattn/go-shellwords v1.0.12 // indirect + github.com/mattn/go-isatty v0.0.21 // indirect + github.com/mattn/go-runewidth v0.0.23 // indirect + github.com/mattn/go-shellwords v1.0.13 // indirect github.com/mattn/go-sqlite3 v1.14.33 // indirect github.com/mfridman/interpolate v0.0.2 // indirect - github.com/microsoft/go-mssqldb v1.9.6 // indirect - github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/microsoft/go-mssqldb v1.10.0 // indirect github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/sys/atomicwriter v0.1.0 // indirect - github.com/moby/sys/sequential v0.6.0 // indirect + github.com/moby/sys/sequential v0.7.0 // indirect github.com/onsi/gomega v1.38.3 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect - github.com/package-url/packageurl-go v0.1.3 // indirect github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee // indirect - github.com/pjbgf/sha1cd v0.5.0 // indirect + github.com/pjbgf/sha1cd v0.6.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -164,52 +149,45 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 // indirect - github.com/samber/lo v1.52.0 // indirect github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect github.com/segmentio/asm v1.2.1 // indirect github.com/segmentio/encoding v0.5.3 // indirect github.com/sergi/go-diff v1.4.0 // indirect github.com/sethvargo/go-retry v0.3.0 // indirect + github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.4 // indirect github.com/skeema/knownhosts v1.3.2 // indirect - github.com/spf13/afero v1.15.0 // indirect github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect github.com/technoweenie/multipartstreamer v1.0.1 // indirect - github.com/twitchtv/twirp v8.1.3+incompatible // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect - github.com/zclconf/go-cty v1.17.0 // indirect go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 // indirect go.lsp.dev/uri v0.3.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect - go.opentelemetry.io/otel v1.40.0 // indirect - go.opentelemetry.io/otel/metric v1.40.0 // indirect - go.opentelemetry.io/otel/sdk v1.40.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect - go.opentelemetry.io/otel/trace v1.40.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.42.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.69.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect + go.opentelemetry.io/otel v1.44.0 // indirect + go.opentelemetry.io/otel/metric v1.44.0 // indirect + go.opentelemetry.io/otel/sdk v1.44.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect + go.opentelemetry.io/otel/trace v1.44.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.yaml.in/yaml/v4 v4.0.0-rc.4 // indirect + go.yaml.in/yaml/v4 v4.0.0-rc.6 // indirect golang.org/x/image v0.20.0 // indirect - golang.org/x/mod v0.33.0 // indirect - golang.org/x/sys v0.42.0 // indirect - golang.org/x/term v0.41.0 // indirect - golang.org/x/text v0.35.0 // indirect - golang.org/x/time v0.14.0 // indirect - golang.org/x/tools v0.42.0 // indirect - golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/term v0.45.0 // indirect + golang.org/x/text v0.40.0 // indirect + golang.org/x/time v0.15.0 // indirect google.golang.org/api v0.265.0 // indirect google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect - google.golang.org/grpc v1.79.1 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/grpc v1.81.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/driver/mysql v1.5.7 // indirect gorm.io/driver/postgres v1.5.11 // indirect gorm.io/driver/sqlserver v1.5.4 // indirect - k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d // indirect ) diff --git a/core/go.sum b/core/go.sum index a9b4cd9b..60d3d1bb 100644 --- a/core/go.sum +++ b/core/go.sum @@ -1,661 +1,55 @@ -ariga.io/atlas v0.36.2-0.20250806044935-5bb51a0a956e h1:7upp27oOT/fmM5Dz3z9k8cmYwKJ2NAzuTqfT/rEP+50= -ariga.io/atlas v0.36.2-0.20250806044935-5bb51a0a956e/go.mod h1:Ex5l1xHsnWQUc3wYnrJ9gD7RUEzG76P7ZRQp8wNr0wc= ariga.io/atlas v1.1.0 h1:Dk9Xemh6pr5RogNCsFylf/9ozhSPWDqzHb8EkR2rA78= ariga.io/atlas v1.1.0/go.mod h1:esBbk3F+pi/mM2PvbCymDm+kWhaOk4PaaiegQdNELk8= -ariga.io/atlas-provider-gorm v0.6.0 h1:nJx1jLKr8pKeIxuYX3NmBuchWR9VldopWyElnPakRDw= -ariga.io/atlas-provider-gorm v0.6.0/go.mod h1:iod/+0ODkmcNBJsmNrs76btfETyqC+zCyy7sh7aXaig= +ariga.io/atlas-provider-gorm v0.6.1 h1:Do57gN6CEdXiH0+L44OB0GH/3bBUUmOpvONK56xYTE4= +ariga.io/atlas-provider-gorm v0.6.1/go.mod h1:iod/+0ODkmcNBJsmNrs76btfETyqC+zCyy7sh7aXaig= cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= -cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= -cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= -cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= -cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= -cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= -cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= -cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= -cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= -cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= -cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= -cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= -cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= -cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM= -cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ= -cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= -cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= -cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= -cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ= -cloud.google.com/go/aiplatform v1.36.1/go.mod h1:WTm12vJRPARNvJ+v6P52RDHCNe4AhvjcIZ/9/RRHy/k= -cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw= -cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= -cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= -cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M= -cloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE= -cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE= -cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= -cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= -cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= -cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc= -cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= -cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= -cloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY= -cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM= -cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc= -cloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU= -cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI= -cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8= -cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno= -cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= -cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84= -cloud.google.com/go/appengine v1.7.0/go.mod h1:eZqpbHFCqRGa2aCdope7eC0SWLV1j0neb/QnMJVWx6A= -cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E= -cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= -cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= -cloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY= -cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= -cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= -cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= -cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= -cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= -cloud.google.com/go/artifactregistry v1.11.1/go.mod h1:lLYghw+Itq9SONbCa1YWBoWs1nOucMH0pwXN1rOBZFI= -cloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ= -cloud.google.com/go/artifactregistry v1.12.0/go.mod h1:o6P3MIvtzTOnmvGagO9v/rOjjA0HmhJ+/6KAXrmYDCI= -cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08= -cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= -cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= -cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= -cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ= -cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= -cloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo= -cloud.google.com/go/asset v1.12.0/go.mod h1:h9/sFOa4eDIyKmH6QMpm4eUK3pDojWnUhTgJlk762Hg= -cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw= -cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= -cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= -cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= -cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= -cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= -cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= -cloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0= -cloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo= cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM= cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= -cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= -cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= -cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= -cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= -cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= -cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc= -cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= -cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= -cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE= -cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= -cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= -cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= -cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= -cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= -cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= -cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw= -cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= -cloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/ZurWFIxmF9I/E= -cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac= -cloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9yBh7Oy7/4Q= -cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= -cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= -cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= -cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= -cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= -cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss= -cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc= -cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= -cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= -cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= -cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= -cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= -cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= -cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= -cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= -cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= -cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= -cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE= -cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU= -cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= -cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= -cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M= -cloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg= -cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s= -cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= -cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= -cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= -cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= -cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= -cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= -cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= -cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y= -cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs= -cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= -cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= -cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= -cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= -cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= -cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= -cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= -cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= -cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= -cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= -cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= -cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= -cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= -cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= -cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= -cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= -cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= -cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= -cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= -cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= -cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= -cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= -cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= -cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= -cloud.google.com/go/container v1.14.0/go.mod h1:3AoJMPhHfLDxLvrlVWaK57IXzaPnLaZq63WX59aQBfM= -cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA= -cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= -cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= -cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= -cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s= -cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= -cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= -cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= -cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE= -cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= -cloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M= -cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0= -cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= -cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= -cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= -cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= -cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= -cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= -cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= -cloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA= -cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE= -cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= -cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= -cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= -cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= -cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= -cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= -cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= -cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= -cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ= -cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs= -cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= -cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= -cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= -cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= -cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= -cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= -cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= -cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= -cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= -cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= -cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4= -cloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs= -cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww= -cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= -cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= -cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI= -cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ= -cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= -cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= -cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= -cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek= -cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= -cloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM= -cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4= -cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= -cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= -cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= -cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= -cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= -cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= -cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= -cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= -cloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM= -cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs= -cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= -cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= -cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= -cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= -cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= -cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc= -cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY= -cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= -cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= -cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= -cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= -cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= -cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= -cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw= -cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY= -cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= -cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= -cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs= -cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= -cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= -cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= -cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= -cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= -cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08= -cloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw= -cloud.google.com/go/functions v1.12.0/go.mod h1:AXWGrF3e2C/5ehvwYo/GH6O5s09tOPksiKhz+hH8WkA= -cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c= -cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= -cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= -cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= -cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM= -cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= -cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60= -cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= -cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= -cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= -cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= -cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= -cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= -cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= -cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E= -cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw= -cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= -cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= -cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= -cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= -cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= -cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= -cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= -cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= -cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= -cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= -cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= -cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= -cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= -cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY= -cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= -cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= -cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= -cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= -cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= -cloud.google.com/go/iap v1.7.0/go.mod h1:beqQx56T9O1G1yNPph+spKpNibDlYIiIixiqsQXxLIo= -cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74= -cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= -cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= -cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= -cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= -cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= -cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o= -cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE= -cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= -cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= -cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= -cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg= -cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= -cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24= -cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= -cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= -cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= -cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= -cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= -cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= -cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= -cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= -cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= -cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= -cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= -cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= -cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= -cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= -cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E= -cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY= cloud.google.com/go/longrunning v0.8.0 h1:LiKK77J3bx5gDLi4SMViHixjD2ohlkwBi+mKA7EhfW8= cloud.google.com/go/longrunning v0.8.0/go.mod h1:UmErU2Onzi+fKDg2gR7dusz11Pe26aknR4kHmJJqIfk= -cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= -cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= -cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= -cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= -cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw= -cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= -cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= -cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= -cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= -cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= -cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= -cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA= -cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= -cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= -cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= -cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= -cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8= -cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= -cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= -cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= -cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= -cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= -cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE= cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI= -cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= -cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= -cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= -cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= -cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E= -cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= -cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= -cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= -cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= -cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= -cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= -cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k= -cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= -cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= -cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= -cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= -cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= -cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE= -cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= -cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= -cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= -cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= -cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA= -cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= -cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= -cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE= -cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= -cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= -cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= -cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= -cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= -cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw= -cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= -cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= -cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= -cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70= -cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= -cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= -cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= -cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= -cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= -cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= -cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= -cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw= -cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= -cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= -cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= -cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg= -cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= -cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= -cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8= -cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= -cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= -cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= -cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= -cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= -cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= -cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= -cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= -cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE= -cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U= -cloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA= -cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c= -cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= -cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= -cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= -cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= -cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= -cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs= -cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= -cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= -cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= -cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= -cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= -cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= -cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= -cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= -cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= -cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots= -cloud.google.com/go/resourcemanager v1.6.0/go.mod h1:YcpXGRs8fDzcUl1Xw8uOVmI8JEadvhRIkoXXUNVYcVo= -cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= -cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= -cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= -cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= -cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= -cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= -cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc= -cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= -cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= -cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= -cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= -cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM= -cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= -cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= -cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= -cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= -cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= -cloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc= -cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= -cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= -cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= -cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= -cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= -cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= -cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= -cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= -cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q= -cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= -cloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8= -cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= -cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= -cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= -cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= -cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= -cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0= -cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= -cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= -cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= -cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA= -cloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc= -cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk= -cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= -cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= -cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4= -cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= -cloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY= -cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s= -cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= -cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= -cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc= -cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4= -cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E= -cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= -cloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec= -cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA= -cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4= -cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= -cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= -cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= -cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= -cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= -cloud.google.com/go/spanner v1.86.1 h1:lSeVPwUotuKTpf8K6BPitzneQfGu73QcDFIca2lshG8= -cloud.google.com/go/spanner v1.86.1/go.mod h1:bbwCXbM+zljwSPLZ44wZOdzcdmy89hbUGmM/r9sD0ws= -cloud.google.com/go/spanner v1.87.0 h1:M9RGcj/4gJk6yY1lRLOz1Ze+5ufoWhbIiurzXLOOfcw= -cloud.google.com/go/spanner v1.87.0/go.mod h1:tcj735Y2aqphB6/l+X5MmwG4NnV+X1NJIbFSZGaHYXw= cloud.google.com/go/spanner v1.88.0 h1:HS+5TuEYZOVOXj9K+0EtrbTw7bKBLrMe3vgGsbnehmU= cloud.google.com/go/spanner v1.88.0/go.mod h1:MzulBwuuYwQUVdkZXBBFapmXee3N+sQrj2T/yup6uEE= -cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= -cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= -cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= -cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= -cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0= -cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= -cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= -cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= -cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= -cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= -cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= -cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= -cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= -cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= -cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= -cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= -cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= -cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= -cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= -cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8= -cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= -cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= -cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ= -cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= -cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= -cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= -cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= -cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= -cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= -cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= -cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= -cloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0= -cloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= -cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= -cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= -cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= -cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg= -cloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk= -cloud.google.com/go/video v1.14.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= -cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= -cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= -cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= -cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= -cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= -cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= -cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= -cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= -cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= -cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY= -cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= -cloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY= -cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= -cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= -cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= -cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc= -cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= -cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= -cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8= -cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= -cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= -cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= -cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= -cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= -cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= -cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc= -cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= -cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= -cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo= -cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= -cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= -cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= -cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= -cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= -cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= -cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= -connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14= -connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w= +connectrpc.com/connect v1.20.0 h1:6TNDAB+WeNd2uolWNlYczB5E0KNNaVMNUEx8JEUsPmQ= +connectrpc.com/connect v1.20.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4= connectrpc.com/cors v0.1.0 h1:f3gTXJyDZPrDIZCQ567jxfD9PAIpopHiRDnJRt3QuOQ= connectrpc.com/cors v0.1.0/go.mod h1:v8SJZCPfHtGH1zsm+Ttajpozd4cYIUryl4dFB6QEpfg= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= -fyne.io/systray v1.12.0 h1:CA1Kk0e2zwFlxtc02L3QFSiIbxJ/P0n582YrZHT7aTM= -fyne.io/systray v1.12.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs= -gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= -git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= +fyne.io/systray v1.12.2 h1:Y8DZxgLHsVQt6rY9Zrkkg+j67S7vv/1F2viOWKPpVeA= +fyne.io/systray v1.12.2/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1 h1:jHb/wfvRikGdxMXYV3QG/SzUOPYN9KEUUuC0Yd0/vC0= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1/go.mod h1:pzBXCYn05zvYIrwLgtK8Ap8QcjRg+0i76tMQdWN6wOk= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1/go.mod h1:uE9zaUfEQT/nbQjVi2IblCG9iaLtZsuYZ8ne+PuQ02M= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 h1:Wgf5rZba3YZqeTNJPtvqZoBu1sBN/L4sry+u2U3Y75w= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1/go.mod h1:xxCBG/f/4Vbmh2XQJBsOmNdxWUY5j/s27jujKPbQf14= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 h1:E4MgwLBGeVB5f2MdcIVD3ELVAWpr+WD6MUe1i+tM/PA= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0/go.mod h1:Y2b/1clN4zsAoUd/pgNAQHjLDnTis/6ROkUfyob6psM= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= @@ -663,165 +57,80 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83 github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.3 h1:2afWGsMzkIcN8Qm4mgPJKZWyroE5QBszMiDMYEBrnfw= -github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.3/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0= github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.6.0 h1:BzsL0qE7LvtTEtXG7Dt5NS1EP0CQwI21HZfj9aGghhw= github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.6.0/go.mod h1:I7kE2kM3qCr9QPT4cU4cCFYkEpVyVr16YOGUHzy+nR0= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= -github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 h1:DHa2U07rk8syqvCge0QIGMCE1WxGj9njT44GH7zNJLQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= -github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= -github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= -github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= -github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= -github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= -github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= github.com/akavel/rsrc v0.10.2 h1:Zxm8V5eI1hW4gGaYsJQUhxpjkENuG91ki8B4zCrvEsw= github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= -github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= -github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= -github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= -github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= -github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= -github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= -github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= -github.com/aquasecurity/iamgo v0.0.10 h1:t/HG/MI1eSephztDc+Rzh/YfgEa+NqgYRSfr6pHdSCQ= -github.com/aquasecurity/iamgo v0.0.10/go.mod h1:GI9IQJL2a+C+V2+i3vcwnNKuIJXZ+HAfqxZytwy+cPk= -github.com/aquasecurity/jfather v0.0.8 h1:tUjPoLGdlkJU0qE7dSzd1MHk2nQFNPR0ZfF+6shaExE= -github.com/aquasecurity/jfather v0.0.8/go.mod h1:Ag+L/KuR/f8vn8okUi8Wc1d7u8yOpi2QTaGX10h71oY= -github.com/aquasecurity/trivy v0.69.1 h1:AHkrHvEwN6irVfGRB1ZodtHTBV4YitWx2A42itwx91o= -github.com/aquasecurity/trivy v0.69.1/go.mod h1:F7SFCaWIQ+7eYxBeHzll6CAH+q7GbyhC4FLIQiwdzLU= -github.com/aquasecurity/trivy-checks v1.12.2-0.20251219190323-79d27547baf5 h1:8HnXyjgCiJwVX1mTKeqdyizd7ZBmXMPL+BMQ5UZd0Nk= -github.com/aquasecurity/trivy-checks v1.12.2-0.20251219190323-79d27547baf5/go.mod h1:hBSA3ziBFwGENK6/PYNIKm6N24SFg0wsv1VXeqPG/3M= -github.com/aquasecurity/trivy-db v0.0.0-20251222105351-a833f47f8f0d h1:mwCxwhDRnW5UkSQdZfekTCjaLyWp1rqfIa6KKRdMDAo= -github.com/aquasecurity/trivy-db v0.0.0-20251222105351-a833f47f8f0d/go.mod h1:B0cbg/BEHbJg2RcS7PLdlbGCzz2TkChcZAiI4oSs0VI= -github.com/aquasecurity/trivy-db v0.0.0-20260224070823-8ee75f8f4fff h1:wRp4bIP2ECZQuK/p41bd6hcOd6hSCZLhcQ+c51SIoic= -github.com/aquasecurity/trivy-db v0.0.0-20260224070823-8ee75f8f4fff/go.mod h1:EA98VprDBxnVAXJlbPGpiApdiHz6UQAO7flz1XrdU5U= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/awesome-gocui/gocui v1.1.0 h1:db2j7yFEoHZjpQFeE2xqiatS8bm1lO3THeLwE6MzOII= github.com/awesome-gocui/gocui v1.1.0/go.mod h1:M2BXkrp7PR97CKnPRT7Rk0+rtswChPtksw/vRAESGpg= -github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE= -github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= -github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/briandowns/spinner v1.23.0 h1:alDF2guRWqa/FOZZYWjlMIx2L6H0wyewPxo/CH4Pt2A= -github.com/briandowns/spinner v1.23.0/go.mod h1:rPG4gmXeN3wQV/TsAY4w8lPdIM6RX3yqeBQJSrbXjuE= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cheggaaa/pb/v3 v3.1.7 h1:2FsIW307kt7A/rz/ZI2lvPO+v3wKazzE4K/0LtTWsOI= -github.com/cheggaaa/pb/v3 v3.1.7/go.mod h1:/Ji89zfVPeC/u5j8ukD0MBPHt2bzTYp74lQ7KlgFWTQ= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.6.2 h1:hL7VBpHHKzrV5WTfHCaBsgx/HGbBYlgrwvNXEVDYYsQ= -github.com/cloudflare/circl v1.6.2/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= +github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY= +github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w= -github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= -github.com/compose-spec/compose-go/v2 v2.10.1 h1:mFbXobojGRFIVi1UknrvaDAZ+PkJfyjqkA1yseh+vAU= -github.com/compose-spec/compose-go/v2 v2.10.1/go.mod h1:Ohac1SzhO/4fXXrzWIztIVB6ckmKBv1Nt5Z5mGVESUg= +github.com/compose-spec/compose-go/v2 v2.13.0 h1:2+2oS3v4SrtAOBdZRAZYBsBy47D571p5EXMSCppmTtE= +github.com/compose-spec/compose-go/v2 v2.13.0/go.mod h1:ZU6zlcweCZKyiB7BVfCizQT9XmkEIMFE+PRZydVcsZg= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/containerd/platforms v1.0.0-rc.2 h1:0SPgaNZPVWGEi4grZdV8VRYQn78y+nm6acgLGv/QzE4= -github.com/containerd/platforms v1.0.0-rc.2/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4= -github.com/containerd/stargz-snapshotter/estargz v0.18.1 h1:cy2/lpgBXDA3cDKSyEfNOFMA/c10O1axL69EU7iirO8= -github.com/containerd/stargz-snapshotter/estargz v0.18.1/go.mod h1:ALIEqa7B6oVDsrF37GkGN20SuvG/pIMm7FwP7ZmRb0Q= -github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc= -github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= -github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/containerd/platforms v1.0.0-rc.4 h1:M42JrUT4zfZTqtkUwkr0GzmUWbfyO5VO0Q5b3op97T4= +github.com/containerd/platforms v1.0.0-rc.4/go.mod h1:lKlMXyLybmBedS/JJm11uDofzI8L2v0J2ZbYvNsbq1A= +github.com/coreos/go-oidc/v3 v3.20.0 h1:EtE0WIBHk03N+DqGkY4+UONzzZHk7amKt6IyNd7OsZE= +github.com/coreos/go-oidc/v3 v3.20.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dchest/jsmin v0.0.0-20220218165748-59f39799265f h1:OGqDDftRTwrvUoL6pOG7rYTmWsTCvyEWFsMjg+HcOaA= -github.com/dchest/jsmin v0.0.0-20220218165748-59f39799265f/go.mod h1:Dv9D0NUlAsaQcGQZa5kc5mqR9ua72SmA8VXi4cd+cBw= github.com/dchest/jsmin v1.0.0 h1:Y2hWXmGZiRxtl+VcTksyucgTlYxnhPzTozCwx9gy9zI= github.com/dchest/jsmin v1.0.0/go.mod h1:AVBIund7Mr7lKXT70hKT2YgL3XEXUaUk5iw9DZ8b0Uc= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= -github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= -github.com/docker/cli v29.1.3+incompatible h1:+kz9uDWgs+mAaIZojWfFt4d53/jv0ZUOOoSh5ZnH36c= -github.com/docker/cli v29.1.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/cli v29.2.1+incompatible h1:n3Jt0QVCN65eiVBoUTZQM9mcQICCJt3akW4pKAbKdJg= -github.com/docker/cli v29.2.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/compose/v5 v5.0.2 h1:OTBsvKsim2rVNUBrb9pP5byiGG5trTt+uO3qr6WIQYo= -github.com/docker/compose/v5 v5.0.2/go.mod h1:MbI7iBpjcgTN27JC4cYYR1mmfmaWEqEgqKKfbEGFK1c= -github.com/docker/compose/v5 v5.1.0 h1:HofsoOEJZSAjXzzeGxOub3WK/uEAL0v7g7J1kgmYETY= -github.com/docker/compose/v5 v5.1.0/go.mod h1:vpHFFnEFlKTz9HjSkRqQz8x77ZuoMoq6CN1VicxFeQM= +github.com/docker/cli v29.6.1+incompatible h1:oO7F4nn3Ovr/5TlfTUWFbMwBSS/B7Xs6Epv26gBrUP8= +github.com/docker/cli v29.6.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/compose/v5 v5.3.1 h1:rZiPwrtg1dBmVGcHiioLtzCS+TcvZq81zl0zs2BwtbE= +github.com/docker/compose/v5 v5.3.1/go.mod h1:qTcMBinqXyp2CQT6OJfBqtG8UPfYgOk1rwqIGYKLpGk= github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= -github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= -github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY= -github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= -github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= -github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= +github.com/docker/docker-credential-helpers v0.9.8 h1:bIREROb7So6PRlq6KTtdS9MPEjC29OQRkFNlvK2OX8Q= +github.com/docker/docker-credential-helpers v0.9.8/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= +github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c= +github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= -github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= @@ -829,201 +138,93 @@ github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FM github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= -github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= -github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= -github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM= -github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329/go.mod h1:Alz8LEClvR7xKsrq3qzoc4N0guvVNSS8KmSChGYr9hs= github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= -github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= -github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ= github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= -github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= -github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= -github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= -github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds= github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0= -github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= +github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw= github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= -github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= -github.com/gdamore/tcell/v2 v2.4.0 h1:W6dxJEmaxYvhICFoTY3WrLLEXsQ11SaFnKGVEXW57KM= github.com/gdamore/tcell/v2 v2.4.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU= github.com/gdamore/tcell/v2 v2.13.8 h1:Mys/Kl5wfC/GcC5Cx4C2BIQH9dbnhnkPgS9/wF3RlfU= github.com/gdamore/tcell/v2 v2.13.8/go.mod h1:+Wfe208WDdB7INEtCsNrAN6O2m+wsTPk1RAovjaILlo= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= -github.com/go-co-op/gocron/v2 v2.19.1 h1:B4iLeA0NB/2iO3EKQ7NfKn5KsQgZfjb2fkvoZJU3yBI= -github.com/go-co-op/gocron/v2 v2.19.1/go.mod h1:5lEiCKk1oVJV39Zg7/YG10OnaVrDAV5GGR6O0663k6U= -github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= -github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= -github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= -github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= -github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= +github.com/go-co-op/gocron/v2 v2.22.0 h1:uEuH2F7k7VoESb1BYSaffuuV+T0kkpzsC0aXk7/z79I= +github.com/go-co-op/gocron/v2 v2.22.0/go.mod h1:hiH/U9RMhTi1BBZJmef9s3KC9QwhpBF6PFrvUKaXY9M= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM= -github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E= -github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0= -github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY= +github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA= +github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.16.5 h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s= -github.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M= -github.com/go-git/go-git/v5 v5.17.0 h1:AbyI4xf+7DsjINHMu35quAh4wJygKBKBuXVjV/pxesM= -github.com/go-git/go-git/v5 v5.17.0/go.mod h1:f82C4YiLx+Lhi8eHxltLeGC5uBTXSFa6PC5WW9o4SjI= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= -github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= -github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= -github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= +github.com/go-git/go-git/v5 v5.19.1 h1:nX27AnaU43/K5bKktKwgBmR9lawoYVe1Ckg0rgzzN00= +github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ= +github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= +github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= -github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= -github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= -github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= -github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= -github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= -github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= -github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= -github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw= +github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU= github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM= -github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= -github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= -github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= -github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-containerregistry v0.20.7 h1:24VGNpS0IwrOZ2ms2P1QE3Xa5X9p4phx0aUgzYzHW6I= -github.com/google/go-containerregistry v0.20.7/go.mod h1:Lx5LCZQjLH1QBaMPeGwsME9biPeo1lPx6lbGj/UmzgM= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -1033,74 +234,32 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/yamlfmt v0.21.0 h1:9FKApQkDpMKgBjwLFytBHUCgqnQgxaQnci0uiESfbzs= github.com/google/yamlfmt v0.21.0/go.mod h1:q6FYExB+Ueu7jZDjKECJk+EaeDXJzJ6Ne0dxx69GWfI= -github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= -github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/enterprise-certificate-proxy v0.3.9 h1:TOpi/QG8iDcZlkQlGlFUti/ZtyLkliXvHDcyUIMuFrU= -github.com/googleapis/enterprise-certificate-proxy v0.3.9/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/enterprise-certificate-proxy v0.3.11 h1:vAe81Msw+8tKUxi2Dqh/NZMz7475yUvmRIkXr4oN2ao= github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= -github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= -github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= -github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= -github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= -github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= -github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= -github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y= -github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14= github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc= github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY= github.com/googleapis/go-gorm-spanner v1.8.6 h1:a7tp91LPLnGkTwe375yhNLwCu1ac6COehZ4RzQo04g8= github.com/googleapis/go-gorm-spanner v1.8.6/go.mod h1:ZpiB7Qd2sJxuUH6H6tsD71Nj9Q9IaR/6EUuTrxeM4tg= github.com/googleapis/go-sql-spanner v1.17.0 h1:tYEOVY/uFAdtx5nw8XDdTccTqImPeUM2Ct4sk+Id0EI= github.com/googleapis/go-sql-spanner v1.17.0/go.mod h1:L7dnHbQARFksUgYhTFM/cbfoIUtNrJ9ENZSoZ5cK58Q= -github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= -github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA= +github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE= -github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM= -github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= -github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= -github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= -github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= -github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo= -github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw= +github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0= +github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= @@ -1121,32 +280,17 @@ github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbd github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA= github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A= -github.com/josephburnett/jd/v2 v2.3.0 h1:AyNT0zSStJ2j28zutWDO4fkc95JoICryWQRmDTRzPTQ= -github.com/josephburnett/jd/v2 v2.3.0/go.mod h1:0I5+gbo7y8diuajJjm79AF44eqTheSJy1K7DSbIUFAQ= github.com/josephspurrier/goversioninfo v1.4.1 h1:5LvrkP+n0tg91J9yTkoVnt/QgNnrI1t4uSsWjIonrqY= github.com/josephspurrier/goversioninfo v1.4.1/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= -github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= -github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= -github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= -github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= -github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= +github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -1158,160 +302,92 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+ github.com/logrusorgru/aurora/v4 v4.0.0 h1:sRjfPpun/63iADiSvGGjgA1cAYegEWMPCJdUpJYn9JA= github.com/logrusorgru/aurora/v4 v4.0.0/go.mod h1:lP0iIa2nrnT/qoFXcOZSrZQpJ1o6n2CUf/hyHi2Q4ZQ= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a h1:3Bm7EwfUQUvhNeKIkUct/gl9eod1TcXuj8stxvi/GoI= -github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= -github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= -github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= -github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= -github.com/lyft/protoc-gen-star/v2 v2.0.4 h1:JDlNKttNIRd68AAIychs0AqEpO8/I/WYi01OQ7Raw6Q= -github.com/lyft/protoc-gen-star/v2 v2.0.4/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk= -github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= -github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs= +github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= -github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= -github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw= +github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mattn/go-shellwords v1.0.13 h1:DC0OMEpGjm6LfNFU4ckYcvbQKyp2vE8atyFGXNtDcf4= +github.com/mattn/go-shellwords v1.0.13/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0= github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA= -github.com/microsoft/go-mssqldb v1.9.2 h1:nY8TmFMQOHpm2qVWo6y4I2mAmVdZqlGiMGAYt64Ibbs= -github.com/microsoft/go-mssqldb v1.9.2/go.mod h1:GBbW9ASTiDC+mpgWDGKdm3FnFLTUsLYN3iFL90lQ+PA= -github.com/microsoft/go-mssqldb v1.9.6 h1:1MNQg5UiSsokiPz3++K2KPx4moKrwIqly1wv+RyCKTw= -github.com/microsoft/go-mssqldb v1.9.6/go.mod h1:yYMPDufyoF2vVuVCUGtZARr06DKFIhMrluTcgWlXpr4= -github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= -github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= -github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= -github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/microsoft/go-mssqldb v1.10.0 h1:pHEt+Qz6YFPWqREq10mqSE524QQo+/QremwTCQht7TY= +github.com/microsoft/go-mssqldb v1.10.0/go.mod h1:mnG7lGa9iYJbzJqGCXyuQCegStKMr3kogDLD6+bmggg= github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= -github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= -github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8= -github.com/moby/moby/api v1.53.0 h1:PihqG1ncw4W+8mZs69jlwGXdaYBeb5brF6BL7mPIS/w= -github.com/moby/moby/api v1.53.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc= -github.com/moby/moby/api v1.54.0 h1:7kbUgyiKcoBhm0UrWbdrMs7RX8dnwzURKVbZGy2GnL0= -github.com/moby/moby/api v1.54.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc= -github.com/moby/moby/client v0.2.2 h1:Pt4hRMCAIlyjL3cr8M5TrXCwKzguebPAc2do2ur7dEM= -github.com/moby/moby/client v0.2.2/go.mod h1:2EkIPVNCqR05CMIzL1mfA07t0HvVUUOl85pasRz/GmQ= -github.com/moby/moby/client v0.3.0 h1:UUGL5okry+Aomj3WhGt9Aigl3ZOxZGqR7XPo+RLPlKs= -github.com/moby/moby/client v0.3.0/go.mod h1:HJgFbJRvogDQjbM8fqc1MCEm4mIAGMLjXbgwoZp6jCQ= -github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= -github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/moby/api v1.55.0 h1:2/sexvQyqIWS8pRSCFddBfpW2qE7vR7FCL+vN8pxwMc= +github.com/moby/moby/api v1.55.0/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= +github.com/moby/moby/client v0.5.0 h1:5XhyPk2fuOWf6RlSFa3MkIIgDZkF25xToXW8Q/BH7cc= +github.com/moby/moby/client v0.5.0/go.mod h1:rcVpF8ncl9vo5gaIBdol6CnbEtSj1uxMvEV/UrykF/s= github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= -github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= -github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= -github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= -github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= -github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= -github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/sys/sequential v0.7.0 h1:ASQNGNROJSuOO6LL6bPHbKvuZu6NU8P4ldPWk31zj/8= +github.com/moby/sys/sequential v0.7.0/go.mod h1:NfSTAp6V3fw4tmkD62PEcOKeZKquXT8VKCkf7aVR79o= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/morikuni/aec v1.1.0 h1:vBBl0pUnvi/Je71dsRrhMBtreIqNMYErSAbEeb8jrXQ= github.com/morikuni/aec v1.1.0/go.mod h1:xDRgiq/iw5l+zkao76YTKzKttOp2cwPEne25HDkJnBw= -github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= -github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= +github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncruces/zenity v0.10.14 h1:OBFl7qfXcvsdo1NUEGxTlZvAakgWMqz9nG38TuiaGLI= github.com/ncruces/zenity v0.10.14/go.mod h1:ZBW7uVe/Di3IcRYH0Br8X59pi+O6EPnNIOU66YHpOO4= github.com/nikoksr/notify v1.5.0 h1:mzkCw8eb0P+qHwgmGQyPPGqz4GH+07FJDr44Bs16T9k= github.com/nikoksr/notify v1.5.0/go.mod h1:CEV9Bw9Y59K5oj7d8h83Xl32ATeL43ZEg9qTQsfwcCc= -github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= -github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s= -github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= github.com/onsi/gomega v1.38.3 h1:eTX+W6dobAYfFeGC2PV6RwXRu/MyT+cQguijutvkpSM= github.com/onsi/gomega v1.38.3/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/package-url/packageurl-go v0.1.3 h1:4juMED3hHiz0set3Vq3KeQ75KD1avthoXLtmE3I0PLs= -github.com/package-url/packageurl-go v0.1.3/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0= github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee h1:P6U24L02WMfj9ymZTxl7CxS73JC99x3ukk+DBkgQGQs= github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee/go.mod h1:3uODdxMgOaPYeWU7RzZLxVtJHZ/x1f/iHkBZuKJDzuY= -github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= -github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= -github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= -github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= -github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= +github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU= +github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pkg/sftp v1.13.10 h1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU= -github.com/pkg/sftp v1.13.10/go.mod h1:bJ1a7uDhrX/4OII+agvy28lzRvQrmIQuaHrcI1HbeGA= +github.com/pkg/sftp v1.13.11 h1:0N92SLTB8JqASJB14ZLHHzFnBV8mG9zw4K7jghEFWuE= +github.com/pkg/sftp v1.13.11/go.mod h1:uNkH9roSXglNJqM+glJJi+TQXQUm0fXFWqCFmT8hsN0= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= -github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/pressly/goose/v3 v3.26.0 h1:KJakav68jdH0WDvoAcj8+n61WqOIaPGgH0bJWS6jpmM= -github.com/pressly/goose/v3 v3.26.0/go.mod h1:4hC1KrritdCxtuFsqgs1R4AU5bWtTAf+cnWvfhf2DNY= -github.com/pressly/goose/v3 v3.27.0 h1:/D30gVTuQhu0WsNZYbJi4DMOsx1lNq+6SkLe+Wp59BM= -github.com/pressly/goose/v3 v3.27.0/go.mod h1:3ZBeCXqzkgIRvrEMDkYh1guvtoJTU5oMMuDdkutoM78= +github.com/pressly/goose/v3 v3.27.2 h1:FjKNzcmMdGrQlSIu5alMSmakQtJFBgtw+A0bb1p/LC8= +github.com/pressly/goose/v3 v3.27.2/go.mod h1:qWW+/8dkVtJYjJrbIpwD5xxnEJTUKvxkQ9JKQp9LaIM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844 h1:GranzK4hv1/pqTIhMTXt2X8MmMOuH3hMeUR0o9SP5yc= github.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844/go.mod h1:T1TLSfyWVBRXVGzWd0o9BI4kfoO9InEgfQe4NV3mLz8= -github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= -github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= -github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= -github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= -github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= +github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI= +github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI= github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= -github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= -github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= -github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= -github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= -github.com/samber/oops v1.18.1 h1:qjhZbqbdyhWBKntkY8sxrDNKA8b4c5VHlmI1rli7X7M= -github.com/samber/oops v1.18.1/go.mod h1:xYqvimigkKV70HyLXiBZJFpIWi2CGcc6Xx7eV+2HycI= +github.com/sahilm/fuzzy v0.1.3 h1:juByESSS32nVD81vr6tHmKmA/8zde7gE+x5CLxrzXPU= +github.com/sahilm/fuzzy v0.1.3/go.mod h1:au6//VbVSqu6DFrkL2CfjlJ5iURpNCPeE+1GwY3XsT8= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= @@ -1322,8 +398,6 @@ github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= -github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs= -github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -1331,69 +405,31 @@ github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= -github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= -github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= -github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM= github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= -github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU= -github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY= -github.com/testcontainers/testcontainers-go/modules/localstack v0.40.0 h1:b+lN2Ch4J/6EwqB+Af+QQbSfv4sFGetHlBHpXi+1yJU= -github.com/testcontainers/testcontainers-go/modules/localstack v0.40.0/go.mod h1:8LuTSboTo2MJKFKV5xH6z4ZH1s3jhRJWwvtPJzKogj4= -github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4= -github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0= -github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4= -github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= -github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJXP61mNV3/7iuU= -github.com/twitchtv/twirp v8.1.3+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A= -github.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4= -github.com/vbatts/tar-split v0.12.2/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= github.com/wagoodman/dive v0.13.1 h1:rnC8sjsVJnCHklBlJxBxoR6nRTX1uVsW6OhuniQsYQk= github.com/wagoodman/dive v0.13.1/go.mod h1:2gFFT69u5L7qP+fnimMU5hx+TQsT9UA1P8QwnIOeVGc= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= -github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -github.com/zclconf/go-cty v1.17.0 h1:seZvECve6XX4tmnvRzWtJNHdscMtYEx5R7bnnVyd/d0= -github.com/zclconf/go-cty v1.17.0/go.mod h1:wqFzcImaLTI6A5HfsRwB0nj5n0MRZFwmey8YoFPPs3U= -github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= -github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= -github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= -go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= -go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= go.lsp.dev/jsonrpc2 v0.10.0 h1:Pr/YcXJoEOTMc/b6OTmcR1DPJ3mSWl/SWiU1Cct6VmI= go.lsp.dev/jsonrpc2 v0.10.0/go.mod h1:fmEzIdXPi/rf6d4uFcayi8HpFP1nBF99ERP1htC72Ac= go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 h1:hCzQgh6UcwbKgNSRurYWSqh8MufqRRPODRBblutn4TE= @@ -1402,76 +438,47 @@ go.lsp.dev/protocol v0.12.0 h1:tNprUI9klQW5FAFVM4Sa+AbPFuVQByWhP1ttNUAjIWg= go.lsp.dev/protocol v0.12.0/go.mod h1:Qb11/HgZQ72qQbeyPfJbu3hZBH23s1sr4st8czGeDMQ= go.lsp.dev/uri v0.3.0 h1:KcZJmh6nFIBeJzTugn5JTU6OOyG0lDOo3R9KwTxTYbo= go.lsp.dev/uri v0.3.0/go.mod h1:P5sbO1IQR+qySTWOCnhnK7phBx+W3zbLqSMDJNTw88I= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE= -go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0= -go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= -go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= -go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= -go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= -go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= -go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= -go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= -go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= -go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= -go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= -go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= -go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= -go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= -go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= -go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= -go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= -go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= -go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= -go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= -go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= -go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= -go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= -go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= -go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= +go.opentelemetry.io/contrib/detectors/gcp v1.42.0 h1:kpt2PEJuOuqYkPcktfJqWWDjTEd/FNgrxcniL7kQrXQ= +go.opentelemetry.io/contrib/detectors/gcp v1.42.0/go.mod h1:W9zQ439utxymRrXsUOzZbFX4JhLxXU4+ZnCt8GG7yA8= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.69.0 h1:2yEATaop1/a1I4psnSLgWVPLWwCzkqWakgJy7xTDVy0= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.69.0/go.mod h1:D7J12YRapIekYyPWgGPlA/23pRmpSEZC5xJC/TTLI9U= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 h1:4YsVu3B8+3qtWYYrsUYgn0OG78pN0rnNPRGX4SbokQI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0/go.mod h1:+wnlSn0mD1ADVMe3v9Z/WIaiz6q6gL2J/ejaAmdmv80= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0 h1:lgh3PiVrRUWMLOVSkQicxzZll5NjF1r+AtsX1XRIHw0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0/go.mod h1:5Cnhth3m/AgOeTgE3ex12pPmiu/gGtZit03kSzx9X7s= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA= +go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= +go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= -go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go= -go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= -go.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U= -go.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= +go.yaml.in/yaml/v4 v4.0.0-rc.6 h1:1h7H1ohdUh93/FyE4YaDa1Zh64K6VVbjF4K6WUxMtH4= +go.yaml.in/yaml/v4 v4.0.0-rc.6/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= @@ -1480,324 +487,89 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= -golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= -golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= -golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= -golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= +golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= -golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 h1:MDfG8Cvcqlt9XXrmEiD4epKn7VJHZO84hejP9Jmp0MM= -golang.org/x/exp v0.0.0-20251209150349-8475f28825e9/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= -golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= -golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw= golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= -golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= -golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= -golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= -golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= -golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= -golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= -golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= -golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= -golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= -golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= -golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= -golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= -golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= -golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= -golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= -golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= -golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= -golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= +golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= @@ -1805,365 +577,42 @@ golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= -golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= -golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= -golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= -golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= -golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= -golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= -golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk= -golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= -gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= -gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= -gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= -gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= -google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= -google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= -google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= -google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= -google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= -google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= -google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= -google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= -google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= -google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= -google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= -google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08= -google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= -google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= -google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= -google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= -google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= -google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= -google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= -google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= -google.golang.org/api v0.260.0 h1:XbNi5E6bOVEj/uLXQRlt6TKuEzMD7zvW/6tNwltE4P4= -google.golang.org/api v0.260.0/go.mod h1:Shj1j0Phr/9sloYrKomICzdYgsSDImpTxME8rGLaZ/o= -google.golang.org/api v0.264.0 h1:+Fo3DQXBK8gLdf8rFZ3uLu39JpOnhvzJrLMQSoSYZJM= -google.golang.org/api v0.264.0/go.mod h1:fAU1xtNNisHgOF5JooAs8rRaTkl2rT3uaoNGo9NS3R8= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/api v0.265.0 h1:FZvfUdI8nfmuNrE34aOWFPmLC+qRBEiNm3JdivTvAAU= google.golang.org/api v0.265.0/go.mod h1:uAvfEl3SLUj/7n6k+lJutcswVojHPp2Sp08jWCu8hLY= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= -google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= -google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= -google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= -google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= -google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= -google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= -google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= -google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= -google.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= -google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230112194545-e10362b5ecf9/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA= -google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= -google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= -google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= -google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= -google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= -google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= -google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= -google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= -google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934= -google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0= google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM= google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= -google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0= -google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:t/LOSXPJ9R0B6fnZNyALBRfZBH0Uy0gT+uR+SJ6syqQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= -google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= -google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= -google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= -google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= -google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= -google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -2172,28 +621,17 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -2211,67 +649,19 @@ gorm.io/driver/sqlserver v1.5.4 h1:xA+Y1KDNspv79q43bPyjDMUgHoYHLhXYmdFcYPobg8g= gorm.io/driver/sqlserver v1.5.4/go.mod h1:+frZ/qYmuna11zHPlh5oc2O6ZA/lS88Keb0XSH1Zh/g= gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= -gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= -gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= +gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= +gorm.io/gorm v1.31.2/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= -k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= -k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= -modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= -modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= -modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= -modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= -modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= -modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= -modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= -modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= -modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= -modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= -modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= -modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= -modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= -modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= -modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A= -modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I= -modernc.org/libc v1.68.0 h1:PJ5ikFOV5pwpW+VqCK1hKJuEWsonkIJhhIXyuF/91pQ= -modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/libc v1.73.4 h1:+ra4Ui8ngyt8HDcO1FTDPWlkAh6yOdaO2yAoh8MddQA= +modernc.org/libc v1.73.4/go.mod h1:DXZ3eO8qMCNn2SnmTNCiC71nJ9Rcq3PsnpU6Vc4rWK8= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= -modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= -modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= -modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= -modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= -modernc.org/sqlite v1.40.1 h1:VfuXcxcUWWKRBuP8+BR9L7VnmusMgBNNnBYGEe9w/iY= -modernc.org/sqlite v1.40.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE= -modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU= -modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= -modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= -modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= -modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= +modernc.org/sqlite v1.53.0 h1:20WG8N9q4ji/dEqGk4uiI0c6OPjSeLTNYGFCc3+7c1M= +modernc.org/sqlite v1.53.0/go.mod h1:xoEpOIpGrgT48H5iiyt/YXPCZPEzlfmfFwtk8Lklw8s= pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/core/internal/app/app.go b/core/internal/app/app.go index 6c65314e..db71a53f 100644 --- a/core/internal/app/app.go +++ b/core/internal/app/app.go @@ -1,6 +1,7 @@ package app import ( + "context" "fmt" "net/http" "os" @@ -25,6 +26,7 @@ import ( "github.com/RA341/dockman/internal/viewer" "github.com/RA341/dockman/pkg/argos" "github.com/RA341/dockman/pkg/logger" + "github.com/RA341/dockman/pkg/memlimit" "github.com/rs/zerolog/log" ) @@ -67,6 +69,11 @@ func NewApp(opt ...config.AppOpt) (app *App) { logger.InitConsole(conf.Log.Level, conf.Log.Verbose) + // Cap the Go heap to the container's cgroup memory limit. The runtime does + // not do this on its own, so without it a transient spike inflates RSS and + // stays resident. No-op outside a memory-limited container. + memlimit.Configure() + // db and info setup gormDB := database.New(conf.ConfigDir, info.IsDev()) userDb := config.NewUserConfigDB(gormDB) @@ -109,6 +116,12 @@ func NewApp(opt ...config.AppOpt) (app *App) { conf.LocalAddr, ) + // best-effort: remove a leftover self-update helper container from a + // previous update once the local docker host is reachable. + if dkSrv, err := hostManager.GetDockerService(host.LocalDocker); err == nil { + docker.CleanupSelfUpdateHelper(context.Background(), dkSrv.Container.Cli()) + } + fileSrv := files.New( hostManager.GetAlias, dockyamlSrv.GetYaml, @@ -339,7 +352,7 @@ func (a *App) registerApiHostRoutes(hostMux *http.ServeMux) { withSubRouter( hostMux, "/docker", - docker.NewHandlerHttp(a.HostManager.GetDockerService), + docker.NewHandlerHttp(a.HostManager.GetDockerService, a.Config.AllowSelfExec), ) // cleaner hostMux.Handle(cleaner.NewHandler(a.CleanerSrv)) diff --git a/core/internal/app/http_security.go b/core/internal/app/http_security.go new file mode 100644 index 00000000..08127380 --- /dev/null +++ b/core/internal/app/http_security.go @@ -0,0 +1,66 @@ +package app + +import ( + "fmt" + "net/http" + "net/url" + "strings" + + "github.com/RA341/dockman/internal/config" +) + +const bytesPerMiB = 1024 * 1024 + +func enforceOriginPolicy(conf *config.AppConfig, next http.Handler) http.Handler { + allowed := make(map[string]struct{}) + allowAll := false + for _, origin := range conf.GetAllowedOrigins() { + if origin == "*" { + allowAll = true + continue + } + allowed[origin] = struct{}{} + } + + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + origin := strings.TrimSuffix(strings.TrimSpace(r.Header.Get("Origin")), "/") + if origin == "" || allowAll || requestIsSameOrigin(r, origin) { + next.ServeHTTP(w, r) + return + } + if _, ok := allowed[origin]; ok { + next.ServeHTTP(w, r) + return + } + http.Error(w, "browser origin is not allowed", http.StatusForbidden) + }) +} + +func requestIsSameOrigin(r *http.Request, origin string) bool { + parsed, err := url.Parse(origin) + if err != nil || parsed.Scheme == "" || parsed.Host == "" { + return false + } + return strings.EqualFold(parsed.Host, r.Host) +} + +func limitRequestBodies(conf *config.AppConfig, next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + limitMB := conf.HTTPMaxBodyMB + if strings.HasSuffix(r.URL.Path, "/file/save") { + limitMB = conf.HTTPMaxUploadMB + } + if limitMB <= 0 || r.Body == nil { + next.ServeHTTP(w, r) + return + } + + limit := int64(limitMB) * bytesPerMiB + if r.ContentLength > limit { + http.Error(w, fmt.Sprintf("request body exceeds the %d MiB limit", limitMB), http.StatusRequestEntityTooLarge) + return + } + r.Body = http.MaxBytesReader(w, r.Body, limit) + next.ServeHTTP(w, r) + }) +} diff --git a/core/internal/app/http_security_test.go b/core/internal/app/http_security_test.go new file mode 100644 index 00000000..05f88d0b --- /dev/null +++ b/core/internal/app/http_security_test.go @@ -0,0 +1,72 @@ +package app + +import ( + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/RA341/dockman/internal/config" +) + +func TestOriginPolicy(t *testing.T) { + conf := &config.AppConfig{AllowedOrigins: "https://admin.example"} + handler := enforceOriginPolicy(conf, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNoContent) + })) + + tests := []struct { + name string + host string + origin string + want int + }{ + {"non browser", "dockman.local", "", http.StatusNoContent}, + {"same origin", "dockman.local:8866", "http://dockman.local:8866", http.StatusNoContent}, + {"configured reverse proxy", "dockman.internal:8866", "https://admin.example", http.StatusNoContent}, + {"foreign browser", "dockman.local:8866", "https://evil.example", http.StatusForbidden}, + {"opaque browser", "dockman.local:8866", "null", http.StatusForbidden}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "http://"+test.host+"/api/protected/ping", nil) + req.Host = test.host + if test.origin != "" { + req.Header.Set("Origin", test.origin) + } + res := httptest.NewRecorder() + handler.ServeHTTP(res, req) + if res.Code != test.want { + t.Fatalf("status = %d, want %d", res.Code, test.want) + } + }) + } +} + +func TestRequestBodyLimitsUseLargerFileAllowance(t *testing.T) { + conf := &config.AppConfig{HTTPMaxBodyMB: 1, HTTPMaxUploadMB: 2} + handler := limitRequestBodies(conf, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, err := io.Copy(io.Discard, r.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusRequestEntityTooLarge) + return + } + w.WriteHeader(http.StatusNoContent) + })) + body := strings.Repeat("x", bytesPerMiB+1) + + regular := httptest.NewRequest(http.MethodPost, "http://dockman/api/protected/rpc", strings.NewReader(body)) + regularRes := httptest.NewRecorder() + handler.ServeHTTP(regularRes, regular) + if regularRes.Code != http.StatusRequestEntityTooLarge { + t.Fatalf("regular body status = %d, want %d", regularRes.Code, http.StatusRequestEntityTooLarge) + } + + upload := httptest.NewRequest(http.MethodPost, "http://dockman/api/protected/local/file/save", strings.NewReader(body)) + uploadRes := httptest.NewRecorder() + handler.ServeHTTP(uploadRes, upload) + if uploadRes.Code != http.StatusNoContent { + t.Fatalf("upload body status = %d, want %d", uploadRes.Code, http.StatusNoContent) + } +} diff --git a/core/internal/app/server.go b/core/internal/app/server.go index 5db587a7..91baeda4 100644 --- a/core/internal/app/server.go +++ b/core/internal/app/server.go @@ -43,7 +43,7 @@ func NewServer(app *App) { AllowedHeaders: connectcors.AllowedHeaders(), ExposedHeaders: connectcors.ExposedHeaders(), }) - finalMux := corsConfig.Handler(router) + finalMux := enforceOriginPolicy(conf, corsConfig.Handler(limitRequestBodies(conf, router))) port := fmt.Sprintf(":%d", conf.Port) log.Info().Str("port", port). @@ -53,8 +53,11 @@ func NewServer(app *App) { ht2Srv := &http2.Server{} srv := &http.Server{ - Addr: port, - Handler: h2c.NewHandler(finalMux, ht2Srv), + Addr: port, + Handler: h2c.NewHandler(finalMux, ht2Srv), + ReadHeaderTimeout: time.Duration(conf.HTTPReadHeaderSeconds) * time.Second, + IdleTimeout: time.Duration(conf.HTTPIdleSeconds) * time.Second, + MaxHeaderBytes: 1 << 20, } go func() { diff --git a/core/internal/app/ui/spa.go b/core/internal/app/ui/spa.go index fa1f124a..b3fefe0a 100644 --- a/core/internal/app/ui/spa.go +++ b/core/internal/app/ui/spa.go @@ -32,9 +32,22 @@ func (h *SpaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Check if the file exists in the filesystem. if _, err := fs.Stat(h.staticFS, fsPath); os.IsNotExist(err) { // The file does not exist, so serve index.html. + // Embedded files carry no modification time, so without an explicit + // policy browsers cache the app shell heuristically and keep + // launching an old bundle long after the server was updated — + // index.html must be revalidated on every load. + w.Header().Set("Cache-Control", "no-cache") http.ServeFileFS(w, r, h.staticFS, "index.html") return } + // Vite content-hashes everything under assets/, safe to cache forever; + // the entry files (index.html, favicon...) must always be revalidated. + if strings.HasPrefix(fsPath, "assets/") { + w.Header().Set("Cache-Control", "public, max-age=31536000, immutable") + } else { + w.Header().Set("Cache-Control", "no-cache") + } + h.fileServer.ServeHTTP(w, r) } diff --git a/core/internal/auth/middleware.go b/core/internal/auth/middleware.go index 46bc30b4..65ab7e38 100644 --- a/core/internal/auth/middleware.go +++ b/core/internal/auth/middleware.go @@ -14,7 +14,7 @@ const KeyUserCtx = "user" func Middleware(service *Service, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ok := CheckAuth(w, r, service) + r, ok := CheckAuth(w, r, service) if !ok { return } @@ -24,14 +24,20 @@ func Middleware(service *Service, next http.Handler) http.Handler { const oidcPage = "/api/auth/login/oidc" -func CheckAuth(w http.ResponseWriter, r *http.Request, srv *Service) (ok bool) { +// CheckAuth verifies the request's auth cookie. On success it returns a request +// whose context carries the authenticated user (under KeyUserCtx) together with +// ok=true; callers must forward the returned request downstream. On failure it +// writes the appropriate response and returns ok=false. +func CheckAuth(w http.ResponseWriter, r *http.Request, srv *Service) (*http.Request, bool) { u, err := verifyCookie(r.Cookies(), srv) if err == nil { - r.WithContext(context.WithValue( + // http.Request.WithContext returns a shallow copy; we must return it so + // the enriched context actually reaches the downstream handler. + r = r.WithContext(context.WithValue( r.Context(), KeyUserCtx, u, )) - return true + return r, true } if srv.config.OIDCEnable && srv.config.OIDCAutoRedirect { @@ -42,11 +48,11 @@ func CheckAuth(w http.ResponseWriter, r *http.Request, srv *Service) (ok bool) { if err != nil { log.Warn().Err(err).Msg("Failed to write response") } - return false + return r, false } http.Error(w, err.Error(), http.StatusUnauthorized) - return false + return r, false } func getCookie(cookieName string, cookies []*http.Cookie) (*http.Cookie, error) { diff --git a/core/internal/auth/middleware_test.go b/core/internal/auth/middleware_test.go new file mode 100644 index 00000000..88914a70 --- /dev/null +++ b/core/internal/auth/middleware_test.go @@ -0,0 +1,72 @@ +package auth + +import ( + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +// fakeSessionStore is a minimal SessionStore that returns a fixed session, +// letting us exercise the middleware without a database. +type fakeSessionStore struct { + session Session + err error +} + +func (f *fakeSessionStore) NewSession(*Session) error { return nil } +func (f *fakeSessionStore) DeleteSession(uint) error { return nil } +func (f *fakeSessionStore) GetSession(uint) (Session, error) { return f.session, f.err } +func (f *fakeSessionStore) GetSessionByToken(string) (Session, error) { return f.session, f.err } + +// TestMiddleware_PropagatesUserToContext guards against a regression where +// CheckAuth discarded the *http.Request returned by WithContext, so the +// authenticated user never reached downstream handlers. +func TestMiddleware_PropagatesUserToContext(t *testing.T) { + want := User{Username: "alice"} + srv := &Service{ + config: &Config{}, + sessionStore: &fakeSessionStore{ + session: Session{ + User: want, + Expires: time.Now().Add(time.Hour), + }, + }, + } + + var got *User + downstream := http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { + if u, ok := r.Context().Value(KeyUserCtx).(*User); ok { + got = u + } + }) + + req := httptest.NewRequest(http.MethodGet, "/api/protected/info", nil) + req.AddCookie(&http.Cookie{Name: CookieHeaderAuth, Value: "any-token"}) + rec := httptest.NewRecorder() + + Middleware(srv, downstream).ServeHTTP(rec, req) + + require.Equal(t, http.StatusOK, rec.Code) + require.NotNil(t, got, "authenticated user must reach the downstream handler via context") + require.Equal(t, want.Username, got.Username) +} + +// TestMiddleware_RejectsMissingCookie ensures unauthenticated requests are +// stopped with 401 and never reach the downstream handler. +func TestMiddleware_RejectsMissingCookie(t *testing.T) { + srv := &Service{config: &Config{}, sessionStore: &fakeSessionStore{}} + + called := false + downstream := http.HandlerFunc(func(http.ResponseWriter, *http.Request) { called = true }) + + req := httptest.NewRequest(http.MethodGet, "/api/protected/info", nil) + rec := httptest.NewRecorder() + + Middleware(srv, downstream).ServeHTTP(rec, req) + + require.Equal(t, http.StatusUnauthorized, rec.Code) + require.False(t, called, "downstream must not run for unauthenticated requests") +} diff --git a/core/internal/cleaner/handler.go b/core/internal/cleaner/handler.go index 6ed7077c..c43fd0c0 100644 --- a/core/internal/cleaner/handler.go +++ b/core/internal/cleaner/handler.go @@ -180,6 +180,10 @@ func (h *Handler) EditConfig(ctx context.Context, req *connect.Request[v1.EditCo cf.FromProto(req.Msg.Config) cf.Host = hostname + if cf.Enabled && cf.Interval <= 0 { + return nil, fmt.Errorf("set a maintenance interval greater than 0 to enable auto-pruning") + } + err = h.srv.store.UpdateConfig(&cf) if err != nil { return nil, fmt.Errorf("unable to update config: %v", err) @@ -190,9 +194,14 @@ func (h *Handler) EditConfig(ctx context.Context, req *connect.Request[v1.EditCo return nil, err } - err = h.srv.RunWithScheduler(hostname, true) - if err != nil { - return nil, err + // Only (re)schedule when enabled; saving a disabled config must tear down any + // running job instead of erroring out. + if getConfig.Enabled { + if err = h.srv.RunWithScheduler(hostname, true); err != nil { + return nil, err + } + } else { + h.srv.StopScheduler(hostname) } return connect.NewResponse(&v1.EditConfigResponse{ diff --git a/core/internal/cleaner/service.go b/core/internal/cleaner/service.go index c53f2551..427268c8 100644 --- a/core/internal/cleaner/service.go +++ b/core/internal/cleaner/service.go @@ -126,7 +126,10 @@ func (s *Service) RunWithScheduler(host string, edit bool) error { return err } if !getConfig.Enabled { - return fmt.Errorf("enabled cleaner run") + return fmt.Errorf("cleaner is disabled for host %q; enable it first", host) + } + if getConfig.Interval <= 0 { + return fmt.Errorf("cleaner interval must be greater than 0 for host %q", host) } var jb gocron.Job @@ -151,6 +154,19 @@ func (s *Service) RunWithScheduler(host string, edit bool) error { return jb.RunNow() } +// StopScheduler removes any scheduled cleaner job for the host. It is a no-op +// when nothing is scheduled, so it is safe to call whenever a config is saved +// with the cleaner disabled. +func (s *Service) StopScheduler(host string) { + jb, ok := s.taskList.LoadAndDelete(host) + if !ok { + return + } + if err := s.schd.RemoveJob(jb.ID()); err != nil { + s.log.Warn().Err(err).Str("host", host).Msg("failed to remove cleaner job") + } +} + func (s *Service) clean(ctx context.Context, host string) { log.Debug().Msg("running automated docker cleaner") diff --git a/core/internal/cleaner/store_gorm.go b/core/internal/cleaner/store_gorm.go index 73b0beb9..fc81fe3e 100644 --- a/core/internal/cleaner/store_gorm.go +++ b/core/internal/cleaner/store_gorm.go @@ -2,6 +2,7 @@ package cleaner import ( "gorm.io/gorm" + "gorm.io/gorm/clause" ) type GormStore struct { @@ -24,7 +25,19 @@ func (g *GormStore) GetConfig(host string) (PruneConfig, error) { } func (g *GormStore) UpdateConfig(config *PruneConfig) error { - return g.db.Updates(config).Error + // Upsert keyed on the unique host column. Updates() alone fails with + // "WHERE conditions required" because the incoming config carries no primary + // key, and on the first save no row exists for the host yet. ON CONFLICT(host) + // DO UPDATE inserts the first time and updates thereafter, and (via + // AssignmentColumns) correctly persists toggles set back to false. + return g.db.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "host"}}, + DoUpdates: clause.AssignmentColumns([]string{ + "enabled", "interval", + "volumes", "networks", "images", "containers", "build_cache", + "updated_at", + }), + }).Create(config).Error } const maxPruneResults = 10 diff --git a/core/internal/config/config.go b/core/internal/config/config.go index 0f50e631..d77a3c42 100644 --- a/core/internal/config/config.go +++ b/core/internal/config/config.go @@ -15,13 +15,18 @@ const EnvPrefix = "DOCKMAN" // AppConfig tags are parsed by processStruct type AppConfig struct { - Port int `config:"flag=port,env=PORT,default=8866,usage=Port to run the server on"` - AllowedOrigins string `config:"flag=origins,env=ORIGINS,default=*,usage=Allowed origins for the API (in CSV)"` - UIPath string `config:"flag=ui,env=UI_PATH,default=dist,usage=Path to frontend files"` - LocalAddr string `config:"flag=ma,env=MACHINE_ADDR,default=0.0.0.0,usage=Local machine IP address"` - ComposeRoot string `config:"flag=cr,env=COMPOSE_ROOT,default=./compose,usage=Root directory for compose files"` - ConfigDir string `config:"flag=conf,env=CONFIG,default=./config,usage=Directory to store dockman config"` - DockYaml string `config:"flag=dyp,env=YAML_PATH,default=./config/dockyaml,usage=custom path for dockman.yml files"` + Port int `config:"flag=port,env=PORT,default=8866,usage=Port to run the server on"` + AllowedOrigins string `config:"flag=origins,env=ORIGINS,default=,usage=Extra allowed browser origins in CSV; same-origin is always allowed"` + HTTPMaxBodyMB int `config:"flag=httpMaxBodyMB,env=HTTP_MAX_BODY_MB,default=16,usage=Maximum request body size in MiB (0 disables the limit)"` + HTTPMaxUploadMB int `config:"flag=httpMaxUploadMB,env=HTTP_MAX_UPLOAD_MB,default=1024,usage=Maximum file upload size in MiB (0 disables the limit)"` + HTTPReadHeaderSeconds int `config:"flag=httpReadHeaderTimeout,env=HTTP_READ_HEADER_TIMEOUT,default=10,usage=HTTP header read timeout in seconds"` + HTTPIdleSeconds int `config:"flag=httpIdleTimeout,env=HTTP_IDLE_TIMEOUT,default=120,usage=HTTP keep-alive idle timeout in seconds"` + AllowSelfExec bool `config:"flag=allowSelfExec,env=ALLOW_SELF_EXEC,default=false,usage=Allow exec sessions inside Dockman containers (unsafe; troubleshooting only)"` + UIPath string `config:"flag=ui,env=UI_PATH,default=dist,usage=Path to frontend files"` + LocalAddr string `config:"flag=ma,env=MACHINE_ADDR,default=0.0.0.0,usage=Local machine IP address"` + ComposeRoot string `config:"flag=cr,env=COMPOSE_ROOT,default=./compose,usage=Root directory for compose files"` + ConfigDir string `config:"flag=conf,env=CONFIG,default=./config,usage=Directory to store dockman config"` + DockYaml string `config:"flag=dyp,env=YAML_PATH,default=./config/dockyaml,usage=custom path for dockman.yml files"` Auth auth.Config `config:""` // empty tag to indicate to parse struct Log Logger `config:""` @@ -34,11 +39,13 @@ type AppConfig struct { } func (c *AppConfig) GetAllowedOrigins() []string { - elems := strings.Split(c.AllowedOrigins, ",") - for i := range elems { - elems[i] = strings.TrimSpace(elems[i]) + var origins []string + for _, elem := range strings.Split(c.AllowedOrigins, ",") { + if origin := strings.TrimSpace(elem); origin != "" { + origins = append(origins, strings.TrimSuffix(origin, "/")) + } } - return elems + return origins } func (c *AppConfig) GetDockmanWithMachineUrl() string { diff --git a/core/internal/config/service.go b/core/internal/config/service.go index 6498de48..6e2cc317 100644 --- a/core/internal/config/service.go +++ b/core/internal/config/service.go @@ -7,7 +7,6 @@ import ( "net" "os" "path/filepath" - "strings" "github.com/RA341/dockman/pkg/argos" "github.com/RA341/dockman/pkg/fileutil" @@ -93,10 +92,6 @@ func defaultIfNotSet(config *AppConfig) { } } - if len(strings.TrimSpace(config.AllowedOrigins)) == 0 { - config.AllowedOrigins = "*" // allow all origins - } - if config.Port == 0 { config.Port = 8866 } diff --git a/core/internal/docker/compose/command.go b/core/internal/docker/compose/command.go new file mode 100644 index 00000000..ffcac72d --- /dev/null +++ b/core/internal/docker/compose/command.go @@ -0,0 +1,120 @@ +package compose + +import ( + "bytes" + "context" + "fmt" + "io" + "strings" +) + +// RunDockerCommand executes a user-provided docker CLI command line on this +// host through the same runner compose uses (local exec or ssh), streaming +// its combined output. Only the docker binary is allowed. +func (c *Service) RunDockerCommand(ctx context.Context, rawCommand string, stream io.Writer) error { + args, err := splitCommandLine(rawCommand) + if err != nil { + return err + } + if len(args) == 0 || args[0] != "docker" { + return fmt.Errorf("only docker commands are allowed, e.g. docker run --rm nginx:alpine") + } + + if stream != nil { + if _, err = stream.Write([]byte(green(strings.Join(args, " ")))); err != nil { + return fmt.Errorf("could not write to stream: %w", err) + } + } + + errWriter := new(bytes.Buffer) + if err = c.runner.Run(ctx, args, ".", stream, errWriter); err != nil { + if errWriter.Len() > 0 { + return fmt.Errorf("%s", errWriter.String()) + } + return err + } + return nil +} + +// splitCommandLine splits a command line into arguments, honoring single +// quotes, double quotes and backslash escapes (outside single quotes) +func splitCommandLine(input string) ([]string, error) { + var args []string + var current strings.Builder + inArg := false + + const ( + modePlain = iota + modeSingle + modeDouble + ) + mode := modePlain + + flush := func() { + if inArg { + args = append(args, current.String()) + current.Reset() + inArg = false + } + } + + runes := []rune(input) + for i := 0; i < len(runes); i++ { + ch := runes[i] + switch mode { + case modeSingle: + if ch == '\'' { + mode = modePlain + } else { + current.WriteRune(ch) + } + case modeDouble: + if ch == '"' { + mode = modePlain + } else if ch == '\\' && i+1 < len(runes) && (runes[i+1] == '"' || runes[i+1] == '\\') { + i++ + current.WriteRune(runes[i]) + } else { + current.WriteRune(ch) + } + default: + switch { + case ch == ' ' || ch == '\t' || ch == '\n': + flush() + case ch == '\'': + mode = modeSingle + inArg = true + case ch == '"': + mode = modeDouble + inArg = true + case ch == '\\' && i+1 < len(runes): + i++ + current.WriteRune(runes[i]) + inArg = true + default: + current.WriteRune(ch) + inArg = true + } + } + } + if mode != modePlain { + return nil, fmt.Errorf("unbalanced quote in command") + } + flush() + return args, nil +} + +// PullImage pulls an image through the host's docker CLI in the compose +// runner context, so registry credentials (docker login, credential +// helpers) apply exactly as they do for compose — a bare daemon API pull +// is unauthenticated and fails on private registries. +func (c *Service) PullImage(ctx context.Context, imageTag string, out io.Writer) error { + errWriter := new(bytes.Buffer) + if err := c.runner.Run(ctx, []string{"docker", "pull", imageTag}, ".", out, errWriter); err != nil { + if errWriter.Len() > 0 { + return fmt.Errorf("%s", strings.TrimSpace(errWriter.String())) + } + return err + } + return nil +} diff --git a/core/internal/docker/compose/command_test.go b/core/internal/docker/compose/command_test.go new file mode 100644 index 00000000..7de7e170 --- /dev/null +++ b/core/internal/docker/compose/command_test.go @@ -0,0 +1,42 @@ +package compose + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSplitCommandLine(t *testing.T) { + args, err := splitCommandLine(`docker run --rm -p 8080:80 nginx:alpine`) + require.NoError(t, err) + require.Equal(t, []string{"docker", "run", "--rm", "-p", "8080:80", "nginx:alpine"}, args) + + // double quotes keep spaces, backslash escapes work inside them + args, err = splitCommandLine(`docker run -e "GREETING=hello world" -e NAME=\"quoted\" img`) + require.NoError(t, err) + require.Equal(t, []string{"docker", "run", "-e", "GREETING=hello world", "-e", `NAME="quoted"`, "img"}, args) + + // single quotes are literal + args, err = splitCommandLine(`docker run -e 'A=$HOME and "stuff"' img`) + require.NoError(t, err) + require.Equal(t, []string{"docker", "run", "-e", `A=$HOME and "stuff"`, "img"}, args) + + // collapsed whitespace, tabs, empty quoted args + args, err = splitCommandLine("docker ps\t-a ''") + require.NoError(t, err) + require.Equal(t, []string{"docker", "ps", "-a", ""}, args) + + // escaped space outside quotes + args, err = splitCommandLine(`docker run -v /my\ path:/data img`) + require.NoError(t, err) + require.Equal(t, []string{"docker", "run", "-v", "/my path:/data", "img"}, args) + + // unbalanced quotes are rejected + _, err = splitCommandLine(`docker run "unterminated`) + require.Error(t, err) + + // empty input + args, err = splitCommandLine(" ") + require.NoError(t, err) + require.Empty(t, args) +} diff --git a/core/internal/docker/compose/compose_terminal.go b/core/internal/docker/compose/compose_terminal.go index 48e2850c..2b0c9168 100644 --- a/core/internal/docker/compose/compose_terminal.go +++ b/core/internal/docker/compose/compose_terminal.go @@ -36,6 +36,9 @@ type Service struct { parser FilenameParser runner CmdRunner hostname string + // reverse of parser: absolute compose path -> dockman filename; + // injected by the host service which owns the alias table + pathResolver PathResolver } func NewComposeTerminal( @@ -209,6 +212,35 @@ func (c *Service) Up( ) } +// Redeploy runs `up -d` with explicit force flags so a stack can be +// re-rolled in one action: pull images, rebuild, or recreate containers +// even when nothing changed. +func (c *Service) Redeploy( + ctx context.Context, + filename string, + out io.Writer, + pull, build, recreate bool, + services ...string, +) error { + return c.withCmd( + ctx, filename, out, + func(cmdList []string) []string { + cmdList = append(cmdList, "up", "-d", "-y", "--remove-orphans") + if pull { + cmdList = append(cmdList, "--pull", "always") + } + if build { + cmdList = append(cmdList, "--build") + } + if recreate { + cmdList = append(cmdList, "--force-recreate") + } + return cmdList + }, + services, + ) +} + func (c *Service) Down( ctx context.Context, filename string, @@ -312,11 +344,14 @@ func (c *Service) List(ctx context.Context, filename string) ([]container2.Summa } func (c *Service) Stats(ctx context.Context, filename string) ([]container.Stats, error) { - lines, err := c.listIds(ctx, filename) + // Match the stack's containers by the compose config-files label: one + // ContainerList against the daemon instead of spawning a `docker compose + // ps` subprocess plus a second listing on every stats poll. + absPath, err := c.ComposeAbsPath(filename) if err != nil { return nil, err } - ds, err := c.cont.ContainerListByIDs(ctx, lines...) + ds, err := c.cont.ContainerListByComposeFile(ctx, absPath) if err != nil { return nil, err } @@ -407,6 +442,19 @@ type StackState struct { UnhealthyCount uint } +// ComposeAbsPath returns the absolute path of the compose file as Docker Compose +// records it in the com.docker.compose.project.config_files label. It reuses the +// exact resolution the compose commands use (working dir = Fs.Root(), -f Relpath), +// so running containers can be matched back to their compose file from a single +// container listing — no per-stack `docker compose ps` process. +func (c *Service) ComposeAbsPath(filename string) (string, error) { + parts, err := c.parser(filename, c.hostname) + if err != nil { + return "", err + } + return parts.Fs.Join(parts.Fs.Root(), parts.Relpath), nil +} + func (c *Service) Validate(ctx context.Context, filename string) []error { buf := new(bytes.Buffer) err := c.withCmd(ctx, filename, buf, diff --git a/core/internal/docker/compose/hoststats.go b/core/internal/docker/compose/hoststats.go new file mode 100644 index 00000000..a5e6394a --- /dev/null +++ b/core/internal/docker/compose/hoststats.go @@ -0,0 +1,147 @@ +package compose + +import ( + "bytes" + "context" + "fmt" + "os" + "strconv" + "strings" + "time" +) + +// HostStats is whole-host usage read from /proc through the host's runner: +// locally /proc/stat and /proc/meminfo are not namespaced, and on ssh hosts +// the same files are read remotely — no agent needed. +type HostStats struct { + CPUPercent float64 + MemUsed int64 + MemTotal int64 + CPUs int32 +} + +// gap between the two /proc/stat reads a CPU percentage is computed from; +// top measures over a comparable window +const cpuSampleGap = 500 * time.Millisecond + +type procSample struct { + idle uint64 + total uint64 + cpus int32 + memTotal int64 + memAvail int64 + haveCPU bool +} + +// HostStats measures usage over its own two-read window, so concurrent +// callers can't corrupt each other's baseline. +func (c *Service) HostStats(ctx context.Context) (HostStats, error) { + before, err := c.readProc(ctx) + if err != nil { + return HostStats{}, err + } + select { + case <-ctx.Done(): + return HostStats{}, ctx.Err() + case <-time.After(cpuSampleGap): + } + after, err := c.readProc(ctx) + if err != nil { + return HostStats{}, err + } + return hostStatsFromSamples(before, after), nil +} + +func (c *Service) readProc(ctx context.Context) (procSample, error) { + // On the local Docker host, reading procfs directly avoids starting two + // external `cat` processes on every refresh. Remote hosts still go through + // their SSH runner, where direct local file access would be incorrect. + if _, local := c.runner.(*LocalRunner); local { + if err := ctx.Err(); err != nil { + return procSample{}, err + } + stat, err := os.ReadFile("/proc/stat") + if err != nil { + return procSample{}, err + } + mem, err := os.ReadFile("/proc/meminfo") + if err != nil { + return procSample{}, err + } + return parseProcSample(string(stat) + "\n" + string(mem)), nil + } + out := new(bytes.Buffer) + errW := new(bytes.Buffer) + if err := c.runner.Run(ctx, []string{"cat", "/proc/stat", "/proc/meminfo"}, ".", out, errW); err != nil { + if errW.Len() > 0 { + return procSample{}, fmt.Errorf("%s", errW.String()) + } + return procSample{}, err + } + return parseProcSample(out.String()), nil +} + +// parseProcSample digests concatenated /proc/stat + /proc/meminfo output. +func parseProcSample(raw string) procSample { + var s procSample + + for _, line := range strings.Split(raw, "\n") { + fields := strings.Fields(line) + if len(fields) < 2 { + continue + } + switch { + case fields[0] == "cpu": + // aggregate line: user nice system idle iowait irq softirq steal + // guest guest_nice — guest time is already included in user, so + // counting fields 8+ again would inflate the busy share + for i, f := range fields[1:] { + if i >= 8 { + break + } + v, err := strconv.ParseUint(f, 10, 64) + if err != nil { + continue + } + s.total += v + if i == 3 || i == 4 { // idle + iowait count as idle time + s.idle += v + } + } + s.haveCPU = true + case strings.HasPrefix(fields[0], "cpu"): + // cpu0, cpu1... one per core + s.cpus++ + case fields[0] == "MemTotal:": + if kb, err := strconv.ParseInt(fields[1], 10, 64); err == nil { + s.memTotal = kb * 1024 + } + case fields[0] == "MemAvailable:": + if kb, err := strconv.ParseInt(fields[1], 10, 64); err == nil { + s.memAvail = kb * 1024 + } + } + } + + return s +} + +// hostStatsFromSamples turns two /proc reads into usage numbers; CPU is the +// busy fraction of the interval between them, top-style. +func hostStatsFromSamples(before, after procSample) HostStats { + stats := HostStats{ + MemTotal: after.memTotal, + CPUs: after.cpus, + } + if after.memTotal > after.memAvail { + stats.MemUsed = after.memTotal - after.memAvail + } + + // counters can reset (host reboot between reads): report 0, not garbage + if before.haveCPU && after.haveCPU && after.total > before.total && after.idle >= before.idle { + dTotal := float64(after.total - before.total) + dIdle := float64(after.idle - before.idle) + stats.CPUPercent = min(max((1-dIdle/dTotal)*100, 0), 100) + } + return stats +} diff --git a/core/internal/docker/compose/hoststats_test.go b/core/internal/docker/compose/hoststats_test.go new file mode 100644 index 00000000..4b627eb1 --- /dev/null +++ b/core/internal/docker/compose/hoststats_test.go @@ -0,0 +1,65 @@ +package compose + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +// guest column (9th value) is non-zero to prove it is excluded: the kernel +// already counts guest ticks inside user +const procSampleA = `cpu 1000 0 500 8000 500 0 0 0 100 0 +cpu0 500 0 250 4000 250 0 0 0 50 0 +cpu1 500 0 250 4000 250 0 0 0 50 0 +intr 12345 +MemTotal: 32000000 kB +MemFree: 8000000 kB +MemAvailable: 20000000 kB +Buffers: 500000 kB +` + +// +2000 counted ticks, +1000 of them idle+iowait -> 50% busy; guest moved +// +800 and must not skew the result +const procSampleB = `cpu 1600 0 900 8800 700 0 0 0 900 0 +cpu0 800 0 450 4400 350 0 0 0 450 0 +cpu1 800 0 450 4400 350 0 0 0 450 0 +intr 12345 +MemTotal: 32000000 kB +MemFree: 8000000 kB +MemAvailable: 20000000 kB +Buffers: 500000 kB +` + +func TestParseProcSample(t *testing.T) { + s := parseProcSample(procSampleA) + + require.True(t, s.haveCPU) + require.EqualValues(t, 2, s.cpus) + require.EqualValues(t, uint64(10000), s.total) // guest excluded + require.EqualValues(t, uint64(8500), s.idle) // idle + iowait + require.EqualValues(t, int64(32000000)*1024, s.memTotal) + require.EqualValues(t, int64(20000000)*1024, s.memAvail) +} + +func TestParseHostProc(t *testing.T) { + stats := hostStatsFromSamples(parseProcSample(procSampleA), parseProcSample(procSampleB)) + + require.EqualValues(t, 2, stats.CPUs) + require.EqualValues(t, int64(32000000)*1024, stats.MemTotal) + require.EqualValues(t, int64(12000000)*1024, stats.MemUsed) // total - available + require.InDelta(t, 50.0, stats.CPUPercent, 0.01) +} + +func TestParseHostProcCounterReset(t *testing.T) { + // counters went backwards (host rebooted between reads): 0, not garbage + stats := hostStatsFromSamples(parseProcSample(procSampleB), parseProcSample(procSampleA)) + require.Zero(t, stats.CPUPercent) +} + +func TestParseHostProcGarbage(t *testing.T) { + garbage := parseProcSample("not proc output at all") + stats := hostStatsFromSamples(garbage, garbage) + require.Zero(t, stats.CPUPercent) + require.Zero(t, stats.MemTotal) + require.Zero(t, stats.CPUs) +} diff --git a/core/internal/docker/compose/path_resolver.go b/core/internal/docker/compose/path_resolver.go new file mode 100644 index 00000000..7290d7b1 --- /dev/null +++ b/core/internal/docker/compose/path_resolver.go @@ -0,0 +1,32 @@ +package compose + +import ( + "path/filepath" + "strings" +) + +// PathResolver maps an absolute compose-file path (as recorded by the +// daemon in the com.docker.compose.project.config_files label) back to a +// dockman filename ("alias/relpath"). Returns "" when the path lives +// outside every alias root. +type PathResolver func(absPath string) string + +// SetPathResolver wires the host-level reverse mapping; the host service +// owns the alias table, so the closure is injected after construction. +func (c *Service) SetPathResolver(resolver PathResolver) { + c.pathResolver = resolver +} + +// DockmanPath resolves a config_files label value to a dockman filename. +// The label can list several files comma-separated; the first one names +// the stack's main compose file. +func (c *Service) DockmanPath(configFilesLabel string) string { + if c.pathResolver == nil || configFilesLabel == "" { + return "" + } + first := strings.TrimSpace(strings.Split(configFilesLabel, ",")[0]) + if first == "" { + return "" + } + return c.pathResolver(filepath.ToSlash(first)) +} diff --git a/core/internal/docker/compose/shell.go b/core/internal/docker/compose/shell.go new file mode 100644 index 00000000..965ab5f6 --- /dev/null +++ b/core/internal/docker/compose/shell.go @@ -0,0 +1,168 @@ +package compose + +import ( + "context" + "fmt" + "io" + "os" + "os/exec" + "path" + "strings" + + "github.com/creack/pty" + "golang.org/x/crypto/ssh" +) + +// InteractiveShell is a PTY-backed shell: reads return terminal output, +// writes feed keystrokes, Resize follows the client's window. +type InteractiveShell interface { + io.ReadWriteCloser + Resize(cols, rows uint16) error +} + +// ShellRunner is implemented by runners that can open an interactive shell +// in the same context compose and docker commands execute in. +type ShellRunner interface { + StartShell(ctx context.Context, wd string, cols, rows uint16) (InteractiveShell, error) +} + +// StartShell opens an interactive shell on this host. With a filename the +// shell starts in that compose file's directory, otherwise in the runner +// user's home. +func (c *Service) StartShell(ctx context.Context, filename string, cols, rows uint16) (InteractiveShell, error) { + runner, ok := c.runner.(ShellRunner) + if !ok { + return nil, fmt.Errorf("interactive shell is not supported on this host") + } + + wd := "" + if filename != "" { + fileParts, err := c.parser(filename, c.hostname) + if err != nil { + return nil, fmt.Errorf("unable to resolve %q: %w", filename, err) + } + wd = path.Dir(fileParts.Fs.Join(fileParts.Fs.Root(), fileParts.Relpath)) + } + + return runner.StartShell(ctx, wd, cols, rows) +} + +// local: a PTY in the dockman container — the exact context local compose +// and docker commands run in + +func (l *LocalRunner) StartShell(ctx context.Context, wd string, cols, rows uint16) (InteractiveShell, error) { + shellBin := "/bin/sh" + if p, err := exec.LookPath("bash"); err == nil { + shellBin = p + } + + if wd == "" { + if home, err := os.UserHomeDir(); err == nil { + wd = home + } + } + + cmd := exec.CommandContext(ctx, shellBin) + cmd.Dir = wd + cmd.Env = append(os.Environ(), "TERM=xterm-256color") + + ptmx, err := pty.StartWithSize(cmd, &pty.Winsize{Cols: cols, Rows: rows}) + if err != nil { + return nil, fmt.Errorf("unable to start shell: %w", err) + } + + return &localShell{ptmx: ptmx, cmd: cmd}, nil +} + +type localShell struct { + ptmx *os.File + cmd *exec.Cmd +} + +func (s *localShell) Read(p []byte) (int, error) { return s.ptmx.Read(p) } +func (s *localShell) Write(p []byte) (int, error) { return s.ptmx.Write(p) } + +func (s *localShell) Resize(cols, rows uint16) error { + return pty.Setsize(s.ptmx, &pty.Winsize{Cols: cols, Rows: rows}) +} + +func (s *localShell) Close() error { + err := s.ptmx.Close() + if s.cmd.Process != nil { + _ = s.cmd.Process.Kill() + } + _ = s.cmd.Wait() + return err +} + +// remote: an ssh session with a requested PTY on the configured host + +func (r *RemoteRunner) StartShell(ctx context.Context, wd string, cols, rows uint16) (InteractiveShell, error) { + session, err := r.cli.NewSession() + if err != nil { + return nil, fmt.Errorf("unable to create ssh session: %w", err) + } + + modes := ssh.TerminalModes{ + ssh.ECHO: 1, + ssh.TTY_OP_ISPEED: 14400, + ssh.TTY_OP_OSPEED: 14400, + } + if err := session.RequestPty("xterm-256color", int(rows), int(cols), modes); err != nil { + _ = session.Close() + return nil, fmt.Errorf("unable to request pty: %w", err) + } + + stdin, err := session.StdinPipe() + if err != nil { + _ = session.Close() + return nil, err + } + stdout, err := session.StdoutPipe() + if err != nil { + _ = session.Close() + return nil, err + } + + // with a PTY the remote's stderr is folded into the terminal stream, so + // stdout alone carries everything + if wd != "" { + err = session.Start(fmt.Sprintf("cd %s && exec ${SHELL:-sh} -l", shellQuote(wd))) + } else { + err = session.Shell() + } + if err != nil { + _ = session.Close() + return nil, fmt.Errorf("unable to start remote shell: %w", err) + } + + sh := &remoteShell{session: session, stdin: stdin, stdout: stdout} + go func() { + <-ctx.Done() + _ = sh.Close() + }() + return sh, nil +} + +type remoteShell struct { + session *ssh.Session + stdin io.WriteCloser + stdout io.Reader +} + +func (s *remoteShell) Read(p []byte) (int, error) { return s.stdout.Read(p) } +func (s *remoteShell) Write(p []byte) (int, error) { return s.stdin.Write(p) } + +func (s *remoteShell) Resize(cols, rows uint16) error { + return s.session.WindowChange(int(rows), int(cols)) +} + +func (s *remoteShell) Close() error { + _ = s.stdin.Close() + return s.session.Close() +} + +// shellQuote single-quotes a path for a remote sh command line +func shellQuote(s string) string { + return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'" +} diff --git a/core/internal/docker/compose/shell_test.go b/core/internal/docker/compose/shell_test.go new file mode 100644 index 00000000..ec8d818f --- /dev/null +++ b/core/internal/docker/compose/shell_test.go @@ -0,0 +1,15 @@ +package compose + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestShellQuote(t *testing.T) { + require.Equal(t, "'/srv/stacks/app'", shellQuote("/srv/stacks/app")) + require.Equal(t, "'/srv/it'\\''s here'", shellQuote("/srv/it's here")) + require.Equal(t, "'/srv/with space'", shellQuote("/srv/with space")) + // injection attempts stay inert inside single quotes + require.Equal(t, "'/srv;rm -rf /'", shellQuote("/srv;rm -rf /")) +} diff --git a/core/internal/docker/container/container.go b/core/internal/docker/container/container.go index 8f032860..c4f3be00 100644 --- a/core/internal/docker/container/container.go +++ b/core/internal/docker/container/container.go @@ -2,14 +2,13 @@ package container import ( "context" - "errors" "fmt" "io" + "strings" - lu "github.com/RA341/dockman/pkg/listutils" + "github.com/docker/compose/v5/pkg/api" "github.com/moby/moby/api/types/container" "github.com/moby/moby/client" - "github.com/rs/zerolog/log" ) // LocalClient is the name given to the local docker daemon instance @@ -81,6 +80,26 @@ func (s *Service) ContainersStop(ctx context.Context, containerId ...string) err return nil } +func (s *Service) ContainersPause(ctx context.Context, containerId ...string) error { + for _, cont := range containerId { + _, err := s.Client.ContainerPause(ctx, cont, client.ContainerPauseOptions{}) + if err != nil { + return fmt.Errorf("unable to pause Container: %s => %w", cont, err) + } + } + return nil +} + +func (s *Service) ContainersUnpause(ctx context.Context, containerId ...string) error { + for _, cont := range containerId { + _, err := s.Client.ContainerUnpause(ctx, cont, client.ContainerUnpauseOptions{}) + if err != nil { + return fmt.Errorf("unable to unpause Container: %s => %w", cont, err) + } + } + return nil +} + func (s *Service) ContainersRestart(ctx context.Context, containerId ...string) error { for _, cont := range containerId { _, err := s.Client.ContainerRestart(ctx, cont, client.ContainerRestartOptions{}) @@ -103,13 +122,14 @@ func (s *Service) ContainersRemove(ctx context.Context, containerId ...string) e return nil } -func (s *Service) ContainerExec(ctx context.Context, containerID string, cmd string) (client.HijackedResponse, error) { +func (s *Service) ContainerExec(ctx context.Context, containerID string, cmd string, user string) (client.HijackedResponse, error) { execConfig := client.ExecCreateOptions{ AttachStdin: true, AttachStdout: true, AttachStderr: true, TTY: true, Cmd: []string{cmd}, + User: user, } execResp, err := s.Client.ExecCreate(ctx, containerID, execConfig) @@ -129,6 +149,12 @@ func (s *Service) ContainerExec(ctx context.Context, containerID string, cmd str return resp.HijackedResponse, nil } +// defaultLogTail bounds how much history is replayed when a log stream opens. +// Without a Tail the daemon streams the container's entire json-file backlog +// before following, a large transient memory spike on every open for a +// long-lived container. +const defaultLogTail = "1000" + func (s *Service) ContainerLogs(ctx context.Context, containerID string) (io.ReadCloser, bool, error) { inspect, err := s.Client.ContainerInspect(ctx, containerID, client.ContainerInspectOptions{}) if err != nil { @@ -140,6 +166,7 @@ func (s *Service) ContainerLogs(ctx context.Context, containerID string) (io.Rea ShowStderr: true, Follow: true, Details: true, + Tail: defaultLogTail, }) if err != nil { return nil, false, fmt.Errorf("unable to get container logs: %w", err) @@ -148,6 +175,26 @@ func (s *Service) ContainerLogs(ctx context.Context, containerID string) (io.Rea return logStream, inspect.Container.Config.Tty, nil } +// ContainersListRunning lists ALL containers (any state) and prunes cached +// sampling state for containers that no longer exist (this is the host-wide +// listing the stats views poll). Non-running containers flow through the +// stats stream as identity-only rows — statsFor skips their metric read — +// so state counts (stopped/paused/...) stay truthful without extra cost. +func (s *Service) ContainersListRunning(ctx context.Context) ([]container.Summary, error) { + list, err := s.Client.ContainerList(ctx, client.ContainerListOptions{All: true}) + if err != nil { + return nil, fmt.Errorf("could not list containers: %w", err) + } + + live := make(map[string]struct{}, len(list.Items)) + for _, c := range list.Items { + live[c.ID] = struct{}{} + } + cacheFor(s.Client).prune(live) + + return list.Items, nil +} + func (s *Service) Stats(ctx context.Context, filter client.ContainerListOptions) ([]Stats, error) { contRes, err := s.Client.ContainerList(ctx, filter) if err != nil { @@ -155,6 +202,14 @@ func (s *Service) Stats(ctx context.Context, filter client.ContainerListOptions) } containers := contRes.Items + // this is the host-wide listing: drop cached sampling state for + // containers that no longer exist + live := make(map[string]struct{}, len(containers)) + for _, c := range containers { + live[c.ID] = struct{}{} + } + cacheFor(s.Client).prune(live) + if len(containers) == 0 { return []Stats{}, nil } @@ -164,14 +219,33 @@ func (s *Service) Stats(ctx context.Context, filter client.ContainerListOptions) } func (s *Service) ContainerGetStatsFromList(ctx context.Context, containers []container.Summary) []Stats { - return lu.ParallelLoop(containers, func(r container.Summary) (Stats, bool) { - stats, err := s.getAndFormatStats(ctx, r) - if err != nil && !errors.Is(err, context.Canceled) { - log.Warn().Err(err).Str("container", r.ID[:12]).Msg("could not convert stats, skipping...") - return Stats{}, false + return s.collectStats(ctx, containers) +} + +// ContainerListByComposeFile returns every container whose compose +// config-files label references the given absolute compose file path. One +// host-wide listing matched in memory: a label equality filter can't be used +// because config_files may hold several comma-separated paths (overrides). +func (s *Service) ContainerListByComposeFile(ctx context.Context, absPath string) ([]container.Summary, error) { + list, err := s.Client.ContainerList(ctx, client.ContainerListOptions{All: true}) + if err != nil { + return nil, fmt.Errorf("could not list containers: %w", err) + } + + var out []container.Summary + for _, ct := range list.Items { + cfg := ct.Labels[api.ConfigFilesLabel] + if cfg == "" { + continue } - return stats, true - }) + for _, p := range strings.Split(cfg, ",") { + if strings.TrimSpace(p) == absPath { + out = append(out, ct) + break + } + } + } + return out, nil } func (s *Service) Inspect(ctx context.Context, containerId string) (container.InspectResponse, error) { diff --git a/core/internal/docker/container/events_hub.go b/core/internal/docker/container/events_hub.go new file mode 100644 index 00000000..cb973fd9 --- /dev/null +++ b/core/internal/docker/container/events_hub.go @@ -0,0 +1,199 @@ +package container + +import ( + "context" + "strconv" + "strings" + "sync" + "time" + + "github.com/RA341/dockman/pkg/syncmap" + "github.com/moby/moby/api/types/events" + "github.com/moby/moby/client" + "github.com/rs/zerolog/log" +) + +// Event is a filtered daemon event relevant to container state, ready for +// the UI to react to (and for the activity log to persist later). +type Event struct { + // base action: create/start/stop/die/kill/restart/pause/unpause/destroy/ + // rename/update/oom/health_status + Action string + // health_status only: healthy / unhealthy / ... + Status string + ID string // 12-char container id + Name string + Image string + TimeNano int64 +} + +// allowedActions lists the container actions worth reacting to; everything +// else (exec_*, attach, top, archive...) is noise for state-driven views. +var allowedActions = map[string]struct{}{ + "create": {}, "start": {}, "stop": {}, "die": {}, "kill": {}, + "restart": {}, "pause": {}, "unpause": {}, "destroy": {}, + "rename": {}, "update": {}, "oom": {}, "health_status": {}, +} + +// eventsHub fans a single daemon /events subscription out to every listener +// of a host. It lives package-wide keyed by the moby client — one per +// connected host — because the request-scoped services are rebuilt on every +// RPC. The daemon subscription starts with the first listener, reconnects +// with backoff when the daemon drops it, and stops with the last listener. +type eventsHub struct { + mu sync.Mutex + subscribers map[chan Event]struct{} + stop context.CancelFunc + + // the daemon repeats health_status on every probe; only transitions are + // interesting + lastHealth map[string]string + // transport-level dedup, Dockhand-style: the same (container, action, + // timestamp) delivered twice is dropped, distinct events never are + recent map[string]int64 +} + +var eventHubs syncmap.Map[*client.Client, *eventsHub] + +func hubFor(cli *client.Client) *eventsHub { + hub, _ := eventHubs.LoadOrStore(cli, &eventsHub{ + subscribers: make(map[chan Event]struct{}), + lastHealth: make(map[string]string), + recent: make(map[string]int64), + }) + return hub +} + +// SubscribeEvents delivers this host's filtered container events until the +// returned cancel function is called. A slow consumer drops events rather +// than blocking the other listeners. +func (s *Service) SubscribeEvents() (<-chan Event, func()) { + hub := hubFor(s.Client) + + ch := make(chan Event, 16) + hub.mu.Lock() + hub.subscribers[ch] = struct{}{} + if hub.stop == nil { + runCtx, cancel := context.WithCancel(context.Background()) + hub.stop = cancel + go hub.run(runCtx, s.Client) + } + hub.mu.Unlock() + + var once sync.Once + unsubscribe := func() { + once.Do(func() { + hub.mu.Lock() + delete(hub.subscribers, ch) + if len(hub.subscribers) == 0 && hub.stop != nil { + hub.stop() + hub.stop = nil + } + hub.mu.Unlock() + }) + } + return ch, unsubscribe +} + +func (h *eventsHub) run(ctx context.Context, cli *client.Client) { + filters := client.Filters{} + filters.Add("type", string(events.ContainerEventType)) + + backoff := time.Second + for { + res := cli.Events(ctx, client.EventsListOptions{Filters: filters}) + + stream: + for { + select { + case <-ctx.Done(): + return + case msg := <-res.Messages: + backoff = time.Second + if ev, ok := h.filter(msg); ok { + h.broadcast(ev) + } + case err := <-res.Err: + if ctx.Err() != nil { + return + } + log.Warn().Err(err).Msg("docker events stream interrupted, reconnecting") + break stream + } + } + + // the client does not reopen the stream itself; back off and resubscribe + select { + case <-ctx.Done(): + return + case <-time.After(backoff): + } + if backoff < 30*time.Second { + backoff *= 2 + } + } +} + +func (h *eventsHub) broadcast(ev Event) { + h.mu.Lock() + defer h.mu.Unlock() + for ch := range h.subscribers { + select { + case ch <- ev: + default: // slow consumer: drop rather than block the hub + } + } +} + +func (h *eventsHub) filter(msg events.Message) (Event, bool) { + if msg.Type != events.ContainerEventType { + return Event{}, false + } + + // health arrives as "health_status: healthy" + action, status, _ := strings.Cut(string(msg.Action), ": ") + if _, ok := allowedActions[action]; !ok { + return Event{}, false + } + + id := msg.Actor.ID + if len(id) > 12 { + id = id[:12] + } + + h.mu.Lock() + defer h.mu.Unlock() + + if action == "health_status" { + if h.lastHealth[id] == status { + return Event{}, false + } + h.lastHealth[id] = status + } + if action == "destroy" { + delete(h.lastHealth, id) + } + + key := id + "|" + string(msg.Action) + "|" + strconv.FormatInt(msg.TimeNano, 10) + now := time.Now().UnixNano() + if last, ok := h.recent[key]; ok && now-last < 5*int64(time.Second) { + return Event{}, false + } + h.recent[key] = now + if len(h.recent) > 512 { + for k, ts := range h.recent { + if now-ts > 10*int64(time.Second) { + delete(h.recent, k) + } + } + } + + return Event{ + Action: action, + Status: status, + ID: id, + Name: msg.Actor.Attributes["name"], + Image: msg.Actor.Attributes["image"], + TimeNano: msg.TimeNano, + }, true +} diff --git a/core/internal/docker/container/image.go b/core/internal/docker/container/image.go index 6c0a8f3f..55f9b804 100644 --- a/core/internal/docker/container/image.go +++ b/core/internal/docker/container/image.go @@ -4,8 +4,10 @@ import ( "context" "fmt" "io" + "strings" "github.com/RA341/dockman/pkg/fileutil" + "github.com/docker/compose/v5/pkg/api" "github.com/moby/moby/api/types/image" "github.com/moby/moby/client" "github.com/rs/zerolog/log" @@ -43,6 +45,46 @@ func (s *Service) ImageInspect(ctx context.Context, id string) (client.ImageInsp return inspect, hist, nil } +// ImageContainer describes one container created from a given image. +type ImageContainer struct { + ID string + Name string + State string + ComposeProject string +} + +// ImageContainers returns every container created from the given image. The +// image inspect data does not carry this, so it is derived from the container +// list filtered by the image "ancestor". +func (s *Service) ImageContainers(ctx context.Context, imageID string) ([]ImageContainer, error) { + filters := client.Filters{} + filters.Add("ancestor", imageID) + + resp, err := s.Client.ContainerList(ctx, client.ContainerListOptions{ + All: true, + Filters: filters, + }) + if err != nil { + return nil, fmt.Errorf("failed to list containers for image %s: %w", imageID, err) + } + + out := make([]ImageContainer, 0, len(resp.Items)) + for _, c := range resp.Items { + name := "" + if len(c.Names) > 0 { + name = strings.TrimPrefix(c.Names[0], "/") + } + out = append(out, ImageContainer{ + ID: c.ID, + Name: name, + State: string(c.State), + ComposeProject: c.Labels[api.ProjectLabel], + }) + } + + return out, nil +} + func (s *Service) ImagePull(ctx context.Context, imageTag string, writer io.Writer) error { log.Info().Msg("Pulling latest image") @@ -70,12 +112,72 @@ func (s *Service) ImageDelete(ctx context.Context, imageId string) ([]image.Dele return remove.Items, err } +// ImageUsageCounts returns, per image ID, how many containers (running or +// stopped) were created from it. The image list's own Containers field is +// unreliable (-1 when not computed, 0 on some image stores), while image +// prune decides from the daemon's actual reference counts — so usage comes +// from the disk-usage report (the daemon-computed count) complemented by the +// container list, and matches what prune would really do. +func (s *Service) ImageUsageCounts(ctx context.Context) (map[string]int64, error) { + counts := make(map[string]int64) + + diskUsage, err := s.Client.DiskUsage(ctx, client.DiskUsageOptions{ + Images: true, + Verbose: true, + }) + if err != nil { + return nil, fmt.Errorf("failed to get disk usage data: %w", err) + } + for _, img := range diskUsage.Images.Items { + if img.Containers > 0 { + counts[img.ID] = img.Containers + } + } + + // an image missing from disk usage (or reported without a computed count) + // must still show as used while a container references it + resp, err := s.Client.ContainerList(ctx, client.ContainerListOptions{All: true}) + if err != nil { + return nil, fmt.Errorf("failed to list containers: %w", err) + } + perID := make(map[string]int64, len(resp.Items)) + for _, c := range resp.Items { + perID[c.ImageID]++ + } + for id, n := range perID { + if n > counts[id] { + counts[id] = n + } + } + + return counts, nil +} + +// ImageDescendantContainers reports how many containers were created from the +// image or from any image built on top of it, via the daemon's "ancestor" +// filter. This catches base/parent images with no direct container: prune +// keeps them because a dependent child image is still in use, so they must +// not be reported as unused. +func (s *Service) ImageDescendantContainers(ctx context.Context, imageID string) (int64, error) { + filters := client.Filters{} + filters.Add("ancestor", imageID) + + resp, err := s.Client.ContainerList(ctx, client.ContainerListOptions{ + All: true, + Filters: filters, + }) + if err != nil { + return 0, fmt.Errorf("failed to list containers for image %s: %w", imageID, err) + } + return int64(len(resp.Items)), nil +} + func (s *Service) ImagePruneUntagged(ctx context.Context) (image.PruneReport, error) { filter := client.Filters{} // removes dangling (untagged) mostly due to image being updated filter.Add("dangling", "true") - prune, err := s.Client.ImagePrune(ctx, client.ImagePruneOptions{}) + prune, err := s.Client.ImagePrune(ctx, client.ImagePruneOptions{Filters: filter}) if err != nil { return prune.Report, err } @@ -111,6 +213,9 @@ func (s *Service) ImageDive(ctx context.Context, imageId string) (*diveImg.Analy if err != nil { return nil, fmt.Errorf("failed to extract image data %s: %w", imageId, err) } + // body streams the whole image tar (can be many GB); it must be closed or + // the response body and the daemon-side export stay pinned in memory. + defer fileutil.Close(body) parse, err := docker.NewImageArchive(body) if err != nil { diff --git a/core/internal/docker/container/logs_stream.go b/core/internal/docker/container/logs_stream.go new file mode 100644 index 00000000..bd3237c8 --- /dev/null +++ b/core/internal/docker/container/logs_stream.go @@ -0,0 +1,289 @@ +package container + +import ( + "bytes" + "context" + "fmt" + "io" + "strconv" + "strings" + "sync" + "time" + + "github.com/moby/moby/api/pkg/stdcopy" + "github.com/moby/moby/client" + "github.com/rs/zerolog/log" +) + +const ( + // defaultStreamTail bounds the history replayed per container when the + // client does not ask for a specific amount + defaultStreamTail = 1000 + + // mergedTailFloor is the per-container minimum when a merged request + // splits its tail budget across containers + mergedTailFloor = 50 + + // maxPartialLine force-flushes a line that never sees a newline + // (\r-only progress output) so the carry buffer stays bounded + maxPartialLine = 64 * 1024 + + // StreamInternal tags lines dockman itself injects (stream failures); + // they carry no daemon timestamp and are not container output + StreamInternal int32 = 0 + StreamStdout int32 = 1 + StreamStderr int32 = 2 +) + +// LogsStreamOptions mirrors the ContainerLogsStream request +type LogsStreamOptions struct { + Tail int32 + Since int64 // unix seconds, 0 = unbounded + Until int64 + Follow bool +} + +// LogLine is one demuxed container log line; TimeNano is 0 when the daemon +// line carried no parsable timestamp +type LogLine struct { + ContainerID string + ContainerName string + Text string + TimeNano int64 + Stream int32 +} + +// LogsStream reads the logs of every requested container concurrently and +// calls emit for each line until all readers finish (follow=false) or ctx is +// canceled. emit may be called from multiple goroutines. +func (s *Service) LogsStream(ctx context.Context, containerIDs []string, opts LogsStreamOptions, emit func(LogLine)) error { + if len(containerIDs) == 0 { + return fmt.Errorf("at least one container id is required") + } + + tail := opts.Tail + if tail <= 0 { + tail = defaultStreamTail + } + // merged view: treat tail as a global budget so N containers do not each + // replay the full amount (the client caps its buffer anyway) + if n := int32(len(containerIDs)); n > 1 { + perContainer := tail / n + if perContainer < mergedTailFloor { + perContainer = mergedTailFloor + } + if perContainer < tail { + tail = perContainer + } + } + + logOpts := client.ContainerLogsOptions{ + ShowStdout: true, + ShowStderr: true, + // always ask the daemon for timestamps: they are stripped from the + // text and carried separately so the client can toggle them + Timestamps: true, + Follow: opts.Follow, + Tail: strconv.Itoa(int(tail)), + } + if opts.Since > 0 { + logOpts.Since = strconv.FormatInt(opts.Since, 10) + } + if opts.Until > 0 { + logOpts.Until = strconv.FormatInt(opts.Until, 10) + } + + streamCtx, cancel := context.WithCancel(ctx) + defer cancel() + + var wg sync.WaitGroup + for _, id := range containerIDs { + wg.Add(1) + go func(containerID string) { + defer wg.Done() + err := s.streamContainerLogs(streamCtx, containerID, logOpts, emit) + if err != nil && streamCtx.Err() == nil { + log.Warn().Err(err).Str("container", containerID).Msg("container log stream failed") + // surface the failure in the viewer instead of dying silently; + // StreamInternal + no timestamp keeps it out of the client's + // replay watermark and reconnect pacing + emit(LogLine{ + ContainerID: containerID, + ContainerName: containerID, + Text: "dockman: log stream error: " + err.Error(), + Stream: StreamInternal, + }) + } + }(id) + } + wg.Wait() + return nil +} + +// streamContainerLogs keeps a container's logs flowing for as long as the +// request lives. The daemon ends a follow stream every time the container +// stops, so in follow mode the reader is reopened, resuming with nanosecond +// precision right after the last delivered line — a restarted container keeps +// logging into the same stream instead of going silent. +func (s *Service) streamContainerLogs(ctx context.Context, containerID string, logOpts client.ContainerLogsOptions, emit func(LogLine)) error { + var lastNano int64 + emitTracked := func(l LogLine) { + if l.TimeNano > lastNano { + lastNano = l.TimeNano + } + emit(l) + } + + opts := logOpts + reopenDelay := time.Second + for { + emittedBefore := lastNano + err := s.openContainerLogsOnce(ctx, containerID, opts, emitTracked) + if err != nil || !opts.Follow || ctx.Err() != nil { + return err + } + + // resume just past the last delivered line; Tail switches to "all" so + // nothing inside the resume window is skipped + if lastNano > 0 { + opts.Since = time.Unix(0, lastNano+1).UTC().Format(time.RFC3339Nano) + opts.Tail = "all" + } + + // a container that stays down should not be polled aggressively + if lastNano > emittedBefore { + reopenDelay = time.Second + } else { + reopenDelay = min(reopenDelay*2, 10*time.Second) + } + select { + case <-ctx.Done(): + return nil + case <-time.After(reopenDelay): + } + } +} + +func (s *Service) openContainerLogsOnce(ctx context.Context, containerID string, logOpts client.ContainerLogsOptions, emit func(LogLine)) error { + inspect, err := s.Client.ContainerInspect(ctx, containerID, client.ContainerInspectOptions{}) + if err != nil { + return fmt.Errorf("unable to inspect container: %w", err) + } + name := strings.TrimPrefix(inspect.Container.Name, "/") + + reader, err := s.Client.ContainerLogs(ctx, containerID, logOpts) + if err != nil { + return fmt.Errorf("unable to open container logs: %w", err) + } + defer func() { _ = reader.Close() }() + + // the demux loop below blocks on reader.Reader; closing the reader when + // the client goes away is what unblocks it. openCtx scopes the closer + // goroutine to this open, so reopen cycles do not accumulate goroutines. + openCtx, openCancel := context.WithCancel(ctx) + defer openCancel() + go func() { + <-openCtx.Done() + _ = reader.Close() + }() + + stdout := &logLineWriter{id: containerID, name: name, stream: StreamStdout, emit: emit} + stderr := &logLineWriter{id: containerID, name: name, stream: StreamStderr, emit: emit} + defer stdout.Flush() + defer stderr.Flush() + + if inspect.Container.Config.Tty { + // tty streams have no multiplex framing, everything is stdout + _, err = io.Copy(stdout, reader) + } else { + _, err = stdcopy.StdCopy(stdout, stderr, reader) + } + if err != nil && ctx.Err() == nil { + return err + } + return nil +} + +// logLineWriter splits a raw log byte stream into lines, keeping the trailing +// partial line between writes until its newline (or Flush) arrives; the carry +// buffer is reused across writes and force-flushed if it grows pathological +type logLineWriter struct { + id string + name string + stream int32 + emit func(LogLine) + partial []byte +} + +func (w *logLineWriter) Write(p []byte) (int, error) { + w.partial = append(w.partial, p...) + + start := 0 + for { + idx := bytes.IndexByte(w.partial[start:], '\n') + if idx < 0 { + break + } + w.emitLine(w.partial[start : start+idx]) + start += idx + 1 + } + if start > 0 { + kept := copy(w.partial, w.partial[start:]) + w.partial = w.partial[:kept] + } + + // \r-only progress output never produces a newline: flush a bounded + // snapshot instead of growing forever + if len(w.partial) > maxPartialLine { + w.emitLine(w.partial) + w.partial = w.partial[:0] + } + return len(p), nil +} + +// Flush emits the pending partial line, if any; call it when the stream ends +func (w *logLineWriter) Flush() { + if len(w.partial) > 0 { + w.emitLine(w.partial) + w.partial = w.partial[:0] + } +} + +func (w *logLineWriter) emitLine(line []byte) { + text := strings.TrimSuffix(string(line), "\r") + // the daemon timestamp sits at the start of the line: take it off before + // collapsing any carriage-return overwrites + timeNano, text := splitLogTimestamp(text) + // carriage-return overwrites (progress bars): a terminal only shows what + // was written after the last \r, so keep exactly that + if idx := strings.LastIndexByte(text, '\r'); idx >= 0 { + text = text[idx+1:] + } + w.emit(LogLine{ + ContainerID: w.id, + ContainerName: w.name, + Text: text, + TimeNano: timeNano, + Stream: w.stream, + }) +} + +// splitLogTimestamp strips the RFC3339Nano prefix the daemon adds when logs +// are requested with Timestamps: true; lines without one pass through as-is +func splitLogTimestamp(line string) (int64, string) { + idx := strings.IndexByte(line, ' ') + if idx <= 0 { + return 0, line + } + // cheap shape check before the (comparatively) expensive time.Parse: + // daemon timestamps always look like 2006-01-02T15:04:05... + head := line[:idx] + if len(head) < 20 || head[4] != '-' || head[7] != '-' || head[10] != 'T' { + return 0, line + } + ts, err := time.Parse(time.RFC3339Nano, head) + if err != nil { + return 0, line + } + return ts.UnixNano(), line[idx+1:] +} diff --git a/core/internal/docker/container/logs_stream_test.go b/core/internal/docker/container/logs_stream_test.go new file mode 100644 index 00000000..85eabaf3 --- /dev/null +++ b/core/internal/docker/container/logs_stream_test.go @@ -0,0 +1,130 @@ +package container + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func collectLines(w *logLineWriter) *[]LogLine { + var got []LogLine + w.emit = func(l LogLine) { got = append(got, l) } + return &got +} + +func TestLogLineWriterSplitsLines(t *testing.T) { + w := &logLineWriter{id: "abc", name: "web", stream: StreamStdout} + got := collectLines(w) + + _, err := w.Write([]byte("first line\nsecond line\n")) + require.NoError(t, err) + + require.Len(t, *got, 2) + require.Equal(t, "first line", (*got)[0].Text) + require.Equal(t, "second line", (*got)[1].Text) + require.Equal(t, "abc", (*got)[0].ContainerID) + require.Equal(t, "web", (*got)[0].ContainerName) + require.Equal(t, StreamStdout, (*got)[0].Stream) +} + +func TestLogLineWriterCarryoverBetweenWrites(t *testing.T) { + w := &logLineWriter{stream: StreamStderr} + got := collectLines(w) + + _, _ = w.Write([]byte("split in ")) + require.Empty(t, *got, "no newline yet, nothing should be emitted") + + _, _ = w.Write([]byte("the middle\nnext")) + require.Len(t, *got, 1) + require.Equal(t, "split in the middle", (*got)[0].Text) + require.Equal(t, StreamStderr, (*got)[0].Stream) + + // stream ends without a trailing newline: Flush emits the leftover + w.Flush() + require.Len(t, *got, 2) + require.Equal(t, "next", (*got)[1].Text) + + // flushing twice must not duplicate the line + w.Flush() + require.Len(t, *got, 2) +} + +func TestLogLineWriterTrimsCarriageReturn(t *testing.T) { + w := &logLineWriter{} + got := collectLines(w) + + _, _ = w.Write([]byte("windows style\r\n")) + require.Len(t, *got, 1) + require.Equal(t, "windows style", (*got)[0].Text) +} + +func TestLogLineWriterCollapsesProgressOverwrites(t *testing.T) { + w := &logLineWriter{} + got := collectLines(w) + + // a terminal shows only what was written after the last \r + _, _ = w.Write([]byte("Downloading 10%\rDownloading 60%\rDone\n")) + require.Len(t, *got, 1) + require.Equal(t, "Done", (*got)[0].Text) + + // the daemon timestamp sits before the overwrites and must survive + stamp := "2026-07-17T10:11:12.123456789Z" + _, _ = w.Write([]byte(stamp + " 10%\r20%\n")) + require.Len(t, *got, 2) + require.Equal(t, "20%", (*got)[1].Text) + require.NotZero(t, (*got)[1].TimeNano) +} + +func TestLogLineWriterBoundsPartialBuffer(t *testing.T) { + w := &logLineWriter{} + got := collectLines(w) + + // \r-only output never produces a newline: the carry buffer must + // force-flush instead of growing forever + chunk := make([]byte, maxPartialLine+16) + for i := range chunk { + chunk[i] = 'x' + } + _, _ = w.Write(chunk) + require.Len(t, *got, 1) + require.LessOrEqual(t, len(w.partial), maxPartialLine) +} + +func TestSplitLogTimestamp(t *testing.T) { + stamp := "2026-07-17T10:11:12.123456789Z" + nano, text := splitLogTimestamp(stamp + " hello world") + expected, err := time.Parse(time.RFC3339Nano, stamp) + require.NoError(t, err) + require.Equal(t, expected.UnixNano(), nano) + require.Equal(t, "hello world", text) + + // no timestamp prefix: line passes through untouched + nano, text = splitLogTimestamp("plain text line") + require.Zero(t, nano) + require.Equal(t, "plain text line", text) + + // empty and spaceless lines + nano, text = splitLogTimestamp("") + require.Zero(t, nano) + require.Equal(t, "", text) + + nano, text = splitLogTimestamp("nospace") + require.Zero(t, nano) + require.Equal(t, "nospace", text) + + // timestamped empty line (blank log line from the daemon) + nano, text = splitLogTimestamp(stamp + " ") + require.Equal(t, expected.UnixNano(), nano) + require.Equal(t, "", text) +} + +func TestLogLineWriterKeepsDaemonTimestamps(t *testing.T) { + w := &logLineWriter{id: "abc", name: "web", stream: StreamStdout} + got := collectLines(w) + + _, _ = w.Write([]byte("2026-07-17T08:00:00.000000000Z app started\n")) + require.Len(t, *got, 1) + require.Equal(t, "app started", (*got)[0].Text) + require.NotZero(t, (*got)[0].TimeNano) +} diff --git a/core/internal/docker/container/network.go b/core/internal/docker/container/network.go index ea4c039a..e30ec691 100644 --- a/core/internal/docker/container/network.go +++ b/core/internal/docker/container/network.go @@ -56,3 +56,16 @@ func (s *Service) NetworksPrune(ctx context.Context) error { _, err := s.Client.NetworkPrune(ctx, client.NetworkPruneOptions{}) return err } + +func (s *Service) NetworkConnectContainer(ctx context.Context, networkID, containerID string) error { + _, err := s.Client.NetworkConnect(ctx, networkID, client.NetworkConnectOptions{Container: containerID}) + return err +} + +func (s *Service) NetworkDisconnectContainer(ctx context.Context, networkID, containerID string) error { + _, err := s.Client.NetworkDisconnect(ctx, networkID, client.NetworkDisconnectOptions{ + Container: containerID, + Force: false, + }) + return err +} diff --git a/core/internal/docker/container/stats_cache.go b/core/internal/docker/container/stats_cache.go new file mode 100644 index 00000000..3f3af585 --- /dev/null +++ b/core/internal/docker/container/stats_cache.go @@ -0,0 +1,321 @@ +package container + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "slices" + "sync" + "time" + + "github.com/RA341/dockman/pkg/fileutil" + "github.com/RA341/dockman/pkg/syncmap" + "github.com/moby/moby/api/types/container" + "github.com/moby/moby/client" + "github.com/rs/zerolog/log" +) + +// statsConcurrency bounds the instantaneous fan-out against the daemon. +// Samples are one-shot and complete quickly; a modest bound smooths CPU and +// socket-proxy load on large hosts without affecting the five-second cadence. +const statsConcurrency = 8 + +// inspectData caches the inspect-only fields served with stats. An entry is +// valid while the summary's Status text ("Up 3 minutes", "Exited (0)...") is +// unchanged: the daemon rewrites it whenever the underlying state moves — +// including restarts ("Up 1 second") — so the cache refreshes itself exactly +// when it could be stale instead of paying one ContainerInspect per container +// on every poll. +type inspectData struct { + status string + startedAt string + restartCount int32 +} + +// hostStatsCache carries per-host inspect data between stats requests. It is +// keyed package-wide by the moby client — one per connected host — because +// container.Service is rebuilt on every RPC and cannot hold state itself. +type hostStatsCache struct { + mu sync.Mutex + inspects map[string]inspectData + cpu map[string]cpuSample +} + +type cpuSample struct { + startedAt string + total uint64 + system uint64 + sampledAt time.Time + percent float64 +} + +var hostCaches syncmap.Map[*client.Client, *hostStatsCache] + +func cacheFor(cli *client.Client) *hostStatsCache { + cache, _ := hostCaches.LoadOrStore(cli, &hostStatsCache{ + inspects: make(map[string]inspectData), + cpu: make(map[string]cpuSample), + }) + return cache +} + +// prune drops cached state for containers that no longer exist, so the map +// doesn't grow forever as containers are recreated. Call it only with a full +// host listing: a filtered subset would evict live neighbors. +func (c *hostStatsCache) prune(live map[string]struct{}) { + c.mu.Lock() + defer c.mu.Unlock() + for id := range c.inspects { + if _, ok := live[id]; !ok { + delete(c.inspects, id) + } + } + for id := range c.cpu { + if _, ok := live[id]; !ok { + delete(c.cpu, id) + } + } +} + +// statsReadTimeout caps a single container's stats collection so one wedged +// container can never stall a whole streaming cycle. +const statsReadTimeout = 8 * time.Second + +// StatsStream reads every container's stats concurrently — one goroutine per +// container, each with its own timeout — and hands each result to emit as +// soon as it is ready. emit is called from a single goroutine, so it may +// write to a network stream without further locking. Failed containers are +// skipped, not fatal. +func (s *Service) StatsStream(ctx context.Context, containers []container.Summary, emit func(Stats)) { + cache := cacheFor(s.Client) + + ch := make(chan Stats) + sem := make(chan struct{}, statsConcurrency) + var wg sync.WaitGroup + for _, cont := range containers { + wg.Add(1) + go func(cont container.Summary) { + defer wg.Done() + select { + case sem <- struct{}{}: + defer func() { <-sem }() + case <-ctx.Done(): + return + } + + cctx, cancel := context.WithTimeout(ctx, statsReadTimeout) + defer cancel() + + stat, err := s.statsFor(cctx, cache, cont) + if err != nil { + if !errors.Is(err, context.Canceled) { + log.Warn().Err(err).Str("container", cont.ID[:12]).Msg("could not collect stats, skipping...") + } + return + } + select { + case ch <- stat: + case <-ctx.Done(): + } + }(cont) + } + go func() { + wg.Wait() + close(ch) + }() + + for stat := range ch { + emit(stat) + } +} + +// collectStats gathers stats for the given containers with bounded concurrency, +// preserving input order. Containers that fail are skipped, not fatal. +func (s *Service) collectStats(ctx context.Context, containers []container.Summary) []Stats { + cache := cacheFor(s.Client) + + results := make([]*Stats, len(containers)) + sem := make(chan struct{}, statsConcurrency) + var wg sync.WaitGroup + for i, cont := range containers { + wg.Add(1) + go func(i int, cont container.Summary) { + defer wg.Done() + sem <- struct{}{} + defer func() { <-sem }() + + stat, err := s.statsFor(ctx, cache, cont) + if err != nil { + if !errors.Is(err, context.Canceled) { + log.Warn().Err(err).Str("container", cont.ID[:12]).Msg("could not collect stats, skipping...") + } + return + } + results[i] = &stat + }(i, cont) + } + wg.Wait() + + out := make([]Stats, 0, len(containers)) + for _, r := range results { + if r != nil { + out = append(out, *r) + } + } + return out +} + +// MetricsPending marks a Stats carrying only identity fields, streamed ahead +// of the real reading so the UI paints rows instantly while the daemon +// samples (~1s per container). The UI renders metric cells as pending until +// the real stats replace the row. +const MetricsPending = -1 + +// IdentityStats returns the summary's identity fields only, metrics pending. +func IdentityStats(info container.Summary) Stats { + return Stats{ + ID: info.ID[:12], + Name: info.Names[0], + Image: info.Image, + State: string(info.State), + Health: summaryHealth(info), + IPAddress: summaryIPs(info), + CPUUsage: MetricsPending, + } +} + +func (s *Service) statsFor(ctx context.Context, cache *hostStatsCache, info container.Summary) (Stats, error) { + stat := IdentityStats(info) + stat.CPUUsage = 0 + + // inspect-only fields, served from cache while the container's status text + // is unchanged; non-fatal so a race with a disappearing container can't + // take the whole table down + var startedAt string + if insp, err := s.inspectDataFor(ctx, cache, info); err == nil { + stat.StartedAt = insp.startedAt + startedAt = insp.startedAt + stat.RestartCount = insp.restartCount + } else if !errors.Is(err, context.Canceled) { + log.Debug().Err(err).Str("container", stat.ID).Msg("could not inspect container for stats") + } + + // a container that isn't running has no live metrics to read + if stat.State != "running" { + cache.mu.Lock() + delete(cache.cpu, info.ID) + cache.mu.Unlock() + return stat, nil + } + + statsJSON, err := s.readStats(ctx, info.ID) + if err != nil { + return Stats{}, err + } + + stat.CPUUsage = cache.cpuPercent(info.ID, startedAt, statsJSON) + stat.MemoryUsage = formatMemory(statsJSON) + stat.MemoryLimit = statsJSON.MemoryStats.Limit + stat.NetworkRx, stat.NetworkTx = formatNetwork(statsJSON) + stat.BlockRead, stat.BlockWrite = formatDiskIO(statsJSON) + + return stat, nil +} + +func (c *hostStatsCache) cpuPercent(id, startedAt string, current container.StatsResponse) float64 { + now := time.Now() + next := cpuSample{ + startedAt: startedAt, + total: current.CPUStats.CPUUsage.TotalUsage, + system: current.CPUStats.SystemUsage, + sampledAt: now, + } + c.mu.Lock() + previous, ok := c.cpu[id] + if !ok || previous.startedAt != startedAt { + c.cpu[id] = next + c.mu.Unlock() + return 0 + } + // Two open clients commonly poll on the same five-second boundary. Reuse + // the just-computed value instead of replacing the baseline with an almost + // identical sample (which creates noisy near-zero deltas for one client). + if now.Sub(previous.sampledAt) < time.Second { + c.mu.Unlock() + return previous.percent + } + next.percent = formatCPU(current, previous.total, previous.system) + c.cpu[id] = next + c.mu.Unlock() + return next.percent +} + +func (s *Service) readStats(ctx context.Context, id string) (container.StatsResponse, error) { + // A one-shot sample returns immediately. The previous counters are kept in + // hostStatsCache, so CPU is calculated over the normal refresh interval + // instead of asking the daemon to spend an extra second sampling every + // container during every cycle. + resp, err := s.Client.ContainerStats(ctx, id, client.ContainerStatsOptions{ + IncludePreviousSample: false, + }) + if err != nil { + return container.StatsResponse{}, fmt.Errorf("failed to get stats for cont %s: %w", id[:12], err) + } + defer fileutil.Close(resp.Body) + + var statsJSON container.StatsResponse + if err := json.NewDecoder(resp.Body).Decode(&statsJSON); err != nil { + return container.StatsResponse{}, fmt.Errorf("failed to unmarshal body for cont %s: %w", id[:12], err) + } + return statsJSON, nil +} + +func (s *Service) inspectDataFor(ctx context.Context, cache *hostStatsCache, info container.Summary) (inspectData, error) { + cache.mu.Lock() + cached, ok := cache.inspects[info.ID] + cache.mu.Unlock() + if ok && cached.status == info.Status { + return cached, nil + } + + inspect, err := s.Client.ContainerInspect(ctx, info.ID, client.ContainerInspectOptions{}) + if err != nil { + return inspectData{}, err + } + + data := inspectData{ + status: info.Status, + restartCount: int32(inspect.Container.RestartCount), + } + if state := inspect.Container.State; state != nil { + data.startedAt = state.StartedAt + } + + cache.mu.Lock() + cache.inspects[info.ID] = data + cache.mu.Unlock() + return data, nil +} + +func summaryHealth(info container.Summary) string { + // Health is nil when the daemon reports no healthcheck data at all + // (depends on daemon/API version) — dereferencing blindly panics + if info.Health == nil || info.Health.Status == container.NoHealthcheck { + return "" + } + return string(info.Health.Status) +} + +func summaryIPs(info container.Summary) []string { + ips := TraefikHosts(info.Labels) + for _, netConf := range info.NetworkSettings.Networks { + ip := netConf.IPAddress.String() + if ip != "invalid IP" && ip != "" { + ips = append(ips, ip) + } + } + // map iteration order is random; keep the column stable between polls + slices.Sort(ips) + return slices.Compact(ips) +} diff --git a/core/internal/docker/container/traefik.go b/core/internal/docker/container/traefik.go new file mode 100644 index 00000000..eb705cc2 --- /dev/null +++ b/core/internal/docker/container/traefik.go @@ -0,0 +1,59 @@ +package container + +import ( + "regexp" + "slices" + "strings" +) + +var ( + traefikHostFunction = regexp.MustCompile(`(?i)\bHost(?:SNI(?:Regexp)?|Regexp)?\s*\(([^)]*)\)`) + traefikQuotedValue = regexp.MustCompile("[`\\\"']([^`\\\"']+)[`\\\"']") +) + +// TraefikHosts returns the distinct host endpoints declared on enabled +// HTTP, TCP or UDP routers. Docker label order is undefined, so the result is +// sorted to keep monitor rows stable between refreshes. +func TraefikHosts(labels map[string]string) []string { + enabled := true + for key, value := range labels { + if strings.EqualFold(key, "traefik.enable") { + enabled = strings.EqualFold(strings.TrimSpace(value), "true") + break + } + } + if !enabled { + return nil + } + + hosts := make(map[string]struct{}) + for key, rule := range labels { + key = strings.ToLower(key) + if !strings.HasPrefix(key, "traefik.http.routers.") && + !strings.HasPrefix(key, "traefik.tcp.routers.") && + !strings.HasPrefix(key, "traefik.udp.routers.") { + continue + } + if !strings.HasSuffix(key, ".rule") { + continue + } + for _, call := range traefikHostFunction.FindAllStringSubmatch(rule, -1) { + for _, quoted := range traefikQuotedValue.FindAllStringSubmatch(call[1], -1) { + host := strings.ToLower(strings.TrimSuffix(strings.TrimSpace(quoted[1]), ".")) + if host != "" && host != "*" { + hosts[host] = struct{}{} + } + } + } + } + + result := make([]string, 0, len(hosts)) + for host := range hosts { + result = append(result, host) + } + if len(result) == 0 { + return nil + } + slices.Sort(result) + return result +} diff --git a/core/internal/docker/container/utils.go b/core/internal/docker/container/utils.go index c2824164..d7559599 100644 --- a/core/internal/docker/container/utils.go +++ b/core/internal/docker/container/utils.go @@ -1,52 +1,9 @@ package container import ( - "context" - "encoding/json" - "fmt" - "io" - - "github.com/RA341/dockman/pkg/fileutil" "github.com/moby/moby/api/types/container" - "github.com/moby/moby/client" ) -func (s *Service) getAndFormatStats(ctx context.Context, info container.Summary) (Stats, error) { - contId := info.ID[:12] - stats, err := s.Client.ContainerStats(ctx, info.ID, client.ContainerStatsOptions{ - IncludePreviousSample: true, - }) - if err != nil { - return Stats{}, fmt.Errorf("failed to get stats for cont %s: %w", contId, err) - } - defer fileutil.Close(stats.Body) - - body, err := io.ReadAll(stats.Body) - if err != nil { - return Stats{}, fmt.Errorf("failed to read body for cont %s: %w", contId, err) - } - var statsJSON container.StatsResponse - if err := json.Unmarshal(body, &statsJSON); err != nil { - return Stats{}, fmt.Errorf("failed to unmarshal body for cont %s: %w", contId, err) - } - - cpuPercent := formatCPU(statsJSON) - rx, tx := formatNetwork(statsJSON) - blkRead, blkWrite := formatDiskIO(statsJSON) - - return Stats{ - ID: contId, - Name: info.Names[0], - CPUUsage: cpuPercent, - MemoryUsage: statsJSON.MemoryStats.Usage, - MemoryLimit: statsJSON.MemoryStats.Limit, - NetworkRx: rx, - NetworkTx: tx, - BlockRead: blkRead, - BlockWrite: blkWrite, - }, nil -} - func formatDiskIO(statsJSON container.StatsResponse) (uint64, uint64) { var blkRead, blkWrite uint64 for _, bioEntry := range statsJSON.BlkioStats.IoServiceBytesRecursive { @@ -60,19 +17,18 @@ func formatDiskIO(statsJSON container.StatsResponse) (uint64, uint64) { return blkRead, blkWrite } -// Collect Network and Disk I/O -func formatNetwork(statsJSON container.StatsResponse) (uint64, uint64) { - var rx, tx uint64 - for _, v := range statsJSON.Networks { - rx += v.RxBytes - tx += v.TxBytes +// formatCPU computes the CPU percentage exactly like `docker stats`: the +// container delta over the system delta between two readings, scaled by +// online CPUs. Keeping the baseline ourselves lets the daemon return each +// reading immediately instead of sampling for an extra second per request. +func formatCPU(statsJSON container.StatsResponse, previousTotal, previousSystem uint64) float64 { + currentTotal := statsJSON.CPUStats.CPUUsage.TotalUsage + currentSystem := statsJSON.CPUStats.SystemUsage + if currentTotal < previousTotal || currentSystem <= previousSystem { + return 0 } - return rx, tx -} - -func formatCPU(statsJSON container.StatsResponse) float64 { - cpuDelta := float64(statsJSON.CPUStats.CPUUsage.TotalUsage - statsJSON.PreCPUStats.CPUUsage.TotalUsage) - systemCpuDelta := float64(statsJSON.CPUStats.SystemUsage - statsJSON.PreCPUStats.SystemUsage) + cpuDelta := float64(currentTotal - previousTotal) + systemCpuDelta := float64(currentSystem - previousSystem) numberCPUs := float64(statsJSON.CPUStats.OnlineCPUs) if numberCPUs == 0.0 { numberCPUs = float64(len(statsJSON.CPUStats.CPUUsage.PercpuUsage)) @@ -84,18 +40,57 @@ func formatCPU(statsJSON container.StatsResponse) float64 { cpuPercent = (cpuDelta / systemCpuDelta) * numberCPUs * 100.0 } + // a container's first samples after start can carry garbage deltas + // (zeroed precpu counters, clock jumps) that explode into absurd + // percentages; nothing real exceeds every core at 100% + if maxPercent := numberCPUs * 100.0; maxPercent > 0 && cpuPercent > maxPercent { + cpuPercent = maxPercent + } + return cpuPercent } +// formatMemory reports the container's working-set memory, the same figure +// `docker stats` shows: the raw cgroup usage includes the page cache +// (inactive_file), which the kernel reclaims freely — a media server +// "using" gigabytes of cache would otherwise dwarf its real footprint. +// total_inactive_file is the cgroup v1 key, inactive_file the v2 one. +func formatMemory(statsJSON container.StatsResponse) uint64 { + mem := statsJSON.MemoryStats + if v, ok := mem.Stats["total_inactive_file"]; ok && v < mem.Usage { + return mem.Usage - v + } + if v, ok := mem.Stats["inactive_file"]; ok && v < mem.Usage { + return mem.Usage - v + } + return mem.Usage +} + +// Collect Network and Disk I/O +func formatNetwork(statsJSON container.StatsResponse) (uint64, uint64) { + var rx, tx uint64 + for _, v := range statsJSON.Networks { + rx += v.RxBytes + tx += v.TxBytes + } + return rx, tx +} + // Stats Stats holds metrics for a single Docker container. type Stats struct { - ID string - Name string - CPUUsage float64 - MemoryUsage uint64 // in bytes - MemoryLimit uint64 // in bytes - NetworkRx uint64 // bytes received - NetworkTx uint64 // bytes sent - BlockRead uint64 // bytes read from block devices - BlockWrite uint64 // bytes written to block devices + ID string + Name string + Image string // image reference the container was created from + State string // running / exited / paused / restarting ... + Health string // healthy / unhealthy / starting; empty when no healthcheck + IPAddress []string // container network IPs + RestartCount int32 + CPUUsage float64 + MemoryUsage uint64 // in bytes + MemoryLimit uint64 // in bytes + NetworkRx uint64 // bytes received + NetworkTx uint64 // bytes sent + BlockRead uint64 // bytes read from block devices + BlockWrite uint64 // bytes written to block devices + StartedAt string // container start time, RFC3339 (empty if unknown) } diff --git a/core/internal/docker/container/utils_test.go b/core/internal/docker/container/utils_test.go new file mode 100644 index 00000000..5bec4182 --- /dev/null +++ b/core/internal/docker/container/utils_test.go @@ -0,0 +1,40 @@ +package container + +import ( + "testing" + + mobycontainer "github.com/moby/moby/api/types/container" +) + +func TestFormatCPUFromCachedCounters(t *testing.T) { + stats := mobycontainer.StatsResponse{} + stats.CPUStats.CPUUsage.TotalUsage = 200 + stats.CPUStats.SystemUsage = 2000 + stats.CPUStats.OnlineCPUs = 4 + + if got := formatCPU(stats, 100, 1000); got != 40 { + t.Fatalf("formatCPU() = %v, want 40", got) + } +} + +func TestFormatCPURejectsResetCounters(t *testing.T) { + stats := mobycontainer.StatsResponse{} + stats.CPUStats.CPUUsage.TotalUsage = 100 + stats.CPUStats.SystemUsage = 1000 + stats.CPUStats.OnlineCPUs = 2 + + if got := formatCPU(stats, 200, 2000); got != 0 { + t.Fatalf("formatCPU() after counter reset = %v, want 0", got) + } +} + +func TestFormatCPUClampsImpossibleFirstDelta(t *testing.T) { + stats := mobycontainer.StatsResponse{} + stats.CPUStats.CPUUsage.TotalUsage = 10_000 + stats.CPUStats.SystemUsage = 100 + stats.CPUStats.OnlineCPUs = 2 + + if got := formatCPU(stats, 0, 0); got != 200 { + t.Fatalf("formatCPU() = %v, want two-core ceiling 200", got) + } +} diff --git a/core/internal/docker/container/volumes.go b/core/internal/docker/container/volumes.go index 809a3483..2595685b 100644 --- a/core/internal/docker/container/volumes.go +++ b/core/internal/docker/container/volumes.go @@ -3,6 +3,7 @@ package container import ( "context" "fmt" + "strings" "github.com/docker/compose/v5/pkg/api" "github.com/moby/moby/api/types/mount" @@ -45,18 +46,19 @@ func (s *Service) VolumesList(ctx context.Context) ([]VolumeInfo, error) { } } - volumeFilters := client.Filters{} for i, vol := range listResp.Items { - val, ok := tmpMap[vol.Name] - if ok { + if val, ok := tmpMap[vol.Name]; ok { listResp.Items[i] = val } - volumeFilters.Add("volume", val.Name) } + // List every container and derive volume usage from their mounts. A prior + // version filtered containers by the volume names reported in disk usage, + // but a volume missing from disk usage (e.g. a CIFS/network volume with no + // reported size) was then never matched to its container and shown as + // "Unused" even while mounted. containers, err := s.Client.ContainerList(ctx, client.ContainerListOptions{ - All: true, - Filters: volumeFilters, + All: true, }) if err != nil { return nil, fmt.Errorf("failed to list containers: %w", err) @@ -94,6 +96,53 @@ func (s *Service) VolumesList(ctx context.Context) ([]VolumeInfo, error) { return volumes, nil } +// VolumeContainer describes one container mounting a given volume. +type VolumeContainer struct { + ID string + Name string + Destination string + RW bool + ComposeProject string +} + +// VolumeContainers returns every container that mounts the named volume, along +// with where it is mounted and whether it is read-write. Docker's volume +// inspect does not report this, so it is derived from the containers' mounts. +func (s *Service) VolumeContainers(ctx context.Context, volumeName string) ([]VolumeContainer, error) { + filters := client.Filters{} + filters.Add("volume", volumeName) + + resp, err := s.Client.ContainerList(ctx, client.ContainerListOptions{ + All: true, + Filters: filters, + }) + if err != nil { + return nil, fmt.Errorf("failed to list containers for volume %s: %w", volumeName, err) + } + + out := make([]VolumeContainer, 0, len(resp.Items)) + for _, c := range resp.Items { + name := "" + if len(c.Names) > 0 { + name = strings.TrimPrefix(c.Names[0], "/") + } + for _, mn := range c.Mounts { + if mn.Type == mount.TypeVolume && mn.Name == volumeName { + out = append(out, VolumeContainer{ + ID: c.ID, + Name: name, + Destination: mn.Destination, + RW: mn.RW, + ComposeProject: c.Labels[api.ProjectLabel], + }) + break // one row per container even if it mounts the volume twice + } + } + } + + return out, nil +} + func (s *Service) VolumesCreate(ctx context.Context, name string) (volume.Volume, error) { create, err := s.Client.VolumeCreate(ctx, client.VolumeCreateOptions{ Name: name, diff --git a/core/internal/docker/exec_policy_http_test.go b/core/internal/docker/exec_policy_http_test.go new file mode 100644 index 00000000..0171644b --- /dev/null +++ b/core/internal/docker/exec_policy_http_test.go @@ -0,0 +1,76 @@ +package docker + +import ( + "context" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + + contSrv "github.com/RA341/dockman/internal/docker/container" + hostMid "github.com/RA341/dockman/internal/host/middleware" + "github.com/moby/moby/client" + "github.com/stretchr/testify/require" +) + +func TestExecPolicyRejectsDockmanBeforeWebSocketUpgrade(t *testing.T) { + handler := newExecPolicyTestHandler(t, false) + + for _, path := range []string{"/exec/self-id/options", "/exec/self-id?cmd=/bin/sh"} { + t.Run(path, func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, path, nil) + req = req.WithContext(hostMid.SetHost(context.Background(), contSrv.LocalClient)) + res := httptest.NewRecorder() + + handler.ServeHTTP(res, req) + + require.Equal(t, http.StatusForbidden, res.Code) + require.Contains(t, res.Body.String(), "DOCKMAN_ALLOW_SELF_EXEC=true") + }) + } +} + +func TestExecPolicyCanBeExplicitlyEnabled(t *testing.T) { + handler := newExecPolicyTestHandler(t, true) + req := httptest.NewRequest(http.MethodGet, "/exec/self-id/options", nil) + req = req.WithContext(hostMid.SetHost(context.Background(), contSrv.LocalClient)) + res := httptest.NewRecorder() + + handler.ServeHTTP(res, req) + + require.Equal(t, http.StatusOK, res.Code) + require.JSONEq(t, `{"shells":[]}`, res.Body.String()) +} + +func newExecPolicyTestHandler(t *testing.T, allowSelfExec bool) http.Handler { + t.Helper() + httpClient := &http.Client{Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) { + response := &http.Response{ + StatusCode: http.StatusNotFound, + Header: make(http.Header), + Body: io.NopCloser(strings.NewReader("not found")), + Request: r, + } + if r.Method == http.MethodGet && strings.HasSuffix(r.URL.Path, "/containers/self-id/json") { + response.StatusCode = http.StatusOK + response.Header.Set("Content-Type", "application/json") + response.Body = io.NopCloser(strings.NewReader(`{"Id":"self-id","Config":{"Labels":{"dockman.container":"true"}}}`)) + } + return response, nil + })} + + cli, err := client.New( + client.WithHost("http://docker-proxy"), + client.WithHTTPClient(httpClient), + client.WithVersion("1.52"), + ) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, cli.Close()) }) + + dkSrv := &Service{Container: contSrv.New(cli)} + return NewHandlerHttp(func(host string) (*Service, error) { + require.Equal(t, contSrv.LocalClient, host) + return dkSrv, nil + }, allowSelfExec) +} diff --git a/core/internal/docker/handler.go b/core/internal/docker/handler.go index 724dec89..12ae5937 100644 --- a/core/internal/docker/handler.go +++ b/core/internal/docker/handler.go @@ -8,8 +8,10 @@ import ( "io" "net/http" "net/url" + "strconv" "strings" "sync" + "time" v1 "github.com/RA341/dockman/generated/docker/v1" dockerpc "github.com/RA341/dockman/generated/docker/v1/v1connect" @@ -17,9 +19,9 @@ import ( hm "github.com/RA341/dockman/internal/host/middleware" "github.com/RA341/dockman/pkg/fileutil" "github.com/RA341/dockman/pkg/listutils" - "github.com/RA341/dockman/pkg/syncmap" "connectrpc.com/connect" + "github.com/docker/compose/v5/pkg/api" "github.com/moby/moby/api/types/container" "github.com/rs/zerolog/log" ) @@ -61,44 +63,119 @@ func (h *Handler) getHost(ctx context.Context) (string, *Service, error) { //////////////////////////////////////////// func (h *Handler) ComposeFileStatus(ctx context.Context, c *connect.Request[v1.ComposeFileStatusRequest]) (*connect.Response[v1.ComposeFileStatusResponse], error) { - var results = syncmap.Map[string, *v1.Status]{} + finalResults := make(map[string]*v1.Status, len(c.Msg.Files)) - wg := sync.WaitGroup{} + err := h.WithClient(ctx, func(dkSrv *Service) error { + // One container listing for the whole host, aggregated per compose file + // via the compose config-files label. This replaces one `docker compose + // ps` subprocess per stack, so reporting the status of every stack (even + // collapsed ones) stays cheap no matter how many there are. + containers, err := dkSrv.Container.ContainersList(ctx) + if err != nil { + return err + } - for _, file := range c.Msg.Files { - wg.Go(func() { - err := h.WithClient(ctx, func(dkSrv *Service) error { - stat, err := dkSrv.Compose.Status(ctx, file) - if err != nil { - return err + byFile := make(map[string]*stackStatus) + for i := range containers { + ct := containers[i] + cfg := ct.Labels[api.ConfigFilesLabel] + if cfg == "" { + continue + } + // config_files may list several files (compose + overrides) + for _, p := range strings.Split(cfg, ",") { + if p = strings.TrimSpace(p); p == "" { + continue } + st := byFile[p] + if st == nil { + st = &stackStatus{} + byFile[p] = st + } + st.add(ct) + } + } - results.Store(file, &v1.Status{ - ServicesUp: int32(stat.UpCount), - ServicesDown: int32(stat.DownCount), - ServicesHealthy: int32(stat.HealthyCount), - ServicesUnHealthy: int32(stat.UnhealthyCount), - }) - - return nil - }) - if err != nil { - log.Warn().Str("file", file).Err(err).Msg("Failed to get compose status") + for _, file := range c.Msg.Files { + absPath, resolveErr := dkSrv.Compose.ComposeAbsPath(file) + if resolveErr != nil { + log.Debug().Str("file", file).Err(resolveErr).Msg("could not resolve compose path for status") + finalResults[file] = &v1.Status{} + continue + } + if st, ok := byFile[absPath]; ok { + finalResults[file] = st.toProto() + } else { + // no containers for this stack -> stopped + finalResults[file] = &v1.Status{} } - }) + } + return nil + }) + if err != nil { + return nil, err } - wg.Wait() - finalResults := make(map[string]*v1.Status, len(c.Msg.Files)) - results.Range(func(key string, value *v1.Status) bool { - finalResults[key] = value - return true - }) return connect.NewResponse(&v1.ComposeFileStatusResponse{ Status: finalResults, }), nil } +// stackStatus aggregates the container states of a single compose stack. +// +// It maps onto the v1.Status fields the UI already consumes. ServicesDown carries +// the "in error" count — a service that crashed, is dead, is stuck restarting, or +// exited non-zero — so the UI can distinguish a real problem (red) from a stack +// that is simply stopped (grey). +type stackStatus struct { + up int32 + failed int32 + healthy int32 + unhealthy int32 +} + +func (s *stackStatus) add(ct container.Summary) { + switch string(ct.State) { + case "running": + s.up++ + switch ct.Health.Status { + case container.Healthy: + s.healthy++ + case container.Unhealthy: + s.unhealthy++ + } + case "restarting", "dead": + s.failed++ + case "exited": + if containerExitCode(ct) != 0 { + s.failed++ + } + // exited(0) / created / paused / removing -> cleanly stopped, not counted + } +} + +func (s *stackStatus) toProto() *v1.Status { + return &v1.Status{ + ServicesUp: s.up, + ServicesDown: s.failed, + ServicesHealthy: s.healthy, + ServicesUnHealthy: s.unhealthy, + } +} + +// containerExitCode parses the exit code out of a container summary status line, +// e.g. "Exited (137) 2 hours ago" -> 137. Returns 0 when it can't be determined. +func containerExitCode(ct container.Summary) int { + l := strings.IndexByte(ct.Status, '(') + r := strings.IndexByte(ct.Status, ')') + if l >= 0 && r > l { + if code, err := strconv.Atoi(strings.TrimSpace(ct.Status[l+1 : r])); err == nil { + return code + } + } + return 0 +} + func (h *Handler) ComposeUp(ctx context.Context, req *connect.Request[v1.ComposeFile], responseStream *connect.ServerStream[v1.LogsMessage]) error { return h.WithClientAndStream(ctx, responseStream, func(dkSrv *Service, writer io.Writer) error { return dkSrv.Compose.Up( @@ -155,6 +232,19 @@ func (h *Handler) ComposeRestart(ctx context.Context, req *connect.Request[v1.Co } +func (h *Handler) ComposeRedeploy(ctx context.Context, req *connect.Request[v1.ComposeRedeployRequest], responseStream *connect.ServerStream[v1.LogsMessage]) error { + return h.WithClientAndStream(ctx, responseStream, func(dkSrv *Service, writer io.Writer) error { + file := req.Msg.GetFile() + return dkSrv.Compose.Redeploy( + ctx, + file.GetFilename(), + writer, + req.Msg.GetPull(), req.Msg.GetBuild(), req.Msg.GetRecreate(), + file.GetSelectedServices()..., + ) + }) +} + func (h *Handler) ComposeUpdate(ctx context.Context, req *connect.Request[v1.ComposeFile], responseStream *connect.ServerStream[v1.LogsMessage]) error { return h.WithClientAndStream(ctx, responseStream, func(dkSrv *Service, writer io.Writer) error { return dkSrv.Compose.Update(ctx, req.Msg.Filename, writer, req.Msg.SelectedServices...) @@ -165,6 +255,12 @@ func (h *Handler) ComposeUpdate(ctx context.Context, req *connect.Request[v1.Com //return nil } +func (h *Handler) DockerCommand(ctx context.Context, req *connect.Request[v1.DockerCommandRequest], responseStream *connect.ServerStream[v1.LogsMessage]) error { + return h.WithClientAndStream(ctx, responseStream, func(dkSrv *Service, writer io.Writer) error { + return dkSrv.Compose.RunDockerCommand(ctx, req.Msg.GetCommand(), writer) + }) +} + func (h *Handler) ComposeValidate(ctx context.Context, req *connect.Request[v1.ComposeFile]) (*connect.Response[v1.ComposeValidateResponse], error) { var validationResult []error @@ -252,15 +348,21 @@ func (l *LogStreamWriter) Write(p []byte) (n int, err error) { func ToRPCStat(cont contSrv.Stats) *v1.ContainerStats { return &v1.ContainerStats{ - Id: cont.ID, - Name: strings.TrimPrefix(cont.Name, "/"), - CpuUsage: cont.CPUUsage, - MemoryUsage: cont.MemoryUsage, - MemoryLimit: cont.MemoryLimit, - NetworkRx: cont.NetworkRx, - NetworkTx: cont.NetworkTx, - BlockRead: cont.BlockRead, - BlockWrite: cont.BlockWrite, + Id: cont.ID, + Name: strings.TrimPrefix(cont.Name, "/"), + Image: cont.Image, + State: cont.State, + Health: cont.Health, + IpAddress: cont.IPAddress, + RestartCount: cont.RestartCount, + CpuUsage: cont.CPUUsage, + MemoryUsage: cont.MemoryUsage, + MemoryLimit: cont.MemoryLimit, + NetworkRx: cont.NetworkRx, + NetworkTx: cont.NetworkTx, + BlockRead: cont.BlockRead, + BlockWrite: cont.BlockWrite, + StartedAt: cont.StartedAt, } } @@ -290,6 +392,13 @@ func getSortFn(field v1.SORT_FIELD) func(a, b contSrv.Stats) int { return func(a, b contSrv.Stats) int { return cmp.Compare(b.BlockRead, a.BlockRead) } + case v1.SORT_FIELD_STARTED: + return func(a, b contSrv.Stats) int { + // Compare parsed times, not the raw strings: RFC3339Nano trims + // trailing zeros, so lexicographic order is wrong across values + // with and without fractional seconds. + return parseStarted(b.StartedAt).Compare(parseStarted(a.StartedAt)) + } case v1.SORT_FIELD_NAME: fallthrough default: @@ -299,6 +408,16 @@ func getSortFn(field v1.SORT_FIELD) func(a, b contSrv.Stats) int { } } +// parseStarted parses a container's RFC3339 start time, returning the zero time +// (sorts first) when empty or unparseable (e.g. a never-started container). +func parseStarted(s string) time.Time { + t, err := time.Parse(time.RFC3339Nano, s) + if err != nil { + return time.Time{} + } + return t +} + func sendReqToUpdater(addr, key, path string) { log.Debug().Str("addr", addr).Msg("sending request to updating dockman") if key != "" && addr != "" { @@ -360,16 +479,6 @@ func toRPCPort(p container.PortSummary) *v1.Port { } } -func (h *Handler) getComposeFilePath(fullPath string) string { - // todo - //composePath := filepath.ToSlash( - // strings.TrimPrefix( - // fullPath, h.compose().ComposeRoot, - // ), - //) - return strings.TrimPrefix("", "/") -} - type ContainerLogWriter struct { responseStream *connect.ServerStream[v1.LogsMessage] } diff --git a/core/internal/docker/handler_containers.go b/core/internal/docker/handler_containers.go index 06cac831..d42bbeac 100644 --- a/core/internal/docker/handler_containers.go +++ b/core/internal/docker/handler_containers.go @@ -3,12 +3,14 @@ package docker import ( "cmp" "context" + "encoding/json" "fmt" "io" "maps" "net/netip" - "regexp" + "os" "slices" + "strconv" "strings" "time" @@ -75,6 +77,34 @@ func (h *Handler) ContainerStop(ctx context.Context, req *connect.Request[v1.Con return connect.NewResponse(&v1.LogsMessage{}), nil } +func (h *Handler) ContainerPause(ctx context.Context, req *connect.Request[v1.ContainerRequest]) (*connect.Response[v1.LogsMessage], error) { + _, dkSrv, err := h.getHost(ctx) + if err != nil { + return nil, err + } + + err = dkSrv.Container.ContainersPause(ctx, req.Msg.ContainerIds...) + if err != nil { + return nil, err + } + + return connect.NewResponse(&v1.LogsMessage{}), nil +} + +func (h *Handler) ContainerUnpause(ctx context.Context, req *connect.Request[v1.ContainerRequest]) (*connect.Response[v1.LogsMessage], error) { + _, dkSrv, err := h.getHost(ctx) + if err != nil { + return nil, err + } + + err = dkSrv.Container.ContainersUnpause(ctx, req.Msg.ContainerIds...) + if err != nil { + return nil, err + } + + return connect.NewResponse(&v1.LogsMessage{}), nil +} + func (h *Handler) ContainerRemove(ctx context.Context, req *connect.Request[v1.ContainerRequest]) (*connect.Response[v1.LogsMessage], error) { _, dkSrv, err := h.getHost(ctx) if err != nil { @@ -157,6 +187,15 @@ func (h *Handler) ContainerInspect(ctx context.Context, req *connect.Request[v1. ExposedPorts: exposedPorts, } + // Keep the typed legacy fields above for existing consumers, and expose + // the complete daemon response for the details view. Marshaling the + // embedded API value (rather than hand-copying fields) also makes Dockman + // forward-compatible with inspect fields added by newer daemon APIs. + rawInspect, err := json.MarshalIndent(inspect, "", " ") + if err != nil { + return nil, fmt.Errorf("encode container inspect: %w", err) + } + return connect.NewResponse(&v1.ContainerInspectMessage{ ID: inspect.ID, Name: inspect.Name, @@ -166,6 +205,7 @@ func (h *Handler) ContainerInspect(ctx context.Context, req *connect.Request[v1. Image: inspect.Image, HostsPath: inspect.HostsPath, Mounts: mounts, + RawJson: string(rawInspect), }), nil } @@ -192,18 +232,21 @@ func (h *Handler) ContainerTop(ctx context.Context, req *connect.Request[v1.Cont }), nil } -func (h *Handler) ContainerUpdate(ctx context.Context, req *connect.Request[v1.ContainerRequest]) (*connect.Response[v1.Empty], error) { - _, _, err := h.getHost(ctx) - if err != nil { - return nil, err - } - - // todo - //err = h.updater(host).ContainersUpdateByContainerID(ctx, req.Msg.ContainerIds...) - //if err != nil { - // return nil, err - //} - return connect.NewResponse(&v1.Empty{}), nil +// ContainerUpdate force-updates the given containers' images and streams +// per-step progress: pull the tag through the compose CLI runner (so the +// host's registry credentials apply), and when a newer image came down, +// recreate the container on it with rollback on failure. +func (h *Handler) ContainerUpdate(ctx context.Context, req *connect.Request[v1.ContainerRequest], responseStream *connect.ServerStream[v1.LogsMessage]) error { + return h.WithClientAndStream(ctx, responseStream, func(dkSrv *Service, writer io.Writer) error { + return dkSrv.Updater.ContainersForceUpdate( + ctx, + func(pullCtx context.Context, imageTag string) error { + return dkSrv.Compose.PullImage(pullCtx, imageTag, writer) + }, + writer, + req.Msg.ContainerIds..., + ) + }) } func (h *Handler) ContainerStats(ctx context.Context, req *connect.Request[v1.StatsRequest]) (*connect.Response[v1.StatsResponse], error) { @@ -251,6 +294,111 @@ func (h *Handler) ContainerStats(ctx context.Context, req *connect.Request[v1.St }), nil } +func (h *Handler) HostStats(ctx context.Context, _ *connect.Request[v1.Empty]) (*connect.Response[v1.HostStatsResponse], error) { + _, dkSrv, err := h.getHost(ctx) + if err != nil { + return nil, err + } + + stats, err := dkSrv.Compose.HostStats(ctx) + if err != nil { + return nil, err + } + + return connect.NewResponse(&v1.HostStatsResponse{ + CpuPercent: stats.CPUPercent, + MemUsed: stats.MemUsed, + MemTotal: stats.MemTotal, + Cpus: stats.CPUs, + }), nil +} + +// ContainerStatsStream emits each container's stats as soon as its one-shot +// read completes, so the client paints progressively instead of waiting for +// the slowest container. +// No server-side sort: order is arrival order, the client sorts. +func (h *Handler) ContainerStatsStream(ctx context.Context, req *connect.Request[v1.StatsRequest], stream *connect.ServerStream[v1.ContainerStats]) error { + file := req.Msg.GetFile() + _, dkSrv, err := h.getHost(ctx) + if err != nil { + return err + } + + var containers []container.Summary + if file != nil && file.Filename != "" { + absPath, err := dkSrv.Compose.ComposeAbsPath(file.Filename) + if err != nil { + return err + } + containers, err = dkSrv.Container.ContainerListByComposeFile(ctx, absPath) + if err != nil { + return err + } + } else { + containers, err = dkSrv.Container.ContainersListRunning(ctx) + if err != nil { + return err + } + } + + // paint-first: emit each container's identity immediately (metrics + // pending) so every view fills in the time of a container listing; the + // real stats replace the rows as each one-shot read completes + for _, ct := range containers { + if err := stream.Send(ToRPCStat(contSrv.IdentityStats(ct))); err != nil { + return err + } + } + + var sendErr error + dkSrv.Container.StatsStream(ctx, containers, func(st contSrv.Stats) { + if sendErr != nil { + return + } + sendErr = stream.Send(ToRPCStat(st)) + }) + return sendErr +} + +// ContainerEvents streams this host's filtered container lifecycle events to +// the client. A keepalive frame goes out every 30s so an otherwise silent +// stream survives reverse-proxy idle timeouts. One daemon subscription is +// shared by every connected client (see container.SubscribeEvents). +func (h *Handler) ContainerEvents(ctx context.Context, req *connect.Request[v1.EventsRequest], stream *connect.ServerStream[v1.ContainerEvent]) error { + _, dkSrv, err := h.getHost(ctx) + if err != nil { + return err + } + + eventsCh, unsubscribe := dkSrv.Container.SubscribeEvents() + defer unsubscribe() + + keepalive := time.NewTicker(30 * time.Second) + defer keepalive.Stop() + + for { + select { + case <-ctx.Done(): + return nil + case ev := <-eventsCh: + if err := stream.Send(&v1.ContainerEvent{ + Action: ev.Action, + Status: ev.Status, + ContainerId: ev.ID, + ContainerName: ev.Name, + Image: ev.Image, + TimeNano: ev.TimeNano, + }); err != nil { + return err + } + case <-keepalive.C: + if err := stream.Send(&v1.ContainerEvent{}); err != nil { + return err + } + } + } +} + func (h *Handler) ContainerLogs(ctx context.Context, req *connect.Request[v1.ContainerLogsRequest], responseStream *connect.ServerStream[v1.LogsMessage]) error { if req.Msg.GetContainerID() == "" { return fmt.Errorf("container id is required") @@ -285,6 +433,82 @@ func (h *Handler) ContainerLogs(ctx context.Context, req *connect.Request[v1.Con return nil } +// logsKeepAliveInterval paces empty LogLine frames so proxies do not cut the +// stream during quiet periods. 5s survives even aggressive idle timeouts +// (Traefik defaults to 10s); DOCKMAN_LOGS_KEEPALIVE overrides it in seconds. +var logsKeepAliveInterval = func() time.Duration { + if raw := os.Getenv("DOCKMAN_LOGS_KEEPALIVE"); raw != "" { + if secs, err := strconv.Atoi(raw); err == nil && secs > 0 { + return time.Duration(secs) * time.Second + } + } + return 5 * time.Second +}() + +func (h *Handler) ContainerLogsStream(ctx context.Context, req *connect.Request[v1.LogsStreamRequest], responseStream *connect.ServerStream[v1.LogLine]) error { + ids := req.Msg.GetContainerIds() + if len(ids) == 0 { + return fmt.Errorf("at least one container id is required") + } + _, dkSrv, err := h.getHost(ctx) + if err != nil { + return err + } + + streamCtx, cancel := context.WithCancel(ctx) + defer cancel() + + lines := make(chan contSrv.LogLine, 256) + streamErr := make(chan error, 1) + go func() { + streamErr <- dkSrv.Container.LogsStream(streamCtx, ids, contSrv.LogsStreamOptions{ + Tail: req.Msg.GetTail(), + Since: req.Msg.GetSince(), + Until: req.Msg.GetUntil(), + Follow: req.Msg.GetFollow(), + }, func(l contSrv.LogLine) { + select { + case lines <- l: + case <-streamCtx.Done(): + } + }) + // all reader goroutines are done: closing drains the buffered lines + // through the single receive loop below, then ends the stream + close(lines) + }() + + keepalive := time.NewTicker(logsKeepAliveInterval) + defer keepalive.Stop() + + for { + select { + case <-ctx.Done(): + return nil + case line, ok := <-lines: + if !ok { + return <-streamErr + } + if err := responseStream.Send(logLineToProto(line)); err != nil { + return err + } + case <-keepalive.C: + if err := responseStream.Send(&v1.LogLine{}); err != nil { + return err + } + } + } +} + +func logLineToProto(l contSrv.LogLine) *v1.LogLine { + return &v1.LogLine{ + ContainerId: l.ContainerID, + ContainerName: l.ContainerName, + Text: l.Text, + TimeNano: l.TimeNano, + Stream: l.Stream, + } +} + func (h *Handler) containersToRpc(result []container.Summary, host string, srv *Service) ([]*v1.ContainerList, map[string]int32) { var dockerResult []*v1.ContainerList statusCount := map[string]int32{} @@ -335,16 +559,17 @@ func (h *Handler) containersToRpc(result []container.Summary, host string, srv * stack, portSlice, updater.ImageUpdate{}, + srv.Compose.DockmanPath(stack.Labels[api.ConfigFilesLabel]), )) } return dockerResult, statusCount } -func (h *Handler) ToProto(stack container.Summary, portSlice []*v1.Port, update updater.ImageUpdate) *v1.ContainerList { +func (h *Handler) ToProto(stack container.Summary, portSlice []*v1.Port, update updater.ImageUpdate, servicePath string) *v1.ContainerList { ipAddr := extractIPAddr(stack) var he string - if stack.Health.Status != container.NoHealthcheck { + if stack.Health != nil && stack.Health.Status != container.NoHealthcheck { he = string(stack.Health.Status) } @@ -361,7 +586,7 @@ func (h *Handler) ToProto(stack container.Summary, portSlice []*v1.Port, update Ports: portSlice, ServiceName: stack.Labels[api.ServiceLabel], StackName: stack.Labels[api.ProjectLabel], - ServicePath: h.getComposeFilePath(stack.Labels[api.ConfigFilesLabel]), + ServicePath: servicePath, } } @@ -383,33 +608,5 @@ func extractIPAddr(stack container.Summary) (hosts []string) { } func extractTraefikLabel(labels map[string]string) (hosts []string) { - val, ok := labels["traefik.enable"] - if !(ok && val == "true") { - return hosts - } - - // looks for the Host() or HostRegexp() functions - // It captures everything inside the parenthesis - hostRegex := regexp.MustCompile(`Host(?:Regexp)?\((.*?)\)`) - // This regex identifies the actual domain names inside the quotes/backticks - domainRegex := regexp.MustCompile(`[` + "`" + `"]([^` + "`" + `",\s]+)[` + "`" + `"]`) - for key, value := range labels { - if strings.HasPrefix(key, "traefik.http.routers.") && strings.HasSuffix(key, ".rule") { - // Find all Host(...) or HostRegexp(...) occurrences in the rule - matches := hostRegex.FindAllStringSubmatch(value, -1) - for _, match := range matches { - if len(match) > 1 { - // (Handles comma separated: Host(`a.com`, `b.com`)) - domains := domainRegex.FindAllStringSubmatch(match[1], -1) - for _, d := range domains { - if len(d) > 1 { - hosts = append(hosts, d[1]) - } - } - } - } - } - } - - return hosts + return contSrv.TraefikHosts(labels) } diff --git a/core/internal/docker/handler_containers_test.go b/core/internal/docker/handler_containers_test.go index 9852ebdf..60a36ae6 100644 --- a/core/internal/docker/handler_containers_test.go +++ b/core/internal/docker/handler_containers_test.go @@ -18,15 +18,21 @@ func Test_extractTraefikLabel(t *testing.T) { labels := map[string]string{ "traefik.enable": "true", "traefik.http.routers.my-service.rule": "Host(`myapp.localhost`, `api.localhost`) && PathPrefix(`/api`) ", - "traefik.http.routers.my-app.rule": "Host(`myapp.example.com`)", + "traefik.http.routers.my-app.rule": "Host(`myapp.example.com`, `MYAPP.EXAMPLE.COM`)", + "traefik.tcp.routers.secure.rule": "HostSNI(`tcp.example.com`)", } hostsActual := extractTraefikLabel(labels) - expectedHosts := []string{"myapp.localhost", "api.localhost", "myapp.example.com"} - require.ElementsMatch(t, expectedHosts, hostsActual) + expectedHosts := []string{"api.localhost", "myapp.example.com", "myapp.localhost", "tcp.example.com"} + require.Equal(t, expectedHosts, hostsActual) + + delete(labels, "traefik.enable") + require.Equal(t, expectedHosts, extractTraefikLabel(labels), "router labels use Traefik's expose-by-default behavior") + labels["traefik.enable"] = "true" labels["traefik.http.routers.my-service.rule"] = "" labels["traefik.http.routers.my-app.rule"] = "" + labels["traefik.tcp.routers.secure.rule"] = "" hostsActual = extractTraefikLabel(labels) require.Nil(t, hostsActual) diff --git a/core/internal/docker/handler_http.go b/core/internal/docker/handler_http.go index 6aba6ff8..608700e8 100644 --- a/core/internal/docker/handler_http.go +++ b/core/internal/docker/handler_http.go @@ -2,11 +2,16 @@ package docker import ( "context" + "encoding/json" "fmt" "io" "net/http" "net/url" + "strings" + "sync" + "time" + contSrv "github.com/RA341/dockman/internal/docker/container" "github.com/RA341/dockman/internal/docker/debug" hostMid "github.com/RA341/dockman/internal/host/middleware" fu "github.com/RA341/dockman/pkg/fileutil" @@ -22,28 +27,149 @@ var upgrader = websocket.Upgrader{ } type HandlerHttp struct { - srv ServiceProvider + srv ServiceProvider + allowSelfExec bool } -func NewHandlerHttp(srv ServiceProvider) http.Handler { - hand := &HandlerHttp{srv: srv} +func NewHandlerHttp(srv ServiceProvider, allowSelfExec bool) http.Handler { + hand := &HandlerHttp{srv: srv, allowSelfExec: allowSelfExec} return hand.register() } func (h *HandlerHttp) register() http.Handler { subMux := http.NewServeMux() + subMux.HandleFunc("GET /exec/{contId}/options", h.containerExecOptions) subMux.HandleFunc("GET /exec/{contId}", h.containerExec) subMux.HandleFunc("GET /logs/{contId}", h.containerLogs) + subMux.HandleFunc("GET /shell", h.hostShell) + subMux.HandleFunc("POST /update/dockman", h.updateDockman) + subMux.HandleFunc("POST /restart/dockman", h.restartDockman) return subMux } +var execShellCandidates = []string{ + "/bin/sh", "/bin/bash", "/bin/ash", "/bin/zsh", "/bin/fish", + "/usr/bin/bash", "/usr/bin/zsh", "/usr/bin/fish", "/usr/local/bin/bash", +} + +func (h *HandlerHttp) containerExecOptions(w http.ResponseWriter, r *http.Request) { + dkSrv, contID, err := getContainerIdAndService(r, h) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + if err = h.checkExecAllowed(r.Context(), dkSrv, contID); err != nil { + http.Error(w, err.Error(), http.StatusForbidden) + return + } + + available := make([]bool, len(execShellCandidates)) + var wg sync.WaitGroup + for index, shell := range execShellCandidates { + wg.Add(1) + go func() { + defer wg.Done() + _, statErr := dkSrv.Container.Cli().ContainerStatPath(r.Context(), contID, client.ContainerStatPathOptions{Path: shell}) + available[index] = statErr == nil + }() + } + wg.Wait() + shells := make([]string, 0, len(execShellCandidates)) + for index, shell := range execShellCandidates { + if available[index] { + shells = append(shells, shell) + } + } + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(struct { + Shells []string `json:"shells"` + }{Shells: shells}); err != nil { + log.Debug().Err(err).Msg("could not encode container exec options") + } +} + +// restartDockman asks the local daemon to restart this container. The daemon +// performs the full stop/start operation, so it keeps going after Dockman's +// process exits. A short delay lets the accepted response reach the browser +// before the connection is interrupted. +func (h *HandlerHttp) restartDockman(w http.ResponseWriter, r *http.Request) { + host, err := hostMid.GetHost(r.Context()) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + if host != contSrv.LocalClient { + http.Error(w, "restart is only supported on the local host", http.StatusBadRequest) + return + } + + dkSrv, err := h.srv(host) + if err != nil { + http.Error(w, fmt.Sprintf("error getting docker service: %v", err), http.StatusBadRequest) + return + } + cli := dkSrv.Container.Cli() + self, err := findSelfContainer(r.Context(), cli) + if err != nil { + log.Error().Err(err).Msg("dockman self-restart failed to locate its container") + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusAccepted) + _, _ = w.Write([]byte("Dockman restart scheduled.")) + + go func(containerID string) { + time.Sleep(time.Second) + if _, restartErr := cli.ContainerRestart(context.Background(), containerID, client.ContainerRestartOptions{}); restartErr != nil { + log.Error().Err(restartErr).Str("container", containerID).Msg("dockman self-restart failed") + } + }(self.ID) +} + +// updateDockman triggers a manual self-update of the Dockman container on the +// local host. It launches a detached helper that recreates Dockman with the +// latest image; see SelfUpdate. +func (h *HandlerHttp) updateDockman(w http.ResponseWriter, r *http.Request) { + host, err := hostMid.GetHost(r.Context()) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + if host != contSrv.LocalClient { + http.Error(w, "self-update is only supported on the local host", http.StatusBadRequest) + return + } + + dkSrv, err := h.srv(host) + if err != nil { + http.Error(w, fmt.Sprintf("error getting docker service: %v", err), http.StatusBadRequest) + return + } + + // Detached context: the update must finish even if the client disconnects + // (Dockman is about to restart anyway). + if err = SelfUpdate(context.Background(), dkSrv); err != nil { + log.Error().Err(err).Msg("dockman self-update failed") + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusAccepted) + _, _ = w.Write([]byte("Dockman update started; it will restart shortly.")) +} + func (h *HandlerHttp) containerExec(w http.ResponseWriter, r *http.Request) { dkSrv, contId, err := getContainerIdAndService(r, h) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } + if err = h.checkExecAllowed(r.Context(), dkSrv, contId); err != nil { + http.Error(w, err.Error(), http.StatusForbidden) + return + } log.Debug().Str("id", contId).Msg("getting container logs") @@ -53,9 +179,11 @@ func (h *HandlerHttp) containerExec(w http.ResponseWriter, r *http.Request) { return } defer fu.Close(ws) + wsu.LimitClientMessages(ws) query := r.URL.Query() execCmd := getExecCmd(query, ws) + execUser := strings.TrimSpace(query.Get("user")) ctx := r.Context() var resp client.HijackedResponse @@ -77,7 +205,7 @@ func (h *HandlerHttp) containerExec(w http.ResponseWriter, r *http.Request) { } defer cleanup() } else { - resp, err = dkSrv.Container.ContainerExec(ctx, contId, execCmd) + resp, err = dkSrv.Container.ContainerExec(ctx, contId, execCmd, execUser) if err != nil { wsu.WErr(ws, err) return @@ -85,13 +213,16 @@ func (h *HandlerHttp) containerExec(w http.ResponseWriter, r *http.Request) { log.Debug().Msg("Attached to exec process") } defer func(resp *client.HijackedResponse) { - // IMPORTANT: use CloseWrite since it stops the internal process - // instead of Close which keeps it open + // CloseWrite sends EOF to the process stdin so a well-behaved program + // exits on its own. Close then tears down the hijacked connection so the + // reader goroutine below always unblocks: a process that ignores stdin + // EOF would otherwise leave resp.Reader.Read blocked forever, leaking the + // goroutine and the hijacked connection. log.Debug().Err(err).Msg("closing con") - err = resp.CloseWrite() - if err != nil { - log.Warn().Err(err).Msg("error occurred while closing connection") + if cerr := resp.CloseWrite(); cerr != nil { + log.Warn().Err(cerr).Msg("error occurred while closing connection") } + resp.Close() }(&resp) wsu.WInf(ws, "Connected to Container") @@ -99,6 +230,9 @@ func (h *HandlerHttp) containerExec(w http.ResponseWriter, r *http.Request) { wsu.WInf(ws, fmt.Sprintf("Debug Image: %s", debuggerImage)) } wsu.WInf(ws, fmt.Sprintf("Entrypoint: %s", execCmd)) + if execUser != "" { + wsu.WInf(ws, fmt.Sprintf("User: %s", execUser)) + } ctx, cancel := context.WithCancel(ctx) defer cancel() @@ -142,6 +276,24 @@ func (h *HandlerHttp) containerExec(w http.ResponseWriter, r *http.Request) { log.Debug().Str("container", contId).Msg("exec done") } +// checkExecAllowed enforces the policy before both shell discovery and the +// WebSocket upgrade. Keeping the authoritative check here means hiding or +// re-enabling a UI control can never bypass the server-side boundary. +func (h *HandlerHttp) checkExecAllowed(ctx context.Context, dkSrv *Service, containerID string) error { + if h.allowSelfExec { + return nil + } + + inspect, err := dkSrv.Container.Cli().ContainerInspect(ctx, containerID, client.ContainerInspectOptions{}) + if err != nil { + return fmt.Errorf("unable to verify exec target: %w", err) + } + if inspect.Container.Config != nil && inspect.Container.Config.Labels[dockmanContainerLabel] == "true" { + return fmt.Errorf("exec into Dockman is disabled by policy; set DOCKMAN_ALLOW_SELF_EXEC=true and recreate Dockman to enable it temporarily") + } + return nil +} + func (h *HandlerHttp) containerLogs(w http.ResponseWriter, r *http.Request) { dkSrv, contId, err := getContainerIdAndService(r, h) if err != nil { @@ -166,17 +318,24 @@ func (h *HandlerHttp) containerLogs(w http.ResponseWriter, r *http.Request) { return } defer fu.Close(ws) + wsu.LimitClientMessages(ws) writer := wsu.NewWsWriter(ws) go func() { + var copyErr error if tty { // tty streams dont need docker demultiplexing - _, err = io.Copy(writer, logsReader) + _, copyErr = io.Copy(writer, logsReader) } else { // docker multiplexed stream - _, err = stdcopy.StdCopy(writer, writer, logsReader) + _, copyErr = stdcopy.StdCopy(writer, writer, logsReader) } - log.Debug().Err(err).Str("cont", contId).Msg("closing logs writer") + log.Debug().Err(copyErr).Str("cont", contId).Msg("closing logs writer") + // The log stream can end before the client disconnects (e.g. the + // container stops). Close the socket so the ws.ReadMessage loop below + // unblocks; otherwise this handler goroutine leaks, pinning the socket + // buffers and the moby follow connection until the browser tab closes. + _ = ws.Close() }() for { diff --git a/core/internal/docker/handler_images.go b/core/internal/docker/handler_images.go index f6ded765..8b1c15d4 100644 --- a/core/internal/docker/handler_images.go +++ b/core/internal/docker/handler_images.go @@ -35,6 +35,13 @@ func (h *Handler) ImageList(ctx context.Context, req *connect.Request[v1.ListIma // return nil, err //} + // usage comes from the container list rather than the summary's + // Containers field, so the unused count matches what prune would do + usage, err := dkSrv.Container.ImageUsageCounts(ctx) + if err != nil { + return nil, err + } + var unusedContainers int64 var totalDisk int64 var untagged int64 @@ -47,12 +54,22 @@ func (h *Handler) ImageList(ctx context.Context, req *connect.Request[v1.ListIma untagged++ } - if img.Containers == 0 { + containers := usage[img.ID] + if containers == 0 { + // no direct container: the image may still be the base of an + // image whose containers run — prune keeps those, so they must + // count as used too + containers, err = dkSrv.Container.ImageDescendantContainers(ctx, img.ID) + if err != nil { + return nil, err + } + } + if containers == 0 { unusedContainers++ } rpcImages = append(rpcImages, &v1.Image{ - Containers: img.Containers, + Containers: containers, Created: img.Created, Id: img.ID, Labels: img.Labels, @@ -156,6 +173,20 @@ func (h *Handler) ImageInspect(ctx context.Context, req *connect.Request[v1.Imag name = sd } + conts, err := dkSrv.Container.ImageContainers(ctx, req.Msg.ImageId) + if err != nil { + return nil, err + } + rpcConts := make([]*v1.ImageContainerInspect, 0, len(conts)) + for _, c := range conts { + rpcConts = append(rpcConts, &v1.ImageContainerInspect{ + Name: c.Name, + Id: c.ID, + State: c.State, + ComposeProject: c.ComposeProject, + }) + } + var insp = &v1.ImageInspect{ Name: name, Id: inspect.ID, @@ -163,6 +194,7 @@ func (h *Handler) ImageInspect(ctx context.Context, req *connect.Request[v1.Imag Size: humanize.Bytes(uint64(inspect.Size)), CreatedIso: inspect.Created, Layers: layers, + Containers: rpcConts, } return connect.NewResponse(&v1.ImageInspectResponse{ diff --git a/core/internal/docker/handler_network.go b/core/internal/docker/handler_network.go index 0b3721ec..d398277f 100644 --- a/core/internal/docker/handler_network.go +++ b/core/internal/docker/handler_network.go @@ -116,3 +116,31 @@ func (h *Handler) NetworkDelete(ctx context.Context, req *connect.Request[v1.Del return connect.NewResponse(&v1.DeleteNetworkResponse{}), nil } + +func (h *Handler) NetworkConnectContainer(ctx context.Context, req *connect.Request[v1.NetworkConnectContainerRequest]) (*connect.Response[v1.NetworkConnectContainerResponse], error) { + _, dkSrv, err := h.getHost(ctx) + if err != nil { + return nil, err + } + if req.Msg.NetworkId == "" || req.Msg.ContainerId == "" { + return nil, fmt.Errorf("network and container are required") + } + if err := dkSrv.Container.NetworkConnectContainer(ctx, req.Msg.NetworkId, req.Msg.ContainerId); err != nil { + return nil, err + } + return connect.NewResponse(&v1.NetworkConnectContainerResponse{}), nil +} + +func (h *Handler) NetworkDisconnectContainer(ctx context.Context, req *connect.Request[v1.NetworkDisconnectContainerRequest]) (*connect.Response[v1.NetworkDisconnectContainerResponse], error) { + _, dkSrv, err := h.getHost(ctx) + if err != nil { + return nil, err + } + if req.Msg.NetworkId == "" || req.Msg.ContainerId == "" { + return nil, fmt.Errorf("network and container are required") + } + if err := dkSrv.Container.NetworkDisconnectContainer(ctx, req.Msg.NetworkId, req.Msg.ContainerId); err != nil { + return nil, err + } + return connect.NewResponse(&v1.NetworkDisconnectContainerResponse{}), nil +} diff --git a/core/internal/docker/handler_volumes.go b/core/internal/docker/handler_volumes.go index 092216ac..53497486 100644 --- a/core/internal/docker/handler_volumes.go +++ b/core/internal/docker/handler_volumes.go @@ -34,7 +34,7 @@ func (h *Handler) VolumeList(ctx context.Context, req *connect.Request[v1.ListVo CreatedAt: vol.CreatedAt, Labels: getVolumeProjectNameFromLabel(vol.Labels), MountPoint: vol.Mountpoint, - ComposePath: h.getComposeFilePath(vol.ComposePath), + ComposePath: dkSrv.Compose.DockmanPath(vol.ComposePath), ComposeProjectName: vol.ComposeProjectName, }) } @@ -42,6 +42,68 @@ func (h *Handler) VolumeList(ctx context.Context, req *connect.Request[v1.ListVo return connect.NewResponse(&v1.ListVolumesResponse{Volumes: rpcVolumes}), nil } +func (h *Handler) VolumeInspect(ctx context.Context, req *connect.Request[v1.VolumeInspectRequest]) (*connect.Response[v1.VolumeInspectResponse], error) { + _, dkSrv, err := h.getHost(ctx) + if err != nil { + return nil, err + } + + name := req.Msg.VolumeName + if name == "" { + return nil, fmt.Errorf("volumeName is required") + } + + // Reuse the list so the metadata matches exactly what the volumes table shows. + volumes, err := dkSrv.Container.VolumesList(ctx) + if err != nil { + return nil, err + } + + var volProto *v1.Volume + for _, vol := range volumes { + if vol.Name != name { + continue + } + volProto = &v1.Volume{ + Name: vol.Name, + ContainerID: vol.ContainerID, + Size: safeGetSize(vol), + CreatedAt: vol.CreatedAt, + Labels: getVolumeProjectNameFromLabel(vol.Labels), + MountPoint: vol.Mountpoint, + ComposePath: dkSrv.Compose.DockmanPath(vol.ComposePath), + ComposeProjectName: vol.ComposeProjectName, + } + break + } + if volProto == nil { + return nil, fmt.Errorf("volume %q not found", name) + } + + conts, err := dkSrv.Container.VolumeContainers(ctx, name) + if err != nil { + return nil, err + } + + rpcConts := make([]*v1.VolumeContainerInspect, 0, len(conts)) + for _, c := range conts { + rpcConts = append(rpcConts, &v1.VolumeContainerInspect{ + Name: c.Name, + Id: c.ID, + Destination: c.Destination, + Rw: c.RW, + ComposeProject: c.ComposeProject, + }) + } + + return connect.NewResponse(&v1.VolumeInspectResponse{ + Inspect: &v1.VolumeInspectInfo{ + Vol: volProto, + Containers: rpcConts, + }, + }), nil +} + func safeGetSize(vol contSrv.VolumeInfo) int64 { if vol.UsageData == nil { return 0 diff --git a/core/internal/docker/hostshell_http.go b/core/internal/docker/hostshell_http.go new file mode 100644 index 00000000..891c815e --- /dev/null +++ b/core/internal/docker/hostshell_http.go @@ -0,0 +1,118 @@ +package docker + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strconv" + "time" + + hostMid "github.com/RA341/dockman/internal/host/middleware" + fu "github.com/RA341/dockman/pkg/fileutil" + wsu "github.com/RA341/dockman/pkg/ws" + "github.com/gorilla/websocket" + "github.com/rs/zerolog/log" +) + +// clients send keystrokes as text frames; binary frames carry control JSON +type shellResize struct { + Cols uint16 `json:"cols"` + Rows uint16 `json:"rows"` +} + +// hostShell attaches a websocket to an interactive shell in the same context +// compose and docker commands run in: the dockman container for the local +// host, an ssh session for remote hosts. Optional query params: file (start +// in that compose file's directory), cols/rows (initial terminal size). +func (h *HandlerHttp) hostShell(w http.ResponseWriter, r *http.Request) { + host, err := hostMid.GetHost(r.Context()) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + dkSrv, err := h.srv(host) + if err != nil { + http.Error(w, fmt.Sprintf("error getting docker service: %v", err), http.StatusBadRequest) + return + } + + query := r.URL.Query() + file := query.Get("file") + cols := parseShellDim(query.Get("cols"), 80) + rows := parseShellDim(query.Get("rows"), 24) + + ws, err := upgrader.Upgrade(w, r, nil) + if err != nil { + http.Error(w, "Error upgrading to websocket "+err.Error(), http.StatusInternalServerError) + return + } + defer fu.Close(ws) + wsu.LimitClientMessages(ws) + + ctx, cancel := context.WithCancel(r.Context()) + defer cancel() + + shell, err := dkSrv.Compose.StartShell(ctx, file, cols, rows) + if err != nil { + wsu.WErr(ws, fmt.Errorf("unable to start host shell: %w", err)) + return + } + defer fu.Close(shell) + + wsu.WInf(ws, fmt.Sprintf("Connected to %s", host)) + + go func() { + // shell output goes out as binary frames: a text frame split inside + // a multi-byte character would be rejected by the browser + buf := make([]byte, 4096) + for { + n, err := shell.Read(buf) + if n > 0 { + if werr := ws.WriteMessage(websocket.BinaryMessage, buf[:n]); werr != nil { + break + } + } + if err != nil { + break + } + } + cancel() + // unblock the ReadMessage loop below + _ = ws.SetReadDeadline(time.Now()) + }() + + for { + if ctx.Err() != nil { + break + } + + mt, msg, err := ws.ReadMessage() + if err != nil { + break + } + + if mt == websocket.BinaryMessage { + var rs shellResize + if json.Unmarshal(msg, &rs) == nil && rs.Cols > 0 && rs.Rows > 0 { + _ = shell.Resize(rs.Cols, rs.Rows) + } + continue + } + + if _, err = shell.Write(msg); err != nil { + break + } + } + + log.Debug().Str("host", host).Msg("host shell closed") +} + +func parseShellDim(raw string, def uint16) uint16 { + v, err := strconv.Atoi(raw) + if err != nil || v <= 0 || v > 1000 { + return def + } + return uint16(v) +} diff --git a/core/internal/docker/hostshell_http_test.go b/core/internal/docker/hostshell_http_test.go new file mode 100644 index 00000000..3a4c4cf7 --- /dev/null +++ b/core/internal/docker/hostshell_http_test.go @@ -0,0 +1,16 @@ +package docker + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParseShellDim(t *testing.T) { + require.EqualValues(t, 120, parseShellDim("120", 80)) + require.EqualValues(t, 80, parseShellDim("", 80)) + require.EqualValues(t, 80, parseShellDim("garbage", 80)) + require.EqualValues(t, 80, parseShellDim("-3", 80)) + // absurd sizes fall back instead of allocating huge ptys + require.EqualValues(t, 24, parseShellDim("99999", 24)) +} diff --git a/core/internal/docker/selfrestart_http_test.go b/core/internal/docker/selfrestart_http_test.go new file mode 100644 index 00000000..52243e78 --- /dev/null +++ b/core/internal/docker/selfrestart_http_test.go @@ -0,0 +1,94 @@ +package docker + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + contSrv "github.com/RA341/dockman/internal/docker/container" + hostMid "github.com/RA341/dockman/internal/host/middleware" + apiContainer "github.com/moby/moby/api/types/container" + "github.com/moby/moby/client" + "github.com/stretchr/testify/require" +) + +func TestRestartDockmanUsesDaemonAfterAcceptedResponse(t *testing.T) { + restarted := make(chan string, 1) + httpClient := &http.Client{Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) { + response := &http.Response{ + StatusCode: http.StatusOK, + Header: make(http.Header), + Body: io.NopCloser(strings.NewReader("")), + Request: r, + } + switch { + case r.Method == http.MethodGet && strings.HasSuffix(r.URL.Path, "/containers/json"): + var body strings.Builder + require.NoError(t, json.NewEncoder(&body).Encode([]apiContainer.Summary{{ + ID: "self-id", + Labels: map[string]string{dockmanContainerLabel: "true"}, + }})) + response.Header.Set("Content-Type", "application/json") + response.Body = io.NopCloser(strings.NewReader(body.String())) + case r.Method == http.MethodPost && strings.HasSuffix(r.URL.Path, "/containers/self-id/restart"): + restarted <- r.URL.Path + response.StatusCode = http.StatusNoContent + default: + response.StatusCode = http.StatusNotFound + response.Body = io.NopCloser(strings.NewReader("unexpected daemon request")) + } + return response, nil + })} + + cli, err := client.New( + client.WithHost("http://docker-proxy"), + client.WithHTTPClient(httpClient), + client.WithVersion("1.52"), + ) + require.NoError(t, err) + defer cli.Close() + + dkSrv := &Service{Container: contSrv.New(cli)} + handler := NewHandlerHttp(func(host string) (*Service, error) { + require.Equal(t, contSrv.LocalClient, host) + return dkSrv, nil + }, false) + + req := httptest.NewRequest(http.MethodPost, "/restart/dockman", nil) + req = req.WithContext(hostMid.SetHost(context.Background(), contSrv.LocalClient)) + res := httptest.NewRecorder() + handler.ServeHTTP(res, req) + + require.Equal(t, http.StatusAccepted, res.Code) + select { + case path := <-restarted: + require.True(t, strings.HasSuffix(path, "/containers/self-id/restart")) + case <-time.After(3 * time.Second): + t.Fatal("Docker daemon did not receive the delayed self-restart request") + } +} + +type roundTripFunc func(*http.Request) (*http.Response, error) + +func (fn roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) { + return fn(r) +} + +func TestRestartDockmanRejectsRemoteHost(t *testing.T) { + handler := NewHandlerHttp(func(string) (*Service, error) { + t.Fatal("service provider must not be called for a remote host") + return nil, nil + }, false) + req := httptest.NewRequest(http.MethodPost, "/restart/dockman", nil) + req = req.WithContext(hostMid.SetHost(context.Background(), "remote")) + res := httptest.NewRecorder() + + handler.ServeHTTP(res, req) + + require.Equal(t, http.StatusBadRequest, res.Code) +} diff --git a/core/internal/docker/selfupdate.go b/core/internal/docker/selfupdate.go new file mode 100644 index 00000000..32d3a533 --- /dev/null +++ b/core/internal/docker/selfupdate.go @@ -0,0 +1,178 @@ +package docker + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/docker/compose/v5/pkg/api" + "github.com/moby/moby/api/types/container" + "github.com/moby/moby/api/types/mount" + "github.com/moby/moby/client" + "github.com/rs/zerolog/log" +) + +const ( + // selfUpdateContainerName is the fixed name of the throwaway helper so it can + // be found and cleaned up on the next Dockman startup. + selfUpdateContainerName = "dockman-self-update" + // selfUpdateHelperImage carries a docker CLI + compose plugin. + selfUpdateHelperImage = "docker:cli" + // dockmanContainerLabel is set on the Dockman image (see pkg/docker/Dockerfile). + dockmanContainerLabel = "dockman.container" +) + +// SelfUpdate pulls the latest Dockman image and recreates the Dockman container. +// +// A container cannot recreate itself while running (stopping itself kills the +// process doing the work), so Dockman launches a short-lived, DETACHED helper +// container that runs `docker compose up -d` for the Dockman service and then +// exits. The helper outlives Dockman during the swap. +// +// On a SUCCESSFUL update the helper removes itself (via the raw socket, so it +// never depends on Dockman's socket proxy allowing a DELETE). On FAILURE it is +// left in place so `docker logs dockman-self-update` stays inspectable; that +// leftover is swept on the next successful Dockman startup by +// CleanupSelfUpdateHelper. +func SelfUpdate(ctx context.Context, dkSrv *Service) error { + cli := dkSrv.Container.Cli() + + self, err := findSelfContainer(ctx, cli) + if err != nil { + return err + } + + composeFile := self.Labels[api.ConfigFilesLabel] + service := self.Labels[api.ServiceLabel] + project := self.Labels[api.ProjectLabel] + if composeFile == "" || service == "" { + return fmt.Errorf("the Dockman container is not managed by docker compose " + + "(no compose labels found), self-update is unavailable") + } + + // Remove a leftover helper from a previous run, if any. + cleanupSelfUpdateHelper(ctx, cli) + + if err = pullImage(ctx, cli, selfUpdateHelperImage); err != nil { + return fmt.Errorf("failed to pull helper image %s: %w", selfUpdateHelperImage, err) + } + + composeDir := filepath.Dir(composeFile) + // Mount the compose dir's parent so both the compose file and a parent-level + // env file (e.g. `env_file: ../.env`) are visible to the helper. Guard against + // mounting the host root. + mountDir := filepath.Dir(composeDir) + if mountDir == "/" || mountDir == "." || mountDir == "" { + mountDir = composeDir + } + + // The helper talks to the daemon over the raw docker socket (not Dockman's + // proxy) and recreates ONLY the Dockman service: --no-deps so sidecars such + // as a socket proxy are never touched, --force-recreate to guarantee a fresh + // container on the pulled image. A parent-level `../.env` is passed + // explicitly since compose only auto-loads a .env from the project directory. + // Identity is passed in via env, resolved by Dockman from its own container. + // + // On success the helper removes itself over the same raw socket. `set -e` + // means a failed compose run skips the self-removal, leaving the helper (and + // its logs) in place for troubleshooting. + const script = `set -e +sleep 3 +cd "$DK_COMPOSE_DIR" +ENVFLAG="" +[ -f ../.env ] && ENVFLAG="--env-file ../.env" +PROJ="" +[ -n "$DK_PROJECT" ] && PROJ="-p $DK_PROJECT" +echo "Updating Dockman ($DK_SERVICE)..." +docker compose $ENVFLAG $PROJ -f "$DK_COMPOSE_FILE" up -d --pull always --build --no-deps --force-recreate "$DK_SERVICE" +echo "Update complete; removing helper." +docker rm -f "$DK_SELF" || true` + + created, err := cli.ContainerCreate(ctx, client.ContainerCreateOptions{ + Name: selfUpdateContainerName, + Config: &container.Config{ + Image: selfUpdateHelperImage, + Entrypoint: []string{"sh"}, + Cmd: []string{"-c", script}, + Env: []string{ + "DK_COMPOSE_FILE=" + composeFile, + "DK_COMPOSE_DIR=" + composeDir, + "DK_PROJECT=" + project, + "DK_SERVICE=" + service, + "DK_SELF=" + selfUpdateContainerName, + }, + // Never let Dockman mistake the helper for itself. + Labels: map[string]string{dockmanContainerLabel: "false"}, + }, + HostConfig: &container.HostConfig{ + Mounts: []mount.Mount{ + {Type: mount.TypeBind, Source: "/var/run/docker.sock", Target: "/var/run/docker.sock"}, + {Type: mount.TypeBind, Source: mountDir, Target: mountDir}, + }, + }, + }) + if err != nil { + return fmt.Errorf("failed to create self-update helper: %w", err) + } + + if _, err = cli.ContainerStart(ctx, created.ID, client.ContainerStartOptions{}); err != nil { + return fmt.Errorf("failed to start self-update helper: %w", err) + } + + log.Info().Str("service", service).Str("composeFile", composeFile). + Msg("Dockman self-update helper launched; Dockman will restart shortly") + return nil +} + +// CleanupSelfUpdateHelper removes a leftover self-update helper container if one +// is still around. Safe to call on every startup; a no-op when nothing exists. +func CleanupSelfUpdateHelper(ctx context.Context, cli *client.Client) { + cleanupSelfUpdateHelper(ctx, cli) +} + +func cleanupSelfUpdateHelper(ctx context.Context, cli *client.Client) { + if _, err := cli.ContainerRemove(ctx, selfUpdateContainerName, client.ContainerRemoveOptions{ + Force: true, + }); err != nil { + log.Debug().Err(err).Msg("no self-update helper container to clean up") + } +} + +// findSelfContainer locates the running Dockman container via its image label, +// preferring the one whose id matches this process's hostname. +func findSelfContainer(ctx context.Context, cli *client.Client) (container.Summary, error) { + list, err := cli.ContainerList(ctx, client.ContainerListOptions{All: true}) + if err != nil { + return container.Summary{}, err + } + + hostname, _ := os.Hostname() + var fallback *container.Summary + for i := range list.Items { + c := list.Items[i] + if c.Labels[dockmanContainerLabel] != "true" { + continue + } + if hostname != "" && strings.HasPrefix(c.ID, hostname) { + return c, nil + } + if fallback == nil { + cpy := c + fallback = &cpy + } + } + if fallback != nil { + return *fallback, nil + } + return container.Summary{}, fmt.Errorf("could not find the Dockman container (label %s=true)", dockmanContainerLabel) +} + +func pullImage(ctx context.Context, cli *client.Client, image string) error { + progress, err := cli.ImagePull(ctx, image, client.ImagePullOptions{}) + if err != nil { + return err + } + return progress.Wait(ctx) +} diff --git a/core/internal/docker/updater/updater.go b/core/internal/docker/updater/updater.go index 9d36dddf..f56443cd 100644 --- a/core/internal/docker/updater/updater.go +++ b/core/internal/docker/updater/updater.go @@ -2,7 +2,9 @@ package updater import ( "context" + "errors" "fmt" + "io" "net/http" "os" "strings" @@ -12,6 +14,7 @@ import ( "github.com/RA341/dockman/pkg/fileutil" "github.com/moby/moby/api/types/container" + "github.com/moby/moby/api/types/network" "github.com/moby/moby/client" "github.com/rs/zerolog/log" "golang.org/x/sync/errgroup" @@ -81,6 +84,79 @@ func (u *Service) ContainersUpdateByContainerID(ctx context.Context, containerID return u.containersUpdateLoop(ctx, list) } +// ImagePuller pulls an image tag; injected so the caller decides HOW to +// pull (the compose CLI runner carries the host's registry credentials, +// unlike the bare daemon API). +type ImagePuller func(ctx context.Context, imageTag string) error + +// ContainersForceUpdate pulls each container's image tag and recreates the +// container when the pull brought down a different image. Unlike the +// metadata-driven update loop it needs no registry digest lookup, and it +// reports failures instead of skipping silently — this backs the explicit +// per-container Update action in the UI. Progress lines go to out. +func (u *Service) ContainersForceUpdate(ctx context.Context, pull ImagePuller, out io.Writer, containerID ...string) error { + list, err := u.srv.ContainerListByIDs(ctx, containerID...) + if err != nil { + return err + } + if len(list) == 0 { + return fmt.Errorf("no containers found for the given ids") + } + + report := func(format string, args ...any) { + _, _ = fmt.Fprintf(out, format+"\r\n", args...) + } + + var errs []error + for _, cur := range list { + name := strings.TrimPrefix(cur.Names[0], "/") + imgTag := cur.Image + + // a reference without a repository tag (image removed/retagged, or + // only ever built locally) cannot be pulled + if imgTag == "" || strings.HasPrefix(imgTag, "sha256:") { + report("%s: image %q has no pullable tag (locally built?), skipping", name, imgTag) + errs = append(errs, fmt.Errorf("%s: image %q has no pullable tag", name, imgTag)) + continue + } + + report("Pulling %s for %s...", imgTag, name) + if err := pull(ctx, imgTag); err != nil { + report("%s: pull failed: %v", name, err) + errs = append(errs, fmt.Errorf("%s: pull %s: %w", name, imgTag, err)) + continue + } + + localImages, err := u.cli().ImageList(ctx, client.ImageListOptions{ + Filters: client.Filters{}.Add("reference", imgTag), + }) + if err != nil { + errs = append(errs, fmt.Errorf("%s: inspect %s: %w", name, imgTag, err)) + continue + } + + newID := "" + if len(localImages.Items) > 0 { + newID = localImages.Items[0].ID + } + if newID == "" || newID == cur.ImageID { + report("%s: image already up to date, container kept as is", name) + log.Info().Str("container", name).Str("img", imgTag). + Msg("image unchanged after pull, container kept as is") + continue + } + + report("Recreating %s on the new image...", name) + if err := u.ContainerRecreate(ctx, imgTag, cur); err != nil { + report("%s: recreate failed: %v", name, err) + errs = append(errs, fmt.Errorf("%s: recreate: %w", name, err)) + continue + } + report("%s updated successfully", name) + } + return errors.Join(errs...) +} + // ContainersUpdateByImage finds all containers using the specified image, // pulls the latest version of the image, and recreates the containers // with the new image while preserving their configuration. @@ -299,78 +375,69 @@ func UpdateDockman(containerID, updaterUrl string) error { return nil } +// ContainerRecreate swaps a container onto a (freshly pulled) image while +// keeping its whole configuration. A running container is replaced through a +// create-start-healthcheck-swap sequence with rollback to the old container +// on any failure; a stopped one is swapped in place and left stopped. func (u *Service) ContainerRecreate(ctx context.Context, imageTag string, oldContainer container.Summary) error { - //containerName := "Untagged" - //if len(oldContainer.Names) > 0 { - // containerName = strings.TrimPrefix(oldContainer.Names[0], "/") - //} - // - //log.Debug().Msgf("Processing container: %s (ID: %s)", containerName, oldContainer.ID[:12]) - // - //inspectedData, err := s.Daemon.ContainerInspect(ctx, oldContainer.ID, client.ContainerInspectOptions{}) - //if err != nil { - // return fmt.Errorf("failed to inspect container %s: %w", oldContainer.ID, err) - //} - // - //log.Debug().Msgf("Stopping old container %s...", containerName) - //if err := s.Daemon.ContainerStop(ctx, oldContainer.ID, container.StopOptions{}); err != nil { - // return fmt.Errorf("failed to stop container %s: %w", oldContainer.ID, err) - //} - // - //// if container was not running before create but do not start - //if !inspectedData.State.Running { - // if err := s.Daemon.ContainerRemove(ctx, oldContainer.ID, container.RemoveOptions{}); err != nil { - // return fmt.Errorf("failed to remove old container %s: %w", oldContainer.ID, err) - // } - // - // _, err := s.containerCreate(ctx, imageTag, containerName, inspectedData) - // if err != nil { - // return fmt.Errorf("failed to create container %s: %w", containerName, err) - // } - // - // return nil - //} - // - //newContainer, err := s.containerCreate(ctx, imageTag, containerName+"_updated", inspectedData) - //if err != nil { - // return s.containerRollbackToOldContainer(ctx, oldContainer.ID, containerName, err) - //} - // - //log.Debug().Msgf("Starting new container %s...", newContainer.ID[:12]) - //if err = s.Daemon.ContainerStart(ctx, newContainer.ID, container.StartOptions{}); err != nil { - // - // err = s.Daemon.ContainerRemove(ctx, newContainer.ID, container.RemoveOptions{Force: true}) - // if err != nil { - // return err - // } - // - // return s.containerRollbackToOldContainer(ctx, oldContainer.ID, containerName, err) - //} - // - //if err = s.ContainerHealthCheck(newContainer.ID, &inspectedData); err != nil { - // - // err = s.Daemon.ContainerRemove(ctx, newContainer.ID, container.RemoveOptions{Force: true}) - // if err != nil { - // return err - // } - // - // return s.containerRollbackToOldContainer(ctx, oldContainer.ID, containerName, err) - //} - // - //// Health check passed - now we can safely remove old container and rename new one - //log.Debug().Msgf("Health check passed, finalizing update...") - // - //if err := s.Daemon.ContainerRemove(ctx, oldContainer.ID, container.RemoveOptions{Force: true}); err != nil { - // log.Warn().Msgf("Failed to remove old container: %v", err) - //} - // - //// Rename new container to original name - //if err := s.Daemon.ContainerRename(ctx, newContainer.ID, containerName); err != nil { - // log.Warn().Msgf("Failed to rename container to original name: %v", err) - //} - // - //log.Info().Msgf("Successfully updated container %s", containerName) - return fmt.Errorf("unimplemented dumbass") + containerName := "untagged" + if len(oldContainer.Names) > 0 { + containerName = strings.TrimPrefix(oldContainer.Names[0], "/") + } + log.Debug().Msgf("Recreating container %s (%s) on image %s", containerName, oldContainer.ID[:12], imageTag) + + inspected, err := u.cli().ContainerInspect(ctx, oldContainer.ID, client.ContainerInspectOptions{}) + if err != nil { + return fmt.Errorf("failed to inspect container %s: %w", containerName, err) + } + inspectedData := inspected.Container + + wasRunning := inspectedData.State != nil && inspectedData.State.Running + + // container at rest: swap in place, leave it stopped + if !wasRunning { + if _, err := u.cli().ContainerRemove(ctx, oldContainer.ID, client.ContainerRemoveOptions{}); err != nil { + return fmt.Errorf("failed to remove old container %s: %w", containerName, err) + } + if _, err := u.containerCreate(ctx, imageTag, containerName, inspectedData); err != nil { + return fmt.Errorf("failed to create container %s: %w", containerName, err) + } + return nil + } + + if _, err := u.cli().ContainerStop(ctx, oldContainer.ID, client.ContainerStopOptions{}); err != nil { + return fmt.Errorf("failed to stop container %s: %w", containerName, err) + } + + newContainer, err := u.containerCreate(ctx, imageTag, containerName+"_updated", inspectedData) + if err != nil { + return u.containerRollbackToOldContainer(ctx, oldContainer.ID, containerName, err) + } + + if _, err = u.cli().ContainerStart(ctx, newContainer.ID, client.ContainerStartOptions{}); err != nil { + if _, rmErr := u.cli().ContainerRemove(ctx, newContainer.ID, client.ContainerRemoveOptions{Force: true}); rmErr != nil { + log.Warn().Err(rmErr).Msg("failed to clean up the replacement container") + } + return u.containerRollbackToOldContainer(ctx, oldContainer.ID, containerName, err) + } + + if err = u.ContainerHealthCheck(newContainer.ID, &inspectedData); err != nil { + if _, rmErr := u.cli().ContainerRemove(ctx, newContainer.ID, client.ContainerRemoveOptions{Force: true}); rmErr != nil { + log.Warn().Err(rmErr).Msg("failed to clean up the replacement container") + } + return u.containerRollbackToOldContainer(ctx, oldContainer.ID, containerName, err) + } + + // healthy: drop the old container and take over its name + if _, err := u.cli().ContainerRemove(ctx, oldContainer.ID, client.ContainerRemoveOptions{Force: true}); err != nil { + log.Warn().Err(err).Msgf("failed to remove old container %s", containerName) + } + if _, err := u.cli().ContainerRename(ctx, newContainer.ID, client.ContainerRenameOptions{NewName: containerName}); err != nil { + log.Warn().Err(err).Msgf("failed to rename the new container to %s", containerName) + } + + log.Info().Msgf("Successfully updated container %s", containerName) + return nil } func (u *Service) containerRollbackToOldContainer(ctx context.Context, oldContainerID, containerName string, originalErr error) error { @@ -385,29 +452,38 @@ func (u *Service) containerRollbackToOldContainer(ctx context.Context, oldContai return fmt.Errorf("update failed, rolled back to previous version: %w", originalErr) } +// containerCreate creates a new container carrying the old one's whole +// configuration (config, host config, network endpoints) on a new image. func (u *Service) containerCreate( ctx context.Context, imageTag, containerName string, inspectedData container.InspectResponse, -) (container.CreateResponse, error) { - //// Create a new container with the same configuration but the new image - //// The inspected config has the old image name, so we update it. - //log.Debug().Msgf("Creating new container %s with updated image...", containerName) - //inspectedData.Config.Image = imageTag - //newContainer, err := s.Daemon.ContainerCreate(ctx, - // inspectedData.Config, - // inspectedData.HostConfig, - // &network.NetworkingConfig{ - // EndpointsConfig: inspectedData.NetworkSettings.Networks, - // }, - // nil, - // containerName, - //) - //if err != nil { - // return container.CreateResponse{}, fmt.Errorf("failed to create new container for %s: %w", containerName, err) - //} - //return newContainer, nil - return container.CreateResponse{}, fmt.Errorf("unimplemented container create") +) (client.ContainerCreateResult, error) { + cfg := inspectedData.Config + if cfg == nil { + cfg = &container.Config{} + } + // the inspected config still names the old image + newCfg := *cfg + newCfg.Image = imageTag + + var netConfig *network.NetworkingConfig + if inspectedData.NetworkSettings != nil { + netConfig = &network.NetworkingConfig{ + EndpointsConfig: inspectedData.NetworkSettings.Networks, + } + } + + created, err := u.cli().ContainerCreate(ctx, client.ContainerCreateOptions{ + Name: containerName, + Config: &newCfg, + HostConfig: inspectedData.HostConfig, + NetworkingConfig: netConfig, + }) + if err != nil { + return client.ContainerCreateResult{}, fmt.Errorf("failed to create new container for %s: %w", containerName, err) + } + return created, nil } func (u *Service) ContainerHealthCheck(containerID string, c *container.InspectResponse) error { diff --git a/core/internal/dockyaml/dockyaml.go b/core/internal/dockyaml/dockyaml.go index 23bb97b1..c58ebe28 100644 --- a/core/internal/dockyaml/dockyaml.go +++ b/core/internal/dockyaml/dockyaml.go @@ -27,6 +27,19 @@ var defaultDockmanYaml = DockmanYaml{ Order: "asc", }, }, + StatsPage: StatsConfig{ + Sort: Sort{ + Field: "Memory", + Order: "desc", + }, + }, + ComposePage: ComposeConfig{ + DefaultTab: "editor", + }, + MonitorPage: MonitorConfig{ + StackRows: "full", + }, + DefaultView: "files", } type DockmanYaml struct { @@ -53,6 +66,22 @@ type DockmanYaml struct { ContainerPage ContainerConfig `yaml:"containers"` + // configure the stats (system resources) page + StatsPage StatsConfig `yaml:"stats"` + + // configure the compose stack view + ComposePage ComposeConfig `yaml:"compose"` + + // configure the monitor view + MonitorPage MonitorConfig `yaml:"monitor"` + + // view opened when landing on a host: files (default), monitor, stats, + // containers, images, volumes, networks or cleaner + DefaultView string `yaml:"defaultView"` + + // configure the file editor + EditorPage EditorConfig `yaml:"editor"` + // define a max search limit for files SearchLimit int `yaml:"searchLimit"` @@ -75,6 +104,27 @@ type ImageConfig struct { Sort Sort `yaml:"sort"` } +type StatsConfig struct { + Sort Sort `yaml:"sort"` +} + +type ComposeConfig struct { + // tab shown when opening a compose stack: editor (default), deploy or stats + DefaultTab string `yaml:"defaultTab"` +} + +type MonitorConfig struct { + // stack row density in the monitor view: "full" (default) shows CPU/RAM + // values with their charts, "compact" shows the values only + StackRows string `yaml:"stackRows"` +} + +type EditorConfig struct { + // allow scrolling half a viewport past the last line (it stops at + // mid-view), for files taller than the viewport + ScrollPastEnd bool `yaml:"scrollPastEnd"` +} + type Sort struct { Order string `yaml:"order"` Field string `yaml:"field"` diff --git a/core/internal/dockyaml/handler.go b/core/internal/dockyaml/handler.go index 94c5ad5b..b8738fab 100644 --- a/core/internal/dockyaml/handler.go +++ b/core/internal/dockyaml/handler.go @@ -71,6 +71,17 @@ func (d *DockmanYaml) ToProto() *v1.DockmanYaml { NetworkPage: d.NetworkPage.toProto(), ImagePage: d.ImagePage.toProto(), ContainerPage: d.ContainerPage.toProto(), + StatsPage: d.StatsPage.toProto(), + ComposePage: d.ComposePage.toProto(), + EditorPage: d.EditorPage.toProto(), + MonitorPage: d.MonitorPage.toProto(), + DefaultView: d.DefaultView, + } +} + +func (m MonitorConfig) toProto() *v1.MonitorConfig { + return &v1.MonitorConfig{ + StackRows: m.StackRows, } } @@ -104,3 +115,21 @@ func (i ImageConfig) toProto() *v1.ImageConfig { Sort: i.Sort.toProto(), } } + +func (st StatsConfig) toProto() *v1.StatsConfig { + return &v1.StatsConfig{ + Sort: st.Sort.toProto(), + } +} + +func (c ComposeConfig) toProto() *v1.ComposeConfig { + return &v1.ComposeConfig{ + DefaultTab: c.DefaultTab, + } +} + +func (e EditorConfig) toProto() *v1.EditorConfig { + return &v1.EditorConfig{ + ScrollPastEnd: e.ScrollPastEnd, + } +} diff --git a/core/internal/files/handler.go b/core/internal/files/handler.go index 0908f0cd..c5181fec 100644 --- a/core/internal/files/handler.go +++ b/core/internal/files/handler.go @@ -90,6 +90,7 @@ func (h *Handler) List(ctx context.Context, req *connect.Request[v1.ListRequest] Filename: entry.fullpath, IsDir: entry.isDir, IsFetched: true, + Pinned: h.srv.IsPinned(hostname, entry.fullpath), SubFiles: ToMap(entry.children, func(childEntry Entry) *v1.FsEntry { hasComposeExt := strings.HasSuffix(childEntry.fullpath, "compose.yaml") || strings.HasSuffix(childEntry.fullpath, "compose.yml") @@ -103,6 +104,7 @@ func (h *Handler) List(ctx context.Context, req *connect.Request[v1.ListRequest] IsDir: childEntry.isDir, // max depth is 2 so indicate that it is unfetched IsFetched: false, + Pinned: h.srv.IsPinned(hostname, childEntry.fullpath), SubFiles: []*v1.FsEntry{}, } }), diff --git a/core/internal/files/handler_http.go b/core/internal/files/handler_http.go index ab7b7eaa..0a01e6a2 100644 --- a/core/internal/files/handler_http.go +++ b/core/internal/files/handler_http.go @@ -3,11 +3,14 @@ package files import ( b64 "encoding/base64" "errors" + "io" + "io/fs" "net/http" "strconv" "github.com/RA341/dockman/internal/host/middleware" fu "github.com/RA341/dockman/pkg/fileutil" + wsu "github.com/RA341/dockman/pkg/ws" "github.com/gorilla/websocket" "github.com/rs/zerolog/log" ) @@ -56,12 +59,18 @@ func (h *FileHandler) loadFile(w http.ResponseWriter, r *http.Request) { reader, modTime, err := h.srv.LoadFilePath(filename, getHost, download) if err != nil { log.Error().Err(err).Str("path", filename).Msg("Error loading file") - if errors.Is(err, ErrFileNotSupported) { + switch { + case errors.Is(err, ErrFileNotSupported): http.Error(w, "binary file detected, it will not be opened", http.StatusConflict) - return + case errors.Is(err, fs.ErrNotExist): + http.Error(w, "file not found", http.StatusNotFound) + case errors.Is(err, fs.ErrPermission): + http.Error(w, "permission denied: the server process cannot read this file. "+ + "Check the file's owner/permissions, or grant the container CAP_DAC_READ_SEARCH.", + http.StatusForbidden) + default: + http.Error(w, "failed to read file", http.StatusInternalServerError) } - - http.Error(w, "Filename not found", http.StatusBadRequest) return } defer fu.Close(reader) @@ -70,51 +79,101 @@ func (h *FileHandler) loadFile(w http.ResponseWriter, r *http.Request) { } func (h *FileHandler) saveFile(w http.ResponseWriter, r *http.Request) { - // 10 MB is the maximum upload size - if err := r.ParseMultipartForm(10 << 20); err != nil { - log.Fatal().Err(err).Msg("Error parsing multipart form") - http.Error(w, "Could not parse multipart form", http.StatusBadRequest) - return - } - getHost, err := middleware.GetHost(r.Context()) if err != nil { http.Error(w, "host not provided", http.StatusBadRequest) return } - content, meta, err := r.FormFile(fileContentsFormKey) - if err != nil { - log.Error().Err(err).Msg("Error retrieving file from form") - http.Error(w, "Error retrieving file from form", http.StatusBadRequest) - return + createFile := false + if createStr := r.URL.Query().Get(QueryKeyCreate); createStr != "" { + createFile, err = strconv.ParseBool(createStr) + if err != nil { + log.Warn().Err(err).Str("param", createStr).Msg("Error converting create query param to bool") + createFile = false + } } - defer fu.Close(content) - decodedFileName, err := b64.StdEncoding.DecodeString(meta.Filename) + // Stream the upload straight to disk. ParseMultipartForm would first buffer + // the whole file (up to 10 MB in memory, the rest in a temp file) before we + // write it — under a tight container memory limit a large upload can push + // the process into an OOM kill. A MultipartReader keeps memory flat. + reader, err := r.MultipartReader() if err != nil { - http.Error(w, "Error converting file name from base64", http.StatusBadRequest) + log.Error().Err(err).Msg("Error reading multipart body") + writeUploadError(w, err, "Could not read multipart body", http.StatusBadRequest) return } - createFile := false - createStr := r.URL.Query().Get(QueryKeyCreate) - if createStr != "" { - createFile, err = strconv.ParseBool(createStr) + for { + part, err := reader.NextPart() + if errors.Is(err, io.EOF) { + break + } if err != nil { - log.Warn().Err(err).Str("param", createStr).Msg("Error converting create query param to bool") - createFile = false + log.Error().Err(err).Msg("Error reading multipart part") + writeUploadError(w, err, "Error reading upload", http.StatusBadRequest) + return + } + if part.FormName() != fileContentsFormKey { + _ = part.Close() + continue + } + + decodedFileName, err := decodeUploadFilename(part.FileName()) + if err != nil { + _ = part.Close() + http.Error(w, "Error converting file name from base64", http.StatusBadRequest) + return + } + + if err = h.srv.Save(string(decodedFileName), getHost, createFile, part); err != nil { + _ = part.Close() + log.Error().Err(err). + Str("host", getHost). + Str("path", string(decodedFileName)). + Bool("create", createFile). + Msg("Error saving file") + switch { + case errors.Is(err, fs.ErrPermission): + http.Error(w, "permission denied while saving file", http.StatusForbidden) + case errors.Is(err, fs.ErrNotExist): + http.Error(w, "file path not found", http.StatusNotFound) + case isRequestTooLarge(err): + http.Error(w, "upload exceeds the configured size limit", http.StatusRequestEntityTooLarge) + default: + http.Error(w, "Error saving file", http.StatusInternalServerError) + } + return } + _ = part.Close() + return } - err = h.srv.Save(string(decodedFileName), getHost, createFile, content) - if err != nil { - log.Error().Err(err).Msg("Error saving file") - http.Error(w, "Error saving file", http.StatusInternalServerError) + http.Error(w, "no file provided in form", http.StatusBadRequest) +} + +func isRequestTooLarge(err error) bool { + var maxBytesErr *http.MaxBytesError + return errors.As(err, &maxBytesErr) +} + +func writeUploadError(w http.ResponseWriter, err error, message string, fallbackStatus int) { + if isRequestTooLarge(err) { + http.Error(w, "upload exceeds the configured size limit", http.StatusRequestEntityTooLarge) return } + http.Error(w, message, fallbackStatus) +} + +func decodeUploadFilename(encoded string) ([]byte, error) { + decoded, err := b64.RawURLEncoding.DecodeString(encoded) + if err == nil { + return decoded, nil + } - //log.Debug().Str("filename", meta.Filename).Msg("Successfully saved File") + // Accept uploads from older frontends during rolling upgrades. + return b64.StdEncoding.DecodeString(encoded) } var upgrader = websocket.Upgrader{ @@ -145,6 +204,7 @@ func (h *FileHandler) searchFile(w http.ResponseWriter, r *http.Request) { return } defer fu.Close(ws) + wsu.LimitClientMessages(ws) var response SearchResponse diff --git a/core/internal/files/handler_http_test.go b/core/internal/files/handler_http_test.go new file mode 100644 index 00000000..7d0ddab5 --- /dev/null +++ b/core/internal/files/handler_http_test.go @@ -0,0 +1,46 @@ +package files + +import ( + b64 "encoding/base64" + "testing" +) + +func TestDecodeUploadFilename(t *testing.T) { + t.Parallel() + + paths := []string{ + "compose/docker-compose.yml", + "production/configuration avec espaces.yaml", + "données/élément-€-東京.txt", + } + + for _, path := range paths { + path := path + t.Run(path, func(t *testing.T) { + t.Parallel() + + encoded := b64.RawURLEncoding.EncodeToString([]byte(path)) + decoded, err := decodeUploadFilename(encoded) + if err != nil { + t.Fatalf("decode URL-safe filename: %v", err) + } + if string(decoded) != path { + t.Fatalf("decoded path %q, want %q", decoded, path) + } + }) + } +} + +func TestDecodeUploadFilenameAcceptsLegacyBase64(t *testing.T) { + t.Parallel() + + const path = "compose/legacy.yml" + encoded := b64.StdEncoding.EncodeToString([]byte(path)) + decoded, err := decodeUploadFilename(encoded) + if err != nil { + t.Fatalf("decode legacy filename: %v", err) + } + if string(decoded) != path { + t.Fatalf("decoded path %q, want %q", decoded, path) + } +} diff --git a/core/internal/files/service.go b/core/internal/files/service.go index f0e6ea3f..8250a2f4 100644 --- a/core/internal/files/service.go +++ b/core/internal/files/service.go @@ -398,25 +398,34 @@ func (s *Service) GetTemplates(fPath string, hostname string) ([]Template, error return tmpls, nil } -func (s *Service) Save(filename, hostname string, create bool, source io.Reader) error { +func (s *Service) Save(filename, hostname string, _ bool, source io.Reader) error { sfCli, filename, _, err := s.LoadFs(filename, hostname) if err != nil { - return err + return fmt.Errorf("resolve destination: %w", err) } - flag := os.O_RDWR | os.O_TRUNC - if create { - flag |= os.O_CREATE - } + // pkg/sftp recommends WRITE|CREATE|TRUNC for write-only compatibility. + // Several SFTP servers reject WRITE|TRUNC with "permission denied", even + // when the destination already exists and is writable. O_CREATE does not + // replace an existing file; it only makes the open request portable and + // recreates a file that disappeared between loading and saving. + flag := os.O_WRONLY | os.O_CREATE | os.O_TRUNC dest, err := sfCli.OpenFile(filename, flag, os.ModePerm) if err != nil { - return err + return fmt.Errorf("open destination: %w", err) } - defer fileutil.Close(dest) - _, err = io.Copy(dest, source) - return err + _, copyErr := io.Copy(dest, source) + closeErr := dest.Close() + if copyErr != nil { + return fmt.Errorf("write destination: %w", copyErr) + } + if closeErr != nil { + return fmt.Errorf("close destination: %w", closeErr) + } + + return nil } func (s *Service) getFileContents(filename, hostname string) ([]byte, error) { @@ -611,33 +620,49 @@ func (s *Service) sortFiles(a, b *Entry, host string) int { if ra > rb { return 1 } - return strings.Compare(a.fullpath, b.fullpath) + + // Same rank: compare names case-insensitively (VS Code style), falling back + // to a case-sensitive compare so entries differing only by case stay in a + // deterministic order. A dotfile's leading "." naturally floats it to the + // top of its group. + an, bn := filepath.Base(a.fullpath), filepath.Base(b.fullpath) + if c := strings.Compare(strings.ToLower(an), strings.ToLower(bn)); c != 0 { + return c + } + return strings.Compare(an, bn) +} + +// IsPinned reports whether an entry's basename is pinned in the host's +// dockman.yml (pinnedFiles). Exposed so the RPC layer can flag pinned entries +// for the UI without duplicating the pin lookup. +func (s *Service) IsPinned(host, fullpath string) bool { + _, ok := s.dockYml(host).PinnedFiles[filepath.Base(fullpath)] + return ok } -// getSortRank determines priority: dotfiles, directories, then files by getFileSortRank +// getSortRank orders entries folders-first, then files: pinned files win (in +// their configured order), then directories, then files (with a small bias so +// compose/yaml files surface first). Dotfiles are NOT a separate group — the +// case-insensitive name compare in sortFiles floats them to the top of their +// own group, matching the folders-first behaviour of editors like VS Code. func (s *Service) getSortRank(entry *Entry, host string) int { conf := s.dockYml(host) base := filepath.Base(entry.fullpath) - // -1: pinned files (highest priority) + // Pinned files (explicit user-defined order) always come first. if priority, ok := conf.PinnedFiles[base]; ok { // potential bug, but if someone is manually writing the order of 100000 files i say get a life // -999 > -12 in this context, pretty stupid but i cant be bothered to fix this mathematically return priority - 100_000 } - // 0: dotfiles (highest priority) - if strings.HasPrefix(base, ".") { - return 1 - } - - // Check if it's a directory (has subfiles) + // Directories before files. if entry.isDir { - return 2 + return 0 } - // 2+: normal files, ranked by getFileSortRank - return 3 + s.getFileSortRank(entry.fullpath) + // Files, ranked so compose/yaml files surface first within the group. + return 1 + s.getFileSortRank(entry.fullpath) } // getFileSortRank assigns priority within normal files diff --git a/core/internal/files/service_test.go b/core/internal/files/service_test.go index 691cdd8b..31b0f200 100644 --- a/core/internal/files/service_test.go +++ b/core/internal/files/service_test.go @@ -1,17 +1,47 @@ package files import ( + "bytes" "fmt" "math/rand" "os" "path/filepath" + "slices" "strings" "testing" + "github.com/RA341/dockman/internal/dockyaml" "github.com/RA341/dockman/internal/host/filesystem" "github.com/stretchr/testify/require" ) +func TestSaveUsesCreateCompatibleWriteMode(t *testing.T) { + t.Parallel() + + root := t.TempDir() + srv := New(func(host, alias string) (filesystem.FileSystem, error) { + require.Equal(t, "remote", host) + require.Equal(t, "compose", alias) + return filesystem.NewLocal(root), nil + }, nil) + + // Editor saves use create=false. The destination should still be opened + // with CREATE so SFTP servers that require WRITE|CREATE|TRUNC accept it. + err := srv.Save("compose/new-file.yml", "remote", false, bytes.NewBufferString("services: {}\n")) + require.NoError(t, err) + + contents, err := os.ReadFile(filepath.Join(root, "new-file.yml")) + require.NoError(t, err) + require.Equal(t, "services: {}\n", string(contents)) + + err = srv.Save("compose/new-file.yml", "remote", false, bytes.NewBufferString("services:\n app: {}\n")) + require.NoError(t, err) + + contents, err = os.ReadFile(filepath.Join(root, "new-file.yml")) + require.NoError(t, err) + require.Equal(t, "services:\n app: {}\n", string(contents)) +} + func TestList(t *testing.T) { // todo //structure, err := CreateRandomDirStructure(5) @@ -55,6 +85,65 @@ func TestTemplateRead(t *testing.T) { } } +func sortNames(srv *Service, entries []Entry) []string { + slices.SortFunc(entries, func(a, b Entry) int { + return srv.sortFiles(&a, &b, "local") + }) + names := make([]string, len(entries)) + for i, e := range entries { + names[i] = e.fullpath + } + return names +} + +func dirEntry(name string) Entry { return Entry{fullpath: name, isDir: true} } +func fileEntry(name string) Entry { return Entry{fullpath: name, isDir: false} } + +// TestSortFoldersFirst covers the VS Code-style ordering: folders first, then +// files, each case-insensitive, with dotfiles floating to the top of their own +// group rather than forming a separate group above the directories. +func TestSortFoldersFirst(t *testing.T) { + srv := &Service{dockYml: func(string) *dockyaml.DockmanYaml { return &dockyaml.DockmanYaml{} }} + + // Same set as the bytebot repo root, shuffled. + input := []Entry{ + fileEntry("README.md"), dirEntry("docker"), fileEntry(".gitignore"), dirEntry("static"), + dirEntry(".github"), fileEntry("LICENSE"), dirEntry("helm"), dirEntry(".git"), + dirEntry("packages"), fileEntry(".prettierignore"), dirEntry("docs"), + } + + want := []string{ + // directories first (dotfolders float to the top), case-insensitive + ".git", ".github", "docker", "docs", "helm", "packages", "static", + // then files (dotfiles float to the top), case-insensitive + ".gitignore", ".prettierignore", "LICENSE", "README.md", + } + require.Equal(t, want, sortNames(srv, input)) +} + +// TestSortComposePinnedAndCase covers the Dockman-specific extras kept on top +// of the VS Code ordering: pinned files win outright, compose/yaml files +// surface first within the files group, and case is ignored ("Backups" < "data"). +func TestSortComposePinnedAndCase(t *testing.T) { + srv := &Service{dockYml: func(string) *dockyaml.DockmanYaml { + return &dockyaml.DockmanYaml{PinnedFiles: map[string]int{"notes.md": 0}} + }} + + input := []Entry{ + fileEntry("app.env"), dirEntry("data"), fileEntry("values.yaml"), dirEntry("Backups"), + fileEntry("compose.yaml"), fileEntry("notes.md"), fileEntry(".env"), + } + + want := []string{ + "notes.md", // pinned wins over everything + "Backups", "data", // folders, case-insensitive + "compose.yaml", // files: compose first + "values.yaml", // then other yaml + ".env", "app.env", // then remaining files, case-insensitive (dot floats) + } + require.Equal(t, want, sortNames(srv, input)) +} + func CreateRandomDirStructure(rootDir string, maxDepth int) (string, error) { err := os.MkdirAll(rootDir, 0755) if err != nil { diff --git a/core/internal/host/docker_connection.go b/core/internal/host/docker_connection.go index 02357e33..67c4c93c 100644 --- a/core/internal/host/docker_connection.go +++ b/core/internal/host/docker_connection.go @@ -24,6 +24,11 @@ func testDockerConnection(cli *client.Client) (system.Info, error) { func NewDockerLocalClient() (*client.Client, error) { return client.New( client.FromEnv, + // Negotiate the API version with the daemon so an older Docker engine + // (whose max supported API is below our client's default) doesn't reject + // calls with "client version is too new" — which otherwise fails the + // connection test and drops the host at startup. + client.WithAPIVersionNegotiation(), ) } @@ -32,6 +37,7 @@ func newDockerSSHClient(cli *ssh.Client) (*client.Client, error) { // Create a Docker client using the custom dialer. return client.New( client.WithDialContext(dockerSSHDialer(cli)), + client.WithAPIVersionNegotiation(), ) } diff --git a/core/internal/host/filesystem/filesystem.go b/core/internal/host/filesystem/filesystem.go index 620d1e23..3822ddf1 100644 --- a/core/internal/host/filesystem/filesystem.go +++ b/core/internal/host/filesystem/filesystem.go @@ -1,12 +1,15 @@ package filesystem import ( + "errors" "io" "io/fs" "os" "time" ) +var ErrPathOutsideRoot = errors.New("path is outside the configured root") + type FileSystem interface { Root() string diff --git a/core/internal/host/filesystem/filesystem_local.go b/core/internal/host/filesystem/filesystem_local.go index d8783012..287edc58 100644 --- a/core/internal/host/filesystem/filesystem_local.go +++ b/core/internal/host/filesystem/filesystem_local.go @@ -1,6 +1,7 @@ package filesystem import ( + "fmt" "io" "io/fs" "os" @@ -23,19 +24,58 @@ func (l *LocalFileSystem) Root() string { } func (l *LocalFileSystem) MkdirAll(path string, perm os.FileMode) error { - return os.MkdirAll(l.fullPath(path), perm) + rel, err := l.relativePath(path) + if err != nil { + return err + } + root, err := os.OpenRoot(l.root) + if err != nil { + return err + } + defer root.Close() + return root.MkdirAll(rel, perm) } func (l *LocalFileSystem) Abs(path string) (string, error) { - return l.fullPath(path), nil + rel, err := l.relativePath(path) + if err != nil { + return "", err + } + root, err := os.OpenRoot(l.root) + if err != nil { + return "", err + } + defer root.Close() + if _, err = root.Stat(rel); err != nil { + return "", err + } + return filepath.Join(l.root, rel), nil } func (l *LocalFileSystem) ReadDir(name string) ([]fs.DirEntry, error) { - return os.ReadDir(l.fullPath(name)) + rel, err := l.relativePath(name) + if err != nil { + return nil, err + } + root, err := os.OpenRoot(l.root) + if err != nil { + return nil, err + } + defer root.Close() + return fs.ReadDir(root.FS(), filepath.ToSlash(rel)) } func (l *LocalFileSystem) OpenFile(filename string, flag int, perm fs.FileMode) (io.ReadWriteCloser, error) { - return os.OpenFile(l.fullPath(filename), flag, perm) + rel, err := l.relativePath(filename) + if err != nil { + return nil, err + } + root, err := os.OpenRoot(l.root) + if err != nil { + return nil, err + } + defer root.Close() + return root.OpenFile(rel, flag, perm) } func (l *LocalFileSystem) Join(elem ...string) string { @@ -43,7 +83,16 @@ func (l *LocalFileSystem) Join(elem ...string) string { } func (l *LocalFileSystem) LoadFile(filename string) (io.ReadSeekCloser, time.Time, error) { - file, err := os.OpenFile(l.fullPath(filename), os.O_RDONLY, os.ModePerm) + rel, err := l.relativePath(filename) + if err != nil { + return nil, time.Time{}, err + } + root, err := os.OpenRoot(l.root) + if err != nil { + return nil, time.Time{}, err + } + defer root.Close() + file, err := root.OpenFile(rel, os.O_RDONLY, os.ModePerm) if err != nil { return nil, time.Time{}, err } @@ -55,32 +104,91 @@ func (l *LocalFileSystem) LoadFile(filename string) (io.ReadSeekCloser, time.Tim } func (l *LocalFileSystem) Stat(name string) (os.FileInfo, error) { - path := l.fullPath(name) - return os.Stat(path) + rel, err := l.relativePath(name) + if err != nil { + return nil, err + } + root, err := os.OpenRoot(l.root) + if err != nil { + return nil, err + } + defer root.Close() + return root.Stat(rel) } func (l *LocalFileSystem) RemoveAll(path string) error { - return os.RemoveAll(l.fullPath(path)) + rel, err := l.relativePath(path) + if err != nil { + return err + } + if rel == "." { + return fmt.Errorf("refusing to remove filesystem root: %w", ErrPathOutsideRoot) + } + root, err := os.OpenRoot(l.root) + if err != nil { + return err + } + defer root.Close() + return root.RemoveAll(rel) } func (l *LocalFileSystem) Rename(oldName string, newName string) error { - return os.Rename(l.fullPath(oldName), l.fullPath(newName)) + oldRel, err := l.relativePath(oldName) + if err != nil { + return err + } + newRel, err := l.relativePath(newName) + if err != nil { + return err + } + root, err := os.OpenRoot(l.root) + if err != nil { + return err + } + defer root.Close() + return root.Rename(oldRel, newRel) } func (l *LocalFileSystem) ReadFile(path string) ([]byte, error) { - return os.ReadFile(l.fullPath(path)) + rel, err := l.relativePath(path) + if err != nil { + return nil, err + } + root, err := os.OpenRoot(l.root) + if err != nil { + return nil, err + } + defer root.Close() + return root.ReadFile(rel) } -func (l *LocalFileSystem) WalkDir(path string, f func(path string, d fs.DirEntry, err error) error) error { - return filepath.WalkDir(l.fullPath(path), f) +func (l *LocalFileSystem) WalkDir(name string, f func(path string, d fs.DirEntry, err error) error) error { + rel, err := l.relativePath(name) + if err != nil { + return err + } + root, err := os.OpenRoot(l.root) + if err != nil { + return err + } + defer root.Close() + return fs.WalkDir(root.FS(), filepath.ToSlash(rel), func(path string, d fs.DirEntry, walkErr error) error { + return f(filepath.Join(l.root, filepath.FromSlash(path)), d, walkErr) + }) } -func (l *LocalFileSystem) fullPath(name string) string { - clean := l.Join(l.root, filepath.Clean(name)) - if !strings.HasPrefix(clean, l.root) { - // todo maybe err its annoying - //return "", fmt.Errorf("security violation: path %s is outside root %s", name, l.root) - return l.root +func (l *LocalFileSystem) relativePath(name string) (string, error) { + root := filepath.Clean(l.root) + clean := filepath.Clean(name) + if filepath.IsAbs(clean) { + var err error + clean, err = filepath.Rel(root, clean) + if err != nil { + return "", fmt.Errorf("resolve %q from %q: %w", name, root, err) + } + } + if clean == ".." || strings.HasPrefix(clean, ".."+string(filepath.Separator)) { + return "", fmt.Errorf("%w: %q", ErrPathOutsideRoot, name) } - return clean + return clean, nil } diff --git a/core/internal/host/filesystem/filesystem_local_test.go b/core/internal/host/filesystem/filesystem_local_test.go new file mode 100644 index 00000000..e9210834 --- /dev/null +++ b/core/internal/host/filesystem/filesystem_local_test.go @@ -0,0 +1,65 @@ +package filesystem + +import ( + "errors" + "os" + "path/filepath" + "testing" +) + +func TestLocalFileSystemConfinesPathsAndSymlinks(t *testing.T) { + parent := t.TempDir() + root := filepath.Join(parent, "compose") + sibling := filepath.Join(parent, "compose-secret") + if err := os.MkdirAll(root, 0o755); err != nil { + t.Fatal(err) + } + if err := os.MkdirAll(sibling, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(sibling, "secret"), []byte("secret"), 0o600); err != nil { + t.Fatal(err) + } + + fsys := NewLocal(root) + for _, escaped := range []string{"../compose-secret/secret", filepath.Join(sibling, "secret")} { + if _, err := fsys.ReadFile(escaped); !errors.Is(err, ErrPathOutsideRoot) { + t.Fatalf("ReadFile(%q) error = %v, want ErrPathOutsideRoot", escaped, err) + } + } + + if err := os.Symlink(sibling, filepath.Join(root, "escape")); err != nil { + t.Fatal(err) + } + if _, err := fsys.ReadFile("escape/secret"); err == nil { + t.Fatal("ReadFile followed a symlink outside the configured root") + } +} + +func TestLocalFileSystemAllowsRootedAndRelativePaths(t *testing.T) { + root := t.TempDir() + fsys := NewLocal(root) + if err := fsys.MkdirAll("stack", 0o755); err != nil { + t.Fatal(err) + } + file, err := fsys.OpenFile(filepath.Join(root, "stack", "compose.yml"), os.O_CREATE|os.O_WRONLY, 0o600) + if err != nil { + t.Fatal(err) + } + if _, err = file.Write([]byte("services: {}")); err != nil { + t.Fatal(err) + } + if err = file.Close(); err != nil { + t.Fatal(err) + } + if _, err = fsys.ReadFile("stack/compose.yml"); err != nil { + t.Fatal(err) + } +} + +func TestLocalFileSystemRefusesRootRemoval(t *testing.T) { + fsys := NewLocal(t.TempDir()) + if err := fsys.RemoveAll("."); !errors.Is(err, ErrPathOutsideRoot) { + t.Fatalf("RemoveAll(.) error = %v, want ErrPathOutsideRoot", err) + } +} diff --git a/core/internal/host/filesystem/filesystem_sftp.go b/core/internal/host/filesystem/filesystem_sftp.go index 72fec5cb..b3de780e 100644 --- a/core/internal/host/filesystem/filesystem_sftp.go +++ b/core/internal/host/filesystem/filesystem_sftp.go @@ -2,10 +2,11 @@ package filesystem import ( "errors" + "fmt" "io" "io/fs" "os" - "path/filepath" + "path" "strings" "time" @@ -20,7 +21,7 @@ type SftpFileSystem struct { } func (s *SftpFileSystem) Abs(path string) (string, error) { - return s.fullPath(path), nil + return s.fullPath(path) } func NewSftp(client *sftp.Client, root string) *SftpFileSystem { @@ -35,12 +36,20 @@ func (s *SftpFileSystem) Root() string { } func (s *SftpFileSystem) MkdirAll(path string, perm os.FileMode) error { - return s.client.MkdirAll(s.fullPath(path)) + full, err := s.fullPath(path) + if err != nil { + return err + } + return s.client.MkdirAll(full) } func (s *SftpFileSystem) ReadDir(path string) ([]fs.DirEntry, error) { client := s.client - dirs, err := client.ReadDir(s.fullPath(path)) + full, err := s.fullPath(path) + if err != nil { + return nil, err + } + dirs, err := client.ReadDir(full) if err != nil { return nil, err } @@ -53,7 +62,11 @@ func (s *SftpFileSystem) ReadDir(path string) ([]fs.DirEntry, error) { } func (s *SftpFileSystem) OpenFile(filename string, flag int, perm fs.FileMode) (io.ReadWriteCloser, error) { - return s.client.OpenFile(s.fullPath(filename), flag) + full, err := s.fullPath(filename) + if err != nil { + return nil, err + } + return s.client.OpenFile(full, flag) } func (s *SftpFileSystem) Join(elem ...string) string { @@ -61,7 +74,11 @@ func (s *SftpFileSystem) Join(elem ...string) string { } func (s *SftpFileSystem) LoadFile(filename string) (io.ReadSeekCloser, time.Time, error) { - file, err := s.client.OpenFile(s.fullPath(filename), os.O_RDONLY) + full, err := s.fullPath(filename) + if err != nil { + return nil, time.Time{}, err + } + file, err := s.client.OpenFile(full, os.O_RDONLY) if err != nil { return nil, time.Time{}, err } @@ -74,19 +91,46 @@ func (s *SftpFileSystem) LoadFile(filename string) (io.ReadSeekCloser, time.Time } func (s *SftpFileSystem) Stat(filename string) (os.FileInfo, error) { - return s.client.Stat(s.fullPath(filename)) + full, err := s.fullPath(filename) + if err != nil { + return nil, err + } + return s.client.Stat(full) } -func (s *SftpFileSystem) RemoveAll(path string) error { - return s.client.RemoveAll(s.fullPath(path)) +func (s *SftpFileSystem) RemoveAll(name string) error { + full, err := s.fullPath(name) + if err != nil { + return err + } + realRoot, err := s.client.RealPath(path.Clean(filepathToSlash(s.root))) + if err != nil { + return fmt.Errorf("resolve SFTP root %q: %w", s.root, err) + } + if path.Clean(full) == path.Clean(realRoot) { + return fmt.Errorf("refusing to remove filesystem root: %w", ErrPathOutsideRoot) + } + return s.client.RemoveAll(full) } func (s *SftpFileSystem) Rename(name string, filename string) error { - return s.client.Rename(name, s.fullPath(filename)) + oldFull, err := s.fullPath(name) + if err != nil { + return err + } + newFull, err := s.fullPath(filename) + if err != nil { + return err + } + return s.client.Rename(oldFull, newFull) } func (s *SftpFileSystem) ReadFile(fullpath string) ([]byte, error) { - open, err := s.client.Open(s.fullPath(fullpath)) + full, err := s.fullPath(fullpath) + if err != nil { + return nil, err + } + open, err := s.client.Open(full) if err != nil { return nil, err } @@ -95,7 +139,11 @@ func (s *SftpFileSystem) ReadFile(fullpath string) ([]byte, error) { } func (s *SftpFileSystem) WalkDir(root string, fn func(path string, d fs.DirEntry, err error) error) error { - walker := s.client.Walk(s.fullPath(root)) + full, err := s.fullPath(root) + if err != nil { + return err + } + walker := s.client.Walk(full) for walker.Step() { err := walker.Err() path := walker.Path() @@ -116,13 +164,82 @@ func (s *SftpFileSystem) WalkDir(root string, fn func(path string, d fs.DirEntry return nil } -func (s *SftpFileSystem) fullPath(name string) string { - // todo possible bug: filepath.clean using local system may fail on windows - clean := s.Join(s.root, filepath.Clean(name)) - if !strings.HasPrefix(clean, s.root) { - // todo maybe err its annoying - //return "", fmt.Errorf("security violation: path %s is outside root %s", name, l.root) - return s.root +func (s *SftpFileSystem) fullPath(name string) (string, error) { + root := path.Clean(filepathToSlash(s.root)) + clean := path.Clean(filepathToSlash(name)) + if path.IsAbs(clean) { + var ok bool + clean, ok = remoteRelative(root, clean) + if !ok { + return "", fmt.Errorf("%w: %q", ErrPathOutsideRoot, name) + } + } + if clean == ".." || strings.HasPrefix(clean, "../") { + return "", fmt.Errorf("%w: %q", ErrPathOutsideRoot, name) + } + candidate := path.Join(root, clean) + + // Resolve existing paths (or the closest existing parent for creations) + // server-side. This prevents a symlink below the configured root from + // redirecting SFTP operations elsewhere on the remote host. + realRoot, err := s.client.RealPath(root) + if err != nil { + return "", fmt.Errorf("resolve SFTP root %q: %w", root, err) } - return clean + realCandidate, err := s.realPathOrParent(candidate) + if err != nil { + return "", err + } + if !remotePathWithin(realRoot, realCandidate) { + return "", fmt.Errorf("%w: %q", ErrPathOutsideRoot, name) + } + return realCandidate, nil +} + +func (s *SftpFileSystem) realPathOrParent(candidate string) (string, error) { + current := candidate + var missing []string + for { + resolved, err := s.client.RealPath(current) + if err == nil { + for i := len(missing) - 1; i >= 0; i-- { + resolved = path.Join(resolved, missing[i]) + } + return resolved, nil + } + if !os.IsNotExist(err) { + return "", fmt.Errorf("resolve SFTP path %q: %w", candidate, err) + } + parent := path.Dir(current) + if parent == current { + return "", fmt.Errorf("resolve SFTP path %q: %w", candidate, err) + } + missing = append(missing, path.Base(current)) + current = parent + } +} + +func remotePathWithin(root, candidate string) bool { + _, ok := remoteRelative(path.Clean(root), path.Clean(candidate)) + return ok +} + +func remoteRelative(root, candidate string) (string, bool) { + root = path.Clean(root) + candidate = path.Clean(candidate) + if candidate == root { + return ".", true + } + if root == "/" && strings.HasPrefix(candidate, "/") { + return strings.TrimPrefix(candidate, "/"), true + } + prefix := root + "/" + if strings.HasPrefix(candidate, prefix) { + return strings.TrimPrefix(candidate, prefix), true + } + return "", false +} + +func filepathToSlash(value string) string { + return strings.ReplaceAll(value, "\\", "/") } diff --git a/core/internal/host/filesystem/filesystem_sftp_test.go b/core/internal/host/filesystem/filesystem_sftp_test.go new file mode 100644 index 00000000..fd760149 --- /dev/null +++ b/core/internal/host/filesystem/filesystem_sftp_test.go @@ -0,0 +1,22 @@ +package filesystem + +import "testing" + +func TestRemotePathWithinUsesDirectoryBoundaries(t *testing.T) { + tests := []struct { + root string + candidate string + want bool + }{ + {"/compose", "/compose", true}, + {"/compose", "/compose/stack/compose.yml", true}, + {"/compose", "/compose-secret/file", false}, + {"/compose", "/etc/passwd", false}, + {"/", "/etc/passwd", true}, + } + for _, test := range tests { + if got := remotePathWithin(test.root, test.candidate); got != test.want { + t.Errorf("remotePathWithin(%q, %q) = %v, want %v", test.root, test.candidate, got, test.want) + } + } +} diff --git a/core/internal/host/service.go b/core/internal/host/service.go index 1137897f..4553370e 100644 --- a/core/internal/host/service.go +++ b/core/internal/host/service.go @@ -3,9 +3,11 @@ package host import ( "fmt" fs2 "io/fs" + "path/filepath" "slices" "strings" "sync" + "time" "github.com/RA341/dockman/internal/docker" "github.com/RA341/dockman/internal/docker/compose" @@ -55,7 +57,12 @@ const RootAlias = "compose" const LocalDocker = "local" func (s *Service) initLocalDocker(composeRoot string, localAddr string) { - conf, err := s.store.Get(LocalDocker) + // Look the local host up by its type, not by the reserved "local" name: + // the Name is user-editable, and keying on it meant that renaming the local + // host made this lookup fail on every startup, silently creating a duplicate + // local host (new ID) with only the default alias and orphaning the user's + // custom aliases (they are linked by host ID). Type is stable across renames. + conf, err := s.store.GetLocal() // Case 1: Create new if it doesn't exist if err != nil { @@ -164,6 +171,26 @@ func (s *Service) GetDockerService(name string) (*docker.Service, error) { }, ) + // reverse of the parser above: map the daemon's absolute compose-file + // path back to "alias/relpath" by matching it against the alias roots, + // so containers can link back to the dockman file that deployed them + service.Compose.SetPathResolver(func(absPath string) string { + aliases, err := val.As.List() + if err != nil { + return "" + } + for _, alias := range aliases { + root := strings.TrimSuffix(filepath.ToSlash(alias.Fullpath), "/") + if root == "" { + continue + } + if rel, found := strings.CutPrefix(absPath, root+"/"); found { + return alias.Alias + "/" + rel + } + } + return "" + }) + return service, nil } @@ -252,11 +279,15 @@ func (s *Service) LoadAll() { for _, host := range all { wg.Go(func() { - err2 := s.Add(&host, false) - if err2 != nil { - log.Error(). + if err2 := s.Add(&host, false); err2 != nil { + // The Docker daemon may simply not be reachable yet (e.g. dockman + // started before dockerd finished coming up after a reboot). + // Keep trying in the background instead of dropping the host, + // which otherwise leaves it "lost" until manually re-added. + log.Warn(). Err(err2).Str("name", host.Name). - Msg("Failed to load host") + Msg("host not reachable at startup, retrying in background") + go s.retryLoad(host.Name) } }) } @@ -266,6 +297,44 @@ func (s *Service) LoadAll() { log.Info().Strs("clients", s.activeClients.Keys()).Msg("loaded hosts") } +// retryLoad keeps trying to connect a host that failed its initial load, so a +// host whose Docker daemon isn't ready yet at startup is recovered +// automatically instead of staying "lost" until it is manually re-added. It +// re-reads the host each attempt (stopping if it was disabled or removed), +// backs off exponentially, and gives up after a bounded number of attempts so +// a permanently unreachable host cannot leak a goroutine forever. +func (s *Service) retryLoad(name string) { + const maxAttempts = 10 + const maxDelay = 30 * time.Second + delay := 2 * time.Second + + for attempt := 1; attempt <= maxAttempts; attempt++ { + time.Sleep(delay) + + conf, err := s.store.Get(name) + if err != nil || !conf.Enable { + // removed or disabled in the meantime, stop retrying + return + } + + if err = s.Add(&conf, false); err != nil { + log.Debug().Err(err).Str("name", name).Int("attempt", attempt). + Msg("host reconnect attempt failed") + delay *= 2 + if delay > maxDelay { + delay = maxDelay + } + continue + } + + log.Info().Str("name", name).Int("attempt", attempt).Msg("host reconnected") + return + } + + log.Error().Str("name", name). + Msg("gave up connecting host after retries; re-enable it once its Docker daemon is reachable") +} + func (s *Service) Add(config *Config, create bool) (err error) { ah, err := s.loadHost(config, create) if err != nil { diff --git a/core/internal/host/store_host_config.go b/core/internal/host/store_host_config.go index b82c8cb0..abbe7466 100644 --- a/core/internal/host/store_host_config.go +++ b/core/internal/host/store_host_config.go @@ -7,6 +7,7 @@ import ( type Store interface { Get(Host string) (Config, error) + GetLocal() (Config, error) Add(conf *Config) error Delete(conf *Config) error Update(conf *Config) error diff --git a/core/internal/host/store_host_config_gorm.go b/core/internal/host/store_host_config_gorm.go index 6791a275..fde8d081 100644 --- a/core/internal/host/store_host_config_gorm.go +++ b/core/internal/host/store_host_config_gorm.go @@ -23,6 +23,21 @@ func (s *gormStore) Get(name string) (Config, error) { return conf, err } +// GetLocal retrieves the local host by its (immutable) Type rather than its +// user-editable Name, so a renamed local host is still found instead of being +// treated as missing. If several exist (e.g. duplicates from a previous bug), +// the earliest-created one is returned so its aliases are preserved. +func (s *gormStore) GetLocal() (Config, error) { + var conf Config + err := s.db. + Preload("SSHOptions"). + Preload("FolderAliases"). + Where("type = ?", LOCAL). + Order("created_at ASC"). + First(&conf).Error + return conf, err +} + // Add inserts a new Config and its associations into the database func (s *gormStore) Add(conf *Config) error { return s.db.Create(conf).Error diff --git a/core/internal/viewer/service.go b/core/internal/viewer/service.go index 25d8b8f9..3a6dc46d 100644 --- a/core/internal/viewer/service.go +++ b/core/internal/viewer/service.go @@ -6,6 +6,7 @@ import ( "github.com/RA341/dockman/internal/docker" "github.com/RA341/dockman/internal/info" + "github.com/RA341/dockman/pkg/fileutil" "github.com/RA341/dockman/pkg/syncmap" "github.com/google/uuid" "github.com/moby/moby/api/types/container" @@ -112,6 +113,8 @@ func (s *Service) StartSession(ctx context.Context, relPath string, alias string if err != nil { return "", nil, err } + // close the pull-progress stream so its response body/connection is released + defer fileutil.Close(progress) err = progress.Wait(context.Background()) if err != nil { diff --git a/core/pkg/memlimit/memlimit.go b/core/pkg/memlimit/memlimit.go new file mode 100644 index 00000000..1d75d8cc --- /dev/null +++ b/core/pkg/memlimit/memlimit.go @@ -0,0 +1,107 @@ +// Package memlimit derives the Go runtime soft memory limit (GOMEMLIMIT) from +// the container's cgroup memory limit. +// +// As of Go 1.26 the runtime is cgroup-aware for GOMAXPROCS but still does NOT +// derive GOMEMLIMIT from cgroups. Left unset, the garbage collector sizes the +// heap goal at roughly 2x the live heap (GOGC=100) and returns freed pages to +// the OS only lazily. Any transient spike — exporting/diving an image, replaying +// a container's log backlog, decoding stats — inflates RSS and then stays +// resident. In a memory-capped container that reads as steadily high memory and, +// at worst, an OOM kill. +// +// Configure reads the cgroup (v2 first, then v1) memory limit and hands the GC a +// soft limit at a fraction of it, leaving headroom for memory the Go GC cannot +// manage (goroutine stacks, the CGO/SQLite allocator, OS overhead). An explicit +// GOMEMLIMIT in the environment always wins and disables this logic. +package memlimit + +import ( + "os" + "runtime/debug" + "strconv" + "strings" + + "github.com/dustin/go-humanize" + "github.com/rs/zerolog/log" +) + +// defaultRatio is the fraction of the detected cgroup limit handed to the GC as +// its soft limit; the remainder is headroom for off-heap / CGO / stack memory. +const defaultRatio = 0.9 + +const ( + // cgroup v2 memory limit; the file holds "max" when unlimited. + cgroupV2Max = "/sys/fs/cgroup/memory.max" + // cgroup v1 memory limit. + cgroupV1Max = "/sys/fs/cgroup/memory/memory.limit_in_bytes" +) + +// unlimitedThreshold guards against cgroup v1's "no limit" sentinel, which is a +// page-aligned near-max int64 (PAGE_COUNTER_MAX). Any value at or above this is +// treated as unlimited rather than as a real limit. +const unlimitedThreshold = int64(1) << 62 + +// Configure sets the Go runtime soft memory limit from the cgroup memory limit +// and returns the applied limit in bytes. It returns 0 (a no-op) when GOMEMLIMIT +// is already set, when no cgroup limit is found, or when the cgroup is unlimited. +func Configure() int64 { + return configure(defaultRatio, readCgroupLimit) +} + +// configure is the testable core of Configure with an injectable cgroup reader. +func configure(ratio float64, read func() (int64, bool)) int64 { + if v := strings.TrimSpace(os.Getenv("GOMEMLIMIT")); v != "" { + // The runtime already parsed and applied GOMEMLIMIT at startup; never + // second-guess an explicit operator choice. + log.Info().Str("GOMEMLIMIT", v).Msg("respecting GOMEMLIMIT from environment") + return 0 + } + + limit, ok := read() + if !ok { + log.Debug().Msg("no cgroup memory limit detected; leaving Go soft memory limit unset") + return 0 + } + + soft := int64(float64(limit) * ratio) + if soft <= 0 { + return 0 + } + + debug.SetMemoryLimit(soft) + log.Info(). + Str("cgroup_limit", humanize.IBytes(uint64(limit))). + Str("go_mem_limit", humanize.IBytes(uint64(soft))). + Msg("configured Go soft memory limit from cgroup") + return soft +} + +// readCgroupLimit returns the cgroup memory limit in bytes, preferring cgroup v2 +// and falling back to v1. ok is false when no finite limit is configured. +func readCgroupLimit() (int64, bool) { + if v, ok := parseLimitFile(cgroupV2Max); ok { + return v, true + } + return parseLimitFile(cgroupV1Max) +} + +// parseLimitFile reads a single-value cgroup limit file and returns the byte +// limit. ok is false when the file is missing, empty, "max", or an unlimited +// sentinel. +func parseLimitFile(path string) (int64, bool) { + raw, err := os.ReadFile(path) + if err != nil { + return 0, false + } + + s := strings.TrimSpace(string(raw)) + if s == "" || s == "max" { // cgroup v2 unlimited + return 0, false + } + + v, err := strconv.ParseInt(s, 10, 64) + if err != nil || v <= 0 || v >= unlimitedThreshold { // cgroup v1 unlimited sentinel + return 0, false + } + return v, true +} diff --git a/core/pkg/memlimit/memlimit_test.go b/core/pkg/memlimit/memlimit_test.go new file mode 100644 index 00000000..e97b9b7a --- /dev/null +++ b/core/pkg/memlimit/memlimit_test.go @@ -0,0 +1,91 @@ +package memlimit + +import ( + "os" + "path/filepath" + "runtime/debug" + "testing" +) + +func TestParseLimitFile(t *testing.T) { + dir := t.TempDir() + + write := func(name, content string) string { + p := filepath.Join(dir, name) + if err := os.WriteFile(p, []byte(content), 0o600); err != nil { + t.Fatalf("write %s: %v", name, err) + } + return p + } + + tests := []struct { + name string + content string + want int64 + wantOK bool + }{ + {"finite", "536870912\n", 536870912, true}, + {"v2 max", "max\n", 0, false}, + {"empty", " \n", 0, false}, + {"zero", "0", 0, false}, + {"negative", "-1", 0, false}, + {"v1 unlimited sentinel", "9223372036854771712", 0, false}, + {"garbage", "not-a-number", 0, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, ok := parseLimitFile(write(tt.name, tt.content)) + if ok != tt.wantOK || got != tt.want { + t.Fatalf("parseLimitFile(%q) = (%d, %v), want (%d, %v)", + tt.content, got, ok, tt.want, tt.wantOK) + } + }) + } + + if _, ok := parseLimitFile(filepath.Join(dir, "does-not-exist")); ok { + t.Fatalf("parseLimitFile(missing) returned ok=true") + } +} + +func TestConfigureAppliesRatio(t *testing.T) { + // restore the process-wide limit after the test + prev := debug.SetMemoryLimit(-1) + defer debug.SetMemoryLimit(prev) + + const cgroupLimit = int64(1000) + got := configure(0.9, func() (int64, bool) { return cgroupLimit, true }) + + if want := int64(900); got != want { + t.Fatalf("configure applied %d, want %d", got, want) + } + if applied := debug.SetMemoryLimit(-1); applied != 900 { + t.Fatalf("runtime soft limit = %d, want 900", applied) + } +} + +func TestConfigureNoLimitIsNoop(t *testing.T) { + prev := debug.SetMemoryLimit(-1) + defer debug.SetMemoryLimit(prev) + + if got := configure(0.9, func() (int64, bool) { return 0, false }); got != 0 { + t.Fatalf("configure with no cgroup limit = %d, want 0", got) + } + if applied := debug.SetMemoryLimit(-1); applied != prev { + t.Fatalf("no-op configure changed the soft limit: got %d, want %d", applied, prev) + } +} + +func TestConfigureRespectsEnv(t *testing.T) { + t.Setenv("GOMEMLIMIT", "512MiB") + + called := false + got := configure(0.9, func() (int64, bool) { called = true; return 1 << 30, true }) + + if got != 0 { + t.Fatalf("configure with GOMEMLIMIT set = %d, want 0", got) + } + if called { + t.Fatalf("configure read the cgroup despite GOMEMLIMIT being set") + } +} diff --git a/core/pkg/ws/ws.go b/core/pkg/ws/ws.go index 52ae43ea..9e54271c 100644 --- a/core/pkg/ws/ws.go +++ b/core/pkg/ws/ws.go @@ -9,6 +9,14 @@ type WsWriter struct { ws *websocket.Conn } +// LimitClientMessages bounds browser-to-server frames without constraining +// server-to-browser log and terminal streams. Terminal input, resize events +// and file-search queries are all tiny; 1 MiB leaves ample compatibility +// headroom while preventing a single frame from exhausting server memory. +func LimitClientMessages(ws *websocket.Conn) { + ws.SetReadLimit(1 << 20) +} + func NewWsWriter(ws *websocket.Conn) *WsWriter { return &WsWriter{ ws: ws, diff --git a/pkg/docker/Dockerfile b/pkg/docker/Dockerfile index ee444f6e..391b0b1c 100644 --- a/pkg/docker/Dockerfile +++ b/pkg/docker/Dockerfile @@ -1,67 +1,144 @@ -FROM node:24-alpine AS front +# syntax=docker/dockerfile:1.7 -WORKDIR /frontend - -COPY ui/package.json ui/package-lock.json ./ - -RUN npm i - -COPY ui . - -RUN npm run build - -FROM golang:1.26-alpine AS back - -WORKDIR /build +ARG NODE_VERSION=24.18.0 +ARG GO_VERSION=1.26.5 +ARG ALPINE_VERSION=3.24.1 +ARG COMPOSE_VERSION=v5.3.1 +ARG COMPOSE_X_CRYPTO_VERSION=v0.54.0 +ARG COMPOSE_X_TEXT_VERSION=v0.40.0 +ARG NODE_IMAGE_DIGEST=sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd +ARG GO_IMAGE_DIGEST=sha256:0178a641fbb4858c5f1b48e34bdaabe0350a330a1b1149aabd498d0699ff5fb2 +ARG ALPINE_IMAGE_DIGEST=sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b -RUN apk update && apk add --no-cache gcc musl-dev git curl +FROM node:${NODE_VERSION}-alpine3.24@${NODE_IMAGE_DIGEST} AS front -RUN sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin - -RUN task --help - -COPY core/go.mod core/go.sum core/ - -RUN cd core && go mod download - -COPY .git .git -COPY core/ core/ -COPY Taskfile.yml . +WORKDIR /frontend -RUN task go:b:docker OUT_EXE=../dockman PROD=1 +# Electron is a desktop-only development dependency. The container build only +# compiles the web frontend, so avoid downloading an unused platform binary. +ENV ELECTRON_SKIP_BINARY_DOWNLOAD=1 -FROM alpine:latest AS compose_cli_downloader +COPY ui/package.json ui/package-lock.json ./ -WORKDIR /download +RUN --mount=type=cache,target=/root/.npm \ + npm ci -RUN apk --no-cache add curl ca-certificates +COPY ui/ ./ -ARG COMPOSE_VERSION=v5.1.0 +# Browser diagnostics are compiled out by default. A dedicated diagnostic +# image can opt in with --build-arg VITE_DEBUG=true. +ARG VITE_DEBUG=false +RUN VITE_DEBUG="${VITE_DEBUG}" npm run build -# $(uname -m) to automatically detect x86_64 or aarch64 (ARM) -RUN curl -SL "https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-linux-$(uname -m)" \ - -o ./docker-compose && \ - chmod +x ./docker-compose +FROM golang:${GO_VERSION}-alpine3.24@${GO_IMAGE_DIGEST} AS back -FROM alpine:latest AS alpine +WORKDIR /build -RUN apk add --no-cache tzdata su-exec +RUN apk add --no-cache gcc musl-dev + +COPY core/go.mod core/go.sum ./ + +RUN --mount=type=cache,target=/go/pkg/mod \ + go mod download + +COPY core/ ./ + +ARG VERSION=development +ARG COMMIT_INFO=unknown +ARG BUILD_DATE=1970-01-01T00:00:00Z +ARG BRANCH=unknown + +RUN --mount=type=cache,target=/go/pkg/mod \ + --mount=type=cache,target=/root/.cache/go-build \ + mkdir -p /out && \ + CGO_ENABLED=1 go build \ + -trimpath \ + -buildvcs=false \ + -ldflags "-s -w \ + -X github.com/RA341/dockman/internal/info.Version=${VERSION} \ + -X github.com/RA341/dockman/internal/info.CommitInfo=${COMMIT_INFO} \ + -X github.com/RA341/dockman/internal/info.BuildDate=${BUILD_DATE} \ + -X github.com/RA341/dockman/internal/info.Branch=${BRANCH}" \ + -o /out/dockman \ + ./cmd/docker + +FROM golang:${GO_VERSION}-alpine3.24@${GO_IMAGE_DIGEST} AS compose_cli_builder + +ARG COMPOSE_VERSION +ARG COMPOSE_X_CRYPTO_VERSION +ARG COMPOSE_X_TEXT_VERSION + +# Rebuild the upstream Compose release with the patched Go toolchain and +# explicit compatible security updates. Modules are authenticated by sum.golang.org. +RUN --mount=type=cache,target=/go/pkg/mod \ + --mount=type=cache,target=/root/.cache/go-build \ + go mod download "github.com/docker/compose/v5@${COMPOSE_VERSION}" && \ + cp -R "/go/pkg/mod/github.com/docker/compose/v5@${COMPOSE_VERSION}" /compose-source && \ + chmod -R u+w /compose-source && \ + cd /compose-source && \ + go mod edit \ + -require="golang.org/x/crypto@${COMPOSE_X_CRYPTO_VERSION}" \ + -require="golang.org/x/text@${COMPOSE_X_TEXT_VERSION}" && \ + go mod tidy && \ + CGO_ENABLED=0 go build \ + -trimpath \ + -ldflags "-s -w -X github.com/docker/compose/v5/internal.Version=${COMPOSE_VERSION}" \ + -o /out/docker-compose \ + ./cmd && \ + /out/docker-compose version + +FROM alpine:${ALPINE_VERSION}@${ALPINE_IMAGE_DIGEST} AS runtime + +# docker-cli powers the run-a-docker-command feature on the local host +# (remote hosts run it over ssh); it also works through DOCKER_HOST/socketproxy. +RUN apk add --no-cache tzdata su-exec docker-cli COPY pkg/docker/docker-entrypoint.sh /usr/local/bin/entrypoint.sh RUN chmod +x /usr/local/bin/entrypoint.sh -COPY --from=compose_cli_downloader /download/docker-compose /usr/local/bin/docker-compose +COPY --from=compose_cli_builder /out/docker-compose /usr/local/bin/docker-compose # identify dockman containers LABEL dockman.container=true +ARG VERSION=development +ARG COMMIT_INFO=unknown +ARG BUILD_DATE=1970-01-01T00:00:00Z +ARG BRANCH=unknown +ARG NODE_VERSION +ARG GO_VERSION +ARG ALPINE_VERSION +ARG COMPOSE_VERSION +ARG COMPOSE_X_CRYPTO_VERSION +ARG COMPOSE_X_TEXT_VERSION +ARG NODE_IMAGE_DIGEST +ARG GO_IMAGE_DIGEST +ARG ALPINE_IMAGE_DIGEST + +LABEL org.opencontainers.image.title="Dockman" \ + org.opencontainers.image.description="Docker management UI built from the Dockman integration fork" \ + org.opencontainers.image.source="https://github.com/cerede2000/dockman" \ + org.opencontainers.image.version="${VERSION}" \ + org.opencontainers.image.revision="${COMMIT_INFO}" \ + org.opencontainers.image.created="${BUILD_DATE}" \ + dev.dockman.build.branch="${BRANCH}" \ + dev.dockman.build.node="${NODE_VERSION}" \ + dev.dockman.build.go="${GO_VERSION}" \ + dev.dockman.build.alpine="${ALPINE_VERSION}" \ + dev.dockman.build.compose="${COMPOSE_VERSION}" \ + dev.dockman.build.compose-x-crypto="${COMPOSE_X_CRYPTO_VERSION}" \ + dev.dockman.build.compose-x-text="${COMPOSE_X_TEXT_VERSION}" \ + dev.dockman.build.node-digest="${NODE_IMAGE_DIGEST}" \ + dev.dockman.build.go-digest="${GO_IMAGE_DIGEST}" \ + dev.dockman.build.alpine-digest="${ALPINE_IMAGE_DIGEST}" + WORKDIR /app -COPY --from=back /build/dockman dockman +COPY --from=back /out/dockman dockman COPY --from=front /frontend/dist/ ./dist -RUN docker-compose version +RUN docker-compose version && docker --version EXPOSE 8866 diff --git a/scripts/check-govuln.sh b/scripts/check-govuln.sh new file mode 100644 index 00000000..a02710cc --- /dev/null +++ b/scripts/check-govuln.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +set -euo pipefail + +repository_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +allowlist_file="$repository_root/security/govulncheck-allowlist.txt" +report_file="$(mktemp)" +findings_file="$(mktemp)" +allowed_file="$(mktemp)" + +cleanup() { + rm -f "$report_file" "$findings_file" "$allowed_file" +} +trap cleanup EXIT + +cd "$repository_root/core" + +set +e +govulncheck -json ./cmd/docker > "$report_file" +scan_status=$? +set -e + +if [[ $scan_status -ne 0 && $scan_status -ne 3 ]]; then + echo "govulncheck failed with status $scan_status" >&2 + exit "$scan_status" +fi + +# A one-element trace is a module/package presence finding. Longer traces are +# symbols reachable from the shipping cmd/docker binary and must be reviewed. +jq -r 'select(.finding and (.finding.trace | length) > 1) | .finding.osv' \ + "$report_file" | sort -u > "$findings_file" +sed -E '/^[[:space:]]*(#|$)/d' "$allowlist_file" | sort -u > "$allowed_file" + +unexpected="$(comm -23 "$findings_file" "$allowed_file")" +stale="$(comm -13 "$findings_file" "$allowed_file")" + +if [[ -n "$unexpected" ]]; then + echo "Unexpected reachable Go vulnerabilities:" >&2 + echo "$unexpected" >&2 + exit 1 +fi + +if [[ -n "$stale" ]]; then + echo "Resolved or unreachable allowlist entries must be removed:" >&2 + echo "$stale" >&2 + exit 1 +fi + +if [[ -s "$findings_file" ]]; then + echo "Only reviewed, currently-unfixed Moby findings remain:" + cat "$findings_file" +else + echo "No reachable Go vulnerabilities found." +fi diff --git a/security/govulncheck-allowlist.txt b/security/govulncheck-allowlist.txt new file mode 100644 index 00000000..9c1ba932 --- /dev/null +++ b/security/govulncheck-allowlist.txt @@ -0,0 +1,8 @@ +# Moby server-side advisories currently reported through package init traces. +# Dockman imports the Docker client through Compose/Dive but does not embed or +# run a Docker daemon. No fixed github.com/docker/docker module exists yet. +GO-2026-4883 +GO-2026-4887 +GO-2026-5617 +GO-2026-5668 +GO-2026-5746 diff --git a/spec/protos/docker/v1/docker.proto b/spec/protos/docker/v1/docker.proto index c5146c9c..6e0ced71 100644 --- a/spec/protos/docker/v1/docker.proto +++ b/spec/protos/docker/v1/docker.proto @@ -10,12 +10,30 @@ service DockerService { rpc ContainerStop(ContainerRequest) returns (LogsMessage) {} rpc ContainerRemove(ContainerRequest) returns (LogsMessage) {} rpc ContainerRestart(ContainerRequest) returns (LogsMessage) {} - rpc ContainerUpdate(ContainerRequest) returns (Empty) {} + rpc ContainerPause(ContainerRequest) returns (LogsMessage) {} + rpc ContainerUnpause(ContainerRequest) returns (LogsMessage) {} + // force-updates the containers' images (pull, recreate when the image + // changed, rollback on failure), streaming per-step progress + rpc ContainerUpdate(ContainerRequest) returns (stream LogsMessage) {} rpc ContainerTop(ContainerTopRequest) returns (ContainerTopResponse) {} rpc ContainerList(ContainerListRequest) returns (ListResponse) {} rpc ContainerStats(StatsRequest) returns (StatsResponse) {} + // streams each container's stats as soon as its read completes, so the UI + // fills in progressively instead of waiting for the slowest container + // (fully qualified return type: the sibling ContainerStats rpc otherwise + // shadows the message name inside the service scope) + rpc ContainerStatsStream(StatsRequest) returns (stream .docker.v1.ContainerStats) {} + // real host-level usage (from /proc via the host's runner, so it works for + // ssh hosts too) — the general stats view shows this instead of summing + // per-container numbers + rpc HostStats(Empty) returns (HostStatsResponse) {} rpc ContainerLogs(ContainerLogsRequest) returns (stream LogsMessage) {} + // pushes filtered container lifecycle events (start/stop/die/health + // transitions...) so views can refresh reactively instead of polling; + // empty-action messages are keepalives + rpc ContainerEvents(EventsRequest) returns (stream ContainerEvent) {} + rpc ContainerLogsStream(LogsStreamRequest) returns (stream LogLine) {} rpc ContainerInspect(ContainerLogsRequest) returns (ContainerInspectMessage) {} // compose @@ -25,10 +43,17 @@ service DockerService { rpc ComposeStop(ComposeFile) returns (stream LogsMessage) {} rpc ComposeRestart(ComposeFile) returns (stream LogsMessage) {} rpc ComposeUpdate(ComposeFile) returns (stream LogsMessage) {} + // compose up -d with explicit force flags (pull / build / recreate), + // so a stack can be redeployed in one action + rpc ComposeRedeploy(ComposeRedeployRequest) returns (stream LogsMessage) {} rpc ComposeList(ComposeFile) returns (ListResponse) {} rpc ComposeValidate(ComposeFile) returns (ComposeValidateResponse) {} rpc ComposeFileStatus(ComposeFileStatusRequest) returns (ComposeFileStatusResponse) {} + // runs a user-provided docker CLI command on the selected host and streams + // its combined output; only the docker binary is allowed + rpc DockerCommand(DockerCommandRequest) returns (stream LogsMessage) {} + // images rpc ImageList(ListImagesRequest) returns (ListImagesResponse) {} rpc ImageRemove(RemoveImageRequest) returns (RemoveImageResponse) {} @@ -39,12 +64,15 @@ service DockerService { rpc VolumeList(ListVolumesRequest) returns (ListVolumesResponse) {} rpc VolumeCreate(CreateVolumeRequest) returns (CreateVolumeResponse) {} rpc VolumeDelete(DeleteVolumeRequest) returns (DeleteVolumeResponse) {} + rpc VolumeInspect(VolumeInspectRequest) returns (VolumeInspectResponse) {} // networks rpc NetworkList(ListNetworksRequest) returns (ListNetworksResponse) {} rpc NetworkCreate(CreateNetworkRequest) returns (CreateNetworkResponse) {} rpc NetworkDelete(DeleteNetworkRequest) returns (DeleteNetworkResponse) {} rpc NetworkInspect(NetworkInspectRequest) returns (NetworkInspectResponse) {} + rpc NetworkConnectContainer(NetworkConnectContainerRequest) returns (NetworkConnectContainerResponse) {} + rpc NetworkDisconnectContainer(NetworkDisconnectContainerRequest) returns (NetworkDisconnectContainerResponse) {} } @@ -89,6 +117,9 @@ message ContainerInspectMessage { string HostsPath = 5; repeated ContainerMount mounts = 6; ContainerConfig config = 8; + // Complete daemon inspect response. This deliberately stays JSON so newer + // daemon fields remain visible without forcing a Dockman protocol release. + string raw_json = 9; } message ContainerConfig { @@ -168,6 +199,7 @@ message ImageInspect { string arch = 5; string createdIso = 4; repeated ImageLayer layers = 2; + repeated ImageContainerInspect containers = 7; } message ImageLayer { @@ -177,6 +209,13 @@ message ImageLayer { string totalSizeAtLayer = 4; } +message ImageContainerInspect { + string name = 1; + string id = 2; + string state = 3; + string composeProject = 4; +} + message ComposeValidateResponse { repeated string errs = 1; } @@ -284,6 +323,27 @@ message DeleteVolumeRequest { message DeleteVolumeResponse { } +message VolumeInspectRequest { + string volumeName = 1; +} + +message VolumeInspectResponse { + VolumeInspectInfo inspect = 1; +} + +message VolumeInspectInfo { + Volume vol = 1; + repeated VolumeContainerInspect containers = 2; +} + +message VolumeContainerInspect { + string name = 1; + string id = 2; + string destination = 3; + bool rw = 4; + string composeProject = 5; +} + // Network-related messages message Network { string name = 1; @@ -325,6 +385,39 @@ message DeleteNetworkRequest { message DeleteNetworkResponse { } +message NetworkConnectContainerRequest { + string network_id = 1; + string container_id = 2; +} + +message NetworkConnectContainerResponse { +} + +message NetworkDisconnectContainerRequest { + string network_id = 1; + string container_id = 2; +} + +message NetworkDisconnectContainerResponse { +} + +message EventsRequest { + string host = 1; +} + +message ContainerEvent { + // create / start / stop / die / kill / restart / pause / unpause / + // destroy / rename / update / oom / health_status. + // Empty for keepalive frames. + string action = 1; + // health_status only: healthy / unhealthy / ... + string status = 2; + string containerId = 3; + string containerName = 4; + string image = 5; + int64 timeNano = 6; +} + message ContainerLogsRequest { string containerID = 1; } @@ -333,6 +426,42 @@ message LogsMessage { string message = 1; } +message LogsStreamRequest { + // one id = single container view, several = merged stack view + repeated string containerIds = 1; + // number of trailing lines per container, <= 0 means the server default + int32 tail = 2; + // unix seconds bounds, 0 means unbounded + int64 since = 3; + int64 until = 4; + // keep the stream open for new lines; false ends it once history is sent + bool follow = 5; +} + +// a frame with an empty containerId and text is a keepalive +message LogLine { + string containerId = 1; + string containerName = 2; + // line content without the daemon timestamp prefix + string text = 3; + int64 timeNano = 4; + // 1 = stdout, 2 = stderr + int32 stream = 5; +} + +message DockerCommandRequest { + // full command line, e.g. "docker run --rm -p 8080:80 nginx:alpine" + string command = 1; +} + +message HostStatsResponse { + // whole-host cpu usage in percent (0-100), 0 until two samples exist + double cpuPercent = 1; + int64 memUsed = 2; + int64 memTotal = 3; + int32 cpus = 4; +} + message StatsResponse { SystemInfo system = 1; repeated ContainerStats containers = 2; @@ -346,6 +475,7 @@ enum SORT_FIELD { NETWORK_TX = 4; DISK_R = 5; DISK_W = 6; + STARTED = 7; } enum ORDER { @@ -407,6 +537,19 @@ message ContainerStats { uint64 block_read = 8; // Total bytes written to block devices. uint64 block_write = 9; + // Container start time (RFC3339). Empty if unknown / not running. + string started_at = 10; + // Image reference the container was created from. + string image = 11; + // Container state: running, exited, paused, restarting... + string state = 12; + // Health status: healthy / unhealthy / starting. Empty when the container + // has no healthcheck. + string health = 13; + // Container network IP addresses. + repeated string ip_address = 14; + // How many times the container restarted. + int32 restart_count = 15; } @@ -429,3 +572,13 @@ message ComposeFile { repeated string selectedServices = 3; } + +message ComposeRedeployRequest { + ComposeFile file = 1; + // force-pull images (--pull always) + bool pull = 2; + // force-build images (--build) + bool build = 3; + // recreate containers even when nothing changed (--force-recreate) + bool recreate = 4; +} diff --git a/spec/protos/dockyaml/v1/dockyaml.proto b/spec/protos/dockyaml/v1/dockyaml.proto index 7107a4a6..501fd9b3 100644 --- a/spec/protos/dockyaml/v1/dockyaml.proto +++ b/spec/protos/dockyaml/v1/dockyaml.proto @@ -39,6 +39,30 @@ message DockmanYaml { NetworkConfig networkPage = 3; ImageConfig imagePage = 4; ContainerConfig containerPage = 5; + StatsConfig statsPage = 10; + ComposeConfig composePage = 11; + EditorConfig editorPage = 12; + MonitorConfig monitorPage = 13; + // view opened when landing on a host: files (default), monitor, stats, + // containers, images, volumes, networks or cleaner + string defaultView = 14; +} + +message MonitorConfig { + // stack row density in the monitor view: "full" (default) shows CPU/RAM + // values with their charts, "compact" shows the values only + string stackRows = 1; +} + +message ComposeConfig { + // tab shown when opening a compose stack: editor (default), deploy or stats + string defaultTab = 1; +} + +message EditorConfig { + // allow scrolling half a viewport past the last line (it stops at + // mid-view), for files taller than the viewport + bool scrollPastEnd = 1; } message VolumesConfig { @@ -57,6 +81,10 @@ message ContainerConfig { Sort sort = 1; } +message StatsConfig { + Sort sort = 1; +} + message Sort { string sortOrder = 1; diff --git a/spec/protos/files/v1/files.proto b/spec/protos/files/v1/files.proto index 58dd867f..6f2762ac 100644 --- a/spec/protos/files/v1/files.proto +++ b/spec/protos/files/v1/files.proto @@ -70,6 +70,8 @@ message FsEntry { // flag for lazy loading bool isFetched = 5; string isComposeFolder = 6; + // set when the entry's name is pinned in dockman.yml (pinnedFiles) + bool pinned = 7; } message RenameFile { diff --git a/ui/eslint.config.js b/ui/eslint.config.js index 092408a9..e57bf07a 100644 --- a/ui/eslint.config.js +++ b/ui/eslint.config.js @@ -18,11 +18,53 @@ export default tseslint.config( 'react-refresh': reactRefresh, }, rules: { - ...reactHooks.configs.recommended.rules, + // React Hooks 7 adds compiler-oriented rules to its recommended preset. + // Keep the historical checks here; enable the new rules in dedicated + // refactoring batches so a tooling upgrade cannot change runtime code. + 'react-hooks/rules-of-hooks': + reactHooks.configs.recommended.rules['react-hooks/rules-of-hooks'], + 'react-hooks/exhaustive-deps': + reactHooks.configs.recommended.rules['react-hooks/exhaustive-deps'], 'react-refresh/only-export-components': [ 'warn', - { allowConstantExport: true }, + { + allowConstantExport: true, + // These stable hooks, stores and helpers intentionally share modules + // with their providers or views. Keep the rule active for every new + // mixed export instead of disabling it for entire directories. + allowExportNames: [ + 'FilesContext', + 'HostContext', + 'TabsContext', + 'formatDockyaml', + 'getContextKey', + 'getDir', + 'getEntryDisplayName', + 'getExt', + 'getHost', + 'useAlias', + 'useAliasAddDialogState', + 'useFileCreate', + 'useFileDelete', + 'useFileDnD', + 'useFileRename', + 'useFileSearch', + 'useFiles', + 'useHostFromUrl', + 'useHostManager', + 'useTabs', + 'useTabsStore', + ], + }, ], }, }, + { + // Protobuf output owns its blanket eslint-disable directive. Do not make + // generated files noisy when the current rule set happens not to need it. + files: ['src/gen/**/*.ts'], + linterOptions: { + reportUnusedDisableDirectives: 'off', + }, + }, ) diff --git a/ui/package-lock.json b/ui/package-lock.json index 000b7728..4a58c76f 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -15,13 +15,11 @@ "@emotion/styled": "^11.14.1", "@fontsource/roboto": "^5.2.9", "@monaco-editor/react": "^4.7.0", - "@mui/icons-material": "^7.3.6", - "@mui/material": "^7.3.6", - "@mui/styled-engine-sc": "^7.3.6", + "@mui/icons-material": "^9.2.0", + "@mui/material": "^9.2.0", "@nivo/line": "^0.99.0", - "@xterm/addon-fit": "^0.10.0", - "@xterm/addon-search": "^0.15.0", - "@xterm/xterm": "^5.5.0", + "@xterm/addon-fit": "^0.11.0", + "@xterm/xterm": "^6.0.0", "composerize": "^1.7.5", "immer": "^11.1.0", "react": "^19.2.1", @@ -30,35 +28,35 @@ "react-router-dom": "^7.10.1", "reconnecting-websocket": "^4.4.0", "remark-gfm": "^4.0.1", - "styled-components": "^6.1.19", "zustand": "^5.0.9" }, "devDependencies": { - "@eslint/js": "^9.25.0", + "@eslint/js": "^10.0.1", + "@rolldown/plugin-babel": "^0.2.3", "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", + "@vitejs/plugin-react": "^6.0.3", "babel-plugin-react-compiler": "^1.0.0", "cross-env": "^10.1.0", - "electron": "^39.2.7", - "electron-builder": "^26.4.0", - "eslint": "^9.39.1", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-react-refresh": "^0.4.24", - "globals": "^16.5.0", - "typescript": "~5.8.3", - "typescript-eslint": "^8.49.0", - "vite": "7.1.11", + "electron": "^43.1.1", + "electron-builder": "^26.15.3", + "eslint": "^10.7.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.3", + "globals": "^17.7.0", + "typescript": "~6.0.3", + "typescript-eslint": "^8.64.0", + "vite": "^8.1.5", "wait-on": "^9.0.3" } }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -67,9 +65,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -77,21 +75,21 @@ } }, "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -115,13 +113,13 @@ "license": "MIT" }, "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -131,14 +129,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -148,37 +146,37 @@ } }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -187,38 +185,28 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -226,26 +214,26 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -254,73 +242,41 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/runtime": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", - "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", "debug": "^4.3.1" }, "engines": { @@ -328,95 +284,53 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@bufbuild/protobuf": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz", - "integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==", + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.12.1.tgz", + "integrity": "sha512-BvAMfS6LrgZiryOAZ4pBYucu4wG/Ei/9o9DZ9akbREnMLbPJiom2i8b9C8IsKErQoiKqVhrerzt3kOT/RrzLHg==", "license": "(Apache-2.0 AND BSD-3-Clause)" }, "node_modules/@connectrpc/connect": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-2.1.1.tgz", - "integrity": "sha512-JzhkaTvM73m2K1URT6tv53k2RwngSmCXLZJgK580qNQOXRzZRR/BCMfZw3h+90JpnG6XksP5bYT+cz0rpUzUWQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-2.1.2.tgz", + "integrity": "sha512-MXkBijtcX09R10Eb6sFeIetc6w6746eio6xtfuyVOH7oQAacT1X0GzMIQFux6Qy8cq3W/T5qX5Bei8YbFtmRGA==", "license": "Apache-2.0", "peerDependencies": { "@bufbuild/protobuf": "^2.7.0" } }, "node_modules/@connectrpc/connect-web": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@connectrpc/connect-web/-/connect-web-2.1.1.tgz", - "integrity": "sha512-J8317Q2MaFRCT1jzVR1o06bZhDIBmU0UAzWx6xOIXzOq8+k71/+k7MUF7AwcBUX+34WIvbm5syRgC5HXQA8fOg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@connectrpc/connect-web/-/connect-web-2.1.2.tgz", + "integrity": "sha512-1tfaK85MU+gJjwwmL31d2rzdf0XCYX99chZf63uG89SGBUd4XuZ4ZzhGo2u79TPXOE6nLIZQ2okrpyey42PYdg==", "license": "Apache-2.0", "peerDependencies": { "@bufbuild/protobuf": "^2.7.0", - "@connectrpc/connect": "2.1.1" + "@connectrpc/connect": "2.1.2" } }, - "node_modules/@develar/schema-utils": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", - "integrity": "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==", + "node_modules/@electron-internal/extract-zip": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@electron-internal/extract-zip/-/extract-zip-1.0.4.tgz", + "integrity": "sha512-Zr1Vs7E9tpCNhZHDAbFVXc2gEVCG9RqPDjrno5+bdgB6LRAuvgyMHJut4NCVyYwtAieapMzc3fiQ3CSTi75ARg==", "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.0", - "ajv-keywords": "^3.4.1" - }, + "license": "BSD-2-Clause", "engines": { - "node": ">= 8.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/@develar/schema-utils/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "license": "MIT", - "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/@develar/schema-utils/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "ajv": "^6.9.1" + "node": ">=22.12.0" } }, - "node_modules/@develar/schema-utils/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, "node_modules/@electron/asar": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", @@ -435,10 +349,28 @@ "node": ">=10.12.0" } }, + "node_modules/@electron/asar/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@electron/asar/node_modules/brace-expansion": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/@electron/asar/node_modules/minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -480,9 +412,9 @@ } }, "node_modules/@electron/fuses/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -503,25 +435,61 @@ } }, "node_modules/@electron/get": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", - "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-5.0.0.tgz", + "integrity": "sha512-pjoBpru1KdEtcExBnuHAP1cAc/5faoedw0hzJkL3o4/IJp7HNF1+fbrdxT3gMYRX2oJfvnA/WXeCTVQpYYxyJA==", "dev": true, "license": "MIT", "dependencies": { "debug": "^4.1.1", - "env-paths": "^2.2.0", - "fs-extra": "^8.1.0", - "got": "^11.8.5", + "env-paths": "^3.0.0", + "graceful-fs": "^4.2.11", "progress": "^2.0.3", - "semver": "^6.2.0", + "semver": "^7.6.3", "sumchecker": "^3.0.1" }, "engines": { - "node": ">=12" + "node": ">=22.12.0" }, "optionalDependencies": { - "global-agent": "^3.0.0" + "undici": "^7.24.4" + } + }, + "node_modules/@electron/get/node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@electron/get/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/get/node_modules/undici": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=20.18.1" } }, "node_modules/@electron/notarize": { @@ -556,9 +524,9 @@ } }, "node_modules/@electron/notarize/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -629,9 +597,9 @@ } }, "node_modules/@electron/osx-sign/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -652,25 +620,18 @@ } }, "node_modules/@electron/rebuild": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.0.3.tgz", - "integrity": "sha512-u9vpTHRMkOYCs/1FLiSVAFZ7FbjsXK+bQuzviJZa+lG7BHZl1nz52/IcGvwa3sk80/fc3llutBkbCq10Vh8WQA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.2.0.tgz", + "integrity": "sha512-RKL/O+jGoXJMxrx/5771y1n0xTKmFuOYGO3gMmwypBM6rsH0kou0mswwdXA2JrhIkE4xyC7v9vGk0n6NPzgOxQ==", "dev": true, "license": "MIT", "dependencies": { "@malept/cross-spawn-promise": "^2.0.0", "debug": "^4.1.1", - "detect-libc": "^2.0.1", - "got": "^11.7.0", - "graceful-fs": "^4.2.11", "node-abi": "^4.2.0", "node-api-version": "^0.2.1", - "node-gyp": "^11.2.0", - "ora": "^5.1.0", - "read-binary-file-arch": "^1.0.6", - "semver": "^7.3.5", - "tar": "^7.5.6", - "yargs": "^17.0.1" + "node-gyp": "^12.2.0", + "read-binary-file-arch": "^1.0.6" }, "bin": { "electron-rebuild": "lib/cli.js" @@ -679,19 +640,6 @@ "node": ">=22.12.0" } }, - "node_modules/@electron/rebuild/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@electron/universal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.3.tgz", @@ -712,32 +660,26 @@ } }, "node_modules/@electron/universal/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } + "license": "MIT" }, "node_modules/@electron/universal/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" + "balanced-match": "^1.0.0" } }, "node_modules/@electron/universal/node_modules/fs-extra": { - "version": "11.3.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", - "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.6.tgz", + "integrity": "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==", "dev": true, "license": "MIT", "dependencies": { @@ -750,9 +692,9 @@ } }, "node_modules/@electron/universal/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -763,13 +705,13 @@ } }, "node_modules/@electron/universal/node_modules/minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -811,9 +753,9 @@ } }, "node_modules/@electron/windows-sign/node_modules/fs-extra": { - "version": "11.3.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", - "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.6.tgz", + "integrity": "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==", "dev": true, "license": "MIT", "optional": true, @@ -828,9 +770,9 @@ } }, "node_modules/@electron/windows-sign/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "optional": true, @@ -854,6 +796,42 @@ "node": ">= 10.0.0" } }, + "node_modules/@emnapi/core": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.2.tgz", + "integrity": "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", + "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emotion/babel-plugin": { "version": "11.13.5", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", @@ -1007,1130 +985,520 @@ "dev": true, "license": "MIT" }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "aix" - ], + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, "engines": { - "node": ">=18" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "license": "Apache-2.0", "engines": { - "node": ">=18" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], "engines": { - "node": ">=18" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], + "node_modules/@eslint/config-helpers": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", + "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "license": "Apache-2.0", "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "node_modules/@fontsource/roboto": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.3.0.tgz", + "integrity": "sha512-BapRJOWYP+LZ21zp+wBQjfpPYKRoxc4LspJ/RLuI+HSMBD5u/X4O+ESDrSvEqDSy0rAl7GwBJ+09mdc16cVQ1Q==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], + "node_modules/@hapi/address": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@hapi/address/-/address-5.1.1.tgz", + "integrity": "sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" + }, "engines": { - "node": ">=18" + "node": ">=14.0.0" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], + "node_modules/@hapi/formula": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-3.0.2.tgz", + "integrity": "sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } + "license": "BSD-3-Clause" }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], + "node_modules/@hapi/hoek": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.7.tgz", + "integrity": "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/pinpoint": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.1.tgz", + "integrity": "sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/tlds": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.7.tgz", + "integrity": "sha512-MgNjRwy9Ti92yVAixLmDc8dd1bJIKwO9qlWCfFQRwRmUEDPQHYn4G6hwPFvFGUTzAa0FsS+inMjLin7GnyBRhA==", + "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">=18" + "node": ">=14.0.0" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], + "node_modules/@hapi/topo": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", + "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { - "node": ">=18" + "node": ">=18.18.0" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, "engines": { - "node": ">=18" + "node": ">=18.18.0" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "Apache-2.0", "engines": { - "node": ">=18" + "node": ">=18.18.0" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], + "license": "Apache-2.0", "engines": { - "node": ">=18" + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], + "license": "Apache-2.0", "engines": { - "node": ">=18" + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], "engines": { - "node": ">=18" + "node": ">=6.0.0" } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], + "node_modules/@malept/cross-spawn-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", + "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, "engines": { - "node": ">=18" + "node": ">= 12.13.0" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], + "node_modules/@malept/flatpak-bundler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", + "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "tmp-promise": "^3.0.2" + }, "engines": { - "node": ">=18" + "node": ">= 10.0.0" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.4.3" + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "node": ">=10" } }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "node_modules/@malept/flatpak-bundler/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" }, - "funding": { - "url": "https://opencollective.com/eslint" + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "node_modules/@malept/flatpak-bundler/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "license": "MIT", "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">= 10.0.0" } }, - "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@monaco-editor/loader": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.7.0.tgz", + "integrity": "sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==", + "license": "MIT", "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "state-local": "^1.0.6" } }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", - "dev": true, - "license": "ISC", + "node_modules/@monaco-editor/react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz", + "integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "@monaco-editor/loader": "^1.5.0" }, - "engines": { - "node": "*" + "peerDependencies": { + "monaco-editor": ">= 0.25.0 < 1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node_modules/@mui/core-downloads-tracker": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-9.2.0.tgz", + "integrity": "sha512-+XMav+ZaXkZKUFUgzjrfMEedfyJKxxviAske2q8N8CWDMeqZdDU2lWMkiUPiB388hGaDqhwvOAwkrsc/pUyp8g==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" } }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@mui/icons-material": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-9.2.0.tgz", + "integrity": "sha512-VgBd3z7Qc3vd/thcNSMC03nHRh/U4DzMUd+1dRyJTbm/hGo7+N6N4GDuJZDNHa6LZhhwG6Cu1X3DNvrVv8sNag==", + "license": "MIT", "dependencies": { - "@types/json-schema": "^7.0.15" + "@babel/runtime": "^7.29.2" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^9.2.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", - "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", - "dev": true, + "node_modules/@mui/material": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-9.2.0.tgz", + "integrity": "sha512-+YTRSgGKGrrRo2XJZXs7JRA6qHoHWvNtxyqxnrRJTBmIuLOUpxxh7m4G9lF4tWberxGFY+EqkkRPgJCl+fSMJg==", "license": "MIT", "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" + "@babel/runtime": "^7.29.2", + "@mui/core-downloads-tracker": "^9.2.0", + "@mui/system": "^9.2.0", + "@mui/types": "^9.1.1", + "@mui/utils": "^9.2.0", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.2.3", + "prop-types": "^15.8.1", + "react-is": "^19.2.6", + "react-transition-group": "^4.4.5" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=14.0.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^9.2.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } } }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, + "node_modules/@mui/private-theming": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-9.2.0.tgz", + "integrity": "sha512-w9wpyDxGPGnAACPB2hKhCDmILJIAvQxrfjUbIAEa0AznX1rOjaz5N+yB1uuw8ixnJcpEh/tPbD9oEe19wcWPHw==", "license": "MIT", "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" + "@babel/runtime": "^7.29.2", + "@mui/utils": "^9.2.0", + "prop-types": "^15.8.1" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/js": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", - "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@fontsource/roboto": { - "version": "5.2.9", - "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.2.9.tgz", - "integrity": "sha512-ZTkyHiPk74B/aj8BZWbsxD5Yu+Lq+nR64eV4wirlrac2qXR7jYk2h6JlLYuOuoruTkGQWNw2fMuKNavw7/rg0w==", - "license": "OFL-1.1", - "funding": { - "url": "https://github.com/sponsors/ayuhito" - } - }, - "node_modules/@hapi/address": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@hapi/address/-/address-5.1.1.tgz", - "integrity": "sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^11.0.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@hapi/formula": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-3.0.2.tgz", - "integrity": "sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@hapi/hoek": { - "version": "11.0.7", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.7.tgz", - "integrity": "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@hapi/pinpoint": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.1.tgz", - "integrity": "sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@hapi/tlds": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.4.tgz", - "integrity": "sha512-Fq+20dxsxLaUn5jSSWrdtSRcIUba2JquuorF9UW1wIJS5cSUwxIsO2GIhaWynPRflvxSzFN+gxKte2HEW1OuoA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@hapi/topo": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", - "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@malept/cross-spawn-promise": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", - "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/malept" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" - } - ], - "license": "Apache-2.0", - "dependencies": { - "cross-spawn": "^7.0.1" - }, - "engines": { - "node": ">= 12.13.0" - } - }, - "node_modules/@malept/flatpak-bundler": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", - "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "fs-extra": "^9.0.0", - "lodash": "^4.17.15", - "tmp-promise": "^3.0.2" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@malept/flatpak-bundler/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@malept/flatpak-bundler/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@monaco-editor/loader": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.7.0.tgz", - "integrity": "sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==", - "license": "MIT", - "dependencies": { - "state-local": "^1.0.6" - } - }, - "node_modules/@monaco-editor/react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz", - "integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==", - "license": "MIT", - "dependencies": { - "@monaco-editor/loader": "^1.5.0" - }, - "peerDependencies": { - "monaco-editor": ">= 0.25.0 < 1", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/@mui/core-downloads-tracker": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.7.tgz", - "integrity": "sha512-8jWwS6FweMkpyRkrJooamUGe1CQfO1yJ+lM43IyUJbrhHW/ObES+6ry4vfGi8EKaldHL3t3BG1bcLcERuJPcjg==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - } - }, - "node_modules/@mui/icons-material": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.7.tgz", - "integrity": "sha512-3Q+ulAqG+A1+R4ebgoIs7AccaJhIGy+Xi/9OnvX376jQ6wcy+rz4geDGrxQxCGzdjOQr4Z3NgyFSZCz4T999lA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.28.4" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@mui/material": "^7.3.7", - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/material": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.7.tgz", - "integrity": "sha512-6bdIxqzeOtBAj2wAsfhWCYyMKPLkRO9u/2o5yexcL0C3APqyy91iGSWgT3H7hg+zR2XgE61+WAu12wXPON8b6A==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.28.4", - "@mui/core-downloads-tracker": "^7.3.7", - "@mui/system": "^7.3.7", - "@mui/types": "^7.4.10", - "@mui/utils": "^7.3.7", - "@popperjs/core": "^2.11.8", - "@types/react-transition-group": "^4.4.12", - "clsx": "^2.1.1", - "csstype": "^3.2.3", - "prop-types": "^15.8.1", - "react-is": "^19.2.3", - "react-transition-group": "^4.4.5" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@mui/material-pigment-css": "^7.3.7", - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "@mui/material-pigment-css": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/private-theming": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.7.tgz", - "integrity": "sha512-w7r1+CYhG0syCAQUWAuV5zSaU2/67WA9JXUderdb7DzCIJdp/5RmJv6L85wRjgKCMsxFF0Kfn0kPgPbPgw/jdw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.28.4", - "@mui/utils": "^7.3.7", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=14.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", @@ -2147,12 +1515,12 @@ } }, "node_modules/@mui/styled-engine": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.7.tgz", - "integrity": "sha512-y/QkNXv6cF6dZ5APztd/dFWfQ6LHKPx3skyYO38YhQD4+Cxd6sFAL3Z38WMSSC8LQz145Mpp3CcLrSCLKPwYAg==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-9.1.1.tgz", + "integrity": "sha512-neaYKdJfvEG54q8efHLJR7swpHG/gfSv9xGqW5iTSMsubD7yPCPFrhVBt284j1DOF3uZaaDJSHQL7gz6jGF21Q==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.4", + "@babel/runtime": "^7.29.2", "@emotion/cache": "^11.14.0", "@emotion/serialize": "^1.3.3", "@emotion/sheet": "^1.4.0", @@ -2180,40 +1548,17 @@ } } }, - "node_modules/@mui/styled-engine-sc": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/@mui/styled-engine-sc/-/styled-engine-sc-7.3.7.tgz", - "integrity": "sha512-BJ91ujrXXaYW0wXdEw8K1EoUzsqA6e/sJhxxLlwWjqgRWC8spa+MFP+H+5vCUqAHRiB38dAOcvgZxflUG5oeZg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.28.4", - "@types/hoist-non-react-statics": "^3.3.7", - "csstype": "^3.2.3", - "hoist-non-react-statics": "^3.3.2", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "styled-components": "^6.0.0" - } - }, "node_modules/@mui/system": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.7.tgz", - "integrity": "sha512-DovL3k+FBRKnhmatzUMyO5bKkhMLlQ9L7Qw5qHrre3m8zCZmE+31NDVBFfqrbrA7sq681qaEIHdkWD5nmiAjyQ==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-9.2.0.tgz", + "integrity": "sha512-YvUJwKoGVtbnOm2PyPi5TvX2d1rOA6sqSpEWVs4WmXNIaFTuYmNUaVdU2o1NKUEe31URnD3E8ZVUMcsLQXwcYg==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.4", - "@mui/private-theming": "^7.3.7", - "@mui/styled-engine": "^7.3.7", - "@mui/types": "^7.4.10", - "@mui/utils": "^7.3.7", + "@babel/runtime": "^7.29.2", + "@mui/private-theming": "^9.2.0", + "@mui/styled-engine": "^9.1.1", + "@mui/types": "^9.1.1", + "@mui/utils": "^9.2.0", "clsx": "^2.1.1", "csstype": "^3.2.3", "prop-types": "^15.8.1" @@ -2244,12 +1589,12 @@ } }, "node_modules/@mui/types": { - "version": "7.4.10", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.10.tgz", - "integrity": "sha512-0+4mSjknSu218GW3isRqoxKRTOrTLd/vHi/7UC4+wZcUrOAqD9kRk7UQRL1mcrzqRoe7s3UT6rsRpbLkW5mHpQ==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-9.1.1.tgz", + "integrity": "sha512-Zjt7u8wNvDg40rPTGoL+TnfkpuSKjwubsNSFRH1KAVZLcaV4I3AFNHIFbvH7p4F3alEibSbdd90xAgn5Rnfndg==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.4" + "@babel/runtime": "^7.29.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -2261,17 +1606,17 @@ } }, "node_modules/@mui/utils": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.7.tgz", - "integrity": "sha512-+YjnjMRnyeTkWnspzoxRdiSOgkrcpTikhNPoxOZW0APXx+urHtUoXJ9lbtCZRCA5a4dg5gSbd19alL1DvRs5fg==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-9.2.0.tgz", + "integrity": "sha512-OsUH5zhlSOM4xmLl53+agug1M1UyWb4zxFxWQCqwKTKUeQPvTENtg3JhrroBD2qpCLKsX5W/DYGERJ4mBUbc8g==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.4", - "@mui/types": "^7.4.10", + "@babel/runtime": "^7.29.2", + "@mui/types": "^9.1.1", "@types/prop-types": "^15.7.15", "clsx": "^2.1.1", "prop-types": "^15.8.1", - "react-is": "^19.2.3" + "react-is": "^19.2.6" }, "engines": { "node": ">=14.0.0" @@ -2290,6 +1635,25 @@ } } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, "node_modules/@nivo/annotations": { "version": "0.99.0", "resolved": "https://registry.npmjs.org/@nivo/annotations/-/annotations-0.99.0.tgz", @@ -2496,65 +1860,80 @@ "react": "^16.14 || ^17.0 || ^18.0 || ^19.0" } }, - "node_modules/@npmcli/agent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", - "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", + "node_modules/@noble/hashes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", + "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", "dev": true, - "license": "ISC", - "dependencies": { - "agent-base": "^7.1.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.1", - "lru-cache": "^10.0.1", - "socks-proxy-agent": "^8.0.3" - }, + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@npmcli/agent/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "node_modules/@oxc-project/types": { + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.140.0.tgz", + "integrity": "sha512-h5LUOzGArYemnW1NMz/DuuQhBi96J6JL2Bk8zE4kvqxB5Sg3jxmCiH4uyOWHDkiKSt5vWlG4FIwCR/DbstcNRQ==", "dev": true, - "license": "ISC" + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/Boshen" + } }, - "node_modules/@npmcli/fs": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", - "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", + "node_modules/@peculiar/asn1-schema": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.8.0.tgz", + "integrity": "sha512-7YT0U/ze0tF2QOBbE15gKZwy5tvgGyLRiRHLzhlbOpf7BT032oBSd0haZqXn5W6l26WLlu3dyxzjM+2638/z2Q==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" } }, - "node_modules/@npmcli/fs/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "node_modules/@peculiar/json-schema": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", + "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" }, "engines": { - "node": ">=10" + "node": ">=8.0.0" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "node_modules/@peculiar/utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", "dev": true, "license": "MIT", - "optional": true, + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/webcrypto": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.7.1.tgz", + "integrity": "sha512-ODOov0sGMJMf3jPonOkgGqPknTsu+DdQ7kD++gz8aI+aFMOMHFbWAA2taqXXVTdP+OTOQR/znGvSpmkeI0WTYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/json-schema": "^1.1.12", + "@peculiar/utils": "^2.0.2", + "tslib": "^2.8.1", + "webcrypto-core": "^1.9.2" + }, "engines": { - "node": ">=14" + "node": ">=14.18.0" } }, "node_modules/@popperjs/core": { @@ -2568,27 +1947,27 @@ } }, "node_modules/@react-spring/animated": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-10.0.3.tgz", - "integrity": "sha512-7MrxADV3vaUADn2V9iYhaIL6iOWRx9nCJjYrsk2AHD2kwPr6fg7Pt0v+deX5RnCDmCKNnD6W5fasiyM8D+wzJQ==", + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-10.1.2.tgz", + "integrity": "sha512-yAsQ/bbp6+vko7WNCI1M00c6KLE9XKTGCrgRhQqS4JcK3oF5qBV4rHYrQEprvEvYXxt+1H5FsLS2eVValEPfFw==", "license": "MIT", "dependencies": { - "@react-spring/shared": "~10.0.3", - "@react-spring/types": "~10.0.3" + "@react-spring/shared": "~10.1.2", + "@react-spring/types": "~10.1.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/@react-spring/core": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-10.0.3.tgz", - "integrity": "sha512-D4DwNO68oohDf/0HG2G0Uragzb9IA1oXblxrd6MZAcBcUQG2EHUWXewjdECMPLNmQvlYVyyBRH6gPxXM5DX7DQ==", + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-10.1.2.tgz", + "integrity": "sha512-lPGOAg0V+PV3ucopOajD+YCxoe8twALqAeG4c/+sqVjBsyUNNdx8qfz/DcNSPKA8PV3+AyQELlCxlDfap9cmBQ==", "license": "MIT", "dependencies": { - "@react-spring/animated": "~10.0.3", - "@react-spring/shared": "~10.0.3", - "@react-spring/types": "~10.0.3" + "@react-spring/animated": "~10.1.2", + "@react-spring/shared": "~10.1.2", + "@react-spring/types": "~10.1.2" }, "funding": { "type": "opencollective", @@ -2599,71 +1978,51 @@ } }, "node_modules/@react-spring/rafz": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-10.0.3.tgz", - "integrity": "sha512-Ri2/xqt8OnQ2iFKkxKMSF4Nqv0LSWnxXT4jXFzBDsHgeeH/cHxTLupAWUwmV9hAGgmEhBmh5aONtj3J6R/18wg==", + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-10.1.2.tgz", + "integrity": "sha512-KC6vSFZyPnRJ2rXqipV9QqR4SaYbYXjvKfdYKWipNK2mWuV79gr20WmpVUOsTiEHGRY3WSOdWCHN+P9Pdaqb7Q==", "license": "MIT" }, "node_modules/@react-spring/shared": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-10.0.3.tgz", - "integrity": "sha512-geCal66nrkaQzUVhPkGomylo+Jpd5VPK8tPMEDevQEfNSWAQP15swHm+MCRG4wVQrQlTi9lOzKzpRoTL3CA84Q==", + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-10.1.2.tgz", + "integrity": "sha512-47/8bNQ/o0uEmxEnPBuERlC29VSqiTn5P9ln9tUMSVkYKYxBtouYE686F5kAGj+eHRPoNCw7drxWE9nv2d1LMw==", "license": "MIT", "dependencies": { - "@react-spring/rafz": "~10.0.3", - "@react-spring/types": "~10.0.3" + "@react-spring/rafz": "~10.1.2", + "@react-spring/types": "~10.1.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/@react-spring/types": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-10.0.3.tgz", - "integrity": "sha512-H5Ixkd2OuSIgHtxuHLTt7aJYfhMXKXT/rK32HPD/kSrOB6q6ooeiWAXkBy7L8F3ZxdkBb9ini9zP9UwnEFzWgQ==", + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-10.1.2.tgz", + "integrity": "sha512-G4CWowmVPz+rDG1y9QRVq/prZNxiNwQaHC0kgT/MgY6jAGgdRgTV4VThpvJDwWvUitk3xZB4soP4d36fjeQ09g==", "license": "MIT" }, "node_modules/@react-spring/web": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-10.0.3.tgz", - "integrity": "sha512-ndU+kWY81rHsT7gTFtCJ6mrVhaJ6grFmgTnENipzmKqot4HGf5smPNK+cZZJqoGeDsj9ZsiWPW4geT/NyD484A==", + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-10.1.2.tgz", + "integrity": "sha512-KxDB3zaDqy9qFsu7fdxjyraAxweHH4k5TW5WGT/OuMK6hKaxSDfhrQfRBzfFaSVvMBf+NghbJFanllV4ia6i7A==", "license": "MIT", "dependencies": { - "@react-spring/animated": "~10.0.3", - "@react-spring/core": "~10.0.3", - "@react-spring/shared": "~10.0.3", - "@react-spring/types": "~10.0.3" + "@react-spring/animated": "~10.1.2", + "@react-spring/core": "~10.1.2", + "@react-spring/shared": "~10.1.2", + "@react-spring/types": "~10.1.2", + "csstype": "^3.2.3" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", - "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] + } }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", - "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.0.tgz", + "integrity": "sha512-9yB1l95IrJuNGDFdOYe79vdApdz6WWBCObE+rQ2LUliYUlcyFwSYIb2xb5/Ifw7dAtMy2ZqNyd8QTSOc7duAKw==", "cpu": [ "arm64" ], @@ -2672,12 +2031,16 @@ "optional": true, "os": [ "android" - ] + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", - "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.0.tgz", + "integrity": "sha512-pexNaW9ACLUOaBITOpU6qVu4VrsOFIjTv6bzgu0YUATo4eUJx0V605PxwZfndpPOn0ilqGqvGQ0M8UW0IE24jg==", "cpu": [ "arm64" ], @@ -2686,12 +2049,16 @@ "optional": true, "os": [ "darwin" - ] + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", - "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.0.tgz", + "integrity": "sha512-NqKYaq0355ZmNMG4QGpxtEDxsc7tGDhjhCm4PpE0cwnBW+5Il95LJyq414niEiaKLVjnVHBEjSo1wngKxJNiFw==", "cpu": [ "x64" ], @@ -2700,26 +2067,16 @@ "optional": true, "os": [ "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", - "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", - "cpu": [ - "arm64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", - "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.0.tgz", + "integrity": "sha512-3vPoHzh6eBTz9IbB0/qZdSr0Qeks2echn+I4cHu2joV74VriPDdldswksEDzrl1mBB+oPRi+67+3Ib59paxIPQ==", "cpu": [ "x64" ], @@ -2728,26 +2085,16 @@ "optional": true, "os": [ "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", - "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", - "cpu": [ - "arm" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", - "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.0.tgz", + "integrity": "sha512-E6NNefZ1bUVmKJq2tJkf45J4Zyczj7qm9rUT7NY+Xo2474Y13qWAwc2tvBt0BAVbmtXR1llkxXg0Ou1jbDf2SQ==", "cpu": [ "arm" ], @@ -2756,180 +2103,142 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", - "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", - "cpu": [ - "arm64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", - "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.0.tgz", + "integrity": "sha512-D+TgkdgM1vu+7/Fpf8+v0ARW+RXEP9Ccazgm8zQ4JFFd9Q7SrYQ2TakU5S5ihazQDgpKyAgZDOcIFsvoHmTZ8w==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", - "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", - "cpu": [ - "loong64" + "libc": [ + "glibc" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", - "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", - "cpu": [ - "loong64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", - "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.0.tgz", + "integrity": "sha512-wUqdwJBbAv0APN87GecstdMUtLjjNTs0hBALpxETD73mccFxdmt/XeizXDtN5RAlBwNKmI+Tg+blect2G+8IeQ==", "cpu": [ - "ppc64" + "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", - "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.0.tgz", + "integrity": "sha512-9DtF35qR9/NrfhM4oxLplCzVVjE+KKm8Pjemi0i/sdhAWkUasjmSo8WTTubNJClhSHCfyk2yeyoXDQEDPtDAAw==", "cpu": [ "ppc64" ], "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", - "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", - "cpu": [ - "riscv64" + "libc": [ + "glibc" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", - "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", - "cpu": [ - "riscv64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", - "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.0.tgz", + "integrity": "sha512-RzuHrBh8X8Hntd2N4VR02QGEciq/9JhcZoTpR/Cee6otRrlILGCf3cg2ygHuih+ZebUnWmMrDX6ITI85btO6rQ==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", - "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.0.tgz", + "integrity": "sha512-MK7L0018jjh1jR3mh21G2j1zAVcpscJBlPo2z19pRjv2XOYGRhaV4LyiD8HO6nCDdZln9IFgCMIV5yt4E3klGQ==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", - "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.0.tgz", + "integrity": "sha512-gyrxLQ9NfGb/9LoVnC4kb9miUghw1mghnkfYvNHSnVIXriabnfgGPUP4RLcJm87q3KgYz4FYUG8IDiWUT+CpSw==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", - "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", - "cpu": [ - "x64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", - "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.0.tgz", + "integrity": "sha512-/6VFMQGRmrhP77KXDC+StIxGzcNp5JOIyYtw0CQ8gPlzhpiIRucYfoM5FaFamHd5BJYIdH86yfP46l1p3WdrFA==", "cpu": [ "arm64" ], @@ -2938,40 +2247,54 @@ "optional": true, "os": [ "openharmony" - ] + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", - "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.2.0.tgz", + "integrity": "sha512-rwdbUL465kisF24WEJLvP3JrEG6E5GRuIHt5wpMwHGERtHe4Wm2CIvtf5gTBgr2tGOHKh5NdKEAFS2VkOPE91g==", "cpu": [ - "arm64" + "wasm32" ], "dev": true, "license": "MIT", "optional": true, - "os": [ - "win32" - ] + "peer": true, + "dependencies": { + "@emnapi/core": "1.11.2", + "@emnapi/runtime": "1.11.2", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", - "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.0.tgz", + "integrity": "sha512-+5suHwRiKGmhwyUaNT8a5QbrBvLFh2DbO910TEmGRH1aSxwrCezodvGQnulv4uiWEIv1Kq4ypRsJ5+O+ry1DiA==", "cpu": [ - "ia32" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "win32" - ] + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", - "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.0.tgz", + "integrity": "sha512-WfFv6/qGufotqBSBzBYwgpCkJBk8Nj7697LL9vTz/XWc67e0r3oewu8iMRwQj3AUL45GVD7wVsPjCsAAtW66Wg==", "cpu": [ "x64" ], @@ -2980,21 +2303,49 @@ "optional": true, "os": [ "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", - "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", - "cpu": [ - "x64" ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/plugin-babel": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/plugin-babel/-/plugin-babel-0.2.3.tgz", + "integrity": "sha512-+zEk16yGlz1F9STiRr6uG9hmIXb6nprjLczV/htGptYuLoCuxb+itZ03RKCEeOhBpDDd1NU7qF6x1VLMUp62bw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=22.12.0 || ^24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.29.0 || ^8.0.0-rc.1", + "@babel/plugin-transform-runtime": "^7.29.0 || ^8.0.0-rc.1", + "@babel/runtime": "^7.27.0 || ^8.0.0-rc.1", + "rolldown": "^1.0.0-rc.5", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@babel/plugin-transform-runtime": { + "optional": true + }, + "@babel/runtime": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" }, "node_modules/@sindresorhus/is": { "version": "4.6.0", @@ -3029,49 +2380,15 @@ "node": ">=10" } }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@babel/types": "^7.28.2" + "tslib": "^2.4.0" } }, "node_modules/@types/cacheable-request": { @@ -3157,18 +2474,25 @@ "license": "MIT" }, "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", "license": "MIT", "dependencies": { "@types/ms": "*" } }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "license": "MIT" }, "node_modules/@types/estree-jsx": { @@ -3191,26 +2515,14 @@ } }, "node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", "license": "MIT", "dependencies": { "@types/unist": "*" } }, - "node_modules/@types/hoist-non-react-statics": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.7.tgz", - "integrity": "sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g==", - "license": "MIT", - "dependencies": { - "hoist-non-react-statics": "^3.3.0" - }, - "peerDependencies": { - "@types/react": "*" - } - }, "node_modules/@types/http-cache-semantics": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", @@ -3251,13 +2563,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.19.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.10.tgz", - "integrity": "sha512-tF5VOugLS/EuDlTBijk0MqABfP8UxgYazTLo3uIn3b4yJgg26QRbVYJYsDtHrjdDUIRfP70+VfhTTc+CE1yskw==", + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.18.0" } }, "node_modules/@types/parse-json": { @@ -3266,18 +2578,6 @@ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", "license": "MIT" }, - "node_modules/@types/plist": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz", - "integrity": "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*", - "xmlbuilder": ">=11.0.1" - } - }, "node_modules/@types/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", @@ -3285,9 +2585,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.2.13", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.13.tgz", - "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -3322,12 +2622,6 @@ "@types/node": "*" } }, - "node_modules/@types/stylis": { - "version": "4.2.7", - "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.7.tgz", - "integrity": "sha512-VgDNokpBoKF+wrdvhAAfS55OMQpL6QRglwTwNC3kIgBrzZxA4WsFj+2eLfEA/uMUDzBcEhYmjSbwQakn/i3ajA==", - "license": "MIT" - }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -3342,40 +2636,21 @@ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, - "node_modules/@types/verror": { - "version": "1.10.11", - "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz", - "integrity": "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz", - "integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.64.0.tgz", + "integrity": "sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/type-utils": "8.54.0", - "@typescript-eslint/utils": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/type-utils": "8.64.0", + "@typescript-eslint/utils": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3385,15 +2660,15 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.54.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "@typescript-eslint/parser": "^8.64.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", "dev": true, "license": "MIT", "engines": { @@ -3401,16 +2676,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz", - "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.64.0.tgz", + "integrity": "sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", "debug": "^4.4.3" }, "engines": { @@ -3421,19 +2696,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", - "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.64.0.tgz", + "integrity": "sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.54.0", - "@typescript-eslint/types": "^8.54.0", + "@typescript-eslint/tsconfig-utils": "^8.64.0", + "@typescript-eslint/types": "^8.64.0", "debug": "^4.4.3" }, "engines": { @@ -3444,18 +2719,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", - "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.64.0.tgz", + "integrity": "sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0" + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3466,9 +2741,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", - "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.64.0.tgz", + "integrity": "sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==", "dev": true, "license": "MIT", "engines": { @@ -3479,21 +2754,21 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz", - "integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.64.0.tgz", + "integrity": "sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/utils": "8.54.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/utils": "8.64.0", "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3503,14 +2778,14 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", - "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.64.0.tgz", + "integrity": "sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==", "dev": true, "license": "MIT", "engines": { @@ -3522,21 +2797,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", - "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.64.0.tgz", + "integrity": "sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.54.0", - "@typescript-eslint/tsconfig-utils": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", + "@typescript-eslint/project-service": "8.64.0", + "@typescript-eslint/tsconfig-utils": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", "debug": "^4.4.3", - "minimatch": "^9.0.5", + "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3546,52 +2821,13 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -3602,16 +2838,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", - "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.64.0.tgz", + "integrity": "sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0" + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3621,19 +2857,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", - "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.64.0.tgz", + "integrity": "sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.54.0", - "eslint-visitor-keys": "^4.2.1" + "@typescript-eslint/types": "8.64.0", + "eslint-visitor-keys": "^5.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3644,36 +2880,41 @@ } }, "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", + "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==", "license": "ISC" }, "node_modules/@vitejs/plugin-react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", - "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz", + "integrity": "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.28.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.27", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" + "@rolldown/pluginutils": "^1.0.1" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } } }, "node_modules/@xmldom/xmldom": { - "version": "0.8.11", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", - "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", + "version": "0.8.13", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz", + "integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==", "dev": true, "license": "MIT", "engines": { @@ -3681,50 +2922,34 @@ } }, "node_modules/@xterm/addon-fit": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.10.0.tgz", - "integrity": "sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==", - "license": "MIT", - "peerDependencies": { - "@xterm/xterm": "^5.0.0" - } - }, - "node_modules/@xterm/addon-search": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.15.0.tgz", - "integrity": "sha512-ZBZKLQ+EuKE83CqCmSSz5y1tx+aNOCUaA7dm6emgOX+8J9H1FWXZyrKfzjwzV+V14TV3xToz1goIeRhXBS5qjg==", - "license": "MIT", - "peerDependencies": { - "@xterm/xterm": "^5.0.0" - } - }, - "node_modules/@xterm/xterm": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", - "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.11.0.tgz", + "integrity": "sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==", "license": "MIT" }, - "node_modules/7zip-bin": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz", - "integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==", - "dev": true, - "license": "MIT" + "node_modules/@xterm/xterm": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.0.0.tgz", + "integrity": "sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg==", + "license": "MIT", + "workspaces": [ + "addons/*" + ] }, "node_modules/abbrev": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", - "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", "dev": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "dev": true, "license": "MIT", "bin": { @@ -3755,9 +2980,9 @@ } }, "node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -3779,11 +3004,19 @@ "ajv": "^8.0.1" } }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3805,40 +3038,36 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/app-builder-bin": { - "version": "5.0.0-alpha.12", - "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-5.0.0-alpha.12.tgz", - "integrity": "sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w==", - "dev": true, - "license": "MIT" - }, "node_modules/app-builder-lib": { - "version": "26.7.0", - "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.7.0.tgz", - "integrity": "sha512-/UgCD8VrO79Wv8aBNpjMfsS1pIUfIPURoRn0Ik6tMe5avdZF+vQgl/juJgipcMmH3YS0BD573lCdCHyoi84USg==", + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.15.3.tgz", + "integrity": "sha512-2VnyWkqsP5v5XbBhL3tD5Syx8iNPBYsoU7kY4S2fz7wg8Rj/nztWKCUzGKaFRTv0Xwf3/H058CR1Kvtd/3lRow==", "dev": true, "license": "MIT", "dependencies": { - "@develar/schema-utils": "~2.6.5", "@electron/asar": "3.4.1", "@electron/fuses": "^1.8.0", "@electron/get": "^3.0.0", "@electron/notarize": "2.5.0", "@electron/osx-sign": "1.3.3", - "@electron/rebuild": "^4.0.3", + "@electron/rebuild": "^4.0.4", "@electron/universal": "2.0.3", "@malept/flatpak-bundler": "^0.4.0", + "@noble/hashes": "^2.2.0", + "@peculiar/webcrypto": "^1.7.1", "@types/fs-extra": "9.0.13", + "ajv": "^8.18.0", + "asn1js": "^3.0.10", "async-exit-hook": "^2.0.1", - "builder-util": "26.4.1", - "builder-util-runtime": "9.5.1", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", "chromium-pickle-js": "^0.2.0", "ci-info": "4.3.1", "debug": "^4.3.4", "dotenv": "^16.4.5", "dotenv-expand": "^11.0.6", "ejs": "^3.1.8", - "electron-publish": "26.6.0", + "electron-publish": "26.15.3", "fs-extra": "^10.1.0", "hosted-git-info": "^4.1.0", "isbinaryfile": "^5.0.0", @@ -3846,7 +3075,8 @@ "js-yaml": "^4.1.0", "json5": "^2.2.3", "lazy-val": "^1.0.5", - "minimatch": "^10.0.3", + "minimatch": "^10.2.5", + "pkijs": "^3.4.0", "plist": "3.1.0", "proper-lockfile": "^4.1.2", "resedit": "^1.7.0", @@ -3854,14 +3084,15 @@ "tar": "^7.5.7", "temp-file": "^3.4.0", "tiny-async-pool": "1.3.0", + "unzipper": "^0.12.3", "which": "^5.0.0" }, "engines": { "node": ">=14.0.0" }, "peerDependencies": { - "dmg-builder": "26.7.0", - "electron-builder-squirrel-windows": "26.7.0" + "dmg-builder": "26.15.3", + "electron-builder-squirrel-windows": "26.15.3" } }, "node_modules/app-builder-lib/node_modules/@electron/get": { @@ -3943,9 +3174,9 @@ } }, "node_modules/app-builder-lib/node_modules/fs-extra/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -3966,13 +3197,13 @@ } }, "node_modules/app-builder-lib/node_modules/isexe": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.4.tgz", - "integrity": "sha512-jCErc4h4RnTPjFq53G4whhjAMbUAqinGrCrTT4dmMNyi4zTthK+wphqbRLJtL4BN/Mq7Zzltr0m/b1X0m7PGFQ==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", "dev": true, "license": "BlueOak-1.0.0", "engines": { - "node": ">=20" + "node": ">=18" } }, "node_modules/app-builder-lib/node_modules/semver": { @@ -4011,26 +3242,19 @@ "dev": true, "license": "Python-2.0" }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "node_modules/asn1js": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", + "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", "dev": true, - "license": "MIT", - "optional": true, + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.5", + "tslib": "^2.8.1" + }, "engines": { - "node": ">=8" + "node": ">=12.0.0" } }, "node_modules/async": { @@ -4067,16 +3291,51 @@ "node": ">= 4.0.0" } }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "dev": true, + "license": "MIT" + }, "node_modules/axios": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", - "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", "dev": true, "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.11", + "follow-redirects": "^1.16.0", "form-data": "^4.0.5", - "proxy-from-env": "^1.1.0" + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/axios/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/axios/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" } }, "node_modules/babel-plugin-macros": { @@ -4115,11 +3374,14 @@ } }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } }, "node_modules/base64-js": { "version": "1.5.1", @@ -4143,26 +3405,24 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "version": "2.10.43", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", + "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", "dev": true, "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true, - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } + "license": "MIT" }, "node_modules/boolean": { "version": "3.2.0", @@ -4174,20 +3434,22 @@ "optional": true }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", "dev": true, "funding": [ { @@ -4205,11 +3467,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -4218,41 +3480,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "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" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -4261,16 +3488,14 @@ "license": "MIT" }, "node_modules/builder-util": { - "version": "26.4.1", - "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-26.4.1.tgz", - "integrity": "sha512-FlgH43XZ50w3UtS1RVGDWOz8v9qMXPC7upMtKMtBEnYdt1OVoS61NYhKm/4x+cIaWqJTXua0+VVPI+fSPGXNIw==", + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-26.15.3.tgz", + "integrity": "sha512-q2hn7Mbo2nFNkVekPiHFx6Nfo3hURmES3tfBn+k5Pqxl2RkmP3QGqZUhH/q9Pch/4G05NRhPjDlVj1O8q4Txvw==", "dev": true, "license": "MIT", "dependencies": { "@types/debug": "^4.1.6", - "7zip-bin": "~5.2.0", - "app-builder-bin": "5.0.0-alpha.12", - "builder-util-runtime": "9.5.1", + "builder-util-runtime": "9.7.0", "chalk": "^4.1.2", "cross-spawn": "^7.0.6", "debug": "^4.3.4", @@ -4283,12 +3508,15 @@ "stat-mode": "^1.0.0", "temp-file": "^3.4.0", "tiny-async-pool": "1.3.0" + }, + "engines": { + "node": ">=14.0.0" } }, "node_modules/builder-util-runtime": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.5.1.tgz", - "integrity": "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ==", + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.7.0.tgz", + "integrity": "sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==", "dev": true, "license": "MIT", "dependencies": { @@ -4315,9 +3543,9 @@ } }, "node_modules/builder-util/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -4337,96 +3565,14 @@ "node": ">= 10.0.0" } }, - "node_modules/cacache": { - "version": "19.0.1", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", - "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/fs": "^4.0.0", - "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^10.0.1", - "minipass": "^7.0.3", - "minipass-collect": "^2.0.1", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^7.0.2", - "ssri": "^12.0.0", - "tar": "^7.4.3", - "unique-filename": "^4.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/cacache/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/cacache/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/cacache/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/cacache/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/cacache/node_modules/minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "node_modules/bytestreamjs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", + "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^5.0.2" - }, + "license": "BSD-3-Clause", "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=6.0.0" } }, "node_modules/cacheable-lookup": { @@ -4490,19 +3636,10 @@ "node": ">=6" } }, - "node_modules/camelize": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", - "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/caniuse-lite": { - "version": "1.0.30001769", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", - "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", "dev": true, "funding": [ { @@ -4612,56 +3749,12 @@ "funding": [ { "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dev": true, + "url": "https://github.com/sponsors/sibiraj-s" + } + ], "license": "MIT", - "optional": true, - "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cliui": { @@ -4679,16 +3772,6 @@ "node": ">=12" } }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, "node_modules/clone-response": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", @@ -4775,14 +3858,15 @@ } }, "node_modules/composerize": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/composerize/-/composerize-1.7.5.tgz", - "integrity": "sha512-QIBM2PL9jDneea5JYvBRecM8ZD2nOrrrU1f0a7p+veO174RLqL3UpmX6hBP69FSalhJkNVPVLDIEYrAz9hOySQ==", + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/composerize/-/composerize-1.7.6.tgz", + "integrity": "sha512-zFdKAE6H8j2OSdnOol+U1f+/rJxbd80cCpDSReVMDA2aOZPH+N2IUVor6UdMwtdLCl8Lpr9Qb8xBO0VOTj5jDg==", "license": "MIT", "dependencies": { "composeverter": "latest", "core-js": "^2.5.5", "deepmerge": "^2.1.0", + "enquirer": "^2.4.1", "invariant": "^2.2.4", "yargs-parser": "^13.0.0" }, @@ -4837,12 +3921,11 @@ "license": "MIT" }, "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true, - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/cosmiconfig": { "version": "7.1.0", @@ -4861,25 +3944,14 @@ } }, "node_modules/cosmiconfig/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "license": "ISC", "engines": { "node": ">= 6" } }, - "node_modules/crc": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", - "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "buffer": "^5.1.0" - } - }, "node_modules/cross-dirname": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz", @@ -4922,26 +3994,6 @@ "node": ">= 8" } }, - "node_modules/css-color-keywords": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", - "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", - "license": "ISC", - "engines": { - "node": ">=4" - } - }, - "node_modules/css-to-react-native": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", - "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", - "license": "MIT", - "dependencies": { - "camelize": "^1.0.0", - "css-color-keywords": "^1.0.0", - "postcss-value-parser": "^4.0.2" - } - }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -5160,19 +4212,6 @@ "node": ">=0.10.0" } }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/defer-to-connect": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", @@ -5222,9 +4261,9 @@ } }, "node_modules/delaunator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", - "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", "license": "ISC", "dependencies": { "robust-predicates": "^3.0.2" @@ -5291,10 +4330,28 @@ "p-limit": "^3.1.0 " } }, + "node_modules/dir-compare/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/dir-compare/node_modules/brace-expansion": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/dir-compare/node_modules/minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -5305,20 +4362,16 @@ } }, "node_modules/dmg-builder": { - "version": "26.7.0", - "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.7.0.tgz", - "integrity": "sha512-uOOBA3f+kW3o4KpSoMQ6SNpdXU7WtxlJRb9vCZgOvqhTz4b3GjcoWKstdisizNZLsylhTMv8TLHFPFW0Uxsj/g==", + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.15.3.tgz", + "integrity": "sha512-O3zJUFUYHJKgzPqioHxfxzBzlSC1eXCSr79gMSBKBP5AgjjpmrydMsMLotEg9fAJF36vdUncb+4ndRNxoPdlSQ==", "dev": true, "license": "MIT", "dependencies": { - "app-builder-lib": "26.7.0", - "builder-util": "26.4.1", + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", "fs-extra": "^10.1.0", - "iconv-lite": "^0.6.2", "js-yaml": "^4.1.0" - }, - "optionalDependencies": { - "dmg-license": "^1.0.11" } }, "node_modules/dmg-builder/node_modules/fs-extra": { @@ -5337,9 +4390,9 @@ } }, "node_modules/dmg-builder/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -5359,59 +4412,6 @@ "node": ">= 10.0.0" } }, - "node_modules/dmg-license": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz", - "integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==", - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "@types/plist": "^3.0.1", - "@types/verror": "^1.10.3", - "ajv": "^6.10.0", - "crc": "^3.8.0", - "iconv-corefoundation": "^1.1.7", - "plist": "^3.0.4", - "smart-buffer": "^4.0.2", - "verror": "^1.10.0" - }, - "bin": { - "dmg-license": "bin/dmg-license.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dmg-license/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "license": "MIT", - "optional": 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/dmg-license/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -5423,9 +4423,9 @@ } }, "node_modules/dompurify": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", - "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", + "version": "3.4.12", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.12.tgz", + "integrity": "sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==", "license": "(MPL-2.0 OR Apache-2.0)", "peer": true, "optionalDependencies": { @@ -5476,12 +4476,15 @@ "node": ">= 0.4" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", "dev": true, - "license": "MIT" + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } }, "node_modules/ejs": { "version": "3.1.10", @@ -5500,37 +4503,37 @@ } }, "node_modules/electron": { - "version": "39.5.1", - "resolved": "https://registry.npmjs.org/electron/-/electron-39.5.1.tgz", - "integrity": "sha512-6s/sBQar+bbW59XSqohZj04MPic+kdVUAWjLbfQB/uLOeNw9jWX5FHaTxpHK29Xp3mKOHef7wErsjwMyCuWltg==", + "version": "43.1.1", + "resolved": "https://registry.npmjs.org/electron/-/electron-43.1.1.tgz", + "integrity": "sha512-I5c5vfuVvaXpWx3IZdwvXgxQW44+e7OP1wXGVQkogLeSFSkUZ6sLCcWV05AdEcs65AO5tAIJJwbp7ixw+LdarA==", "dev": true, - "hasInstallScript": true, "license": "MIT", "dependencies": { - "@electron/get": "^2.0.0", - "@types/node": "^22.7.7", - "extract-zip": "^2.0.1" + "@electron-internal/extract-zip": "^1.0.1", + "@electron/get": "^5.0.0", + "@types/node": "^24.9.0" }, "bin": { - "electron": "cli.js" + "electron": "cli.js", + "install-electron": "install.js" }, "engines": { - "node": ">= 12.20.55" + "node": ">= 22.12.0" } }, "node_modules/electron-builder": { - "version": "26.7.0", - "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-26.7.0.tgz", - "integrity": "sha512-LoXbCvSFxLesPneQ/fM7FB4OheIDA2tjqCdUkKlObV5ZKGhYgi5VHPHO/6UUOUodAlg7SrkPx7BZJPby+Vrtbg==", + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-26.15.3.tgz", + "integrity": "sha512-a1KM5heqS3gQCZzizXEI8RjJy3QVogULPdeSknt76uLDpBIW/HDGsMg/XgP0riP6PI9COsRvFITKKGDqA8fJxA==", "dev": true, "license": "MIT", "dependencies": { - "app-builder-lib": "26.7.0", - "builder-util": "26.4.1", - "builder-util-runtime": "9.5.1", + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", "chalk": "^4.1.2", "ci-info": "^4.2.0", - "dmg-builder": "26.7.0", + "dmg-builder": "26.15.3", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "simple-update-notifier": "2.0.0", @@ -5545,15 +4548,15 @@ } }, "node_modules/electron-builder-squirrel-windows": { - "version": "26.7.0", - "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.7.0.tgz", - "integrity": "sha512-3EqkQK+q0kGshdPSKEPb2p5F75TENMKu6Fe5aTdeaPfdzFK4Yjp5L0d6S7K8iyvqIsGQ/ei4bnpyX9wt+kVCKQ==", + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.15.3.tgz", + "integrity": "sha512-Jc19XPV9y9+2bAdZPkXuVNGNIEFBq9poHC61l8Kv6FdK7DRG3+Ic0rerC0DXOaeHNz8yW0fg/JnF8GQROOF5MA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "app-builder-lib": "26.7.0", - "builder-util": "26.4.1", + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", "electron-winstaller": "5.4.0" } }, @@ -5573,9 +4576,9 @@ } }, "node_modules/electron-builder/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -5596,15 +4599,16 @@ } }, "node_modules/electron-publish": { - "version": "26.6.0", - "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.6.0.tgz", - "integrity": "sha512-LsyHMMqbvJ2vsOvuWJ19OezgF2ANdCiHpIucDHNiLhuI+/F3eW98ouzWSRmXXi82ZOPZXC07jnIravY4YYwCLQ==", + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.15.3.tgz", + "integrity": "sha512-g/2bn8YTavY4cuS5F+jOS7zmZbXXBV8KZ8yHKfJjFPoKtzBqrpCdNPxBd3tqdBwP7BVd0lGzf7Bk2s0KesWZ4Q==", "dev": true, "license": "MIT", "dependencies": { "@types/fs-extra": "^9.0.11", - "builder-util": "26.4.1", - "builder-util-runtime": "9.5.1", + "aws4": "^1.13.2", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", "chalk": "^4.1.2", "form-data": "^4.0.5", "fs-extra": "^10.1.0", @@ -5628,9 +4632,9 @@ } }, "node_modules/electron-publish/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -5651,9 +4655,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.286", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", - "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", + "version": "1.5.393", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.393.tgz", + "integrity": "sha512-kiDJdIUawuEIcp9XoICKp1iTYDEbgguIPq526N1Q7jIQDeQ3CqoMx71025PI/7E48Ddtw2HuWsVjY7afEgNxmg==", "dev": true, "license": "ISC" }, @@ -5702,17 +4706,6 @@ "dev": true, "license": "MIT" }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -5723,6 +4716,19 @@ "once": "^1.4.0" } }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -5763,16 +4769,15 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "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==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "dev": true, "license": "MIT", "dependencies": { @@ -5806,48 +4811,6 @@ "license": "MIT", "optional": true }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -5871,33 +4834,33 @@ } }, "node_modules/eslint": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", - "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.7.0.tgz", + "integrity": "sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ==", "dev": true, "license": "MIT", + "workspaces": [ + "packages/*" + ], "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.2", - "@eslint/plugin-kit": "^0.4.1", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.6.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "ajv": "^6.12.4", - "chalk": "^4.0.0", + "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", @@ -5907,8 +4870,7 @@ "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -5916,7 +4878,7 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://eslint.org/donate" @@ -5931,62 +4893,71 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", - "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, "engines": { - "node": ">=10" + "node": ">=18" }, "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" } }, "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.26", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", - "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.3.tgz", + "integrity": "sha512-5EMmLCV98Pi4o/f/3DP/v/tNqLHMIc9I8LKClNDWhZ9JTho89/kQcitCXQBMG7sAfVRK0Ie3T2EDOzp1YXYiVA==", "dev": true, "license": "MIT", "peerDependencies": { - "eslint": ">=8.40" + "eslint": "^9 || ^10" } }, "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -6007,32 +4978,19 @@ "dev": true, "license": "MIT" }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.15.0", + "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" + "eslint-visitor-keys": "^5.0.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" @@ -6107,38 +5065,6 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/extsprintf": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", - "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "license": "MIT", - "optional": true - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -6160,9 +5086,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", + "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", "funding": [ { "type": "github", @@ -6175,16 +5101,6 @@ ], "license": "BSD-3-Clause" }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -6217,19 +5133,26 @@ } }, "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", "dev": true, "license": "Apache-2.0", "dependencies": { "minimatch": "^5.0.1" } }, + "node_modules/filelist/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -6237,9 +5160,9 @@ } }, "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.7.tgz", - "integrity": "sha512-FjiwU9HaHW6YB3H4a1sFudnv93lvydNjz2lmyUXR6IwKhGI+bgL3SOZrBGn6kvvX2pJvhEkGSGjyTHN47O4rqA==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, "license": "ISC", "dependencies": { @@ -6287,16 +5210,16 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "dev": true, "funding": [ { @@ -6314,81 +5237,23 @@ } } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "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==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "dev": true, "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" + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" } }, - "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/fs-minipass": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", - "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -6524,16 +5389,34 @@ "dev": true, "license": "ISC", "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, "node_modules/glob/node_modules/minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -6563,9 +5446,9 @@ } }, "node_modules/global-agent/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "optional": true, @@ -6577,9 +5460,9 @@ } }, "node_modules/globals": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", - "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz", + "integrity": "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==", "dev": true, "license": "MIT", "engines": { @@ -6707,9 +5590,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -6758,6 +5641,23 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -6865,58 +5765,6 @@ "node": ">= 14" } }, - "node_modules/iconv-corefoundation": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", - "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==", - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "cli-truncate": "^2.1.0", - "node-addon-api": "^1.6.3" - }, - "engines": { - "node": "^8.11.2 || >=10" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/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" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -6928,9 +5776,9 @@ } }, "node_modules/immer": { - "version": "11.1.3", - "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.3.tgz", - "integrity": "sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==", + "version": "11.1.15", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.15.tgz", + "integrity": "sha512-VrNANlmnWQnh5COXIIOQXM9oOJw7naGKlBT74ZOOR6lpVXc3gFEu9FJLDFcpCJ2j+NWr8TIwtWD//T6ZX6TKiQ==", "license": "MIT", "funding": { "type": "opencollective", @@ -7006,16 +5854,6 @@ "loose-envify": "^1.0.0" } }, - "node_modules/ip-address": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, "node_modules/is-alphabetical": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", @@ -7047,12 +5885,12 @@ "license": "MIT" }, "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", "license": "MIT", "dependencies": { - "hasown": "^2.0.2" + "hasown": "^2.0.3" }, "engines": { "node": ">= 0.4" @@ -7114,16 +5952,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -7136,18 +5964,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "license": "MIT" }, "node_modules/isbinaryfile": { "version": "5.0.7", @@ -7169,22 +5991,6 @@ "dev": true, "license": "ISC" }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, "node_modules/jake": { "version": "10.9.4", "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", @@ -7204,9 +6010,9 @@ } }, "node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", "dev": true, "license": "MIT", "bin": { @@ -7214,9 +6020,9 @@ } }, "node_modules/joi": { - "version": "18.0.2", - "resolved": "https://registry.npmjs.org/joi/-/joi-18.0.2.tgz", - "integrity": "sha512-RuCOQMIt78LWnktPoeBL0GErkNaJPTBGcYuyaBvUOQSpcpcLfWrHPPihYdOGbV5pam9VTWbeoF7TsGiHugcjGA==", + "version": "18.2.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-18.2.3.tgz", + "integrity": "sha512-N5A3KTWQpPWT4ExxxPlUx7WmykGXRzhNidWhV41d6Abu9YfI2NyWCJuxdPnslJCPWtbRpSVOWSnSS6GakLM/Rg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -7226,7 +6032,7 @@ "@hapi/pinpoint": "^2.0.1", "@hapi/tlds": "^1.1.1", "@hapi/topo": "^6.0.2", - "@standard-schema/spec": "^1.0.0" + "@standard-schema/spec": "^1.1.0" }, "engines": { "node": ">= 20" @@ -7239,10 +6045,20 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -7330,25 +6146,298 @@ "json-buffer": "3.0.1" } }, - "node_modules/lazy-val": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", - "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", + "node_modules/lazy-val": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.8.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "node_modules/lines-and-columns": { @@ -7374,35 +6463,11 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -7445,29 +6510,6 @@ "yallist": "^3.0.2" } }, - "node_modules/make-fetch-happen": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", - "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/agent": "^3.0.0", - "cacache": "^19.0.1", - "http-cache-semantics": "^4.1.1", - "minipass": "^7.0.2", - "minipass-fetch": "^4.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^1.0.0", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1", - "ssri": "^12.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/markdown-table": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", @@ -7544,9 +6586,9 @@ } }, "node_modules/mdast-util-from-markdown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", - "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -8396,16 +7438,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", @@ -8417,13 +7449,13 @@ } }, "node_modules/minimatch": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", - "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^5.0.5" }, "engines": { "node": "18 || 20 || >=22" @@ -8432,29 +7464,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/minimatch/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/minimatch/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -8466,145 +7475,15 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minipass-collect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", - "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } }, - "node_modules/minipass-fetch": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", - "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^3.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "node_modules/minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-flush/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-flush/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-pipeline/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-pipeline/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, - "node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, "node_modules/minizlib": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", @@ -8650,9 +7529,10 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "dev": true, "funding": [ { "type": "github", @@ -8674,20 +7554,10 @@ "dev": true, "license": "MIT" }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/node-abi": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.26.0.tgz", - "integrity": "sha512-8QwIZqikRvDIkXS2S93LjzhsSPJuIbfaMETWH+Bx8oOT9Sa9UsUtBFQlc3gBNd1+QINjaTloitXr1W3dQLi9Iw==", + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.33.0.tgz", + "integrity": "sha512-vLBWCKb+7LWsX+TbfzWOkw0W81m377tyx3hOweBTjO43CXZnRGS1/JPWs20fr0PgZyDXk6ROYrylsEycK8raDA==", "dev": true, "license": "MIT", "dependencies": { @@ -8698,9 +7568,9 @@ } }, "node_modules/node-abi/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -8710,14 +7580,6 @@ "node": ">=10" } }, - "node_modules/node-addon-api": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", - "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/node-api-version": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz", @@ -8729,9 +7591,9 @@ } }, "node_modules/node-api-version/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -8742,34 +7604,34 @@ } }, "node_modules/node-gyp": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz", - "integrity": "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==", + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.4.0.tgz", + "integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==", "dev": true, "license": "MIT", "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^14.0.3", - "nopt": "^8.0.0", - "proc-log": "^5.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.5", - "tar": "^7.4.3", + "tar": "^7.5.4", "tinyglobby": "^0.2.12", - "which": "^5.0.0" + "undici": "^6.25.0", + "which": "^6.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/node-gyp/node_modules/isexe": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.4.tgz", - "integrity": "sha512-jCErc4h4RnTPjFq53G4whhjAMbUAqinGrCrTT4dmMNyi4zTthK+wphqbRLJtL4BN/Mq7Zzltr0m/b1X0m7PGFQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -8777,9 +7639,9 @@ } }, "node_modules/node-gyp/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -8790,42 +7652,52 @@ } }, "node_modules/node-gyp/node_modules/which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", "dev": true, "license": "ISC", "dependencies": { - "isexe": "^3.1.1" + "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "dev": true, "license": "MIT" }, + "node_modules/node-releases": { + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/nopt": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", - "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", "dev": true, "license": "ISC", "dependencies": { - "abbrev": "^3.0.0" + "abbrev": "^4.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/normalize-url": { @@ -8871,22 +7743,6 @@ "wrappy": "1" } }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -8895,38 +7751,14 @@ "license": "MIT", "dependencies": { "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.8.0" } }, "node_modules/p-cancelable": { @@ -8971,26 +7803,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-map": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", - "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -9082,30 +7894,6 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "license": "MIT" }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -9130,13 +7918,6 @@ "url": "https://github.com/sponsors/jet2jet" } }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true, - "license": "MIT" - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -9144,9 +7925,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "engines": { @@ -9156,6 +7937,37 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkijs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.0.tgz", + "integrity": "sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@noble/hashes": "1.4.0", + "asn1js": "^3.0.6", + "bytestreamjs": "^2.0.1", + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/pkijs/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/plist": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", @@ -9172,9 +7984,10 @@ } }, "node_modules/postcss": { - "version": "8.4.49", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "version": "8.5.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.20.tgz", + "integrity": "sha512-lW616l85ucIQL+FocMmL7pQFPqBmwejrCMg+iPxyImlrANNJG9NHq/RkyCZopDhd8C3LA03PHRJDjkbGu8vvug==", + "dev": true, "funding": [ { "type": "opencollective", @@ -9191,7 +8004,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -9199,12 +8012,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "license": "MIT" - }, "node_modules/postject": { "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz", @@ -9246,15 +8053,22 @@ } }, "node_modules/proc-log": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", - "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", "dev": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -9309,9 +8123,9 @@ } }, "node_modules/property-information": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", - "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", "license": "MIT", "funding": { "type": "github", @@ -9319,16 +8133,19 @@ } }, "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==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=10" + } }, "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", "dev": true, "license": "MIT", "dependencies": { @@ -9346,6 +8163,26 @@ "node": ">=6" } }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", + "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -9360,30 +8197,30 @@ } }, "node_modules/react": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", - "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", - "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.4" + "react": "^19.2.7" } }, "node_modules/react-is": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz", - "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==", + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz", + "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==", "license": "MIT" }, "node_modules/react-markdown": { @@ -9413,20 +8250,10 @@ "react": ">=18" } }, - "node_modules/react-refresh": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/react-router": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.0.tgz", - "integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==", + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.1.tgz", + "integrity": "sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -9446,12 +8273,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.0.tgz", - "integrity": "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==", + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.1.tgz", + "integrity": "sha512-KaZh+X/6UtEp28x51AUYZDMg9NGoz2ja3dNHa+ta/tk40vCzKhQ/RypCWBMLbmDr6//E24Vv5uPsrqXFozdkAg==", "license": "MIT", "dependencies": { - "react-router": "7.13.0" + "react-router": "7.18.1" }, "engines": { "node": ">=20.0.0" @@ -9501,18 +8328,19 @@ } }, "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" + "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/reconnecting-websocket": { @@ -9625,11 +8453,12 @@ } }, "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", "license": "MIT", "dependencies": { + "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" @@ -9673,20 +8502,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -9732,54 +8547,44 @@ } }, "node_modules/robust-predicates": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", - "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", "license": "Unlicense" }, - "node_modules/rollup": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", - "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "node_modules/rolldown": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.0.tgz", + "integrity": "sha512-u7tgm5l4Yw1iTqUL4EcYOAt7fFvCgQMLeidrnD4GALlC6aOznCjezYajgxeyKw27u0Q5N7fwgCzjVyPIWzwuBA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@types/estree": "1.0.8" + "@oxc-project/types": "=0.140.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { - "rollup": "dist/bin/rollup" + "rolldown": "bin/cli.mjs" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.57.1", - "@rollup/rollup-android-arm64": "4.57.1", - "@rollup/rollup-darwin-arm64": "4.57.1", - "@rollup/rollup-darwin-x64": "4.57.1", - "@rollup/rollup-freebsd-arm64": "4.57.1", - "@rollup/rollup-freebsd-x64": "4.57.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", - "@rollup/rollup-linux-arm-musleabihf": "4.57.1", - "@rollup/rollup-linux-arm64-gnu": "4.57.1", - "@rollup/rollup-linux-arm64-musl": "4.57.1", - "@rollup/rollup-linux-loong64-gnu": "4.57.1", - "@rollup/rollup-linux-loong64-musl": "4.57.1", - "@rollup/rollup-linux-ppc64-gnu": "4.57.1", - "@rollup/rollup-linux-ppc64-musl": "4.57.1", - "@rollup/rollup-linux-riscv64-gnu": "4.57.1", - "@rollup/rollup-linux-riscv64-musl": "4.57.1", - "@rollup/rollup-linux-s390x-gnu": "4.57.1", - "@rollup/rollup-linux-x64-gnu": "4.57.1", - "@rollup/rollup-linux-x64-musl": "4.57.1", - "@rollup/rollup-openbsd-x64": "4.57.1", - "@rollup/rollup-openharmony-arm64": "4.57.1", - "@rollup/rollup-win32-arm64-msvc": "4.57.1", - "@rollup/rollup-win32-ia32-msvc": "4.57.1", - "@rollup/rollup-win32-x64-gnu": "4.57.1", - "@rollup/rollup-win32-x64-msvc": "4.57.1", - "fsevents": "~2.3.2" + "@rolldown/binding-android-arm64": "1.2.0", + "@rolldown/binding-darwin-arm64": "1.2.0", + "@rolldown/binding-darwin-x64": "1.2.0", + "@rolldown/binding-freebsd-x64": "1.2.0", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.0", + "@rolldown/binding-linux-arm64-gnu": "1.2.0", + "@rolldown/binding-linux-arm64-musl": "1.2.0", + "@rolldown/binding-linux-ppc64-gnu": "1.2.0", + "@rolldown/binding-linux-s390x-gnu": "1.2.0", + "@rolldown/binding-linux-x64-gnu": "1.2.0", + "@rolldown/binding-linux-x64-musl": "1.2.0", + "@rolldown/binding-openharmony-arm64": "1.2.0", + "@rolldown/binding-wasm32-wasi": "1.2.0", + "@rolldown/binding-win32-arm64-msvc": "1.2.0", + "@rolldown/binding-win32-x64-msvc": "1.2.0" } }, "node_modules/rxjs": { @@ -9793,37 +8598,16 @@ } }, "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/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" - } - ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, "license": "MIT" }, "node_modules/sanitize-filename": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", - "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.4.tgz", + "integrity": "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==", "dev": true, "license": "WTFPL OR ISC", "dependencies": { @@ -9831,9 +8615,9 @@ } }, "node_modules/sax": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", - "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -9887,12 +8671,6 @@ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", "license": "MIT" }, - "node_modules/shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", - "license": "MIT" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -9937,9 +8715,9 @@ } }, "node_modules/simple-update-notifier/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -9949,63 +8727,6 @@ "node": ">=10" } }, - "node_modules/slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ip-address": "^10.0.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -10019,6 +8740,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -10063,19 +8785,6 @@ "license": "BSD-3-Clause", "optional": true }, - "node_modules/ssri": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", - "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/stat-mode": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", @@ -10093,13 +8802,13 @@ "license": "MIT" }, "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "license": "MIT", "dependencies": { - "safe-buffer": "~5.2.0" + "safe-buffer": "~5.1.0" } }, "node_modules/string-width": { @@ -10117,22 +8826,6 @@ "node": ">=8" } }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/stringify-entities": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", @@ -10151,21 +8844,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -10174,75 +8852,23 @@ "node": ">=8" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/style-to-js": { "version": "1.1.21", "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", - "license": "MIT", - "dependencies": { - "style-to-object": "1.0.14" - } - }, - "node_modules/style-to-object": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", - "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", - "license": "MIT", - "dependencies": { - "inline-style-parser": "0.2.7" - } - }, - "node_modules/styled-components": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.3.9.tgz", - "integrity": "sha512-J72R4ltw0UBVUlEjTzI0gg2STOqlI9JBhQOL4Dxt7aJOnnSesy0qJDn4PYfMCafk9cWOaVg129Pesl5o+DIh0Q==", - "license": "MIT", - "dependencies": { - "@emotion/is-prop-valid": "1.4.0", - "@emotion/unitless": "0.10.0", - "@types/stylis": "4.2.7", - "css-to-react-native": "3.2.0", - "csstype": "3.2.3", - "postcss": "8.4.49", - "shallowequal": "1.1.0", - "stylis": "4.3.6", - "tslib": "2.8.1" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/styled-components" - }, - "peerDependencies": { - "react": ">= 16.8.0", - "react-dom": ">= 16.8.0" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - } + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" } }, - "node_modules/styled-components/node_modules/stylis": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", - "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", - "license": "MIT" + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } }, "node_modules/stylis": { "version": "4.2.0", @@ -10289,9 +8915,9 @@ } }, "node_modules/tar": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.9.tgz", - "integrity": "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==", + "version": "7.5.20", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.20.tgz", + "integrity": "sha512-9FcyK4PA6+WbzlTM9WhQm6vB5W7cP7dUiPsv1g7YDwEQnQ1CGpK3MGlKk/ITVWMk05kHZuBhmVhiv8LZoy/PFQ==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -10357,9 +8983,9 @@ } }, "node_modules/temp-file/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -10400,14 +9026,14 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -10417,9 +9043,9 @@ } }, "node_modules/tmp": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", - "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", "dev": true, "license": "MIT", "engines": { @@ -10467,9 +9093,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { @@ -10483,6 +9109,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, "license": "0BSD" }, "node_modules/type-check": { @@ -10509,402 +9136,738 @@ "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.64.0.tgz", + "integrity": "sha512-0qg+pDNMnqYzqH9AnNK+39tejHvsShUOUUoRUgtnTGE7QuMZhiFDnozq8nHJVq+Wae6NMLKNWLg5WmkcC/ndyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.64.0", + "@typescript-eslint/parser": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/utils": "8.64.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/undici": { + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz", + "integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unzipper": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.5.tgz", + "integrity": "sha512-tXYOi9R57Uj/2Z25SOs5RRSzq886MBQj2gY8dPL+xl/kv6s6SvByoKfAtvfVeEuhntWDgjd2o9p2lb4TVPAz0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "~3.7.2", + "duplexer2": "~0.1.4", + "fs-extra": "11.3.1", + "graceful-fs": "^4.2.2", + "node-int64": "^0.4.0" + } + }, + "node_modules/unzipper/node_modules/fs-extra": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz", + "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/unzipper/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/unzipper/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "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" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-debounce": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-10.1.1.tgz", + "integrity": "sha512-kvds8BHR2k28cFsxW8k3nc/tRga2rs1RHYCqmmGqb90MEeE++oALwzh2COiuBLO1/QXiOuShXoSN2ZpWnMmvuQ==", + "license": "MIT", + "engines": { + "node": ">= 16.0.0" + }, + "peerDependencies": { + "react": "*" + } + }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" }, - "engines": { - "node": ">=14.17" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/typescript-eslint": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.54.0.tgz", - "integrity": "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==", + "node_modules/vite": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", + "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.54.0", - "@typescript-eslint/parser": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/utils": "8.54.0" + "lightningcss": "^1.32.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.17", + "rolldown": "~1.1.5", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } } }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "node_modules/vite/node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } }, - "node_modules/unified": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", - "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "node_modules/vite/node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@types/unist": "^3.0.0", - "bail": "^2.0.0", - "devlop": "^1.0.0", - "extend": "^3.0.0", - "is-plain-obj": "^4.0.0", - "trough": "^2.0.0", - "vfile": "^6.0.0" - }, + "tslib": "^2.4.0" + } + }, + "node_modules/vite/node_modules/@oxc-project/types": { + "version": "0.139.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", + "dev": true, + "license": "MIT", "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/Boshen" } }, - "node_modules/unique-filename": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", - "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", + "node_modules/vite/node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "unique-slug": "^5.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/unique-slug": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", - "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", + "node_modules/vite/node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4" - }, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/unist-util-is": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", - "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "node_modules/vite/node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", + "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/unist-util-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", - "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "node_modules/vite/node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "node_modules/vite/node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", + "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", + "cpu": [ + "arm" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/unist-util-visit": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", - "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "node_modules/vite/node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", + "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/unist-util-visit-parents": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", - "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "node_modules/vite/node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "node_modules/vite/node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", + "cpu": [ + "ppc64" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 4.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "node_modules/vite/node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", + "cpu": [ + "s390x" + ], "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" - } + "libc": [ + "glibc" ], "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "node_modules/vite/node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/use-debounce": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-10.1.0.tgz", - "integrity": "sha512-lu87Za35V3n/MyMoEpD5zJv0k7hCn0p+V/fK2kWD+3k2u3kOCwO593UArbczg1fhfs2rqPEnHpULJ3KmGdDzvg==", + "libc": [ + "glibc" + ], "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 16.0.0" - }, - "peerDependencies": { - "react": "*" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/utf8-byte-length": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", - "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "node_modules/vite/node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "(WTFPL OR MIT)" + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "node_modules/vite/node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/verror": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", - "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", + "node_modules/vite/node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", + "cpu": [ + "wasm32" + ], "dev": true, "license": "MIT", "optional": true, "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" }, "engines": { - "node": ">=0.6.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/vfile": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "node_modules/vite/node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/vfile-message": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", - "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "node_modules/vite/node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", + "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/vite": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.11.tgz", - "integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==", + "node_modules/vite/node_modules/rolldown": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" + "@oxc-project/types": "=0.139.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { - "vite": "bin/vite.js" + "rolldown": "bin/cli.mjs" }, "engines": { "node": "^20.19.0 || >=22.12.0" }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "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" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" + "@rolldown/binding-android-arm64": "1.1.5", + "@rolldown/binding-darwin-arm64": "1.1.5", + "@rolldown/binding-darwin-x64": "1.1.5", + "@rolldown/binding-freebsd-x64": "1.1.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", + "@rolldown/binding-linux-arm64-gnu": "1.1.5", + "@rolldown/binding-linux-arm64-musl": "1.1.5", + "@rolldown/binding-linux-ppc64-gnu": "1.1.5", + "@rolldown/binding-linux-s390x-gnu": "1.1.5", + "@rolldown/binding-linux-x64-gnu": "1.1.5", + "@rolldown/binding-linux-x64-musl": "1.1.5", + "@rolldown/binding-openharmony-arm64": "1.1.5", + "@rolldown/binding-wasm32-wasi": "1.1.5", + "@rolldown/binding-win32-arm64-msvc": "1.1.5", + "@rolldown/binding-win32-x64-msvc": "1.1.5" } }, "node_modules/wait-on": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-9.0.3.tgz", - "integrity": "sha512-13zBnyYvFDW1rBvWiJ6Av3ymAaq8EDQuvxZnPIw3g04UqGi4TyoIJABmfJ6zrvKo9yeFQExNkOk7idQbDJcuKA==", + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-9.0.10.tgz", + "integrity": "sha512-rCoJEhvMr0X6alHmwc9abbrA5ZrLZFKpFQVKPNFwl2h7DapXOGdmimIHDtLOWhT4PjhZhxFEtZoQgEXbkDWdZw==", "dev": true, "license": "MIT", "dependencies": { - "axios": "^1.13.2", - "joi": "^18.0.1", - "lodash": "^4.17.21", + "axios": "^1.16.0", + "joi": "^18.2.1", + "lodash": "^4.18.1", "minimist": "^1.2.8", "rxjs": "^7.8.2" }, @@ -10915,14 +9878,18 @@ "node": ">=20.0.0" } }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "node_modules/webcrypto-core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.9.2.tgz", + "integrity": "sha512-gsXecm82UQNlTBURJGuqOWy1Ww08S3kZUcr3aOJS02Pk0xLtkfeUAVC0u0xhgdonFme80edSJUIJyuvL/7250Q==", "dev": true, "license": "MIT", "dependencies": { - "defaults": "^1.0.3" + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/json-schema": "^1.1.12", + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" } }, "node_modules/which": { @@ -10969,25 +9936,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "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/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -11023,9 +9971,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -11038,9 +9986,9 @@ } }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "dev": true, "license": "MIT", "dependencies": { @@ -11076,17 +10024,6 @@ "node": ">=12" } }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -11100,10 +10037,33 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, "node_modules/zustand": { - "version": "5.0.11", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.11.tgz", - "integrity": "sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==", + "version": "5.0.14", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.14.tgz", + "integrity": "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==", "license": "MIT", "engines": { "node": ">=12.20.0" diff --git a/ui/package.json b/ui/package.json index 7120b004..78e6ec70 100644 --- a/ui/package.json +++ b/ui/package.json @@ -37,13 +37,11 @@ "@emotion/styled": "^11.14.1", "@fontsource/roboto": "^5.2.9", "@monaco-editor/react": "^4.7.0", - "@mui/icons-material": "^7.3.6", - "@mui/material": "^7.3.6", - "@mui/styled-engine-sc": "^7.3.6", + "@mui/icons-material": "^9.2.0", + "@mui/material": "^9.2.0", "@nivo/line": "^0.99.0", - "@xterm/addon-fit": "^0.10.0", - "@xterm/addon-search": "^0.15.0", - "@xterm/xterm": "^5.5.0", + "@xterm/addon-fit": "^0.11.0", + "@xterm/xterm": "^6.0.0", "composerize": "^1.7.5", "immer": "^11.1.0", "react": "^19.2.1", @@ -52,25 +50,28 @@ "react-router-dom": "^7.10.1", "reconnecting-websocket": "^4.4.0", "remark-gfm": "^4.0.1", - "styled-components": "^6.1.19", "zustand": "^5.0.9" }, "devDependencies": { - "@eslint/js": "^9.25.0", + "@eslint/js": "^10.0.1", + "@rolldown/plugin-babel": "^0.2.3", "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", + "@vitejs/plugin-react": "^6.0.3", "babel-plugin-react-compiler": "^1.0.0", "cross-env": "^10.1.0", - "electron": "^39.2.7", - "electron-builder": "^26.4.0", - "eslint": "^9.39.1", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-react-refresh": "^0.4.24", - "globals": "^16.5.0", - "typescript": "~5.8.3", - "typescript-eslint": "^8.49.0", - "vite": "7.1.11", + "electron": "^43.1.1", + "electron-builder": "^26.15.3", + "eslint": "^10.7.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.3", + "globals": "^17.7.0", + "typescript": "~6.0.3", + "typescript-eslint": "^8.64.0", + "vite": "^8.1.5", "wait-on": "^9.0.3" + }, + "overrides": { + "dompurify": "3.4.12" } } diff --git a/ui/src/App.tsx b/ui/src/App.tsx index f2a0c8e9..1b8b9c22 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -14,9 +14,11 @@ import { Typography } from '@mui/material'; import {SnackbarProvider} from "./context/snackbar-context.tsx"; +import {UploadProgressToast} from "./components/upload-progress-toast.tsx"; import {BrowserRouter, Navigate, Outlet, Route, Routes, useLocation, useNavigate, useParams} from "react-router-dom"; import {AuthProvider} from "./context/auth-context.tsx"; -import React from 'react'; +import React, {useEffect, useState} from 'react'; +import {useConfig} from './hooks/config.ts'; import {useAuth} from "./hooks/auth.ts"; import {AuthPage} from './pages/auth/auth-page.tsx'; import {SettingsPage} from "./pages/settings/settings-page.tsx"; @@ -29,6 +31,7 @@ import ContainersPage from "./pages/containers/containers.tsx"; import ImagesPage from "./pages/images/images.tsx"; import ImageInspectPage from "./pages/images/inspect.tsx"; import VolumesPage from "./pages/volumes/volumes.tsx"; +import VolumesInspect from "./pages/volumes/volumes-inspect.tsx"; import NetworksPage from "./pages/networks/networks.tsx"; import NetworksInspect from "./pages/networks/networks-inspect.tsx"; import DockerCleanerPage from "./pages/cleaner/cleaner.tsx"; @@ -36,6 +39,7 @@ import FileIndexRedirect, {ComposePage, FilesLayout} from "./pages/compose/compo import ContainerInspectPage from "./pages/containers/inspect.tsx"; import scrollbarStyles from "./components/scrollbar-style.tsx"; import StatsPage from "./pages/stats/stats-page.tsx"; +import MonitorPage from "./pages/monitor/monitor-page.tsx"; import {useHostStore} from "./pages/compose/state/files.ts"; import {enableMapSet} from "immer"; import {SettingsOutlined as SettingsIcon} from '@mui/icons-material'; @@ -48,6 +52,7 @@ export function App() { + @@ -58,7 +63,7 @@ export function App() { }/> - }/> + }/> }/> @@ -67,6 +72,10 @@ export function App() { }/> + + }/> + + }/> @@ -83,6 +92,7 @@ export function App() { }/> + }/> @@ -115,6 +125,27 @@ function HomeIndexRedirect() { return ; } +const VALID_DEFAULT_VIEWS = ['files', 'monitor', 'stats', 'containers', 'images', 'volumes', 'networks', 'cleaner']; + +// landing view for a host, per dockman.yml defaultView; waits briefly for +// the config so the preference applies on a cold load, then falls back to +// files if it never arrives +function HostDefaultViewRedirect() { + const {dockYaml} = useConfig(); + const [waited, setWaited] = useState(false); + + useEffect(() => { + const id = setTimeout(() => setWaited(true), 1500); + return () => clearTimeout(id); + }, []); + + if (!dockYaml && !waited) return null; + + const view = (dockYaml?.defaultView ?? '').trim().toLowerCase(); + const target = VALID_DEFAULT_VIEWS.includes(view) ? view : 'files'; + return ; +} + const PrivateRoute = () => { const {isAuthenticated, isLoading} = useAuth(); @@ -163,11 +194,17 @@ function HostGuard() { height: '100vh', }}> - + Loading hosts... - ) + ); } const emptyHostList = !availableHosts || availableHosts.length === 0; @@ -234,7 +271,12 @@ const EmptyHost = ({hostname}: { {isInvalid ? ( - + The hostname provided does not match any configured hosts. ) : ( - + It looks like you haven't added any Docker Hosts yet. Configure your first node to start managing containers. @@ -301,7 +349,9 @@ const EmptyHost = ({hostname}: { > {availableHosts.map((f) => ( - + {f} @@ -315,9 +365,12 @@ const EmptyHost = ({hostname}: { {/* Footer Link */} + sx={{ + color: "text.disabled", + mt: 4, + display: 'block', + textAlign: 'center' + }}> Need help? Check the Documentation @@ -362,8 +415,18 @@ const darkTheme = createTheme({ body: { height: '100%', overflow: 'hidden', + // Disable selecting UI chrome text (labels, buttons, table cells…); + // it reads as a native app and avoids accidental highlights. + userSelect: 'none', + WebkitUserSelect: 'none', ...scrollbarStyles, }, + // …but keep selection where content actually matters: form fields, + // the code editor (Monaco), the terminal/logs (xterm) and code blocks. + 'input, textarea, [contenteditable="true"], pre, code, .monaco-editor, .monaco-editor *, .xterm, .xterm *': { + userSelect: 'text', + WebkitUserSelect: 'text', + }, '*': scrollbarStyles, }, }, diff --git a/ui/src/components/action-buttons.tsx b/ui/src/components/action-buttons.tsx index 9353b605..426ab97d 100644 --- a/ui/src/components/action-buttons.tsx +++ b/ui/src/components/action-buttons.tsx @@ -1,6 +1,6 @@ import useButtonAction from "../hooks/button-action.ts"; -import React from "react"; -import {Button, CircularProgress, Stack, Tooltip} from "@mui/material"; +import React, {useState} from "react"; +import {Button, CircularProgress, Popover, Stack, Tooltip, Typography} from "@mui/material"; interface Action { action: string; @@ -9,36 +9,106 @@ interface Action { disabled: boolean; handler: () => Promise; tooltip: string; + confirm?: string; } interface ActionButtonProps { actions: Action[]; variant?: 'outlined' | 'contained' + // symbol-only buttons: the label moves into the tooltip + iconOnly?: boolean } -function ActionButtons({actions, variant = 'outlined'}: ActionButtonProps) { +// one compact action row shared by every list view (containers, images, +// volumes, networks): small buttons, sentence case, quiet borders that pick +// up the accent on hover — same recipe as the deploy tab's action row +function ActionButtons({actions, variant = 'outlined', iconOnly = false}: ActionButtonProps) { const {buttonAction, activeAction} = useButtonAction() + const [confirmation, setConfirmation] = useState<{anchor: HTMLElement, action: Action} | null>(null) + + const trigger = (event: React.MouseEvent, action: Action) => { + if (action.confirm) { + setConfirmation({anchor: event.currentTarget, action}) + return + } + void buttonAction(action.handler, action.action) + } + + const confirm = () => { + if (!confirmation) return + const action = confirmation.action + setConfirmation(null) + void buttonAction(action.handler, action.action) + } return ( - + {actions.map((action) => ( - - + + + + ))} + setConfirmation(null)} + anchorOrigin={{vertical: 'top', horizontal: 'center'}} + transformOrigin={{vertical: 'bottom', horizontal: 'center'}} + > + + + {confirmation?.action.confirm} + + + + + + + ); } -export default ActionButtons; \ No newline at end of file +export default ActionButtons; diff --git a/ui/src/components/log-viewer/ansi.ts b/ui/src/components/log-viewer/ansi.ts new file mode 100644 index 00000000..dab44c74 --- /dev/null +++ b/ui/src/components/log-viewer/ansi.ts @@ -0,0 +1,198 @@ +// Minimal ANSI SGR parser: turns a raw log line into styled segments that can +// be rendered as plain React spans (no innerHTML). Non-SGR escape sequences +// (cursor movement, OSC titles...) are stripped. +// +// The 16 base colors are kept as palette indexes so the viewer can resolve +// them against its dark or light palette at render time; 256-color and +// truecolor sequences resolve to concrete values. + +export interface AnsiSegment { + text: string; + // 0-15 palette index, resolved by the active theme at render time + colorIdx?: number; + backgroundIdx?: number; + // concrete css color (256-color cube / truecolor) + color?: string; + background?: string; + bold?: boolean; + dim?: boolean; + italic?: boolean; + underline?: boolean; +} + +// the VS Code terminal palette: what `docker logs` looks like in a real +// terminal, and what the previous xterm-based viewer rendered +export const ANSI_PALETTE_DARK = [ + '#3f3f3f', '#cd3131', '#0dbc79', '#e5e510', + '#2472c8', '#bc3fbc', '#11a8cd', '#e5e5e5', + '#666666', '#f14c4c', '#23d18b', '#f5f543', + '#3b8eea', '#d670d6', '#29b8db', '#ffffff', +]; + +// same hues, darkened to stay readable on a light background +export const ANSI_PALETTE_LIGHT = [ + '#424242', '#c62828', '#2e7d32', '#9e7c00', + '#1565c0', '#7b1fa2', '#00838f', '#616161', + '#757575', '#e53935', '#43a047', '#b8860b', + '#1e88e5', '#8e24aa', '#00acc1', '#212121', +]; + +function xterm256Color(n: number): string | undefined { + if (n < 16 || n > 255) return undefined; + if (n < 232) { + // 6x6x6 color cube + const v = n - 16; + const steps = [0, 95, 135, 175, 215, 255]; + const r = steps[Math.floor(v / 36) % 6]; + const g = steps[Math.floor(v / 6) % 6]; + const b = steps[v % 6]; + return `rgb(${r},${g},${b})`; + } + // grayscale ramp + const gray = 8 + (n - 232) * 10; + return `rgb(${gray},${gray},${gray})`; +} + +// SGR state carried across lines: a real terminal keeps the current color +// until a reset, even across newlines (multi-line banners are colored once) +export interface AnsiState { + colorIdx?: number; + backgroundIdx?: number; + color?: string; + background?: string; + bold?: boolean; + dim?: boolean; + italic?: boolean; + underline?: boolean; +} + +type SgrState = AnsiState; + +const hasState = (s: SgrState) => Object.keys(s).length > 0; + +function setFg(state: SgrState, idx?: number, concrete?: string) { + delete state.colorIdx; + delete state.color; + if (idx !== undefined) state.colorIdx = idx; + if (concrete !== undefined) state.color = concrete; +} + +function setBg(state: SgrState, idx?: number, concrete?: string) { + delete state.backgroundIdx; + delete state.background; + if (idx !== undefined) state.backgroundIdx = idx; + if (concrete !== undefined) state.background = concrete; +} + +function applySgr(state: SgrState, params: number[]): SgrState { + let next = {...state}; + for (let i = 0; i < params.length; i++) { + const p = params[i]; + if (p === 0) next = {}; + else if (p === 1) next.bold = true; + else if (p === 2) next.dim = true; + else if (p === 3) next.italic = true; + else if (p === 4) next.underline = true; + else if (p === 22) { delete next.bold; delete next.dim; } + else if (p === 23) delete next.italic; + else if (p === 24) delete next.underline; + else if (p >= 30 && p <= 37) setFg(next, p - 30); + else if (p === 39) setFg(next); + else if (p >= 40 && p <= 47) setBg(next, p - 40); + else if (p === 49) setBg(next); + else if (p >= 90 && p <= 97) setFg(next, p - 90 + 8); + else if (p >= 100 && p <= 107) setBg(next, p - 100 + 8); + else if (p === 38 || p === 48) { + // extended color: 38;5;n or 38;2;r;g;b + const set = p === 38 ? setFg : setBg; + if (params[i + 1] === 5) { + const n = params[i + 2] ?? -1; + if (n >= 0 && n < 16) set(next, n); + else { + const c = xterm256Color(n); + if (c) set(next, undefined, c); + } + i += 2; + } else if (params[i + 1] === 2) { + const [r, g, b] = [params[i + 2] ?? 0, params[i + 3] ?? 0, params[i + 4] ?? 0]; + set(next, undefined, `rgb(${r},${g},${b})`); + i += 4; + } + } + } + return next; +} + +const ESC = '\x1b'; + +export interface ParsedLine { + segments: AnsiSegment[]; + // state left active at the end of the line, to seed the next one + end: AnsiState; +} + +export function parseAnsi(line: string, initial: AnsiState = {}): ParsedLine { + if (!line.includes(ESC)) { + const segments: AnsiSegment[] = line + ? [hasState(initial) ? {text: line, ...initial} : {text: line}] + : []; + return {segments, end: initial}; + } + + const segments: AnsiSegment[] = []; + let state: SgrState = {...initial}; + let plain = ''; + + const push = () => { + if (plain) { + segments.push({text: plain, ...state}); + plain = ''; + } + }; + + let i = 0; + while (i < line.length) { + const ch = line[i]; + if (ch !== ESC) { + plain += ch; + i++; + continue; + } + + const kind = line[i + 1]; + if (kind === '[') { + // CSI sequence: params, then one final byte in @-~ + let j = i + 2; + while (j < line.length && !(line[j] >= '@' && line[j] <= '~')) j++; + if (j >= line.length) break; // truncated sequence, drop the rest + if (line[j] === 'm') { + push(); + const raw = line.slice(i + 2, j); + const params = raw === '' ? [0] : raw.split(';').map(s => Number(s) || 0); + state = applySgr(state, params); + } + i = j + 1; + } else if (kind === ']') { + // OSC sequence: ends with BEL or ESC-backslash + let j = i + 2; + while (j < line.length && line[j] !== '\x07' && !(line[j] === ESC && line[j + 1] === '\\')) j++; + i = line[j] === ESC ? j + 2 : j + 1; + } else { + // two-byte escape (ESC c, ESC 7...), skip it + i += 2; + } + } + push(); + return {segments, end: state}; +} + +// stateful line parser: keeps the running SGR state per source key +// (container|stream) so colors opened on one line carry to the next +export function createAnsiTracker() { + const states = new Map(); + return (key: string, rawText: string): AnsiSegment[] => { + const {segments, end} = parseAnsi(rawText, states.get(key)); + states.set(key, end); + return segments; + }; +} diff --git a/ui/src/components/log-viewer/log-model.ts b/ui/src/components/log-viewer/log-model.ts new file mode 100644 index 00000000..35341af8 --- /dev/null +++ b/ui/src/components/log-viewer/log-model.ts @@ -0,0 +1,155 @@ +import type {LogLine} from "../../gen/docker/v1/docker_pb.ts"; +import type {AnsiSegment} from "./ansi.ts"; + +export interface LogEntry { + id: number; + // plain text with ANSI codes stripped: what search, copy and download see + text: string; + // styled segments parsed once on arrival, with cross-line SGR continuity + segments: AnsiSegment[]; + timeNano: bigint; // 0n when the line had no parsable daemon timestamp + // chronological ordering key: the daemon timestamp, or for lines without + // one the newest timestamp seen at arrival so they keep their position + sortKey: bigint; + stream: number; // 0 = dockman-internal notice, 1 = stdout, 2 = stderr + containerId: string; + containerName: string; +} + +// lines dockman itself injects (stream failures) — not container output +export const STREAM_INTERNAL = 0; + +// soft cap: the buffer may grow to 2x before being compacted back, so steady +// streaming does not reslice the whole array on every batch +const LOG_BUFFER_CAP = 2000; + +let nextEntryId = 1; + +// an empty frame is the server keepalive, not a log line +export const isKeepAlive = (line: LogLine) => + line.containerId === "" && line.text === "" && line.timeNano === 0n; + +export function toLogEntry(line: LogLine, segments: AnsiSegment[], sortKey: bigint): LogEntry { + return { + id: nextEntryId++, + text: segments.map(s => s.text).join(""), + segments, + timeNano: line.timeNano, + sortKey, + stream: line.stream, + containerId: line.containerId, + containerName: line.containerName, + }; +} + +// merges a batch into the buffer keeping it sorted by sortKey: the containers +// of a merged view replay their history concurrently, so lines arrive +// interleaved by reader speed — display must follow the timestamps instead +const bySortKey = (a: LogEntry, b: LogEntry) => + a.sortKey < b.sortKey ? -1 : a.sortKey > b.sortKey ? 1 : 0; + +export function appendEntries(buffer: LogEntry[], batch: LogEntry[]): LogEntry[] { + if (batch.length === 0) return buffer; + // stable sort: equal timestamps keep their arrival order + let sorted = [...batch].sort(bySortKey); + // an oversized batch alone can exceed the cap, pre-trim it + if (sorted.length > LOG_BUFFER_CAP) sorted = sorted.slice(-LOG_BUFFER_CAP); + + let next: LogEntry[]; + if (buffer.length === 0 || sorted[0].sortKey >= buffer[buffer.length - 1].sortKey) { + // fast path: the whole batch belongs at the end + next = [...buffer, ...sorted]; + } else { + // merge two sorted arrays; buffer entries win ties to stay stable + next = new Array(buffer.length + sorted.length); + let i = 0, j = 0, k = 0; + while (i < buffer.length && j < sorted.length) { + next[k++] = buffer[i].sortKey <= sorted[j].sortKey ? buffer[i++] : sorted[j++]; + } + while (i < buffer.length) next[k++] = buffer[i++]; + while (j < sorted.length) next[k++] = sorted[j++]; + } + + if (next.length > LOG_BUFFER_CAP * 2) { + return next.slice(-LOG_BUFFER_CAP); + } + return next; +} + +// merged-view container prefix palette, assigned by position in the request +const CONTAINER_COLORS = [ + '#64b5f6', '#81c784', '#ffb74d', '#e57373', '#ba68c8', + '#f06292', '#4db6ac', '#fff176', '#a1887f', '#90a4ae', +]; + +export const containerColor = (index: number) => + CONTAINER_COLORS[((index % CONTAINER_COLORS.length) + CONTAINER_COLORS.length) % CONTAINER_COLORS.length]; + +export function formatLogTime(timeNano: bigint): string { + if (timeNano === 0n) return ""; + const date = new Date(Number(timeNano / 1000000n)); + const pad = (n: number, w = 2) => String(n).padStart(w, '0'); + return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` + + `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}.${pad(date.getMilliseconds(), 3)}`; +} + +// case-insensitive matching via regex on the original text: unlike a +// toLowerCase+indexOf scan, offsets always align (case folding can change +// string length, e.g. İ), and there is no per-line lowered copy to allocate +export interface LogQuery { + // non-global, for boolean tests + test: RegExp; + // global twin, for highlight scanning + scan: RegExp; +} + +export function compileQuery(raw: string): LogQuery | null { + const trimmed = raw.trim(); + if (!trimmed) return null; + const escaped = trimmed.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + return {test: new RegExp(escaped, 'i'), scan: new RegExp(escaped, 'gi')}; +} + +export const matchesQuery = (entry: LogEntry, query: LogQuery) => + query.test.test(entry.text) || query.test.test(entry.containerName); + +// plain-text export used by copy and download +export function logsToText(entries: LogEntry[], withTimestamps: boolean, withNames: boolean): string { + return entries.map(e => { + let line = ""; + if (withTimestamps && e.timeNano !== 0n) line += `${formatLogTime(e.timeNano)} `; + if (withNames && e.containerName) line += `[${e.containerName}] `; + return line + e.text; + }).join("\n"); +} + +// splits ANSI segments around query matches so the matching parts can be +// wrapped in without disturbing the styling +export interface HighlightedPiece { + segment: AnsiSegment; + isMatch: boolean; +} + +export function highlightSegments(segments: AnsiSegment[], query: LogQuery): HighlightedPiece[] { + const pieces: HighlightedPiece[] = []; + for (const segment of segments) { + const text = segment.text; + let from = 0; + query.scan.lastIndex = 0; + for (let m = query.scan.exec(text); m !== null; m = query.scan.exec(text)) { + if (m.index > from) { + pieces.push({segment: {...segment, text: text.slice(from, m.index)}, isMatch: false}); + } + if (m[0] !== "") { + pieces.push({segment: {...segment, text: m[0]}, isMatch: true}); + from = m.index + m[0].length; + } + // never spin on a zero-length match + if (m[0] === "") query.scan.lastIndex++; + } + if (from < text.length) { + pieces.push({segment: {...segment, text: text.slice(from)}, isMatch: false}); + } + } + return pieces; +} diff --git a/ui/src/components/log-viewer/logs-viewer.tsx b/ui/src/components/log-viewer/logs-viewer.tsx new file mode 100644 index 00000000..936f0648 --- /dev/null +++ b/ui/src/components/log-viewer/logs-viewer.tsx @@ -0,0 +1,731 @@ +import {type CSSProperties, type ReactNode, useCallback, useEffect, useMemo, useRef, useState} from "react"; +import { + Box, + Chip, + IconButton, + InputAdornment, + MenuItem, + Popover, + Select, + Stack, + TextField, + Tooltip, + Typography, +} from "@mui/material"; +import SearchIcon from "@mui/icons-material/Search"; +import FilterAltIcon from "@mui/icons-material/FilterAlt"; +import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; +import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; +import AccessTimeIcon from "@mui/icons-material/AccessTime"; +import WrapTextIcon from "@mui/icons-material/WrapText"; +import VerticalAlignBottomIcon from "@mui/icons-material/VerticalAlignBottom"; +import PauseIcon from "@mui/icons-material/Pause"; +import PlayArrowIcon from "@mui/icons-material/PlayArrow"; +import DeleteSweepIcon from "@mui/icons-material/DeleteSweep"; +import RefreshIcon from "@mui/icons-material/Refresh"; +import ContentCopyIcon from "@mui/icons-material/ContentCopy"; +import CheckIcon from "@mui/icons-material/Check"; +import DownloadIcon from "@mui/icons-material/Download"; +import DateRangeIcon from "@mui/icons-material/DateRange"; +import LocalOfferIcon from "@mui/icons-material/LocalOffer"; +import TagIcon from "@mui/icons-material/Tag"; +import LightModeIcon from "@mui/icons-material/LightMode"; +import DarkModeIcon from "@mui/icons-material/DarkMode"; +import scrollbarStyles from "../scrollbar-style.tsx"; +import {ANSI_PALETTE_DARK, ANSI_PALETTE_LIGHT, type AnsiSegment} from "./ansi.ts"; +import {useCopyButton} from "../../hooks/copy.ts"; +import { + compileQuery, + containerColor, + formatLogTime, + highlightSegments, + type LogEntry, + type LogQuery, + logsToText, + matchesQuery, + STREAM_INTERNAL, +} from "./log-model.ts"; +import {type LogStreamStatus, useLogsStream} from "./use-logs-stream.ts"; + +export interface LogsViewerContainer { + id: string; + name?: string; +} + +interface LogsViewerProps { + containers: LogsViewerContainer[]; + // hidden tabs pass false: the stream suspends (buffer kept) and the view + // snaps back to the bottom when shown again + isActive?: boolean; +} + +// scroll distance from the bottom under which the view is considered "at the +// bottom" and auto-scroll stays engaged +const BOTTOM_STICKINESS_PX = 40; + +const PREF_TIMESTAMPS = 'dockman-logs-timestamps'; +const PREF_WRAP = 'dockman-logs-wrap'; +const PREF_TAIL = 'dockman-logs-tail'; +const PREF_NAMES = 'dockman-logs-names'; +const PREF_LINE_NUMBERS = 'dockman-logs-linenumbers'; +const PREF_FONT_SIZE = 'dockman-logs-fontsize'; +const PREF_LIGHT = 'dockman-logs-light'; + +const TAIL_OPTIONS = [100, 500, 1000, 2000]; +const FONT_SIZES = [10, 12, 14, 16]; + +type StreamFilter = 'all' | 'stdout' | 'stderr'; + +// same default stack as Dockhand's "System Monospace" +const LOG_FONT = 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace'; + +interface ViewerTheme { + bg: string; + fg: string; + timestamp: string; + lineNumber: string; + lineNumberSep: string; + singleName: string; + ansi: string[]; +} + +// backgrounds match Dockhand: zinc-950 dark, gray-50 light +const DARK_THEME: ViewerTheme = { + bg: '#09090b', + fg: '#CCCCCC', + timestamp: '#858585', + lineNumber: '#6a6a6a', + lineNumberSep: 'rgba(255,255,255,0.18)', + singleName: '#9e9e9e', + ansi: ANSI_PALETTE_DARK, +}; + +const LIGHT_THEME: ViewerTheme = { + bg: '#f9fafb', + fg: '#1f1f1f', + timestamp: '#8a8a8a', + lineNumber: '#9e9e9e', + lineNumberSep: 'rgba(0,0,0,0.18)', + singleName: '#757575', + ansi: ANSI_PALETTE_LIGHT, +}; + +const STATUS_META: Record = { + idle: {label: 'Idle', color: '#9e9e9e'}, + connecting: {label: 'Connecting', color: '#ffb74d'}, + live: {label: 'Live', color: '#66bb6a'}, + reconnecting: {label: 'Reconnecting', color: '#ffb74d'}, + paused: {label: 'Paused', color: '#9e9e9e'}, + ended: {label: 'Ended', color: '#64b5f6'}, +}; + +const readBoolPref = (key: string, fallback: boolean) => { + const raw = localStorage.getItem(key); + return raw === null ? fallback : raw === 'true'; +}; + +const segmentStyle = (s: AnsiSegment, ansi: string[]): CSSProperties => ({ + color: s.color ?? (s.colorIdx !== undefined ? ansi[s.colorIdx] : undefined), + backgroundColor: s.background ?? (s.backgroundIdx !== undefined ? ansi[s.backgroundIdx] : undefined), + fontWeight: s.bold ? 700 : undefined, + opacity: s.dim ? 0.6 : undefined, + fontStyle: s.italic ? 'italic' : undefined, + textDecoration: s.underline ? 'underline' : undefined, +}); + +const streamBorder = (stream: number) => { + if (stream === STREAM_INTERNAL) return 'rgba(255,167,38,0.8)'; // dockman notice + if (stream === 2) return 'rgba(244,67,54,0.55)'; // stderr + return 'transparent'; +}; + +function LogRow({entry, lineNumber, query, isCurrentMatch, showTimestamps, showLineNumbers, showName, nameColor, wrap, theme}: { + entry: LogEntry; + lineNumber: number; + query: LogQuery | null; + isCurrentMatch: boolean; + showTimestamps: boolean; + showLineNumbers: boolean; + showName: boolean; + nameColor: string; + wrap: boolean; + theme: ViewerTheme; +}) { + const isInternal = entry.stream === STREAM_INTERNAL; + + let content: ReactNode; + if (query === null) { + // fast path: no search, render the parsed segments directly + content = entry.segments.map((s, i) => ( + {s.text} + )); + } else { + content = highlightSegments(entry.segments, query).map((piece, i) => { + const style = segmentStyle(piece.segment, theme.ansi); + if (!piece.isMatch) { + return {piece.segment.text}; + } + return ( + + {piece.segment.text} + + ); + }); + } + + return ( +
+ {showTimestamps && entry.timeNano !== 0n && ( + {formatLogTime(entry.timeNano)} + )} + {showName && entry.containerName !== "" && ( + [{entry.containerName}] + )} + {content} +
+ ); +} + +const selectSx = {fontSize: '0.8rem', '& .MuiSelect-select': {py: 0.5}}; + +function ToolbarSelect({value, onChange, options}: { + value: T; + onChange: (value: T) => void; + options: { value: T; label: string }[]; +}) { + return ( + + ); +} + +const toolbarRowSx = { + px: 1, py: 0.5, + borderBottom: '1px solid', borderColor: 'divider', + flexShrink: 0, bgcolor: '#1E1E1E', position: 'relative', zIndex: 1, +}; + +export function LogsViewer({containers, isActive = true}: LogsViewerProps) { + const isMerged = containers.length > 1; + const hasNames = containers.some(c => c.name !== undefined) || !isMerged; + + // merged view: chips hide a container's lines from display only — the + // stream keeps running for all of them so the scrollback survives toggles + const [disabledIds, setDisabledIds] = useState>(new Set()); + + const [query, setQuery] = useState(""); + const [filterMode, setFilterMode] = useState(false); + const [currentMatch, setCurrentMatch] = useState(0); + const [streamFilter, setStreamFilter] = useState('all'); + + const [showTimestamps, setShowTimestamps] = useState(() => readBoolPref(PREF_TIMESTAMPS, false)); + const [wrap, setWrap] = useState(() => readBoolPref(PREF_WRAP, true)); + const [showNames, setShowNames] = useState(() => readBoolPref(PREF_NAMES, true)); + const [showLineNumbers, setShowLineNumbers] = useState(() => readBoolPref(PREF_LINE_NUMBERS, false)); + const [light, setLight] = useState(() => readBoolPref(PREF_LIGHT, false)); + const [tail, setTail] = useState(() => Number(localStorage.getItem(PREF_TAIL)) || 1000); + const [fontSize, setFontSize] = useState(() => Number(localStorage.getItem(PREF_FONT_SIZE)) || 12); + + const [paused, setPaused] = useState(false); + const [autoScroll, setAutoScroll] = useState(true); + const [reloadKey, setReloadKey] = useState(0); + + // time range: unix seconds once applied; an upper bound ends the stream + const [range, setRange] = useState<{ since?: number; until?: number }>({}); + const [rangeAnchor, setRangeAnchor] = useState(null); + const [sinceInput, setSinceInput] = useState(""); + const [untilInput, setUntilInput] = useState(""); + + const theme = light ? LIGHT_THEME : DARK_THEME; + + const allIds = useMemo(() => containers.map(c => c.id), [containers]); + const {entries, status, lastError, clear} = useLogsStream({ + containerIds: allIds, + tail, + since: range.since, + until: range.until, + follow: range.until === undefined, + paused: paused || !isActive, + reloadKey, + }); + + const handleReload = () => { + setPaused(false); + setAutoScroll(true); + setReloadKey(k => k + 1); + }; + + const boolToggle = (key: string, set: (fn: (prev: boolean) => boolean) => void) => () => set(prev => { + localStorage.setItem(key, String(!prev)); + return !prev; + }); + const toggleTimestamps = boolToggle(PREF_TIMESTAMPS, setShowTimestamps); + const toggleWrap = boolToggle(PREF_WRAP, setWrap); + const toggleNames = boolToggle(PREF_NAMES, setShowNames); + const toggleLineNumbers = boolToggle(PREF_LINE_NUMBERS, setShowLineNumbers); + const toggleLight = boolToggle(PREF_LIGHT, setLight); + + const changeTail = (value: number) => { + localStorage.setItem(PREF_TAIL, String(value)); + setTail(value); + }; + const changeFontSize = (value: number) => { + localStorage.setItem(PREF_FONT_SIZE, String(value)); + setFontSize(value); + }; + + const logQuery = useMemo(() => compileQuery(query), [query]); + const displayed = useMemo(() => { + const wantedStream = streamFilter === 'stdout' ? 1 : streamFilter === 'stderr' ? 2 : 0; + let visible = entries; + if (isMerged && disabledIds.size > 0) { + visible = visible.filter(e => !disabledIds.has(e.containerId) || e.stream === STREAM_INTERNAL); + } + if (wantedStream !== 0) { + visible = visible.filter(e => e.stream === wantedStream || e.stream === STREAM_INTERNAL); + } + if (filterMode && logQuery) { + visible = visible.filter(e => matchesQuery(e, logQuery)); + } + return visible; + }, [entries, isMerged, disabledIds, streamFilter, filterMode, logQuery]); + + const matchIds = useMemo( + () => (logQuery ? displayed.filter(e => matchesQuery(e, logQuery)).map(e => e.id) : []), + [displayed, logQuery], + ); + // single clamped cursor: the raw state may go stale when matches shrink + const boundedMatch = matchIds.length === 0 ? 0 : Math.min(currentMatch, matchIds.length - 1); + const currentMatchId = matchIds[boundedMatch]; + + const colorFor = useCallback((containerId: string) => { + if (!isMerged) return theme.singleName; + const idx = containers.findIndex(c => c.id === containerId); + return containerColor(idx < 0 ? 0 : idx); + }, [containers, isMerged, theme.singleName]); + + // --- scrolling --- + const scrollRef = useRef(null); + const programmaticScroll = useRef(false); + + const scrollToBottom = useCallback(() => { + const el = scrollRef.current; + if (!el) return; + programmaticScroll.current = true; + el.scrollTop = el.scrollHeight; + requestAnimationFrame(() => { + programmaticScroll.current = false; + }); + }, []); + + useEffect(() => { + if (!autoScroll || paused || !isActive) return; + scrollToBottom(); + }, [entries, autoScroll, paused, isActive, wrap, showTimestamps, fontSize, scrollToBottom]); + + const handleScroll = () => { + if (programmaticScroll.current) return; + const el = scrollRef.current; + if (!el) return; + const fromBottom = el.scrollHeight - el.scrollTop - el.clientHeight; + if (fromBottom <= BOTTOM_STICKINESS_PX) { + if (!autoScroll) setAutoScroll(true); + } else if (autoScroll) { + setAutoScroll(false); + } + }; + + const scrollToEntry = useCallback((entryId: number) => { + setAutoScroll(false); + const el = scrollRef.current?.querySelector(`[data-log-id="${entryId}"]`); + el?.scrollIntoView({block: 'center'}); + }, []); + + const goToMatch = useCallback((direction: 1 | -1) => { + if (matchIds.length === 0) return; + const next = (boundedMatch + direction + matchIds.length) % matchIds.length; + setCurrentMatch(next); + scrollToEntry(matchIds[next]); + }, [matchIds, boundedMatch, scrollToEntry]); + + // --- clipboard / file export --- + const {handleCopy: copyText, copiedId} = useCopyButton(); + const exportText = () => logsToText(displayed, showTimestamps, showNames && hasNames); + const handleCopy = () => copyText(exportText()); + const handleDownload = () => { + const label = isMerged ? 'stack' : (containers[0]?.name ?? containers[0]?.id.substring(0, 12) ?? 'container'); + const blob = new Blob([exportText()], {type: 'text/plain'}); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `${label}-logs.txt`; + document.body.appendChild(a); + a.click(); + a.remove(); + URL.revokeObjectURL(url); + }; + + // --- time range popover --- + const applyRange = () => { + const parse = (value: string) => { + if (!value) return undefined; + const ms = new Date(value).getTime(); + return Number.isNaN(ms) ? undefined : Math.floor(ms / 1000); + }; + setRange({since: parse(sinceInput), until: parse(untilInput)}); + setRangeAnchor(null); + }; + const clearRange = () => { + setSinceInput(""); + setUntilInput(""); + setRange({}); + setRangeAnchor(null); + }; + const rangeActive = range.since !== undefined || range.until !== undefined; + + const enabledCount = containers.length - disabledIds.size; + const toggleContainer = (id: string) => { + setDisabledIds(prev => { + if (prev.has(id)) { + const next = new Set(prev); + next.delete(id); + return next; + } + // never hide the last visible container + if (enabledCount <= 1) return prev; + return new Set(prev).add(id); + }); + }; + + const statusMeta = STATUS_META[status]; + const iconSx = (active: boolean) => ({ + color: active ? 'primary.main' : 'text.secondary', + p: 0.5, + }); + + return ( + + {/* toolbar */} + + { + setQuery(e.target.value); + setCurrentMatch(0); + }} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); + goToMatch(e.shiftKey ? -1 : 1); + } + }} + sx={{width: 210, '& .MuiInputBase-root': {fontSize: '0.8rem'}}} + slotProps={{ + input: { + startAdornment: ( + + + + ), + }, + }} + /> + {logQuery && ( + <> + + {matchIds.length === 0 ? '0/0' : `${boundedMatch + 1}/${matchIds.length}`} + + + goToMatch(-1)}> + + + + + goToMatch(1)}> + + + + + )} + + setFilterMode(f => !f)}> + + + + + + + + value={streamFilter} + onChange={setStreamFilter} + options={[ + {value: 'all', label: 'All streams'}, + {value: 'stdout', label: 'stdout'}, + {value: 'stderr', label: 'stderr'}, + ]} + /> + + value={tail} + onChange={changeTail} + options={TAIL_OPTIONS.map(v => ({value: v, label: `${v} lines`}))} + /> + + value={fontSize} + onChange={changeFontSize} + options={FONT_SIZES.map(v => ({value: v, label: `${v}px`}))} + /> + + + setRangeAnchor(e.currentTarget)}> + + + + + + + + + {hasNames && ( + + + + + + )} + + + + + + + + + + + + + {light ? : } + + + + { + const next = !autoScroll; + setAutoScroll(next); + if (next) scrollToBottom(); + }}> + + + + + setPaused(p => !p)}> + {paused ? : } + + + + + + + + + + + + + + + {copiedId + ? + : } + + + + + + + + + + + + + {statusMeta.label} · {displayed.length} + + + + + {/* merged view: container chips */} + {isMerged && ( + + {containers.map((c, idx) => { + const enabled = !disabledIds.has(c.id); + return ( + toggleContainer(c.id)} + variant={enabled ? 'filled' : 'outlined'} + icon={} + sx={{ + fontSize: '0.72rem', + opacity: enabled ? 1 : 0.5, + bgcolor: enabled ? 'rgba(255,255,255,0.08)' : 'transparent', + }} + /> + ); + })} + + )} + {/* log lines */} + + {displayed.length === 0 ? ( + + {status === 'connecting' ? 'Waiting for logs...' + : lastError ? `No log lines — ${lastError}` + : 'No log lines'} + + ) : ( + displayed.map((entry, index) => ( + + )) + )} + + {/* time range popover */} + setRangeAnchor(null)} + anchorOrigin={{vertical: 'bottom', horizontal: 'right'}} + transformOrigin={{vertical: 'top', horizontal: 'right'}} + > + + Time range + setSinceInput(e.target.value)} + slotProps={{inputLabel: {shrink: true}}} + /> + setUntilInput(e.target.value)} + slotProps={{inputLabel: {shrink: true}}} + helperText="Setting an upper bound stops following" + /> + + + + + + + + ); +} + +export default LogsViewer; diff --git a/ui/src/components/log-viewer/use-logs-stream.ts b/ui/src/components/log-viewer/use-logs-stream.ts new file mode 100644 index 00000000..3d251169 --- /dev/null +++ b/ui/src/components/log-viewer/use-logs-stream.ts @@ -0,0 +1,188 @@ +import {useCallback, useEffect, useRef, useState} from "react"; +import {useHostClient} from "../../lib/api.ts"; +import {DockerService} from "../../gen/docker/v1/docker_pb.ts"; +import {createAnsiTracker} from "./ansi.ts"; +import {appendEntries, isKeepAlive, type LogEntry, STREAM_INTERNAL, toLogEntry} from "./log-model.ts"; + +export type LogStreamStatus = 'idle' | 'connecting' | 'live' | 'reconnecting' | 'paused' | 'ended'; + +export interface LogsStreamParams { + containerIds: string[]; + tail: number; + since?: number; // unix seconds, undefined = from the tail only + until?: number; + follow: boolean; + // pausing (or suspending a hidden tab) closes the stream, keeps the buffer + paused: boolean; + // bump to drop the buffer and reload the stream from scratch + reloadKey?: number; +} + +const FLUSH_DELAY_MS = 80; +const RETRY_MIN_MS = 1000; +const RETRY_MAX_MS = 30000; + +// Streams the requested containers' logs into an immutable, capped LogEntry +// buffer. Resuming (after a pause or a silent reconnection) replays from the +// last seen timestamp and drops only the overlap it already has — never live +// lines: the replay filter compares against a snapshot taken at connect time, +// so distinct live lines sharing one timestamp all pass. +export function useLogsStream(params: LogsStreamParams) { + const client = useHostClient(DockerService); + const [entries, setEntries] = useState([]); + const [status, setStatus] = useState('idle'); + const [lastError, setLastError] = useState(""); + + const idsKey = params.containerIds.join(','); + const {tail, since, until, follow, paused, reloadKey = 0} = params; + + // newest daemon timestamp seen per container, used to bound resumes + const lastNanoRef = useRef>(new Map()); + // newest timestamp seen overall: lines without one inherit it as their + // ordering key so they keep their arrival position in the sorted buffer + const sortKeyRef = useRef(0n); + + const clear = useCallback(() => { + setEntries([]); + }, []); + + // changing what is being streamed starts a fresh buffer; pausing does not + useEffect(() => { + setEntries([]); + lastNanoRef.current = new Map(); + sortKeyRef.current = 0n; + }, [client, idsKey, tail, since, until, follow, reloadKey]); + + useEffect(() => { + if (!idsKey) { + setStatus('idle'); + return; + } + if (paused) { + setStatus('paused'); + return; + } + + const abort = new AbortController(); + let closed = false; + let pending: LogEntry[] = []; + let flushTimer: ReturnType | null = null; + + const flush = () => { + flushTimer = null; + if (pending.length === 0) return; + const batch = pending; + pending = []; + setEntries(prev => appendEntries(prev, batch)); + }; + const scheduleFlush = () => { + if (flushTimer === null) { + flushTimer = setTimeout(flush, FLUSH_DELAY_MS); + } + }; + + const containerIds = idsKey.split(','); + + // colors opened on one line carry to the next (banners are colored + // once for a whole block); state survives silent reconnections + const ansiTracker = createAnsiTracker(); + + // resume/reconnect from just before the oldest "last seen" timestamp; + // the replay snapshot below drops the overlap + const resumeSince = (): number => { + const seen = containerIds + .map(id => lastNanoRef.current.get(id) ?? 0n) + .filter(n => n !== 0n); + if (seen.length === 0) return since ?? 0; + const oldest = seen.reduce((a, b) => (a < b ? a : b)); + return Number(oldest / 1000000000n); + }; + + const run = async () => { + let attempt = 0; + let backoff = RETRY_MIN_MS; + while (!closed) { + setStatus(attempt === 0 ? 'connecting' : 'reconnecting'); + attempt++; + let live = false; + // fixed snapshot: only lines at or before these timestamps are + // replayed history; everything past them is live and never dropped + const replayBar = new Map(lastNanoRef.current); + try { + const stream = client.containerLogsStream({ + containerIds, + tail, + since: BigInt(resumeSince()), + until: BigInt(until ?? 0), + follow, + }, {signal: abort.signal}); + + for await (const line of stream) { + if (isKeepAlive(line)) { + // the server is reachable even if no lines flow + if (!live) { + live = true; + backoff = RETRY_MIN_MS; + setStatus('live'); + setLastError(""); + } + continue; + } + + // dockman-injected failure notices: show them, but they + // are not container output — no watermark, no pacing reset + if (line.stream !== STREAM_INTERNAL) { + if (!live) { + live = true; + backoff = RETRY_MIN_MS; + setStatus('live'); + setLastError(""); + } + if (line.timeNano !== 0n) { + const bar = replayBar.get(line.containerId); + if (bar !== undefined && line.timeNano <= bar) continue; // replayed overlap + const seen = lastNanoRef.current.get(line.containerId) ?? 0n; + if (line.timeNano > seen) { + lastNanoRef.current.set(line.containerId, line.timeNano); + } + } + } + + const segments = ansiTracker(`${line.containerId}|${line.stream}`, line.text); + if (line.timeNano > sortKeyRef.current) { + sortKeyRef.current = line.timeNano; + } + const sortKey = line.timeNano !== 0n ? line.timeNano : sortKeyRef.current; + pending.push(toLogEntry(line, segments, sortKey)); + scheduleFlush(); + } + + if (!follow) { + // bounded query: the stream ending is the happy path + flush(); + setStatus('ended'); + return; + } + // follow stream ended without an abort: server went away + } catch (err) { + if (closed || abort.signal.aborted) return; + setLastError(err instanceof Error ? err.message : String(err)); + } + + flush(); + setStatus('reconnecting'); + await new Promise(resolve => setTimeout(resolve, backoff)); + backoff = Math.min(backoff * 2, RETRY_MAX_MS); + } + }; + void run(); + + return () => { + closed = true; + abort.abort(); + if (flushTimer !== null) clearTimeout(flushTimer); + }; + }, [client, idsKey, tail, since, until, follow, paused, reloadKey]); + + return {entries, status, lastError, clear}; +} diff --git a/ui/src/components/page-header.tsx b/ui/src/components/page-header.tsx new file mode 100644 index 00000000..0db01a72 --- /dev/null +++ b/ui/src/components/page-header.tsx @@ -0,0 +1,86 @@ +import {Box, Button, Chip, CircularProgress, Tooltip, Typography} from "@mui/material"; +import {Refresh} from "@mui/icons-material"; +import type {ReactNode} from "react"; + +// the uniform list-view header: icon, title, count chip, optional extra +// info chip (e.g. total image size), and the host the view looks at. +// `right` pins content (like a search bar) to the same line. +export default function PageHeader({icon, title, count, extra, host, right, compact}: { + icon: ReactNode, + title: string, + count?: number | string, + extra?: string, + host?: string, + right?: ReactNode, + // tighter bottom margin for views that stack more chrome under the title + compact?: boolean, +}) { + return ( + + {icon} + + {title} + + {count !== undefined && ( + + )} + {extra && ( + + )} + {host && ( + + on {host} + + )} + + {right} + + ); +} + +// refresh at the same size and weight as the action buttons, so it sits in +// the same toolbar row instead of floating alone in a corner +export function RefreshButton({onClick, loading, iconOnly}: { + onClick: () => void, + loading?: boolean, + // symbol-only variant matching icon-only action rows + iconOnly?: boolean, +}) { + return ( + + + + + + ); +} diff --git a/ui/src/components/search-bar.tsx b/ui/src/components/search-bar.tsx index ee43acab..b8fcf887 100644 --- a/ui/src/components/search-bar.tsx +++ b/ui/src/components/search-bar.tsx @@ -25,6 +25,7 @@ function SearchBar({inputRef, search, setSearch}: SearchBarProps) { minWidth: 250, '& .MuiOutlinedInput-root': { backgroundColor: 'rgba(255, 255, 255, 0.05)', + height: 32, } }} /> diff --git a/ui/src/components/sparkline.tsx b/ui/src/components/sparkline.tsx new file mode 100644 index 00000000..0edfb4a3 --- /dev/null +++ b/ui/src/components/sparkline.tsx @@ -0,0 +1,71 @@ +import {Box} from "@mui/material"; + +interface SparklineProps { + data: number[]; + color: string; + /** rendered height in px — also the drawing's vertical resolution */ + height?: number; +} + +// Fixed horizontal drawing space, stretched to the parent width. The vertical +// axis is NOT stretched: the viewBox height equals the rendered height +// (Dockhand's cards do the same), so the 1px line stays crisp instead of +// blurring through a fractional vertical scale. +const VIEW_W = 120; + +/** + * Inline SVG area chart for live metric history (CPU %, memory %...), + * reproducing Dockhand's chart rendering: zero-based scale with the ceiling + * at the window maximum (floored at 1), thin 1px line, 15% area fill. + */ +export function Sparkline({data, color, height = 26}: SparklineProps) { + // no data yet: keep the footprint with a subtle placeholder + if (data.length === 0) { + return ( + + ); + } + + // a single reading draws a flat line so the chart shows up on the very + // first poll instead of after two ticks + const series = data.length === 1 ? [data[0], data[0]] : data; + + const max = Math.max(...series, 1); + const step = VIEW_W / (series.length - 1); + const points = series.map((v, i) => { + const x = i * step; + const y = height - (Math.max(v, 0) / max) * height; + return `${x.toFixed(2)},${y.toFixed(2)}`; + }); + + const line = points.join(' '); + const area = `0,${height} ${line} ${VIEW_W},${height}`; + + return ( + + + + + ); +} + +export default Sparkline; diff --git a/ui/src/components/upload-progress-toast.tsx b/ui/src/components/upload-progress-toast.tsx new file mode 100644 index 00000000..620390ac --- /dev/null +++ b/ui/src/components/upload-progress-toast.tsx @@ -0,0 +1,47 @@ +import {Alert, Box, LinearProgress, Snackbar, Typography} from '@mui/material'; +import {CloudUpload} from '@mui/icons-material'; +import {useUploadProgress} from '../hooks/upload-progress.ts'; + +// Global, self-managing progress toast shown while a batch of files uploads. +// It uses the same bottom-centered Alert as the app's snackbars and hides on +// completion — the success/error snackbar then confirms the outcome. The two +// never show at the same time, so they can share the same bottom slot. +export function UploadProgressToast() { + const active = useUploadProgress(s => s.active); + const fileCount = useUploadProgress(s => s.fileCount); + const doneCount = useUploadProgress(s => s.doneCount); + const totalBytes = useUploadProgress(s => s.totalBytes); + const loadedBytes = useUploadProgress(s => s.loadedBytes); + + if (!active) return null; + + const pct = totalBytes > 0 + ? Math.min(100, Math.round((loadedBytes / totalBytes) * 100)) + : 0; + + return ( + + } + sx={{width: '100%', minWidth: 320, alignItems: 'center'}} + > + + + Uploading {fileCount} {fileCount === 1 ? 'file' : 'files'} + {doneCount > 0 && ` — ${doneCount}/${fileCount} done`} + {' · '}{pct}% + + + + + + ); +} diff --git a/ui/src/context/alias-context.tsx b/ui/src/context/alias-context.tsx index 0da9462f..8f89e1f7 100644 --- a/ui/src/context/alias-context.tsx +++ b/ui/src/context/alias-context.tsx @@ -62,7 +62,7 @@ const AliasProvider = ({children}: { children: ReactNode }) => { } setIsLoading(false) - }, [host, hostmanager]) + }, [host, hostmanager, showError]) const addAlias = async (alias: string, host: number, fullpath: string) => { const {err} = await callRPC(() => hostmanager.addAlias({alias: {alias, fullpath, id: host}})) @@ -101,4 +101,4 @@ const AliasProvider = ({children}: { children: ReactNode }) => { ) }; -export default AliasProvider; \ No newline at end of file +export default AliasProvider; diff --git a/ui/src/context/auth-context.tsx b/ui/src/context/auth-context.tsx index 328fd81b..27132505 100644 --- a/ui/src/context/auth-context.tsx +++ b/ui/src/context/auth-context.tsx @@ -2,6 +2,7 @@ import React, {type ReactNode, useCallback, useEffect, useMemo, useState} from ' import {AuthContext} from '../hooks/auth.ts'; import {callRPC, pingWithAuth, useAuthClient} from "../lib/api.ts"; import {AuthService} from "../gen/auth/v1/auth_pb.ts"; +import {debugWarn} from "../lib/debug.ts"; export interface AuthProviderProps { children: ReactNode; @@ -23,7 +24,6 @@ export const AuthProvider: React.FC = ({children}) => { const checkAuthStatus = async () => { pingWithAuth().then(value => { setIsAuthenticated(value); - // console.log(`isAuthenticated is now: ${value}`) }).finally(() => { setIsLoading(false); }); @@ -37,11 +37,11 @@ export const AuthProvider: React.FC = ({children}) => { const logout = useCallback(async () => { const {err} = await callRPC(() => userClient.logout({})) if (err) { - console.warn(err) + debugWarn("Logout failed", err) } refreshAuthStatus(); - }, [refreshAuthStatus]); + }, [refreshAuthStatus, userClient]); const contextValue = useMemo( () => ({ @@ -58,4 +58,4 @@ export const AuthProvider: React.FC = ({children}) => { {children} ); -}; \ No newline at end of file +}; diff --git a/ui/src/context/changelog-context.tsx b/ui/src/context/changelog-context.tsx index ff3df11f..bd6b2c13 100644 --- a/ui/src/context/changelog-context.tsx +++ b/ui/src/context/changelog-context.tsx @@ -37,7 +37,7 @@ export function ChangelogProvider({children}: ChangelogProviderProps) { } setIsLoading(false) - }, [infoClient]) + }, [infoClient, showError]) const dismissChangelog = useCallback(async () => { // Mark changelog as read on the backend @@ -52,7 +52,7 @@ export function ChangelogProvider({children}: ChangelogProviderProps) { setChangelog("") setReleaseUrl("") setVersion("") - }, [infoClient, version]) + }, [infoClient, showError, version]) // Check for changelog on mount useEffect(() => { diff --git a/ui/src/context/config-context.tsx b/ui/src/context/config-context.tsx index 4eeab1ea..f0b31a0f 100644 --- a/ui/src/context/config-context.tsx +++ b/ui/src/context/config-context.tsx @@ -18,17 +18,15 @@ export function UserConfigProvider({children}: { children: ReactNode }) { const [dockYaml, setDockyaml] = useState(null) const fetchDockYaml = useCallback(async () => { - // console.log("reloading dockman yaml") const {val, err} = await callRPC(() => dockyamlClient.getYaml({})) if (err) { showWarning(`Unable to get dockman yaml, ${err}`) } else { setDockyaml(val?.dock ?? null) } - }, [dockyamlClient]) + }, [dockyamlClient, showWarning]) const fetchConfig = useCallback(async () => { - // console.log("Fetching user config...") setIsLoading(true) const {val, err} = await callRPC(() => client.getUserConfig({})) @@ -41,7 +39,7 @@ export function UserConfigProvider({children}: { children: ReactNode }) { await fetchDockYaml() setIsLoading(false) - }, [client, fetchDockYaml]) + }, [client, fetchDockYaml, showError]) const updateSettings = useCallback( async (conf: Config, updaterConfig: UpdateSettingsOption = {}) => { @@ -53,7 +51,7 @@ export function UserConfigProvider({children}: { children: ReactNode }) { } await fetchConfig() - }, [client, fetchConfig] + }, [client, fetchConfig, showError, showSuccess] ) useEffect(() => { diff --git a/ui/src/context/file-context.tsx b/ui/src/context/file-context.tsx index faccb2f5..30f9f9b1 100644 --- a/ui/src/context/file-context.tsx +++ b/ui/src/context/file-context.tsx @@ -2,11 +2,26 @@ import {createContext, type ReactNode, useCallback, useContext, useEffect, useSt import {useNavigate} from 'react-router-dom' import {callRPC, useHostClient, useHostUrl,} from "../lib/api.ts"; import {useSnackbar} from "../hooks/snackbar.ts"; +import {useUploadProgress} from "../hooks/upload-progress.ts"; import {FileService, type FsEntry} from '../gen/files/v1/files_pb.ts'; import {useTabs} from "./tab-context.tsx"; import {useEditorUrl} from "../lib/editor.ts"; -import {useHostStore, useOpenFiles} from "../pages/compose/state/files.ts"; +import {useOpenFiles} from "../pages/compose/state/files.ts"; import {useFileComponents} from "../pages/compose/state/terminal.tsx"; +import {debugError} from "../lib/debug.ts"; + +// btoa only accepts Latin1, so a path with accents, curly quotes or any other +// non-Latin1 character throws. Encode the UTF-8 bytes instead; the backend +// base64-decodes this straight back to the raw UTF-8 path bytes. +function encodePathForMultipart(path: string): string { + const bytes = new TextEncoder().encode(path); + let binary = ''; + bytes.forEach((b) => (binary += String.fromCharCode(b))); + return btoa(binary) + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=+$/, ''); +} export interface FilesContextType { files: FsEntry[] @@ -45,7 +60,6 @@ function FilesProvider({children}: { children: ReactNode }) { const [files, setFiles] = useState([]) const [isLoading, setIsLoading] = useState(true) - const host = useHostStore(state => state.host) // don't use alias store since its dependent on the React lifecycle // const alias = useAliasStore(state => state.alias) const {alias} = useFileComponents() @@ -81,7 +95,7 @@ function FilesProvider({children}: { children: ReactNode }) { } setIsLoading(false) - }, [alias, host, client]); + }, [alias, client, showError]); const closeFolder = useOpenFiles(state => state.delete) const fileUrl = useEditorUrl() @@ -102,7 +116,7 @@ function FilesProvider({children}: { children: ReactNode }) { } await fetchFiles() - }, [client, fetchFiles, host, navigate]) + }, [client, fetchFiles, fileUrl, navigate, showError, showSuccess]) const copyFile = useCallback(async (srcFilename: string, destFilename: string, isDir: boolean) => { const {err} = await callRPC(() => client.copy({ @@ -126,7 +140,7 @@ function FilesProvider({children}: { children: ReactNode }) { } await fetchFiles() - }, []) + }, [client, fetchFiles, fileUrl, navigate, showError, showSuccess]) const deleteFile = async ( @@ -164,44 +178,89 @@ function FilesProvider({children}: { children: ReactNode }) { const getUrl = useHostUrl() - async function uploadFile(fullPath: string, content: File | string, isNew: boolean = false): Promise { + const uploadFile = useCallback(function ( + fullPath: string, + content: File | string, + isNew: boolean = false, + onProgress?: (loaded: number, total: number) => void, + ): Promise { const url = getUrl(`/file/save${isNew ? '?create=true' : ''}`) - try { - const formData = new FormData(); + const fileBlob = typeof content === 'string' + // If it's a string (from editor), wrap it. + ? new File([content], getEntryDisplayName(fullPath)) + // If it's already a File (from DnD), use it. + : content; + + // XMLHttpRequest (not fetch) so we can observe upload progress via + // xhr.upload.onprogress — fetch with a FormData body reports nothing. + return new Promise((resolve) => { + try { + const formData = new FormData(); + // A multipart filename is normalized as a filesystem name by + // Go. URL-safe Base64 avoids '/' being treated as a separator. + formData.append('contents', fileBlob, encodePathForMultipart(fullPath)); + + const xhr = new XMLHttpRequest(); + xhr.open('POST', url, true); + + if (onProgress) { + xhr.upload.onprogress = (e) => { + if (e.lengthComputable) onProgress(e.loaded, e.total); + }; + } - const fileBlob = typeof content === 'string' - // If it's a string (from editor), wrap it. - ? new File([content], getEntryDisplayName(fullPath)) - // If it's already a File (from DnD), use it. - : content; + xhr.onload = () => { + if (xhr.status >= 200 && xhr.status < 300) { + resolve(""); + } else { + resolve(`Error: ${xhr.status} - ${xhr.responseText}`); + } + }; + xhr.onerror = () => { + debugError("Upload failed"); + resolve("Network error"); + }; + + xhr.send(formData); + } catch (error) { + debugError("Upload failed", error); + resolve("Network error"); + } + }); + }, [getUrl]) - formData.append('contents', fileBlob, btoa(fullPath)); + const uploadFilesFromPC = async (targetDir: string, files: File[]) => { + const cleanDir = targetDir.endsWith('/') ? targetDir.slice(0, -1) : targetDir; - const response = await fetch(url, { - method: 'POST', - body: formData, - }); + // Aggregate per-file byte counts into one batch progress figure. Uploads + // run in parallel, so each file writes into its slot and we sum. + const totalBytes = files.reduce((sum, f) => sum + f.size, 0); + const loaded = new Array(files.length).fill(0); + let doneCount = 0; - if (!response.ok) { - const errorText = await response.text(); - return `Error: ${response.status} - ${errorText}`; - } + const pushProgress = () => { + const loadedBytes = loaded.reduce((a, b) => a + b, 0); + useUploadProgress.getState().update(loadedBytes, doneCount); + }; - return ""; - } catch (error) { - console.error("Upload failed:", error); - return "Network error"; - } - } + useUploadProgress.getState().start(files.length, totalBytes); - const uploadFilesFromPC = async (targetDir: string, files: File[]) => { - const results = await Promise.all(files.map(file => { - const cleanDir = targetDir.endsWith('/') ? targetDir.slice(0, -1) : targetDir; + const results = await Promise.all(files.map((file, i) => { const fullPath = `${cleanDir}/${file.name}`; - return uploadFile(fullPath, file, true); + return uploadFile(fullPath, file, true, (l) => { + loaded[i] = l; + pushProgress(); + }).then((res) => { + doneCount++; + loaded[i] = file.size; // a finished file counts as fully sent + pushProgress(); + return res; + }); })); + useUploadProgress.getState().finish(); + const errors = results.filter(res => res !== ""); if (errors.length > 0) { showError(`${errors.length} files failed to upload.`) @@ -213,7 +272,7 @@ function FilesProvider({children}: { children: ReactNode }) { }; - async function downloadFile( + const downloadFile = useCallback(async function ( filename: string, shouldDownload: boolean = false ): Promise<{ file: string; err: string }> { @@ -251,10 +310,10 @@ function FilesProvider({children}: { children: ReactNode }) { return {file: bodyText, err: ""}; } catch (error: unknown) { - console.error(`Error: ${(error as Error).toString()}`); + debugError("File download failed", error); return {file: "", err: (error as Error).toString()}; } - } + }, [getUrl]) useEffect(() => { fetchFiles().then() @@ -290,7 +349,7 @@ function insertAtNestedIndex(list: FsEntry[], indices: number[], value: FsEntry[ for (let i = 0; i < indices.length - 1; i++) { const index = indices[i]; if (!current || !current[index] || !current[index].subFiles) { - console.error('Invalid path at index', i); + debugError('Invalid file-tree path at index', i); return; } current = current[index].subFiles; @@ -299,7 +358,7 @@ function insertAtNestedIndex(list: FsEntry[], indices: number[], value: FsEntry[ // Set the value at the final index const lastIndex = indices[indices.length - 1]; if (!current || !current[lastIndex]) { - console.error('Invalid final index', lastIndex); + debugError('Invalid final file-tree index', lastIndex); return; } @@ -318,7 +377,7 @@ export const getEntryDisplayName = (path: string) => { const split = path.split("/"); const pop = split.pop(); if (!pop) { - console.error("unable to get last element in path", "split: ", split, "last element: ", pop) + debugError("Unable to get the last path element", split) return "ERR_EMPTY_PATH" } return pop diff --git a/ui/src/context/host-context.tsx b/ui/src/context/host-context.tsx index d9b95b18..4a16a154 100644 --- a/ui/src/context/host-context.tsx +++ b/ui/src/context/host-context.tsx @@ -43,7 +43,7 @@ function HostProvider({children}: { children: ReactNode }) { setAvailableHosts(val?.hosts || []) setLoading(false) - }, [hostManagerClient]); + }, [hostManagerClient, showError]); useEffect(() => { fetchHosts().then() diff --git a/ui/src/context/snackbar-context.tsx b/ui/src/context/snackbar-context.tsx index 0d8d5407..74812c9f 100644 --- a/ui/src/context/snackbar-context.tsx +++ b/ui/src/context/snackbar-context.tsx @@ -1,4 +1,4 @@ -import React, {type ReactNode, useState} from 'react'; +import React, {type ReactNode, useCallback, useMemo, useState} from 'react'; import {Alert, type AlertColor, Snackbar} from '@mui/material'; import {SnackbarContext, type SnackbarContextType, type SnackbarOptions} from '../hooks/snackbar.ts'; @@ -23,7 +23,7 @@ export const SnackbarProvider: React.FC = ({children}) => action: null, }); - const showSnackbar = (message: string, options: SnackbarOptions = {}) => { + const showSnackbar = useCallback((message: string, options: SnackbarOptions = {}) => { setSnackbar({ open: true, message, @@ -31,40 +31,40 @@ export const SnackbarProvider: React.FC = ({children}) => duration: options.duration || 3000, action: options.action || null, }); - }; + }, []); - const hideSnackbar = (_event?: React.SyntheticEvent | Event, reason?: string) => { + const hideSnackbar = useCallback((_event?: React.SyntheticEvent | Event, reason?: string) => { if (reason === 'clickaway') { return; } setSnackbar(prev => ({...prev, open: false})); - }; + }, []); // Convenience methods - const showSuccess = (message: string, options: Omit = {}) => { + const showSuccess = useCallback((message: string, options: Omit = {}) => { showSnackbar(message, {...options, severity: 'success'}); - }; + }, [showSnackbar]); - const showError = (message: string, options: Omit = {}) => { + const showError = useCallback((message: string, options: Omit = {}) => { showSnackbar(message, {...options, severity: 'error'}); - }; + }, [showSnackbar]); - const showWarning = (message: string, options: Omit = {}) => { + const showWarning = useCallback((message: string, options: Omit = {}) => { showSnackbar(message, {...options, severity: 'warning'}); - }; + }, [showSnackbar]); - const showInfo = (message: string, options: Omit = {}) => { + const showInfo = useCallback((message: string, options: Omit = {}) => { showSnackbar(message, {...options, severity: 'info'}); - }; + }, [showSnackbar]); - const value: SnackbarContextType = { + const value: SnackbarContextType = useMemo(() => ({ showSnackbar, showSuccess, showError, showWarning, showInfo, hideSnackbar, - }; + }), [hideSnackbar, showError, showInfo, showSnackbar, showSuccess, showWarning]); return ( diff --git a/ui/src/context/tab-context.tsx b/ui/src/context/tab-context.tsx index 5c0344ae..a538dab1 100644 --- a/ui/src/context/tab-context.tsx +++ b/ui/src/context/tab-context.tsx @@ -1,6 +1,7 @@ import {createContext, type ReactNode, useCallback, useContext, useEffect} from 'react' import {useLocation, useNavigate} from 'react-router-dom'; -import {useEditorUrl} from "../lib/editor.ts"; +import {stackDefaultTab, useEditorUrl} from "../lib/editor.ts"; +import {useConfig} from "../hooks/config.ts"; import {create} from "zustand"; import {immer} from "zustand/middleware/immer"; import {useAliasStore, useHostStore} from "../pages/compose/state/files.ts"; @@ -21,11 +22,14 @@ interface EditorState { lastOpened: Record; update: (filename: string, details: Partial) => void; - create: (filename: string, track?: number, tabIndex?: number) => void; + create: (filename: string, track?: number, tabIndex?: number, limit?: number) => void; close: (filename: string, track?: number) => { next: string, wasActive: boolean }; rename: (oldFilename: string, newFilename: string) => string; active: (filename: string, track?: number) => void; load: (filename: string) => TabDetails | undefined; + reorder: (filename: string, targetIndex: number, track?: number) => void; + clear: (track?: number) => void; + reset: () => void; } export const getContextKey = () => { @@ -45,7 +49,7 @@ export const useTabsStore = create()( return get().allTabs[filename]; }, - create: (filename, track = 0, tabIndex = 0) => { + create: (filename, track = 0, tabIndex = 0, limit = 0) => { const key = getContextKey(); set((state) => { if (!state.allTabs[filename]) { @@ -66,7 +70,31 @@ export const useTabsStore = create()( state.contextTabs[key] = {0: new Set(), 1: new Set()}; } - state.contextTabs[key][track].add(filename); + const tabs = state.contextTabs[key][track]; + + // Enforce tab limit: evict the oldest tabs to make room + if (limit > 0 && !tabs.has(filename)) { + const order = Array.from(tabs); + while (order.length >= limit) { + const oldest = order.shift(); + if (oldest === undefined) break; + tabs.delete(oldest); + + // Cleanup allTabs if no longer used anywhere + let stillInUse = false; + for (const k of Object.keys(state.contextTabs)) { + if (state.contextTabs[k][0]?.has(oldest) || state.contextTabs[k][1]?.has(oldest)) { + stillInUse = true; + break; + } + } + if (!stillInUse) { + delete state.allTabs[oldest]; + } + } + } + + tabs.add(filename); }); }, @@ -148,6 +176,63 @@ export const useTabsStore = create()( state.lastOpened[track] = filename; }); }, + + // closes every tab of the current context's track, dropping saved + // details unless another context or track still shows the file + clear: (track = 0) => { + const key = getContextKey(); + set((state) => { + const tabs = state.contextTabs[key]?.[track]; + if (!tabs || tabs.size === 0) return; + + for (const filename of tabs) { + let stillInUse = false; + for (const k of Object.keys(state.contextTabs)) { + for (const tr of [0, 1]) { + if (k === key && tr === track) continue; + if (state.contextTabs[k][tr]?.has(filename)) { + stillInUse = true; + break; + } + } + if (stillInUse) break; + } + if (!stillInUse) { + delete state.allTabs[filename]; + } + } + + state.contextTabs[key][track] = new Set(); + state.lastOpened[track] = ''; + }); + }, + + reset: () => { + set((state) => { + state.allTabs = {}; + state.contextTabs = {}; + state.lastOpened = {0: '', 1: ''}; + }); + }, + + // moves a tab to targetIndex within its track; tab order is the + // Set's insertion order, so the Set is rebuilt in the new order + reorder: (filename, targetIndex, track = 0) => { + const key = getContextKey(); + set((state) => { + const tabs = state.contextTabs[key]?.[track]; + if (!tabs || !tabs.has(filename)) return; + + const order = Array.from(tabs); + const from = order.indexOf(filename); + const to = Math.max(0, Math.min(targetIndex, order.length - 1)); + if (from === to) return; + + order.splice(from, 1); + order.splice(to, 0, filename); + state.contextTabs[key][track] = new Set(order); + }); + }, })) ); @@ -159,6 +244,7 @@ export interface TabsContextType { closeTab: (filename: string, track?: number) => void; renameTab: (oldFilename: string, newFilename: string) => void; onTabClick: (filename: string, track?: number) => void; + closeAllTabs: (track?: number) => void; } export const TabsContext = createContext(undefined); @@ -172,27 +258,28 @@ export const useTabs = (): TabsContextType => { }; export function TabsProvider({children}: { children: ReactNode }) { - // const {dockYaml} = useConfig() - // const tabLimit = dockYaml?.tabLimit ?? 5 + const {dockYaml} = useConfig() + const tabLimit = dockYaml?.tabLimit ?? 5 const location = useLocation(); const navigate = useNavigate(); const editorUrl = useEditorUrl() const {filename, splitFilename} = useFileComponents() - const {active, load, rename, update, create, close} = useTabsStore() + const {active, load, rename, update, create, close, clear} = useTabsStore() const handleTabClick = useCallback((filename: string, track: number = 0) => { - const tabDetail = load(filename) + // files opened for the first time start on the dockman.yml default tab + const tabDetail = load(filename) ?? stackDefaultTab(dockYaml, filename) const url = editorUrl(filename, tabDetail, track); navigate(url); - }, [editorUrl, navigate, load]); + }, [editorUrl, navigate, load, dockYaml]); const handleOpenTab = useCallback((filename: string, track: number = 0) => { const params = new URLSearchParams(location.search); - create(filename, track, Number(params.get("tab") ?? "0")) + create(filename, track, Number(params.get("tab") ?? stackDefaultTab(dockYaml, filename)), tabLimit) active(filename, track) - }, [location.search, create, active]); + }, [location.search, create, active, dockYaml, tabLimit]); const handleCloseTab = useCallback((filename: string, track: number = 0) => { const {next, wasActive} = close(filename, track) @@ -210,6 +297,17 @@ export function TabsProvider({children}: { children: ReactNode }) { } }, [close, editorUrl, navigate]) + const handleCloseAllTabs = useCallback((track: number = 0) => { + clear(track) + if (track === 1) { + navigate(editorUrl(undefined, undefined, 1)) + } else { + const h = useHostStore.getState().host; + const a = useAliasStore.getState().alias; + navigate(`/${h}/files/${a}`); + } + }, [clear, editorUrl, navigate]) + const handleTabRename = useCallback((oldFilename: string, newFilename: string) => { rename(oldFilename, newFilename) @@ -241,6 +339,7 @@ export function TabsProvider({children}: { children: ReactNode }) { const value = { openTab: handleOpenTab, + closeAllTabs: handleCloseAllTabs, closeTab: handleCloseTab, renameTab: handleTabRename, onTabClick: handleTabClick, diff --git a/ui/src/gen/docker/v1/docker_pb.ts b/ui/src/gen/docker/v1/docker_pb.ts index bb2114d2..f153e2f6 100644 --- a/ui/src/gen/docker/v1/docker_pb.ts +++ b/ui/src/gen/docker/v1/docker_pb.ts @@ -10,7 +10,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file docker/v1/docker.proto. */ export const file_docker_v1_docker: GenFile = /*@__PURE__*/ - fileDesc("ChZkb2NrZXIvdjEvZG9ja2VyLnByb3RvEglkb2NrZXIudjEiKQoYQ29tcG9zZUZpbGVTdGF0dXNSZXF1ZXN0Eg0KBWZpbGVzGAEgAygJImYKBlN0YXR1cxISCgpzZXJ2aWNlc1VwGAEgASgFEhQKDHNlcnZpY2VzRG93bhgCIAEoBRIXCg9zZXJ2aWNlc0hlYWx0aHkYAyABKAUSGQoRc2VydmljZXNVbkhlYWx0aHkYBCABKAUinwEKGUNvbXBvc2VGaWxlU3RhdHVzUmVzcG9uc2USQAoGc3RhdHVzGAEgAygLMjAuZG9ja2VyLnYxLkNvbXBvc2VGaWxlU3RhdHVzUmVzcG9uc2UuU3RhdHVzRW50cnkaQAoLU3RhdHVzRW50cnkSCwoDa2V5GAEgASgJEiAKBXZhbHVlGAIgASgLMhEuZG9ja2VyLnYxLlN0YXR1czoCOAEiKgoTQ29udGFpbmVyVG9wUmVxdWVzdBITCgtjb250YWluZXJJZBgBIAEoCSIzChRDb250YWluZXJUb3BSZXNwb25zZRIbCgN0b3AYASABKAsyDi5kb2NrZXIudjEuVG9wIhwKB1Byb2Nlc3MSEQoJUHJvY2Vzc2VzGAEgAygJIjcKA1RvcBIgCgRwcm9jGAEgAygLMhIuZG9ja2VyLnYxLlByb2Nlc3MSDgoGVGl0bGVzGAIgAygJIssBChdDb250YWluZXJJbnNwZWN0TWVzc2FnZRIMCgROYW1lGAEgASgJEgoKAklEGAIgASgJEgwKBFBhdGgYAyABKAkSDwoHQ3JlYXRlZBgHIAEoCRINCgVJbWFnZRgEIAEoCRIRCglIb3N0c1BhdGgYBSABKAkSKQoGbW91bnRzGAYgAygLMhkuZG9ja2VyLnYxLkNvbnRhaW5lck1vdW50EioKBmNvbmZpZxgIIAEoCzIaLmRvY2tlci52MS5Db250YWluZXJDb25maWcirQMKD0NvbnRhaW5lckNvbmZpZxIQCghIb3N0bmFtZRgBIAEoCRISCgpEb21haW5uYW1lGAIgASgJEgwKBFVzZXIYAyABKAkSEwoLQXR0YWNoU3RkaW4YBCABKAgSFAoMQXR0YWNoU3Rkb3V0GAUgASgIEhQKDEF0dGFjaFN0ZGVychgGIAEoCBILCgNUdHkYByABKAgSEQoJT3BlblN0ZGluGAggASgIEhEKCVN0ZGluT25jZRgJIAEoCBITCgtBcmdzRXNjYXBlZBgKIAEoCBINCgVJbWFnZRgLIAEoCRILCgNFbnYYDCADKAkSCwoDQ21kGA0gAygJEg8KB1ZvbHVtZXMYDiADKAkSEgoKV29ya2luZ0RpchgPIAEoCRISCgpFbnRyeXBvaW50GBAgAygJEjYKBkxhYmVscxgRIAMoCzImLmRvY2tlci52MS5Db250YWluZXJDb25maWcuTGFiZWxzRW50cnkSFAoMRXhwb3NlZFBvcnRzGBIgAygJGi0KC0xhYmVsc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiewoOQ29udGFpbmVyTW91bnQSDAoEVHlwZRgBIAEoCRIMCgROYW1lGAIgASgJEg4KBlNvdXJjZRgDIAEoCRITCgtEZXN0aW5hdGlvbhgEIAEoCRIOCgZEcml2ZXIYBSABKAkSDAoETW9kZRgGIAEoCRIKCgJSVxgHIAEoCCIWChRDb250YWluZXJMaXN0UmVxdWVzdCIqChVOZXR3b3JrSW5zcGVjdFJlcXVlc3QSEQoJbmV0d29ya0lkGAEgASgJIkgKFk5ldHdvcmtJbnNwZWN0UmVzcG9uc2USLgoHaW5zcGVjdBgBIAEoCzIdLmRvY2tlci52MS5OZXR3b3JrSW5zcGVjdEluZm8ibAoSTmV0d29ya0luc3BlY3RJbmZvEh8KA25ldBgBIAEoCzISLmRvY2tlci52MS5OZXR3b3JrEjUKCWNvbnRhaW5lchgCIAMoCzIiLmRvY2tlci52MS5OZXR3b3JrQ29udGFpbmVySW5zcGVjdCJiChdOZXR3b3JrQ29udGFpbmVySW5zcGVjdBIMCgROYW1lGAEgASgJEhAKCEVuZHBvaW50GAIgASgJEgwKBElQdjQYAyABKAkSDAoESVB2NhgEIAEoCRILCgNNYWMYBSABKAkiJgoTSW1hZ2VJbnNwZWN0UmVxdWVzdBIPCgdpbWFnZUlkGAEgASgJIkAKFEltYWdlSW5zcGVjdFJlc3BvbnNlEigKB2luc3BlY3QYASABKAsyFy5kb2NrZXIudjEuSW1hZ2VJbnNwZWN0In8KDEltYWdlSW5zcGVjdBIMCgRuYW1lGAEgASgJEgoKAmlkGAYgASgJEgwKBHNpemUYAyABKAkSDAoEYXJjaBgFIAEoCRISCgpjcmVhdGVkSXNvGAQgASgJEiUKBmxheWVycxgCIAMoCzIVLmRvY2tlci52MS5JbWFnZUxheWVyIlIKCkltYWdlTGF5ZXISDwoHTGF5ZXJJZBgDIAEoCRILCgNjbWQYASABKAkSDAoEc2l6ZRgCIAEoCRIYChB0b3RhbFNpemVBdExheWVyGAQgASgJIicKF0NvbXBvc2VWYWxpZGF0ZVJlc3BvbnNlEgwKBGVycnMYASADKAkiPQoVQ29udGFpbmVyRXhlY0NtZElucHV0Eg8KB3VzZXJDbWQYASABKAkSEwoLY29udGFpbmVySUQYAiABKAkiPAoUQ29udGFpbmVyRXhlY1JlcXVlc3QSEwoLY29udGFpbmVySUQYASABKAkSDwoHZXhlY0NtZBgCIAMoCSK2AgoFSW1hZ2USEgoKY29udGFpbmVycxgBIAEoAxIPCgdjcmVhdGVkGAIgASgDEgoKAmlkGAMgASgJEiwKBmxhYmVscxgEIAMoCzIcLmRvY2tlci52MS5JbWFnZS5MYWJlbHNFbnRyeRIRCglwYXJlbnRfaWQYBSABKAkSLQoJbWFuaWZlc3RzGAcgAygLMhouZG9ja2VyLnYxLk1hbmlmZXN0U3VtbWFyeRIUCgxyZXBvX2RpZ2VzdHMYCCADKAkSEQoJcmVwb190YWdzGAkgAygJEhMKC3NoYXJlZF9zaXplGAogASgDEgwKBHNpemUYCyABKAMSEQoJdXBkYXRlUmVmGAwgASgJGi0KC0xhYmVsc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiQwoPTWFuaWZlc3RTdW1tYXJ5Eg4KBmRpZ2VzdBgBIAEoCRISCgptZWRpYV90eXBlGAIgASgJEgwKBHNpemUYAyABKAMiEwoRTGlzdEltYWdlc1JlcXVlc3QihAEKEkxpc3RJbWFnZXNSZXNwb25zZRIWCg50b3RhbERpc2tVc2FnZRgBIAEoAxIYChB1bnVzZWRJbWFnZUNvdW50GAIgASgDEhoKEnVudGFnZ2VkSW1hZ2VDb3VudBgDIAEoAxIgCgZpbWFnZXMYBCADKAsyEC5kb2NrZXIudjEuSW1hZ2UiNAoSUmVtb3ZlSW1hZ2VSZXF1ZXN0EgwKBGhvc3QYAiABKAkSEAoIaW1hZ2VJZHMYASADKAkiFQoTUmVtb3ZlSW1hZ2VSZXNwb25zZSJXChJJbWFnZVBydW5lUmVzcG9uc2USFgoOU3BhY2VSZWNsYWltZWQYASABKAQSKQoHZGVsZXRlZBgCIAMoCzIYLmRvY2tlci52MS5JbWFnZXNEZWxldGVkIjMKEUltYWdlUHJ1bmVSZXF1ZXN0EgwKBGhvc3QYAiABKAkSEAoIcHJ1bmVBbGwYASABKAgiMgoNSW1hZ2VzRGVsZXRlZBIPCgdEZWxldGVkGAEgASgJEhAKCFVudGFnZ2VkGAIgASgJIqEBCgZWb2x1bWUSDAoEbmFtZRgBIAEoCRITCgtjb250YWluZXJJRBgCIAEoCRIRCgljcmVhdGVkQXQYAyABKAkSEgoKbW91bnRQb2ludBgEIAEoCRIMCgRzaXplGAUgASgDEg4KBmxhYmVscxgGIAEoCRITCgtjb21wb3NlUGF0aBgHIAEoCRIaChJjb21wb3NlUHJvamVjdE5hbWUYCCABKAkiFAoSTGlzdFZvbHVtZXNSZXF1ZXN0IjkKE0xpc3RWb2x1bWVzUmVzcG9uc2USIgoHdm9sdW1lcxgBIAMoCzIRLmRvY2tlci52MS5Wb2x1bWUiFQoTQ3JlYXRlVm9sdW1lUmVxdWVzdCIWChRDcmVhdGVWb2x1bWVSZXNwb25zZSJUChNEZWxldGVWb2x1bWVSZXF1ZXN0EgwKBGhvc3QYBCABKAkSEQoJdm9sdW1lSWRzGAEgAygJEgwKBGFub24YAiABKAgSDgoGdW51c2VkGAMgASgIIhYKFERlbGV0ZVZvbHVtZVJlc3BvbnNlIuMBCgdOZXR3b3JrEgwKBG5hbWUYASABKAkSCgoCaWQYAiABKAkSDgoGc3VibmV0GAMgASgJEg0KBXNjb3BlGAQgASgJEg4KBmRyaXZlchgFIAEoCRITCgtlbmFibGVfaXB2NBgGIAEoCBITCgtlbmFibGVfaXB2NhgHIAEoCBIQCghpbnRlcm5hbBgJIAEoCBISCgphdHRhY2hhYmxlGAogASgIEhEKCWNyZWF0ZWRBdBgLIAEoCRIWCg5jb21wb3NlUHJvamVjdBgMIAEoCRIUCgxjb250YWluZXJJZHMYDSADKAkiFQoTTGlzdE5ldHdvcmtzUmVxdWVzdCI8ChRMaXN0TmV0d29ya3NSZXNwb25zZRIkCghuZXR3b3JrcxgBIAMoCzISLmRvY2tlci52MS5OZXR3b3JrIhYKFENyZWF0ZU5ldHdvcmtSZXF1ZXN0IhcKFUNyZWF0ZU5ldHdvcmtSZXNwb25zZSI5ChREZWxldGVOZXR3b3JrUmVxdWVzdBISCgpuZXR3b3JrSWRzGAMgAygJEg0KBXBydW5lGAIgASgIIhcKFURlbGV0ZU5ldHdvcmtSZXNwb25zZSIrChRDb250YWluZXJMb2dzUmVxdWVzdBITCgtjb250YWluZXJJRBgBIAEoCSIeCgtMb2dzTWVzc2FnZRIPCgdtZXNzYWdlGAEgASgJImUKDVN0YXRzUmVzcG9uc2USJQoGc3lzdGVtGAEgASgLMhUuZG9ja2VyLnYxLlN5c3RlbUluZm8SLQoKY29udGFpbmVycxgCIAMoCzIZLmRvY2tlci52MS5Db250YWluZXJTdGF0cyKKAQoMU3RhdHNSZXF1ZXN0EgwKBGhvc3QYBCABKAkSJAoEZmlsZRgBIAEoCzIWLmRvY2tlci52MS5Db21wb3NlRmlsZRIlCgZzb3J0QnkYAiABKA4yFS5kb2NrZXIudjEuU09SVF9GSUVMRBIfCgVvcmRlchgDIAEoDjIQLmRvY2tlci52MS5PUkRFUiItCgpTeXN0ZW1JbmZvEgsKA0NQVRgBIAEoARISCgptZW1JbkJ5dGVzGAIgASgEIqkBCgxMaXN0UmVzcG9uc2USPQoLc3RhdHVzQ291bnQYASADKAsyKC5kb2NrZXIudjEuTGlzdFJlc3BvbnNlLlN0YXR1c0NvdW50RW50cnkSJgoEbGlzdBgCIAMoCzIYLmRvY2tlci52MS5Db250YWluZXJMaXN0GjIKEFN0YXR1c0NvdW50RW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgFOgI4ASKGAgoNQ29udGFpbmVyTGlzdBIKCgJpZBgBIAEoCRIPCgdpbWFnZUlEGAIgASgJEhEKCWltYWdlTmFtZRgDIAEoCRINCgVzdGF0ZRgEIAEoCRIOCgZoZWFsdGgYDSABKAkSDAoEbmFtZRgFIAEoCRIPCgdjcmVhdGVkGAYgASgJEh4KBXBvcnRzGAcgAygLMg8uZG9ja2VyLnYxLlBvcnQSEwoLc2VydmljZU5hbWUYCCABKAkSEwoLc2VydmljZVBhdGgYCSABKAkSEQoJc3RhY2tOYW1lGAogASgJEhcKD3VwZGF0ZUF2YWlsYWJsZRgLIAEoCRIRCglJUEFkZHJlc3MYDCADKAkiugEKDkNvbnRhaW5lclN0YXRzEgoKAmlkGAEgASgJEgwKBG5hbWUYAiABKAkSEQoJY3B1X3VzYWdlGAMgASgBEhQKDG1lbW9yeV91c2FnZRgEIAEoBBIUCgxtZW1vcnlfbGltaXQYBSABKAQSEgoKbmV0d29ya19yeBgGIAEoBBISCgpuZXR3b3JrX3R4GAcgASgEEhIKCmJsb2NrX3JlYWQYCCABKAQSEwoLYmxvY2tfd3JpdGUYCSABKAQiQwoEUG9ydBIOCgZwdWJsaWMYASABKAUSDwoHcHJpdmF0ZRgCIAEoBRIMCgRob3N0GAMgASgJEgwKBHR5cGUYBCABKAkiBwoFRW1wdHkiKAoQQ29udGFpbmVyUmVxdWVzdBIUCgxjb250YWluZXJJZHMYASADKAkiOQoLQ29tcG9zZUZpbGUSEAoIZmlsZW5hbWUYASABKAkSGAoQc2VsZWN0ZWRTZXJ2aWNlcxgDIAMoCSpgCgpTT1JUX0ZJRUxEEggKBE5BTUUQABIHCgNDUFUQARIHCgNNRU0QAhIOCgpORVRXT1JLX1JYEAMSDgoKTkVUV09SS19UWBAEEgoKBkRJU0tfUhAFEgoKBkRJU0tfVxAGKhkKBU9SREVSEgcKA0RTQxAAEgcKA0FTQxABMqISCg1Eb2NrZXJTZXJ2aWNlEkcKDkNvbnRhaW5lclN0YXJ0EhsuZG9ja2VyLnYxLkNvbnRhaW5lclJlcXVlc3QaFi5kb2NrZXIudjEuTG9nc01lc3NhZ2UiABJGCg1Db250YWluZXJTdG9wEhsuZG9ja2VyLnYxLkNvbnRhaW5lclJlcXVlc3QaFi5kb2NrZXIudjEuTG9nc01lc3NhZ2UiABJICg9Db250YWluZXJSZW1vdmUSGy5kb2NrZXIudjEuQ29udGFpbmVyUmVxdWVzdBoWLmRvY2tlci52MS5Mb2dzTWVzc2FnZSIAEkkKEENvbnRhaW5lclJlc3RhcnQSGy5kb2NrZXIudjEuQ29udGFpbmVyUmVxdWVzdBoWLmRvY2tlci52MS5Mb2dzTWVzc2FnZSIAEkIKD0NvbnRhaW5lclVwZGF0ZRIbLmRvY2tlci52MS5Db250YWluZXJSZXF1ZXN0GhAuZG9ja2VyLnYxLkVtcHR5IgASUQoMQ29udGFpbmVyVG9wEh4uZG9ja2VyLnYxLkNvbnRhaW5lclRvcFJlcXVlc3QaHy5kb2NrZXIudjEuQ29udGFpbmVyVG9wUmVzcG9uc2UiABJLCg1Db250YWluZXJMaXN0Eh8uZG9ja2VyLnYxLkNvbnRhaW5lckxpc3RSZXF1ZXN0GhcuZG9ja2VyLnYxLkxpc3RSZXNwb25zZSIAEkUKDkNvbnRhaW5lclN0YXRzEhcuZG9ja2VyLnYxLlN0YXRzUmVxdWVzdBoYLmRvY2tlci52MS5TdGF0c1Jlc3BvbnNlIgASTAoNQ29udGFpbmVyTG9ncxIfLmRvY2tlci52MS5Db250YWluZXJMb2dzUmVxdWVzdBoWLmRvY2tlci52MS5Mb2dzTWVzc2FnZSIAMAESWQoQQ29udGFpbmVySW5zcGVjdBIfLmRvY2tlci52MS5Db250YWluZXJMb2dzUmVxdWVzdBoiLmRvY2tlci52MS5Db250YWluZXJJbnNwZWN0TWVzc2FnZSIAEj8KCUNvbXBvc2VVcBIWLmRvY2tlci52MS5Db21wb3NlRmlsZRoWLmRvY2tlci52MS5Mb2dzTWVzc2FnZSIAMAESQQoLQ29tcG9zZURvd24SFi5kb2NrZXIudjEuQ29tcG9zZUZpbGUaFi5kb2NrZXIudjEuTG9nc01lc3NhZ2UiADABEkIKDENvbXBvc2VTdGFydBIWLmRvY2tlci52MS5Db21wb3NlRmlsZRoWLmRvY2tlci52MS5Mb2dzTWVzc2FnZSIAMAESQQoLQ29tcG9zZVN0b3ASFi5kb2NrZXIudjEuQ29tcG9zZUZpbGUaFi5kb2NrZXIudjEuTG9nc01lc3NhZ2UiADABEkQKDkNvbXBvc2VSZXN0YXJ0EhYuZG9ja2VyLnYxLkNvbXBvc2VGaWxlGhYuZG9ja2VyLnYxLkxvZ3NNZXNzYWdlIgAwARJDCg1Db21wb3NlVXBkYXRlEhYuZG9ja2VyLnYxLkNvbXBvc2VGaWxlGhYuZG9ja2VyLnYxLkxvZ3NNZXNzYWdlIgAwARJACgtDb21wb3NlTGlzdBIWLmRvY2tlci52MS5Db21wb3NlRmlsZRoXLmRvY2tlci52MS5MaXN0UmVzcG9uc2UiABJPCg9Db21wb3NlVmFsaWRhdGUSFi5kb2NrZXIudjEuQ29tcG9zZUZpbGUaIi5kb2NrZXIudjEuQ29tcG9zZVZhbGlkYXRlUmVzcG9uc2UiABJgChFDb21wb3NlRmlsZVN0YXR1cxIjLmRvY2tlci52MS5Db21wb3NlRmlsZVN0YXR1c1JlcXVlc3QaJC5kb2NrZXIudjEuQ29tcG9zZUZpbGVTdGF0dXNSZXNwb25zZSIAEkoKCUltYWdlTGlzdBIcLmRvY2tlci52MS5MaXN0SW1hZ2VzUmVxdWVzdBodLmRvY2tlci52MS5MaXN0SW1hZ2VzUmVzcG9uc2UiABJOCgtJbWFnZVJlbW92ZRIdLmRvY2tlci52MS5SZW1vdmVJbWFnZVJlcXVlc3QaHi5kb2NrZXIudjEuUmVtb3ZlSW1hZ2VSZXNwb25zZSIAElEKEEltYWdlUHJ1bmVVbnVzZWQSHC5kb2NrZXIudjEuSW1hZ2VQcnVuZVJlcXVlc3QaHS5kb2NrZXIudjEuSW1hZ2VQcnVuZVJlc3BvbnNlIgASUQoMSW1hZ2VJbnNwZWN0Eh4uZG9ja2VyLnYxLkltYWdlSW5zcGVjdFJlcXVlc3QaHy5kb2NrZXIudjEuSW1hZ2VJbnNwZWN0UmVzcG9uc2UiABJNCgpWb2x1bWVMaXN0Eh0uZG9ja2VyLnYxLkxpc3RWb2x1bWVzUmVxdWVzdBoeLmRvY2tlci52MS5MaXN0Vm9sdW1lc1Jlc3BvbnNlIgASUQoMVm9sdW1lQ3JlYXRlEh4uZG9ja2VyLnYxLkNyZWF0ZVZvbHVtZVJlcXVlc3QaHy5kb2NrZXIudjEuQ3JlYXRlVm9sdW1lUmVzcG9uc2UiABJRCgxWb2x1bWVEZWxldGUSHi5kb2NrZXIudjEuRGVsZXRlVm9sdW1lUmVxdWVzdBofLmRvY2tlci52MS5EZWxldGVWb2x1bWVSZXNwb25zZSIAElAKC05ldHdvcmtMaXN0Eh4uZG9ja2VyLnYxLkxpc3ROZXR3b3Jrc1JlcXVlc3QaHy5kb2NrZXIudjEuTGlzdE5ldHdvcmtzUmVzcG9uc2UiABJUCg1OZXR3b3JrQ3JlYXRlEh8uZG9ja2VyLnYxLkNyZWF0ZU5ldHdvcmtSZXF1ZXN0GiAuZG9ja2VyLnYxLkNyZWF0ZU5ldHdvcmtSZXNwb25zZSIAElQKDU5ldHdvcmtEZWxldGUSHy5kb2NrZXIudjEuRGVsZXRlTmV0d29ya1JlcXVlc3QaIC5kb2NrZXIudjEuRGVsZXRlTmV0d29ya1Jlc3BvbnNlIgASVwoOTmV0d29ya0luc3BlY3QSIC5kb2NrZXIudjEuTmV0d29ya0luc3BlY3RSZXF1ZXN0GiEuZG9ja2VyLnYxLk5ldHdvcmtJbnNwZWN0UmVzcG9uc2UiAEKPAQoNY29tLmRvY2tlci52MUILRG9ja2VyUHJvdG9QAVosZ2l0aHViLmNvbS9SQTM0MS9kb2NrbWFuL2dlbmVyYXRlZC9kb2NrZXIvdjGiAgNEWFiqAglEb2NrZXIuVjHKAglEb2NrZXJcVjHiAhVEb2NrZXJcVjFcR1BCTWV0YWRhdGHqAgpEb2NrZXI6OlYxYgZwcm90bzM"); + fileDesc("ChZkb2NrZXIvdjEvZG9ja2VyLnByb3RvEglkb2NrZXIudjEiKQoYQ29tcG9zZUZpbGVTdGF0dXNSZXF1ZXN0Eg0KBWZpbGVzGAEgAygJImYKBlN0YXR1cxISCgpzZXJ2aWNlc1VwGAEgASgFEhQKDHNlcnZpY2VzRG93bhgCIAEoBRIXCg9zZXJ2aWNlc0hlYWx0aHkYAyABKAUSGQoRc2VydmljZXNVbkhlYWx0aHkYBCABKAUinwEKGUNvbXBvc2VGaWxlU3RhdHVzUmVzcG9uc2USQAoGc3RhdHVzGAEgAygLMjAuZG9ja2VyLnYxLkNvbXBvc2VGaWxlU3RhdHVzUmVzcG9uc2UuU3RhdHVzRW50cnkaQAoLU3RhdHVzRW50cnkSCwoDa2V5GAEgASgJEiAKBXZhbHVlGAIgASgLMhEuZG9ja2VyLnYxLlN0YXR1czoCOAEiKgoTQ29udGFpbmVyVG9wUmVxdWVzdBITCgtjb250YWluZXJJZBgBIAEoCSIzChRDb250YWluZXJUb3BSZXNwb25zZRIbCgN0b3AYASABKAsyDi5kb2NrZXIudjEuVG9wIhwKB1Byb2Nlc3MSEQoJUHJvY2Vzc2VzGAEgAygJIjcKA1RvcBIgCgRwcm9jGAEgAygLMhIuZG9ja2VyLnYxLlByb2Nlc3MSDgoGVGl0bGVzGAIgAygJIt0BChdDb250YWluZXJJbnNwZWN0TWVzc2FnZRIMCgROYW1lGAEgASgJEgoKAklEGAIgASgJEgwKBFBhdGgYAyABKAkSDwoHQ3JlYXRlZBgHIAEoCRINCgVJbWFnZRgEIAEoCRIRCglIb3N0c1BhdGgYBSABKAkSKQoGbW91bnRzGAYgAygLMhkuZG9ja2VyLnYxLkNvbnRhaW5lck1vdW50EioKBmNvbmZpZxgIIAEoCzIaLmRvY2tlci52MS5Db250YWluZXJDb25maWcSEAoIcmF3X2pzb24YCSABKAkirQMKD0NvbnRhaW5lckNvbmZpZxIQCghIb3N0bmFtZRgBIAEoCRISCgpEb21haW5uYW1lGAIgASgJEgwKBFVzZXIYAyABKAkSEwoLQXR0YWNoU3RkaW4YBCABKAgSFAoMQXR0YWNoU3Rkb3V0GAUgASgIEhQKDEF0dGFjaFN0ZGVychgGIAEoCBILCgNUdHkYByABKAgSEQoJT3BlblN0ZGluGAggASgIEhEKCVN0ZGluT25jZRgJIAEoCBITCgtBcmdzRXNjYXBlZBgKIAEoCBINCgVJbWFnZRgLIAEoCRILCgNFbnYYDCADKAkSCwoDQ21kGA0gAygJEg8KB1ZvbHVtZXMYDiADKAkSEgoKV29ya2luZ0RpchgPIAEoCRISCgpFbnRyeXBvaW50GBAgAygJEjYKBkxhYmVscxgRIAMoCzImLmRvY2tlci52MS5Db250YWluZXJDb25maWcuTGFiZWxzRW50cnkSFAoMRXhwb3NlZFBvcnRzGBIgAygJGi0KC0xhYmVsc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiewoOQ29udGFpbmVyTW91bnQSDAoEVHlwZRgBIAEoCRIMCgROYW1lGAIgASgJEg4KBlNvdXJjZRgDIAEoCRITCgtEZXN0aW5hdGlvbhgEIAEoCRIOCgZEcml2ZXIYBSABKAkSDAoETW9kZRgGIAEoCRIKCgJSVxgHIAEoCCIWChRDb250YWluZXJMaXN0UmVxdWVzdCIqChVOZXR3b3JrSW5zcGVjdFJlcXVlc3QSEQoJbmV0d29ya0lkGAEgASgJIkgKFk5ldHdvcmtJbnNwZWN0UmVzcG9uc2USLgoHaW5zcGVjdBgBIAEoCzIdLmRvY2tlci52MS5OZXR3b3JrSW5zcGVjdEluZm8ibAoSTmV0d29ya0luc3BlY3RJbmZvEh8KA25ldBgBIAEoCzISLmRvY2tlci52MS5OZXR3b3JrEjUKCWNvbnRhaW5lchgCIAMoCzIiLmRvY2tlci52MS5OZXR3b3JrQ29udGFpbmVySW5zcGVjdCJiChdOZXR3b3JrQ29udGFpbmVySW5zcGVjdBIMCgROYW1lGAEgASgJEhAKCEVuZHBvaW50GAIgASgJEgwKBElQdjQYAyABKAkSDAoESVB2NhgEIAEoCRILCgNNYWMYBSABKAkiJgoTSW1hZ2VJbnNwZWN0UmVxdWVzdBIPCgdpbWFnZUlkGAEgASgJIkAKFEltYWdlSW5zcGVjdFJlc3BvbnNlEigKB2luc3BlY3QYASABKAsyFy5kb2NrZXIudjEuSW1hZ2VJbnNwZWN0IrUBCgxJbWFnZUluc3BlY3QSDAoEbmFtZRgBIAEoCRIKCgJpZBgGIAEoCRIMCgRzaXplGAMgASgJEgwKBGFyY2gYBSABKAkSEgoKY3JlYXRlZElzbxgEIAEoCRIlCgZsYXllcnMYAiADKAsyFS5kb2NrZXIudjEuSW1hZ2VMYXllchI0Cgpjb250YWluZXJzGAcgAygLMiAuZG9ja2VyLnYxLkltYWdlQ29udGFpbmVySW5zcGVjdCJSCgpJbWFnZUxheWVyEg8KB0xheWVySWQYAyABKAkSCwoDY21kGAEgASgJEgwKBHNpemUYAiABKAkSGAoQdG90YWxTaXplQXRMYXllchgEIAEoCSJYChVJbWFnZUNvbnRhaW5lckluc3BlY3QSDAoEbmFtZRgBIAEoCRIKCgJpZBgCIAEoCRINCgVzdGF0ZRgDIAEoCRIWCg5jb21wb3NlUHJvamVjdBgEIAEoCSInChdDb21wb3NlVmFsaWRhdGVSZXNwb25zZRIMCgRlcnJzGAEgAygJIj0KFUNvbnRhaW5lckV4ZWNDbWRJbnB1dBIPCgd1c2VyQ21kGAEgASgJEhMKC2NvbnRhaW5lcklEGAIgASgJIjwKFENvbnRhaW5lckV4ZWNSZXF1ZXN0EhMKC2NvbnRhaW5lcklEGAEgASgJEg8KB2V4ZWNDbWQYAiADKAkitgIKBUltYWdlEhIKCmNvbnRhaW5lcnMYASABKAMSDwoHY3JlYXRlZBgCIAEoAxIKCgJpZBgDIAEoCRIsCgZsYWJlbHMYBCADKAsyHC5kb2NrZXIudjEuSW1hZ2UuTGFiZWxzRW50cnkSEQoJcGFyZW50X2lkGAUgASgJEi0KCW1hbmlmZXN0cxgHIAMoCzIaLmRvY2tlci52MS5NYW5pZmVzdFN1bW1hcnkSFAoMcmVwb19kaWdlc3RzGAggAygJEhEKCXJlcG9fdGFncxgJIAMoCRITCgtzaGFyZWRfc2l6ZRgKIAEoAxIMCgRzaXplGAsgASgDEhEKCXVwZGF0ZVJlZhgMIAEoCRotCgtMYWJlbHNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIkMKD01hbmlmZXN0U3VtbWFyeRIOCgZkaWdlc3QYASABKAkSEgoKbWVkaWFfdHlwZRgCIAEoCRIMCgRzaXplGAMgASgDIhMKEUxpc3RJbWFnZXNSZXF1ZXN0IoQBChJMaXN0SW1hZ2VzUmVzcG9uc2USFgoOdG90YWxEaXNrVXNhZ2UYASABKAMSGAoQdW51c2VkSW1hZ2VDb3VudBgCIAEoAxIaChJ1bnRhZ2dlZEltYWdlQ291bnQYAyABKAMSIAoGaW1hZ2VzGAQgAygLMhAuZG9ja2VyLnYxLkltYWdlIjQKElJlbW92ZUltYWdlUmVxdWVzdBIMCgRob3N0GAIgASgJEhAKCGltYWdlSWRzGAEgAygJIhUKE1JlbW92ZUltYWdlUmVzcG9uc2UiVwoSSW1hZ2VQcnVuZVJlc3BvbnNlEhYKDlNwYWNlUmVjbGFpbWVkGAEgASgEEikKB2RlbGV0ZWQYAiADKAsyGC5kb2NrZXIudjEuSW1hZ2VzRGVsZXRlZCIzChFJbWFnZVBydW5lUmVxdWVzdBIMCgRob3N0GAIgASgJEhAKCHBydW5lQWxsGAEgASgIIjIKDUltYWdlc0RlbGV0ZWQSDwoHRGVsZXRlZBgBIAEoCRIQCghVbnRhZ2dlZBgCIAEoCSKhAQoGVm9sdW1lEgwKBG5hbWUYASABKAkSEwoLY29udGFpbmVySUQYAiABKAkSEQoJY3JlYXRlZEF0GAMgASgJEhIKCm1vdW50UG9pbnQYBCABKAkSDAoEc2l6ZRgFIAEoAxIOCgZsYWJlbHMYBiABKAkSEwoLY29tcG9zZVBhdGgYByABKAkSGgoSY29tcG9zZVByb2plY3ROYW1lGAggASgJIhQKEkxpc3RWb2x1bWVzUmVxdWVzdCI5ChNMaXN0Vm9sdW1lc1Jlc3BvbnNlEiIKB3ZvbHVtZXMYASADKAsyES5kb2NrZXIudjEuVm9sdW1lIhUKE0NyZWF0ZVZvbHVtZVJlcXVlc3QiFgoUQ3JlYXRlVm9sdW1lUmVzcG9uc2UiVAoTRGVsZXRlVm9sdW1lUmVxdWVzdBIMCgRob3N0GAQgASgJEhEKCXZvbHVtZUlkcxgBIAMoCRIMCgRhbm9uGAIgASgIEg4KBnVudXNlZBgDIAEoCCIWChREZWxldGVWb2x1bWVSZXNwb25zZSIqChRWb2x1bWVJbnNwZWN0UmVxdWVzdBISCgp2b2x1bWVOYW1lGAEgASgJIkYKFVZvbHVtZUluc3BlY3RSZXNwb25zZRItCgdpbnNwZWN0GAEgASgLMhwuZG9ja2VyLnYxLlZvbHVtZUluc3BlY3RJbmZvImoKEVZvbHVtZUluc3BlY3RJbmZvEh4KA3ZvbBgBIAEoCzIRLmRvY2tlci52MS5Wb2x1bWUSNQoKY29udGFpbmVycxgCIAMoCzIhLmRvY2tlci52MS5Wb2x1bWVDb250YWluZXJJbnNwZWN0ImsKFlZvbHVtZUNvbnRhaW5lckluc3BlY3QSDAoEbmFtZRgBIAEoCRIKCgJpZBgCIAEoCRITCgtkZXN0aW5hdGlvbhgDIAEoCRIKCgJydxgEIAEoCBIWCg5jb21wb3NlUHJvamVjdBgFIAEoCSLjAQoHTmV0d29yaxIMCgRuYW1lGAEgASgJEgoKAmlkGAIgASgJEg4KBnN1Ym5ldBgDIAEoCRINCgVzY29wZRgEIAEoCRIOCgZkcml2ZXIYBSABKAkSEwoLZW5hYmxlX2lwdjQYBiABKAgSEwoLZW5hYmxlX2lwdjYYByABKAgSEAoIaW50ZXJuYWwYCSABKAgSEgoKYXR0YWNoYWJsZRgKIAEoCBIRCgljcmVhdGVkQXQYCyABKAkSFgoOY29tcG9zZVByb2plY3QYDCABKAkSFAoMY29udGFpbmVySWRzGA0gAygJIhUKE0xpc3ROZXR3b3Jrc1JlcXVlc3QiPAoUTGlzdE5ldHdvcmtzUmVzcG9uc2USJAoIbmV0d29ya3MYASADKAsyEi5kb2NrZXIudjEuTmV0d29yayIWChRDcmVhdGVOZXR3b3JrUmVxdWVzdCIXChVDcmVhdGVOZXR3b3JrUmVzcG9uc2UiOQoURGVsZXRlTmV0d29ya1JlcXVlc3QSEgoKbmV0d29ya0lkcxgDIAMoCRINCgVwcnVuZRgCIAEoCCIXChVEZWxldGVOZXR3b3JrUmVzcG9uc2UiSgoeTmV0d29ya0Nvbm5lY3RDb250YWluZXJSZXF1ZXN0EhIKCm5ldHdvcmtfaWQYASABKAkSFAoMY29udGFpbmVyX2lkGAIgASgJIiEKH05ldHdvcmtDb25uZWN0Q29udGFpbmVyUmVzcG9uc2UiTQohTmV0d29ya0Rpc2Nvbm5lY3RDb250YWluZXJSZXF1ZXN0EhIKCm5ldHdvcmtfaWQYASABKAkSFAoMY29udGFpbmVyX2lkGAIgASgJIiQKIk5ldHdvcmtEaXNjb25uZWN0Q29udGFpbmVyUmVzcG9uc2UiHQoNRXZlbnRzUmVxdWVzdBIMCgRob3N0GAEgASgJIn0KDkNvbnRhaW5lckV2ZW50Eg4KBmFjdGlvbhgBIAEoCRIOCgZzdGF0dXMYAiABKAkSEwoLY29udGFpbmVySWQYAyABKAkSFQoNY29udGFpbmVyTmFtZRgEIAEoCRINCgVpbWFnZRgFIAEoCRIQCgh0aW1lTmFubxgGIAEoAyIrChRDb250YWluZXJMb2dzUmVxdWVzdBITCgtjb250YWluZXJJRBgBIAEoCSIeCgtMb2dzTWVzc2FnZRIPCgdtZXNzYWdlGAEgASgJImUKEUxvZ3NTdHJlYW1SZXF1ZXN0EhQKDGNvbnRhaW5lcklkcxgBIAMoCRIMCgR0YWlsGAIgASgFEg0KBXNpbmNlGAMgASgDEg0KBXVudGlsGAQgASgDEg4KBmZvbGxvdxgFIAEoCCJlCgdMb2dMaW5lEhMKC2NvbnRhaW5lcklkGAEgASgJEhUKDWNvbnRhaW5lck5hbWUYAiABKAkSDAoEdGV4dBgDIAEoCRIQCgh0aW1lTmFubxgEIAEoAxIOCgZzdHJlYW0YBSABKAUiJwoURG9ja2VyQ29tbWFuZFJlcXVlc3QSDwoHY29tbWFuZBgBIAEoCSJYChFIb3N0U3RhdHNSZXNwb25zZRISCgpjcHVQZXJjZW50GAEgASgBEg8KB21lbVVzZWQYAiABKAMSEAoIbWVtVG90YWwYAyABKAMSDAoEY3B1cxgEIAEoBSJlCg1TdGF0c1Jlc3BvbnNlEiUKBnN5c3RlbRgBIAEoCzIVLmRvY2tlci52MS5TeXN0ZW1JbmZvEi0KCmNvbnRhaW5lcnMYAiADKAsyGS5kb2NrZXIudjEuQ29udGFpbmVyU3RhdHMiigEKDFN0YXRzUmVxdWVzdBIMCgRob3N0GAQgASgJEiQKBGZpbGUYASABKAsyFi5kb2NrZXIudjEuQ29tcG9zZUZpbGUSJQoGc29ydEJ5GAIgASgOMhUuZG9ja2VyLnYxLlNPUlRfRklFTEQSHwoFb3JkZXIYAyABKA4yEC5kb2NrZXIudjEuT1JERVIiLQoKU3lzdGVtSW5mbxILCgNDUFUYASABKAESEgoKbWVtSW5CeXRlcxgCIAEoBCKpAQoMTGlzdFJlc3BvbnNlEj0KC3N0YXR1c0NvdW50GAEgAygLMiguZG9ja2VyLnYxLkxpc3RSZXNwb25zZS5TdGF0dXNDb3VudEVudHJ5EiYKBGxpc3QYAiADKAsyGC5kb2NrZXIudjEuQ29udGFpbmVyTGlzdBoyChBTdGF0dXNDb3VudEVudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoBToCOAEihgIKDUNvbnRhaW5lckxpc3QSCgoCaWQYASABKAkSDwoHaW1hZ2VJRBgCIAEoCRIRCglpbWFnZU5hbWUYAyABKAkSDQoFc3RhdGUYBCABKAkSDgoGaGVhbHRoGA0gASgJEgwKBG5hbWUYBSABKAkSDwoHY3JlYXRlZBgGIAEoCRIeCgVwb3J0cxgHIAMoCzIPLmRvY2tlci52MS5Qb3J0EhMKC3NlcnZpY2VOYW1lGAggASgJEhMKC3NlcnZpY2VQYXRoGAkgASgJEhEKCXN0YWNrTmFtZRgKIAEoCRIXCg91cGRhdGVBdmFpbGFibGUYCyABKAkSEQoJSVBBZGRyZXNzGAwgAygJIqcCCg5Db250YWluZXJTdGF0cxIKCgJpZBgBIAEoCRIMCgRuYW1lGAIgASgJEhEKCWNwdV91c2FnZRgDIAEoARIUCgxtZW1vcnlfdXNhZ2UYBCABKAQSFAoMbWVtb3J5X2xpbWl0GAUgASgEEhIKCm5ldHdvcmtfcngYBiABKAQSEgoKbmV0d29ya190eBgHIAEoBBISCgpibG9ja19yZWFkGAggASgEEhMKC2Jsb2NrX3dyaXRlGAkgASgEEhIKCnN0YXJ0ZWRfYXQYCiABKAkSDQoFaW1hZ2UYCyABKAkSDQoFc3RhdGUYDCABKAkSDgoGaGVhbHRoGA0gASgJEhIKCmlwX2FkZHJlc3MYDiADKAkSFQoNcmVzdGFydF9jb3VudBgPIAEoBSJDCgRQb3J0Eg4KBnB1YmxpYxgBIAEoBRIPCgdwcml2YXRlGAIgASgFEgwKBGhvc3QYAyABKAkSDAoEdHlwZRgEIAEoCSIHCgVFbXB0eSIoChBDb250YWluZXJSZXF1ZXN0EhQKDGNvbnRhaW5lcklkcxgBIAMoCSI5CgtDb21wb3NlRmlsZRIQCghmaWxlbmFtZRgBIAEoCRIYChBzZWxlY3RlZFNlcnZpY2VzGAMgAygJIm0KFkNvbXBvc2VSZWRlcGxveVJlcXVlc3QSJAoEZmlsZRgBIAEoCzIWLmRvY2tlci52MS5Db21wb3NlRmlsZRIMCgRwdWxsGAIgASgIEg0KBWJ1aWxkGAMgASgIEhAKCHJlY3JlYXRlGAQgASgIKm0KClNPUlRfRklFTEQSCAoETkFNRRAAEgcKA0NQVRABEgcKA01FTRACEg4KCk5FVFdPUktfUlgQAxIOCgpORVRXT1JLX1RYEAQSCgoGRElTS19SEAUSCgoGRElTS19XEAYSCwoHU1RBUlRFRBAHKhkKBU9SREVSEgcKA0RTQxAAEgcKA0FTQxABMs0ZCg1Eb2NrZXJTZXJ2aWNlEkcKDkNvbnRhaW5lclN0YXJ0EhsuZG9ja2VyLnYxLkNvbnRhaW5lclJlcXVlc3QaFi5kb2NrZXIudjEuTG9nc01lc3NhZ2UiABJGCg1Db250YWluZXJTdG9wEhsuZG9ja2VyLnYxLkNvbnRhaW5lclJlcXVlc3QaFi5kb2NrZXIudjEuTG9nc01lc3NhZ2UiABJICg9Db250YWluZXJSZW1vdmUSGy5kb2NrZXIudjEuQ29udGFpbmVyUmVxdWVzdBoWLmRvY2tlci52MS5Mb2dzTWVzc2FnZSIAEkkKEENvbnRhaW5lclJlc3RhcnQSGy5kb2NrZXIudjEuQ29udGFpbmVyUmVxdWVzdBoWLmRvY2tlci52MS5Mb2dzTWVzc2FnZSIAEkcKDkNvbnRhaW5lclBhdXNlEhsuZG9ja2VyLnYxLkNvbnRhaW5lclJlcXVlc3QaFi5kb2NrZXIudjEuTG9nc01lc3NhZ2UiABJJChBDb250YWluZXJVbnBhdXNlEhsuZG9ja2VyLnYxLkNvbnRhaW5lclJlcXVlc3QaFi5kb2NrZXIudjEuTG9nc01lc3NhZ2UiABJKCg9Db250YWluZXJVcGRhdGUSGy5kb2NrZXIudjEuQ29udGFpbmVyUmVxdWVzdBoWLmRvY2tlci52MS5Mb2dzTWVzc2FnZSIAMAESUQoMQ29udGFpbmVyVG9wEh4uZG9ja2VyLnYxLkNvbnRhaW5lclRvcFJlcXVlc3QaHy5kb2NrZXIudjEuQ29udGFpbmVyVG9wUmVzcG9uc2UiABJLCg1Db250YWluZXJMaXN0Eh8uZG9ja2VyLnYxLkNvbnRhaW5lckxpc3RSZXF1ZXN0GhcuZG9ja2VyLnYxLkxpc3RSZXNwb25zZSIAEkUKDkNvbnRhaW5lclN0YXRzEhcuZG9ja2VyLnYxLlN0YXRzUmVxdWVzdBoYLmRvY2tlci52MS5TdGF0c1Jlc3BvbnNlIgASTgoUQ29udGFpbmVyU3RhdHNTdHJlYW0SFy5kb2NrZXIudjEuU3RhdHNSZXF1ZXN0GhkuZG9ja2VyLnYxLkNvbnRhaW5lclN0YXRzIgAwARI9CglIb3N0U3RhdHMSEC5kb2NrZXIudjEuRW1wdHkaHC5kb2NrZXIudjEuSG9zdFN0YXRzUmVzcG9uc2UiABJMCg1Db250YWluZXJMb2dzEh8uZG9ja2VyLnYxLkNvbnRhaW5lckxvZ3NSZXF1ZXN0GhYuZG9ja2VyLnYxLkxvZ3NNZXNzYWdlIgAwARJKCg9Db250YWluZXJFdmVudHMSGC5kb2NrZXIudjEuRXZlbnRzUmVxdWVzdBoZLmRvY2tlci52MS5Db250YWluZXJFdmVudCIAMAESSwoTQ29udGFpbmVyTG9nc1N0cmVhbRIcLmRvY2tlci52MS5Mb2dzU3RyZWFtUmVxdWVzdBoSLmRvY2tlci52MS5Mb2dMaW5lIgAwARJZChBDb250YWluZXJJbnNwZWN0Eh8uZG9ja2VyLnYxLkNvbnRhaW5lckxvZ3NSZXF1ZXN0GiIuZG9ja2VyLnYxLkNvbnRhaW5lckluc3BlY3RNZXNzYWdlIgASPwoJQ29tcG9zZVVwEhYuZG9ja2VyLnYxLkNvbXBvc2VGaWxlGhYuZG9ja2VyLnYxLkxvZ3NNZXNzYWdlIgAwARJBCgtDb21wb3NlRG93bhIWLmRvY2tlci52MS5Db21wb3NlRmlsZRoWLmRvY2tlci52MS5Mb2dzTWVzc2FnZSIAMAESQgoMQ29tcG9zZVN0YXJ0EhYuZG9ja2VyLnYxLkNvbXBvc2VGaWxlGhYuZG9ja2VyLnYxLkxvZ3NNZXNzYWdlIgAwARJBCgtDb21wb3NlU3RvcBIWLmRvY2tlci52MS5Db21wb3NlRmlsZRoWLmRvY2tlci52MS5Mb2dzTWVzc2FnZSIAMAESRAoOQ29tcG9zZVJlc3RhcnQSFi5kb2NrZXIudjEuQ29tcG9zZUZpbGUaFi5kb2NrZXIudjEuTG9nc01lc3NhZ2UiADABEkMKDUNvbXBvc2VVcGRhdGUSFi5kb2NrZXIudjEuQ29tcG9zZUZpbGUaFi5kb2NrZXIudjEuTG9nc01lc3NhZ2UiADABElAKD0NvbXBvc2VSZWRlcGxveRIhLmRvY2tlci52MS5Db21wb3NlUmVkZXBsb3lSZXF1ZXN0GhYuZG9ja2VyLnYxLkxvZ3NNZXNzYWdlIgAwARJACgtDb21wb3NlTGlzdBIWLmRvY2tlci52MS5Db21wb3NlRmlsZRoXLmRvY2tlci52MS5MaXN0UmVzcG9uc2UiABJPCg9Db21wb3NlVmFsaWRhdGUSFi5kb2NrZXIudjEuQ29tcG9zZUZpbGUaIi5kb2NrZXIudjEuQ29tcG9zZVZhbGlkYXRlUmVzcG9uc2UiABJgChFDb21wb3NlRmlsZVN0YXR1cxIjLmRvY2tlci52MS5Db21wb3NlRmlsZVN0YXR1c1JlcXVlc3QaJC5kb2NrZXIudjEuQ29tcG9zZUZpbGVTdGF0dXNSZXNwb25zZSIAEkwKDURvY2tlckNvbW1hbmQSHy5kb2NrZXIudjEuRG9ja2VyQ29tbWFuZFJlcXVlc3QaFi5kb2NrZXIudjEuTG9nc01lc3NhZ2UiADABEkoKCUltYWdlTGlzdBIcLmRvY2tlci52MS5MaXN0SW1hZ2VzUmVxdWVzdBodLmRvY2tlci52MS5MaXN0SW1hZ2VzUmVzcG9uc2UiABJOCgtJbWFnZVJlbW92ZRIdLmRvY2tlci52MS5SZW1vdmVJbWFnZVJlcXVlc3QaHi5kb2NrZXIudjEuUmVtb3ZlSW1hZ2VSZXNwb25zZSIAElEKEEltYWdlUHJ1bmVVbnVzZWQSHC5kb2NrZXIudjEuSW1hZ2VQcnVuZVJlcXVlc3QaHS5kb2NrZXIudjEuSW1hZ2VQcnVuZVJlc3BvbnNlIgASUQoMSW1hZ2VJbnNwZWN0Eh4uZG9ja2VyLnYxLkltYWdlSW5zcGVjdFJlcXVlc3QaHy5kb2NrZXIudjEuSW1hZ2VJbnNwZWN0UmVzcG9uc2UiABJNCgpWb2x1bWVMaXN0Eh0uZG9ja2VyLnYxLkxpc3RWb2x1bWVzUmVxdWVzdBoeLmRvY2tlci52MS5MaXN0Vm9sdW1lc1Jlc3BvbnNlIgASUQoMVm9sdW1lQ3JlYXRlEh4uZG9ja2VyLnYxLkNyZWF0ZVZvbHVtZVJlcXVlc3QaHy5kb2NrZXIudjEuQ3JlYXRlVm9sdW1lUmVzcG9uc2UiABJRCgxWb2x1bWVEZWxldGUSHi5kb2NrZXIudjEuRGVsZXRlVm9sdW1lUmVxdWVzdBofLmRvY2tlci52MS5EZWxldGVWb2x1bWVSZXNwb25zZSIAElQKDVZvbHVtZUluc3BlY3QSHy5kb2NrZXIudjEuVm9sdW1lSW5zcGVjdFJlcXVlc3QaIC5kb2NrZXIudjEuVm9sdW1lSW5zcGVjdFJlc3BvbnNlIgASUAoLTmV0d29ya0xpc3QSHi5kb2NrZXIudjEuTGlzdE5ldHdvcmtzUmVxdWVzdBofLmRvY2tlci52MS5MaXN0TmV0d29ya3NSZXNwb25zZSIAElQKDU5ldHdvcmtDcmVhdGUSHy5kb2NrZXIudjEuQ3JlYXRlTmV0d29ya1JlcXVlc3QaIC5kb2NrZXIudjEuQ3JlYXRlTmV0d29ya1Jlc3BvbnNlIgASVAoNTmV0d29ya0RlbGV0ZRIfLmRvY2tlci52MS5EZWxldGVOZXR3b3JrUmVxdWVzdBogLmRvY2tlci52MS5EZWxldGVOZXR3b3JrUmVzcG9uc2UiABJXCg5OZXR3b3JrSW5zcGVjdBIgLmRvY2tlci52MS5OZXR3b3JrSW5zcGVjdFJlcXVlc3QaIS5kb2NrZXIudjEuTmV0d29ya0luc3BlY3RSZXNwb25zZSIAEnIKF05ldHdvcmtDb25uZWN0Q29udGFpbmVyEikuZG9ja2VyLnYxLk5ldHdvcmtDb25uZWN0Q29udGFpbmVyUmVxdWVzdBoqLmRvY2tlci52MS5OZXR3b3JrQ29ubmVjdENvbnRhaW5lclJlc3BvbnNlIgASewoaTmV0d29ya0Rpc2Nvbm5lY3RDb250YWluZXISLC5kb2NrZXIudjEuTmV0d29ya0Rpc2Nvbm5lY3RDb250YWluZXJSZXF1ZXN0Gi0uZG9ja2VyLnYxLk5ldHdvcmtEaXNjb25uZWN0Q29udGFpbmVyUmVzcG9uc2UiAEKPAQoNY29tLmRvY2tlci52MUILRG9ja2VyUHJvdG9QAVosZ2l0aHViLmNvbS9SQTM0MS9kb2NrbWFuL2dlbmVyYXRlZC9kb2NrZXIvdjGiAgNEWFiqAglEb2NrZXIuVjHKAglEb2NrZXJcVjHiAhVEb2NrZXJcVjFcR1BCTWV0YWRhdGHqAgpEb2NrZXI6OlYxYgZwcm90bzM"); /** * @generated from message docker.v1.ComposeFileStatusRequest @@ -194,6 +194,14 @@ export type ContainerInspectMessage = Message<"docker.v1.ContainerInspectMessage * @generated from field: docker.v1.ContainerConfig config = 8; */ config?: ContainerConfig; + + /** + * Complete daemon inspect response. This deliberately stays JSON so newer + * daemon fields remain visible without forcing a Dockman protocol release. + * + * @generated from field: string raw_json = 9; + */ + rawJson: string; }; /** @@ -525,6 +533,11 @@ export type ImageInspect = Message<"docker.v1.ImageInspect"> & { * @generated from field: repeated docker.v1.ImageLayer layers = 2; */ layers: ImageLayer[]; + + /** + * @generated from field: repeated docker.v1.ImageContainerInspect containers = 7; + */ + containers: ImageContainerInspect[]; }; /** @@ -566,6 +579,38 @@ export type ImageLayer = Message<"docker.v1.ImageLayer"> & { export const ImageLayerSchema: GenMessage = /*@__PURE__*/ messageDesc(file_docker_v1_docker, 18); +/** + * @generated from message docker.v1.ImageContainerInspect + */ +export type ImageContainerInspect = Message<"docker.v1.ImageContainerInspect"> & { + /** + * @generated from field: string name = 1; + */ + name: string; + + /** + * @generated from field: string id = 2; + */ + id: string; + + /** + * @generated from field: string state = 3; + */ + state: string; + + /** + * @generated from field: string composeProject = 4; + */ + composeProject: string; +}; + +/** + * Describes the message docker.v1.ImageContainerInspect. + * Use `create(ImageContainerInspectSchema)` to create a new message. + */ +export const ImageContainerInspectSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_docker_v1_docker, 19); + /** * @generated from message docker.v1.ComposeValidateResponse */ @@ -581,7 +626,7 @@ export type ComposeValidateResponse = Message<"docker.v1.ComposeValidateResponse * Use `create(ComposeValidateResponseSchema)` to create a new message. */ export const ComposeValidateResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 19); + messageDesc(file_docker_v1_docker, 20); /** * forwards commands from user to a running session @@ -605,7 +650,7 @@ export type ContainerExecCmdInput = Message<"docker.v1.ContainerExecCmdInput"> & * Use `create(ContainerExecCmdInputSchema)` to create a new message. */ export const ContainerExecCmdInputSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 20); + messageDesc(file_docker_v1_docker, 21); /** * @generated from message docker.v1.ContainerExecRequest @@ -629,7 +674,7 @@ export type ContainerExecRequest = Message<"docker.v1.ContainerExecRequest"> & { * Use `create(ContainerExecRequestSchema)` to create a new message. */ export const ContainerExecRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 21); + messageDesc(file_docker_v1_docker, 22); /** * Image-related messages @@ -698,7 +743,7 @@ export type Image = Message<"docker.v1.Image"> & { * Use `create(ImageSchema)` to create a new message. */ export const ImageSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 22); + messageDesc(file_docker_v1_docker, 23); /** * @generated from message docker.v1.ManifestSummary @@ -725,7 +770,7 @@ export type ManifestSummary = Message<"docker.v1.ManifestSummary"> & { * Use `create(ManifestSummarySchema)` to create a new message. */ export const ManifestSummarySchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 23); + messageDesc(file_docker_v1_docker, 24); /** * @generated from message docker.v1.ListImagesRequest @@ -738,7 +783,7 @@ export type ListImagesRequest = Message<"docker.v1.ListImagesRequest"> & { * Use `create(ListImagesRequestSchema)` to create a new message. */ export const ListImagesRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 24); + messageDesc(file_docker_v1_docker, 25); /** * @generated from message docker.v1.ListImagesResponse @@ -770,7 +815,7 @@ export type ListImagesResponse = Message<"docker.v1.ListImagesResponse"> & { * Use `create(ListImagesResponseSchema)` to create a new message. */ export const ListImagesResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 25); + messageDesc(file_docker_v1_docker, 26); /** * @generated from message docker.v1.RemoveImageRequest @@ -792,7 +837,7 @@ export type RemoveImageRequest = Message<"docker.v1.RemoveImageRequest"> & { * Use `create(RemoveImageRequestSchema)` to create a new message. */ export const RemoveImageRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 26); + messageDesc(file_docker_v1_docker, 27); /** * @generated from message docker.v1.RemoveImageResponse @@ -805,7 +850,7 @@ export type RemoveImageResponse = Message<"docker.v1.RemoveImageResponse"> & { * Use `create(RemoveImageResponseSchema)` to create a new message. */ export const RemoveImageResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 27); + messageDesc(file_docker_v1_docker, 28); /** * @generated from message docker.v1.ImagePruneResponse @@ -827,7 +872,7 @@ export type ImagePruneResponse = Message<"docker.v1.ImagePruneResponse"> & { * Use `create(ImagePruneResponseSchema)` to create a new message. */ export const ImagePruneResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 28); + messageDesc(file_docker_v1_docker, 29); /** * @generated from message docker.v1.ImagePruneRequest @@ -849,7 +894,7 @@ export type ImagePruneRequest = Message<"docker.v1.ImagePruneRequest"> & { * Use `create(ImagePruneRequestSchema)` to create a new message. */ export const ImagePruneRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 29); + messageDesc(file_docker_v1_docker, 30); /** * @generated from message docker.v1.ImagesDeleted @@ -871,7 +916,7 @@ export type ImagesDeleted = Message<"docker.v1.ImagesDeleted"> & { * Use `create(ImagesDeletedSchema)` to create a new message. */ export const ImagesDeletedSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 30); + messageDesc(file_docker_v1_docker, 31); /** * Volume-related messages @@ -925,7 +970,7 @@ export type Volume = Message<"docker.v1.Volume"> & { * Use `create(VolumeSchema)` to create a new message. */ export const VolumeSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 31); + messageDesc(file_docker_v1_docker, 32); /** * @generated from message docker.v1.ListVolumesRequest @@ -938,7 +983,7 @@ export type ListVolumesRequest = Message<"docker.v1.ListVolumesRequest"> & { * Use `create(ListVolumesRequestSchema)` to create a new message. */ export const ListVolumesRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 32); + messageDesc(file_docker_v1_docker, 33); /** * @generated from message docker.v1.ListVolumesResponse @@ -955,7 +1000,7 @@ export type ListVolumesResponse = Message<"docker.v1.ListVolumesResponse"> & { * Use `create(ListVolumesResponseSchema)` to create a new message. */ export const ListVolumesResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 33); + messageDesc(file_docker_v1_docker, 34); /** * @generated from message docker.v1.CreateVolumeRequest @@ -968,7 +1013,7 @@ export type CreateVolumeRequest = Message<"docker.v1.CreateVolumeRequest"> & { * Use `create(CreateVolumeRequestSchema)` to create a new message. */ export const CreateVolumeRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 34); + messageDesc(file_docker_v1_docker, 35); /** * @generated from message docker.v1.CreateVolumeResponse @@ -981,7 +1026,7 @@ export type CreateVolumeResponse = Message<"docker.v1.CreateVolumeResponse"> & { * Use `create(CreateVolumeResponseSchema)` to create a new message. */ export const CreateVolumeResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 35); + messageDesc(file_docker_v1_docker, 36); /** * @generated from message docker.v1.DeleteVolumeRequest @@ -1013,7 +1058,7 @@ export type DeleteVolumeRequest = Message<"docker.v1.DeleteVolumeRequest"> & { * Use `create(DeleteVolumeRequestSchema)` to create a new message. */ export const DeleteVolumeRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 36); + messageDesc(file_docker_v1_docker, 37); /** * @generated from message docker.v1.DeleteVolumeResponse @@ -1026,7 +1071,100 @@ export type DeleteVolumeResponse = Message<"docker.v1.DeleteVolumeResponse"> & { * Use `create(DeleteVolumeResponseSchema)` to create a new message. */ export const DeleteVolumeResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 37); + messageDesc(file_docker_v1_docker, 38); + +/** + * @generated from message docker.v1.VolumeInspectRequest + */ +export type VolumeInspectRequest = Message<"docker.v1.VolumeInspectRequest"> & { + /** + * @generated from field: string volumeName = 1; + */ + volumeName: string; +}; + +/** + * Describes the message docker.v1.VolumeInspectRequest. + * Use `create(VolumeInspectRequestSchema)` to create a new message. + */ +export const VolumeInspectRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_docker_v1_docker, 39); + +/** + * @generated from message docker.v1.VolumeInspectResponse + */ +export type VolumeInspectResponse = Message<"docker.v1.VolumeInspectResponse"> & { + /** + * @generated from field: docker.v1.VolumeInspectInfo inspect = 1; + */ + inspect?: VolumeInspectInfo; +}; + +/** + * Describes the message docker.v1.VolumeInspectResponse. + * Use `create(VolumeInspectResponseSchema)` to create a new message. + */ +export const VolumeInspectResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_docker_v1_docker, 40); + +/** + * @generated from message docker.v1.VolumeInspectInfo + */ +export type VolumeInspectInfo = Message<"docker.v1.VolumeInspectInfo"> & { + /** + * @generated from field: docker.v1.Volume vol = 1; + */ + vol?: Volume; + + /** + * @generated from field: repeated docker.v1.VolumeContainerInspect containers = 2; + */ + containers: VolumeContainerInspect[]; +}; + +/** + * Describes the message docker.v1.VolumeInspectInfo. + * Use `create(VolumeInspectInfoSchema)` to create a new message. + */ +export const VolumeInspectInfoSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_docker_v1_docker, 41); + +/** + * @generated from message docker.v1.VolumeContainerInspect + */ +export type VolumeContainerInspect = Message<"docker.v1.VolumeContainerInspect"> & { + /** + * @generated from field: string name = 1; + */ + name: string; + + /** + * @generated from field: string id = 2; + */ + id: string; + + /** + * @generated from field: string destination = 3; + */ + destination: string; + + /** + * @generated from field: bool rw = 4; + */ + rw: boolean; + + /** + * @generated from field: string composeProject = 5; + */ + composeProject: string; +}; + +/** + * Describes the message docker.v1.VolumeContainerInspect. + * Use `create(VolumeContainerInspectSchema)` to create a new message. + */ +export const VolumeContainerInspectSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_docker_v1_docker, 42); /** * Network-related messages @@ -1100,7 +1238,7 @@ export type Network = Message<"docker.v1.Network"> & { * Use `create(NetworkSchema)` to create a new message. */ export const NetworkSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 38); + messageDesc(file_docker_v1_docker, 43); /** * @generated from message docker.v1.ListNetworksRequest @@ -1113,7 +1251,7 @@ export type ListNetworksRequest = Message<"docker.v1.ListNetworksRequest"> & { * Use `create(ListNetworksRequestSchema)` to create a new message. */ export const ListNetworksRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 39); + messageDesc(file_docker_v1_docker, 44); /** * @generated from message docker.v1.ListNetworksResponse @@ -1130,7 +1268,7 @@ export type ListNetworksResponse = Message<"docker.v1.ListNetworksResponse"> & { * Use `create(ListNetworksResponseSchema)` to create a new message. */ export const ListNetworksResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 40); + messageDesc(file_docker_v1_docker, 45); /** * @generated from message docker.v1.CreateNetworkRequest @@ -1143,7 +1281,7 @@ export type CreateNetworkRequest = Message<"docker.v1.CreateNetworkRequest"> & { * Use `create(CreateNetworkRequestSchema)` to create a new message. */ export const CreateNetworkRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 41); + messageDesc(file_docker_v1_docker, 46); /** * @generated from message docker.v1.CreateNetworkResponse @@ -1156,7 +1294,7 @@ export type CreateNetworkResponse = Message<"docker.v1.CreateNetworkResponse"> & * Use `create(CreateNetworkResponseSchema)` to create a new message. */ export const CreateNetworkResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 42); + messageDesc(file_docker_v1_docker, 47); /** * @generated from message docker.v1.DeleteNetworkRequest @@ -1178,7 +1316,7 @@ export type DeleteNetworkRequest = Message<"docker.v1.DeleteNetworkRequest"> & { * Use `create(DeleteNetworkRequestSchema)` to create a new message. */ export const DeleteNetworkRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 43); + messageDesc(file_docker_v1_docker, 48); /** * @generated from message docker.v1.DeleteNetworkResponse @@ -1191,7 +1329,142 @@ export type DeleteNetworkResponse = Message<"docker.v1.DeleteNetworkResponse"> & * Use `create(DeleteNetworkResponseSchema)` to create a new message. */ export const DeleteNetworkResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 44); + messageDesc(file_docker_v1_docker, 49); + +/** + * @generated from message docker.v1.NetworkConnectContainerRequest + */ +export type NetworkConnectContainerRequest = Message<"docker.v1.NetworkConnectContainerRequest"> & { + /** + * @generated from field: string network_id = 1; + */ + networkId: string; + + /** + * @generated from field: string container_id = 2; + */ + containerId: string; +}; + +/** + * Describes the message docker.v1.NetworkConnectContainerRequest. + * Use `create(NetworkConnectContainerRequestSchema)` to create a new message. + */ +export const NetworkConnectContainerRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_docker_v1_docker, 50); + +/** + * @generated from message docker.v1.NetworkConnectContainerResponse + */ +export type NetworkConnectContainerResponse = Message<"docker.v1.NetworkConnectContainerResponse"> & { +}; + +/** + * Describes the message docker.v1.NetworkConnectContainerResponse. + * Use `create(NetworkConnectContainerResponseSchema)` to create a new message. + */ +export const NetworkConnectContainerResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_docker_v1_docker, 51); + +/** + * @generated from message docker.v1.NetworkDisconnectContainerRequest + */ +export type NetworkDisconnectContainerRequest = Message<"docker.v1.NetworkDisconnectContainerRequest"> & { + /** + * @generated from field: string network_id = 1; + */ + networkId: string; + + /** + * @generated from field: string container_id = 2; + */ + containerId: string; +}; + +/** + * Describes the message docker.v1.NetworkDisconnectContainerRequest. + * Use `create(NetworkDisconnectContainerRequestSchema)` to create a new message. + */ +export const NetworkDisconnectContainerRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_docker_v1_docker, 52); + +/** + * @generated from message docker.v1.NetworkDisconnectContainerResponse + */ +export type NetworkDisconnectContainerResponse = Message<"docker.v1.NetworkDisconnectContainerResponse"> & { +}; + +/** + * Describes the message docker.v1.NetworkDisconnectContainerResponse. + * Use `create(NetworkDisconnectContainerResponseSchema)` to create a new message. + */ +export const NetworkDisconnectContainerResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_docker_v1_docker, 53); + +/** + * @generated from message docker.v1.EventsRequest + */ +export type EventsRequest = Message<"docker.v1.EventsRequest"> & { + /** + * @generated from field: string host = 1; + */ + host: string; +}; + +/** + * Describes the message docker.v1.EventsRequest. + * Use `create(EventsRequestSchema)` to create a new message. + */ +export const EventsRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_docker_v1_docker, 54); + +/** + * @generated from message docker.v1.ContainerEvent + */ +export type ContainerEvent = Message<"docker.v1.ContainerEvent"> & { + /** + * create / start / stop / die / kill / restart / pause / unpause / + * destroy / rename / update / oom / health_status. + * Empty for keepalive frames. + * + * @generated from field: string action = 1; + */ + action: string; + + /** + * health_status only: healthy / unhealthy / ... + * + * @generated from field: string status = 2; + */ + status: string; + + /** + * @generated from field: string containerId = 3; + */ + containerId: string; + + /** + * @generated from field: string containerName = 4; + */ + containerName: string; + + /** + * @generated from field: string image = 5; + */ + image: string; + + /** + * @generated from field: int64 timeNano = 6; + */ + timeNano: bigint; +}; + +/** + * Describes the message docker.v1.ContainerEvent. + * Use `create(ContainerEventSchema)` to create a new message. + */ +export const ContainerEventSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_docker_v1_docker, 55); /** * @generated from message docker.v1.ContainerLogsRequest @@ -1208,7 +1481,7 @@ export type ContainerLogsRequest = Message<"docker.v1.ContainerLogsRequest"> & { * Use `create(ContainerLogsRequestSchema)` to create a new message. */ export const ContainerLogsRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 45); + messageDesc(file_docker_v1_docker, 56); /** * @generated from message docker.v1.LogsMessage @@ -1225,7 +1498,148 @@ export type LogsMessage = Message<"docker.v1.LogsMessage"> & { * Use `create(LogsMessageSchema)` to create a new message. */ export const LogsMessageSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 46); + messageDesc(file_docker_v1_docker, 57); + +/** + * @generated from message docker.v1.LogsStreamRequest + */ +export type LogsStreamRequest = Message<"docker.v1.LogsStreamRequest"> & { + /** + * one id = single container view, several = merged stack view + * + * @generated from field: repeated string containerIds = 1; + */ + containerIds: string[]; + + /** + * number of trailing lines per container, <= 0 means the server default + * + * @generated from field: int32 tail = 2; + */ + tail: number; + + /** + * unix seconds bounds, 0 means unbounded + * + * @generated from field: int64 since = 3; + */ + since: bigint; + + /** + * @generated from field: int64 until = 4; + */ + until: bigint; + + /** + * keep the stream open for new lines; false ends it once history is sent + * + * @generated from field: bool follow = 5; + */ + follow: boolean; +}; + +/** + * Describes the message docker.v1.LogsStreamRequest. + * Use `create(LogsStreamRequestSchema)` to create a new message. + */ +export const LogsStreamRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_docker_v1_docker, 58); + +/** + * a frame with an empty containerId and text is a keepalive + * + * @generated from message docker.v1.LogLine + */ +export type LogLine = Message<"docker.v1.LogLine"> & { + /** + * @generated from field: string containerId = 1; + */ + containerId: string; + + /** + * @generated from field: string containerName = 2; + */ + containerName: string; + + /** + * line content without the daemon timestamp prefix + * + * @generated from field: string text = 3; + */ + text: string; + + /** + * @generated from field: int64 timeNano = 4; + */ + timeNano: bigint; + + /** + * 1 = stdout, 2 = stderr + * + * @generated from field: int32 stream = 5; + */ + stream: number; +}; + +/** + * Describes the message docker.v1.LogLine. + * Use `create(LogLineSchema)` to create a new message. + */ +export const LogLineSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_docker_v1_docker, 59); + +/** + * @generated from message docker.v1.DockerCommandRequest + */ +export type DockerCommandRequest = Message<"docker.v1.DockerCommandRequest"> & { + /** + * full command line, e.g. "docker run --rm -p 8080:80 nginx:alpine" + * + * @generated from field: string command = 1; + */ + command: string; +}; + +/** + * Describes the message docker.v1.DockerCommandRequest. + * Use `create(DockerCommandRequestSchema)` to create a new message. + */ +export const DockerCommandRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_docker_v1_docker, 60); + +/** + * @generated from message docker.v1.HostStatsResponse + */ +export type HostStatsResponse = Message<"docker.v1.HostStatsResponse"> & { + /** + * whole-host cpu usage in percent (0-100), 0 until two samples exist + * + * @generated from field: double cpuPercent = 1; + */ + cpuPercent: number; + + /** + * @generated from field: int64 memUsed = 2; + */ + memUsed: bigint; + + /** + * @generated from field: int64 memTotal = 3; + */ + memTotal: bigint; + + /** + * @generated from field: int32 cpus = 4; + */ + cpus: number; +}; + +/** + * Describes the message docker.v1.HostStatsResponse. + * Use `create(HostStatsResponseSchema)` to create a new message. + */ +export const HostStatsResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_docker_v1_docker, 61); /** * @generated from message docker.v1.StatsResponse @@ -1247,7 +1661,7 @@ export type StatsResponse = Message<"docker.v1.StatsResponse"> & { * Use `create(StatsResponseSchema)` to create a new message. */ export const StatsResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 47); + messageDesc(file_docker_v1_docker, 62); /** * @generated from message docker.v1.StatsRequest @@ -1279,7 +1693,7 @@ export type StatsRequest = Message<"docker.v1.StatsRequest"> & { * Use `create(StatsRequestSchema)` to create a new message. */ export const StatsRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 48); + messageDesc(file_docker_v1_docker, 63); /** * @generated from message docker.v1.SystemInfo @@ -1303,7 +1717,7 @@ export type SystemInfo = Message<"docker.v1.SystemInfo"> & { * Use `create(SystemInfoSchema)` to create a new message. */ export const SystemInfoSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 49); + messageDesc(file_docker_v1_docker, 64); /** * @generated from message docker.v1.ListResponse @@ -1325,7 +1739,7 @@ export type ListResponse = Message<"docker.v1.ListResponse"> & { * Use `create(ListResponseSchema)` to create a new message. */ export const ListResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 50); + messageDesc(file_docker_v1_docker, 65); /** * @generated from message docker.v1.ContainerList @@ -1404,7 +1818,7 @@ export type ContainerList = Message<"docker.v1.ContainerList"> & { * Use `create(ContainerListSchema)` to create a new message. */ export const ContainerListSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 51); + messageDesc(file_docker_v1_docker, 66); /** * ContainerInfo holds metrics for a single Docker container. @@ -1474,6 +1888,49 @@ export type ContainerStats = Message<"docker.v1.ContainerStats"> & { * @generated from field: uint64 block_write = 9; */ blockWrite: bigint; + + /** + * Container start time (RFC3339). Empty if unknown / not running. + * + * @generated from field: string started_at = 10; + */ + startedAt: string; + + /** + * Image reference the container was created from. + * + * @generated from field: string image = 11; + */ + image: string; + + /** + * Container state: running, exited, paused, restarting... + * + * @generated from field: string state = 12; + */ + state: string; + + /** + * Health status: healthy / unhealthy / starting. Empty when the container + * has no healthcheck. + * + * @generated from field: string health = 13; + */ + health: string; + + /** + * Container network IP addresses. + * + * @generated from field: repeated string ip_address = 14; + */ + ipAddress: string[]; + + /** + * How many times the container restarted. + * + * @generated from field: int32 restart_count = 15; + */ + restartCount: number; }; /** @@ -1481,7 +1938,7 @@ export type ContainerStats = Message<"docker.v1.ContainerStats"> & { * Use `create(ContainerStatsSchema)` to create a new message. */ export const ContainerStatsSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 52); + messageDesc(file_docker_v1_docker, 67); /** * @generated from message docker.v1.Port @@ -1513,7 +1970,7 @@ export type Port = Message<"docker.v1.Port"> & { * Use `create(PortSchema)` to create a new message. */ export const PortSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 53); + messageDesc(file_docker_v1_docker, 68); /** * @generated from message docker.v1.Empty @@ -1526,7 +1983,7 @@ export type Empty = Message<"docker.v1.Empty"> & { * Use `create(EmptySchema)` to create a new message. */ export const EmptySchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 54); + messageDesc(file_docker_v1_docker, 69); /** * @generated from message docker.v1.ContainerRequest @@ -1543,7 +2000,7 @@ export type ContainerRequest = Message<"docker.v1.ContainerRequest"> & { * Use `create(ContainerRequestSchema)` to create a new message. */ export const ContainerRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 55); + messageDesc(file_docker_v1_docker, 70); /** * @generated from message docker.v1.ComposeFile @@ -1565,7 +2022,45 @@ export type ComposeFile = Message<"docker.v1.ComposeFile"> & { * Use `create(ComposeFileSchema)` to create a new message. */ export const ComposeFileSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_docker_v1_docker, 56); + messageDesc(file_docker_v1_docker, 71); + +/** + * @generated from message docker.v1.ComposeRedeployRequest + */ +export type ComposeRedeployRequest = Message<"docker.v1.ComposeRedeployRequest"> & { + /** + * @generated from field: docker.v1.ComposeFile file = 1; + */ + file?: ComposeFile; + + /** + * force-pull images (--pull always) + * + * @generated from field: bool pull = 2; + */ + pull: boolean; + + /** + * force-build images (--build) + * + * @generated from field: bool build = 3; + */ + build: boolean; + + /** + * recreate containers even when nothing changed (--force-recreate) + * + * @generated from field: bool recreate = 4; + */ + recreate: boolean; +}; + +/** + * Describes the message docker.v1.ComposeRedeployRequest. + * Use `create(ComposeRedeployRequestSchema)` to create a new message. + */ +export const ComposeRedeployRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_docker_v1_docker, 72); /** * @generated from enum docker.v1.SORT_FIELD @@ -1605,6 +2100,11 @@ export enum SORT_FIELD { * @generated from enum value: DISK_W = 6; */ DISK_W = 6, + + /** + * @generated from enum value: STARTED = 7; + */ + STARTED = 7, } /** @@ -1675,12 +2175,31 @@ export const DockerService: GenService<{ output: typeof LogsMessageSchema; }, /** + * @generated from rpc docker.v1.DockerService.ContainerPause + */ + containerPause: { + methodKind: "unary"; + input: typeof ContainerRequestSchema; + output: typeof LogsMessageSchema; + }, + /** + * @generated from rpc docker.v1.DockerService.ContainerUnpause + */ + containerUnpause: { + methodKind: "unary"; + input: typeof ContainerRequestSchema; + output: typeof LogsMessageSchema; + }, + /** + * force-updates the containers' images (pull, recreate when the image + * changed, rollback on failure), streaming per-step progress + * * @generated from rpc docker.v1.DockerService.ContainerUpdate */ containerUpdate: { - methodKind: "unary"; + methodKind: "server_streaming"; input: typeof ContainerRequestSchema; - output: typeof EmptySchema; + output: typeof LogsMessageSchema; }, /** * @generated from rpc docker.v1.DockerService.ContainerTop @@ -1706,6 +2225,31 @@ export const DockerService: GenService<{ input: typeof StatsRequestSchema; output: typeof StatsResponseSchema; }, + /** + * streams each container's stats as soon as its read completes, so the UI + * fills in progressively instead of waiting for the slowest container + * (fully qualified return type: the sibling ContainerStats rpc otherwise + * shadows the message name inside the service scope) + * + * @generated from rpc docker.v1.DockerService.ContainerStatsStream + */ + containerStatsStream: { + methodKind: "server_streaming"; + input: typeof StatsRequestSchema; + output: typeof ContainerStatsSchema; + }, + /** + * real host-level usage (from /proc via the host's runner, so it works for + * ssh hosts too) — the general stats view shows this instead of summing + * per-container numbers + * + * @generated from rpc docker.v1.DockerService.HostStats + */ + hostStats: { + methodKind: "unary"; + input: typeof EmptySchema; + output: typeof HostStatsResponseSchema; + }, /** * @generated from rpc docker.v1.DockerService.ContainerLogs */ @@ -1714,6 +2258,26 @@ export const DockerService: GenService<{ input: typeof ContainerLogsRequestSchema; output: typeof LogsMessageSchema; }, + /** + * pushes filtered container lifecycle events (start/stop/die/health + * transitions...) so views can refresh reactively instead of polling; + * empty-action messages are keepalives + * + * @generated from rpc docker.v1.DockerService.ContainerEvents + */ + containerEvents: { + methodKind: "server_streaming"; + input: typeof EventsRequestSchema; + output: typeof ContainerEventSchema; + }, + /** + * @generated from rpc docker.v1.DockerService.ContainerLogsStream + */ + containerLogsStream: { + methodKind: "server_streaming"; + input: typeof LogsStreamRequestSchema; + output: typeof LogLineSchema; + }, /** * @generated from rpc docker.v1.DockerService.ContainerInspect */ @@ -1772,6 +2336,17 @@ export const DockerService: GenService<{ input: typeof ComposeFileSchema; output: typeof LogsMessageSchema; }, + /** + * compose up -d with explicit force flags (pull / build / recreate), + * so a stack can be redeployed in one action + * + * @generated from rpc docker.v1.DockerService.ComposeRedeploy + */ + composeRedeploy: { + methodKind: "server_streaming"; + input: typeof ComposeRedeployRequestSchema; + output: typeof LogsMessageSchema; + }, /** * @generated from rpc docker.v1.DockerService.ComposeList */ @@ -1796,6 +2371,17 @@ export const DockerService: GenService<{ input: typeof ComposeFileStatusRequestSchema; output: typeof ComposeFileStatusResponseSchema; }, + /** + * runs a user-provided docker CLI command on the selected host and streams + * its combined output; only the docker binary is allowed + * + * @generated from rpc docker.v1.DockerService.DockerCommand + */ + dockerCommand: { + methodKind: "server_streaming"; + input: typeof DockerCommandRequestSchema; + output: typeof LogsMessageSchema; + }, /** * images * @@ -1856,6 +2442,14 @@ export const DockerService: GenService<{ input: typeof DeleteVolumeRequestSchema; output: typeof DeleteVolumeResponseSchema; }, + /** + * @generated from rpc docker.v1.DockerService.VolumeInspect + */ + volumeInspect: { + methodKind: "unary"; + input: typeof VolumeInspectRequestSchema; + output: typeof VolumeInspectResponseSchema; + }, /** * networks * @@ -1890,6 +2484,22 @@ export const DockerService: GenService<{ input: typeof NetworkInspectRequestSchema; output: typeof NetworkInspectResponseSchema; }, + /** + * @generated from rpc docker.v1.DockerService.NetworkConnectContainer + */ + networkConnectContainer: { + methodKind: "unary"; + input: typeof NetworkConnectContainerRequestSchema; + output: typeof NetworkConnectContainerResponseSchema; + }, + /** + * @generated from rpc docker.v1.DockerService.NetworkDisconnectContainer + */ + networkDisconnectContainer: { + methodKind: "unary"; + input: typeof NetworkDisconnectContainerRequestSchema; + output: typeof NetworkDisconnectContainerResponseSchema; + }, }> = /*@__PURE__*/ serviceDesc(file_docker_v1_docker, 0); diff --git a/ui/src/gen/dockyaml/v1/dockyaml_pb.ts b/ui/src/gen/dockyaml/v1/dockyaml_pb.ts index d1ddd74e..8bd7da5b 100644 --- a/ui/src/gen/dockyaml/v1/dockyaml_pb.ts +++ b/ui/src/gen/dockyaml/v1/dockyaml_pb.ts @@ -10,7 +10,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file dockyaml/v1/dockyaml.proto. */ export const file_dockyaml_v1_dockyaml: GenFile = /*@__PURE__*/ - fileDesc("Chpkb2NreWFtbC92MS9kb2NreWFtbC5wcm90bxILZG9ja3lhbWwudjEiHwoLU2F2ZVJlcXVlc3QSEAoIY29udGVudHMYAiABKAwiDgoMU2F2ZVJlc3BvbnNlIgwKCkdldFJlcXVlc3QiHwoLR2V0UmVzcG9uc2USEAoIY29udGVudHMYASABKAwiEAoOR2V0WWFtbFJlcXVlc3QiOQoPR2V0WWFtbFJlc3BvbnNlEiYKBGRvY2sYASABKAsyGC5kb2NreWFtbC52MS5Eb2NrbWFuWWFtbCKrAwoLRG9ja21hbllhbWwSPgoLY3VzdG9tVG9vbHMYCSADKAsyKS5kb2NreWFtbC52MS5Eb2NrbWFuWWFtbC5DdXN0b21Ub29sc0VudHJ5EhkKEXVzZUNvbXBvc2VGb2xkZXJzGAEgASgIEiIKGmRpc2FibGVDb21wb3NlUXVpY2tBY3Rpb25zGAcgASgIEhMKC3NlYXJjaExpbWl0GAggASgFEhAKCHRhYkxpbWl0GAYgASgFEi8KC3ZvbHVtZXNQYWdlGAIgASgLMhouZG9ja3lhbWwudjEuVm9sdW1lc0NvbmZpZxIvCgtuZXR3b3JrUGFnZRgDIAEoCzIaLmRvY2t5YW1sLnYxLk5ldHdvcmtDb25maWcSKwoJaW1hZ2VQYWdlGAQgASgLMhguZG9ja3lhbWwudjEuSW1hZ2VDb25maWcSMwoNY29udGFpbmVyUGFnZRgFIAEoCzIcLmRvY2t5YW1sLnYxLkNvbnRhaW5lckNvbmZpZxoyChBDdXN0b21Ub29sc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiMAoNVm9sdW1lc0NvbmZpZxIfCgRzb3J0GAEgASgLMhEuZG9ja3lhbWwudjEuU29ydCIwCg1OZXR3b3JrQ29uZmlnEh8KBHNvcnQYASABKAsyES5kb2NreWFtbC52MS5Tb3J0Ii4KC0ltYWdlQ29uZmlnEh8KBHNvcnQYASABKAsyES5kb2NreWFtbC52MS5Tb3J0IjIKD0NvbnRhaW5lckNvbmZpZxIfCgRzb3J0GAEgASgLMhEuZG9ja3lhbWwudjEuU29ydCIsCgRTb3J0EhEKCXNvcnRPcmRlchgBIAEoCRIRCglzb3J0RmllbGQYAiABKAky1AEKD0RvY2t5YW1sU2VydmljZRI6CgNHZXQSFy5kb2NreWFtbC52MS5HZXRSZXF1ZXN0GhguZG9ja3lhbWwudjEuR2V0UmVzcG9uc2UiABI9CgRTYXZlEhguZG9ja3lhbWwudjEuU2F2ZVJlcXVlc3QaGS5kb2NreWFtbC52MS5TYXZlUmVzcG9uc2UiABJGCgdHZXRZYW1sEhsuZG9ja3lhbWwudjEuR2V0WWFtbFJlcXVlc3QaHC5kb2NreWFtbC52MS5HZXRZYW1sUmVzcG9uc2UiAEKdAQoPY29tLmRvY2t5YW1sLnYxQg1Eb2NreWFtbFByb3RvUAFaLmdpdGh1Yi5jb20vUkEzNDEvZG9ja21hbi9nZW5lcmF0ZWQvZG9ja3lhbWwvdjGiAgNEWFiqAgtEb2NreWFtbC5WMcoCC0RvY2t5YW1sXFYx4gIXRG9ja3lhbWxcVjFcR1BCTWV0YWRhdGHqAgxEb2NreWFtbDo6VjFiBnByb3RvMw"); + fileDesc("Chpkb2NreWFtbC92MS9kb2NreWFtbC5wcm90bxILZG9ja3lhbWwudjEiHwoLU2F2ZVJlcXVlc3QSEAoIY29udGVudHMYAiABKAwiDgoMU2F2ZVJlc3BvbnNlIgwKCkdldFJlcXVlc3QiHwoLR2V0UmVzcG9uc2USEAoIY29udGVudHMYASABKAwiEAoOR2V0WWFtbFJlcXVlc3QiOQoPR2V0WWFtbFJlc3BvbnNlEiYKBGRvY2sYASABKAsyGC5kb2NreWFtbC52MS5Eb2NrbWFuWWFtbCL+BAoLRG9ja21hbllhbWwSPgoLY3VzdG9tVG9vbHMYCSADKAsyKS5kb2NreWFtbC52MS5Eb2NrbWFuWWFtbC5DdXN0b21Ub29sc0VudHJ5EhkKEXVzZUNvbXBvc2VGb2xkZXJzGAEgASgIEiIKGmRpc2FibGVDb21wb3NlUXVpY2tBY3Rpb25zGAcgASgIEhMKC3NlYXJjaExpbWl0GAggASgFEhAKCHRhYkxpbWl0GAYgASgFEi8KC3ZvbHVtZXNQYWdlGAIgASgLMhouZG9ja3lhbWwudjEuVm9sdW1lc0NvbmZpZxIvCgtuZXR3b3JrUGFnZRgDIAEoCzIaLmRvY2t5YW1sLnYxLk5ldHdvcmtDb25maWcSKwoJaW1hZ2VQYWdlGAQgASgLMhguZG9ja3lhbWwudjEuSW1hZ2VDb25maWcSMwoNY29udGFpbmVyUGFnZRgFIAEoCzIcLmRvY2t5YW1sLnYxLkNvbnRhaW5lckNvbmZpZxIrCglzdGF0c1BhZ2UYCiABKAsyGC5kb2NreWFtbC52MS5TdGF0c0NvbmZpZxIvCgtjb21wb3NlUGFnZRgLIAEoCzIaLmRvY2t5YW1sLnYxLkNvbXBvc2VDb25maWcSLQoKZWRpdG9yUGFnZRgMIAEoCzIZLmRvY2t5YW1sLnYxLkVkaXRvckNvbmZpZxIvCgttb25pdG9yUGFnZRgNIAEoCzIaLmRvY2t5YW1sLnYxLk1vbml0b3JDb25maWcSEwoLZGVmYXVsdFZpZXcYDiABKAkaMgoQQ3VzdG9tVG9vbHNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIiIKDU1vbml0b3JDb25maWcSEQoJc3RhY2tSb3dzGAEgASgJIiMKDUNvbXBvc2VDb25maWcSEgoKZGVmYXVsdFRhYhgBIAEoCSIlCgxFZGl0b3JDb25maWcSFQoNc2Nyb2xsUGFzdEVuZBgBIAEoCCIwCg1Wb2x1bWVzQ29uZmlnEh8KBHNvcnQYASABKAsyES5kb2NreWFtbC52MS5Tb3J0IjAKDU5ldHdvcmtDb25maWcSHwoEc29ydBgBIAEoCzIRLmRvY2t5YW1sLnYxLlNvcnQiLgoLSW1hZ2VDb25maWcSHwoEc29ydBgBIAEoCzIRLmRvY2t5YW1sLnYxLlNvcnQiMgoPQ29udGFpbmVyQ29uZmlnEh8KBHNvcnQYASABKAsyES5kb2NreWFtbC52MS5Tb3J0Ii4KC1N0YXRzQ29uZmlnEh8KBHNvcnQYASABKAsyES5kb2NreWFtbC52MS5Tb3J0IiwKBFNvcnQSEQoJc29ydE9yZGVyGAEgASgJEhEKCXNvcnRGaWVsZBgCIAEoCTLUAQoPRG9ja3lhbWxTZXJ2aWNlEjoKA0dldBIXLmRvY2t5YW1sLnYxLkdldFJlcXVlc3QaGC5kb2NreWFtbC52MS5HZXRSZXNwb25zZSIAEj0KBFNhdmUSGC5kb2NreWFtbC52MS5TYXZlUmVxdWVzdBoZLmRvY2t5YW1sLnYxLlNhdmVSZXNwb25zZSIAEkYKB0dldFlhbWwSGy5kb2NreWFtbC52MS5HZXRZYW1sUmVxdWVzdBocLmRvY2t5YW1sLnYxLkdldFlhbWxSZXNwb25zZSIAQp0BCg9jb20uZG9ja3lhbWwudjFCDURvY2t5YW1sUHJvdG9QAVouZ2l0aHViLmNvbS9SQTM0MS9kb2NrbWFuL2dlbmVyYXRlZC9kb2NreWFtbC92MaICA0RYWKoCC0RvY2t5YW1sLlYxygILRG9ja3lhbWxcVjHiAhdEb2NreWFtbFxWMVxHUEJNZXRhZGF0YeoCDERvY2t5YW1sOjpWMWIGcHJvdG8z"); /** * @generated from message dockyaml.v1.SaveRequest @@ -150,6 +150,34 @@ export type DockmanYaml = Message<"dockyaml.v1.DockmanYaml"> & { * @generated from field: dockyaml.v1.ContainerConfig containerPage = 5; */ containerPage?: ContainerConfig; + + /** + * @generated from field: dockyaml.v1.StatsConfig statsPage = 10; + */ + statsPage?: StatsConfig; + + /** + * @generated from field: dockyaml.v1.ComposeConfig composePage = 11; + */ + composePage?: ComposeConfig; + + /** + * @generated from field: dockyaml.v1.EditorConfig editorPage = 12; + */ + editorPage?: EditorConfig; + + /** + * @generated from field: dockyaml.v1.MonitorConfig monitorPage = 13; + */ + monitorPage?: MonitorConfig; + + /** + * view opened when landing on a host: files (default), monitor, stats, + * containers, images, volumes, networks or cleaner + * + * @generated from field: string defaultView = 14; + */ + defaultView: string; }; /** @@ -159,6 +187,65 @@ export type DockmanYaml = Message<"dockyaml.v1.DockmanYaml"> & { export const DockmanYamlSchema: GenMessage = /*@__PURE__*/ messageDesc(file_dockyaml_v1_dockyaml, 6); +/** + * @generated from message dockyaml.v1.MonitorConfig + */ +export type MonitorConfig = Message<"dockyaml.v1.MonitorConfig"> & { + /** + * stack row density in the monitor view: "full" (default) shows CPU/RAM + * values with their charts, "compact" shows the values only + * + * @generated from field: string stackRows = 1; + */ + stackRows: string; +}; + +/** + * Describes the message dockyaml.v1.MonitorConfig. + * Use `create(MonitorConfigSchema)` to create a new message. + */ +export const MonitorConfigSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_dockyaml_v1_dockyaml, 7); + +/** + * @generated from message dockyaml.v1.ComposeConfig + */ +export type ComposeConfig = Message<"dockyaml.v1.ComposeConfig"> & { + /** + * tab shown when opening a compose stack: editor (default), deploy or stats + * + * @generated from field: string defaultTab = 1; + */ + defaultTab: string; +}; + +/** + * Describes the message dockyaml.v1.ComposeConfig. + * Use `create(ComposeConfigSchema)` to create a new message. + */ +export const ComposeConfigSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_dockyaml_v1_dockyaml, 8); + +/** + * @generated from message dockyaml.v1.EditorConfig + */ +export type EditorConfig = Message<"dockyaml.v1.EditorConfig"> & { + /** + * allow scrolling half a viewport past the last line (it stops at + * mid-view), for files taller than the viewport + * + * @generated from field: bool scrollPastEnd = 1; + */ + scrollPastEnd: boolean; +}; + +/** + * Describes the message dockyaml.v1.EditorConfig. + * Use `create(EditorConfigSchema)` to create a new message. + */ +export const EditorConfigSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_dockyaml_v1_dockyaml, 9); + /** * @generated from message dockyaml.v1.VolumesConfig */ @@ -174,7 +261,7 @@ export type VolumesConfig = Message<"dockyaml.v1.VolumesConfig"> & { * Use `create(VolumesConfigSchema)` to create a new message. */ export const VolumesConfigSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_dockyaml_v1_dockyaml, 7); + messageDesc(file_dockyaml_v1_dockyaml, 10); /** * @generated from message dockyaml.v1.NetworkConfig @@ -191,7 +278,7 @@ export type NetworkConfig = Message<"dockyaml.v1.NetworkConfig"> & { * Use `create(NetworkConfigSchema)` to create a new message. */ export const NetworkConfigSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_dockyaml_v1_dockyaml, 8); + messageDesc(file_dockyaml_v1_dockyaml, 11); /** * @generated from message dockyaml.v1.ImageConfig @@ -208,7 +295,7 @@ export type ImageConfig = Message<"dockyaml.v1.ImageConfig"> & { * Use `create(ImageConfigSchema)` to create a new message. */ export const ImageConfigSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_dockyaml_v1_dockyaml, 9); + messageDesc(file_dockyaml_v1_dockyaml, 12); /** * @generated from message dockyaml.v1.ContainerConfig @@ -225,7 +312,24 @@ export type ContainerConfig = Message<"dockyaml.v1.ContainerConfig"> & { * Use `create(ContainerConfigSchema)` to create a new message. */ export const ContainerConfigSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_dockyaml_v1_dockyaml, 10); + messageDesc(file_dockyaml_v1_dockyaml, 13); + +/** + * @generated from message dockyaml.v1.StatsConfig + */ +export type StatsConfig = Message<"dockyaml.v1.StatsConfig"> & { + /** + * @generated from field: dockyaml.v1.Sort sort = 1; + */ + sort?: Sort; +}; + +/** + * Describes the message dockyaml.v1.StatsConfig. + * Use `create(StatsConfigSchema)` to create a new message. + */ +export const StatsConfigSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_dockyaml_v1_dockyaml, 14); /** * @generated from message dockyaml.v1.Sort @@ -247,7 +351,7 @@ export type Sort = Message<"dockyaml.v1.Sort"> & { * Use `create(SortSchema)` to create a new message. */ export const SortSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_dockyaml_v1_dockyaml, 11); + messageDesc(file_dockyaml_v1_dockyaml, 15); /** * @generated from service dockyaml.v1.DockyamlService diff --git a/ui/src/gen/files/v1/files_pb.ts b/ui/src/gen/files/v1/files_pb.ts index 0f8879e4..1f79e452 100644 --- a/ui/src/gen/files/v1/files_pb.ts +++ b/ui/src/gen/files/v1/files_pb.ts @@ -10,7 +10,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file files/v1/files.proto. */ export const file_files_v1_files: GenFile = /*@__PURE__*/ - fileDesc("ChRmaWxlcy92MS9maWxlcy5wcm90bxIIZmlsZXMudjEiQQoQV3JpdGVUbXBsUmVxdWVzdBILCgNkaXIYAiABKAkSIAoEdG1wbBgBIAEoCzISLmZpbGVzLnYxLlRlbXBsYXRlIhMKEVdyaXRlVG1wbFJlc3BvbnNlIiAKD0dldFRtcGxzUmVxdWVzdBINCgVhbGlhcxgBIAEoCSJxCghUZW1wbGF0ZRIMCgROYW1lGAEgASgJEioKBHZhcnMYAiADKAsyHC5maWxlcy52MS5UZW1wbGF0ZS5WYXJzRW50cnkaKwoJVmFyc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiNgoQR2V0VG1wbHNSZXNwb25zZRIiCgZ0ZW1wbHMYASADKAsyEi5maWxlcy52MS5UZW1wbGF0ZSJLCgtDb3B5UmVxdWVzdBIeCgZzb3VyY2UYASABKAsyDi5maWxlcy52MS5GaWxlEhwKBGRlc3QYAiABKAsyDi5maWxlcy52MS5GaWxlIg4KDENvcHlSZXNwb25zZSIbCgtMaXN0UmVxdWVzdBIMCgRwYXRoGAEgASgJIjIKDExpc3RSZXNwb25zZRIiCgdlbnRyaWVzGAEgAygLMhEuZmlsZXMudjEuRnNFbnRyeSIhCg1Gb3JtYXRSZXF1ZXN0EhAKCGZpbGVuYW1lGAEgASgJIiIKDkZvcm1hdFJlc3BvbnNlEhAKCGNvbnRlbnRzGAEgASgJInsKB0ZzRW50cnkSEAoIZmlsZW5hbWUYAiABKAkSDQoFaXNEaXIYAyABKAgSIwoIc3ViRmlsZXMYBCADKAsyES5maWxlcy52MS5Gc0VudHJ5EhEKCWlzRmV0Y2hlZBgFIAEoCBIXCg9pc0NvbXBvc2VGb2xkZXIYBiABKAkiNgoKUmVuYW1lRmlsZRITCgtvbGRGaWxlUGF0aBgBIAEoCRITCgtuZXdGaWxlUGF0aBgCIAEoCSInCgRGaWxlEhAKCGZpbGVuYW1lGAEgASgJEg0KBWlzRGlyGAIgASgIIgcKBUVtcHR5MoUECgtGaWxlU2VydmljZRI3CgRMaXN0EhUuZmlsZXMudjEuTGlzdFJlcXVlc3QaFi5maWxlcy52MS5MaXN0UmVzcG9uc2UiABIrCgZDcmVhdGUSDi5maWxlcy52MS5GaWxlGg8uZmlsZXMudjEuRW1wdHkiABI3CgRDb3B5EhUuZmlsZXMudjEuQ29weVJlcXVlc3QaFi5maWxlcy52MS5Db3B5UmVzcG9uc2UiABIrCgZEZWxldGUSDi5maWxlcy52MS5GaWxlGg8uZmlsZXMudjEuRW1wdHkiABIrCgZFeGlzdHMSDi5maWxlcy52MS5GaWxlGg8uZmlsZXMudjEuRW1wdHkiABIxCgZSZW5hbWUSFC5maWxlcy52MS5SZW5hbWVGaWxlGg8uZmlsZXMudjEuRW1wdHkiABJDCghHZXRUbXBscxIZLmZpbGVzLnYxLkdldFRtcGxzUmVxdWVzdBoaLmZpbGVzLnYxLkdldFRtcGxzUmVzcG9uc2UiABJGCglXcml0ZVRtcGwSGi5maWxlcy52MS5Xcml0ZVRtcGxSZXF1ZXN0GhsuZmlsZXMudjEuV3JpdGVUbXBsUmVzcG9uc2UiABI9CgZGb3JtYXQSFy5maWxlcy52MS5Gb3JtYXRSZXF1ZXN0GhguZmlsZXMudjEuRm9ybWF0UmVzcG9uc2UiAEKIAQoMY29tLmZpbGVzLnYxQgpGaWxlc1Byb3RvUAFaK2dpdGh1Yi5jb20vUkEzNDEvZG9ja21hbi9nZW5lcmF0ZWQvZmlsZXMvdjGiAgNGWFiqAghGaWxlcy5WMcoCCEZpbGVzXFYx4gIURmlsZXNcVjFcR1BCTWV0YWRhdGHqAglGaWxlczo6VjFiBnByb3RvMw"); + fileDesc("ChRmaWxlcy92MS9maWxlcy5wcm90bxIIZmlsZXMudjEiQQoQV3JpdGVUbXBsUmVxdWVzdBILCgNkaXIYAiABKAkSIAoEdG1wbBgBIAEoCzISLmZpbGVzLnYxLlRlbXBsYXRlIhMKEVdyaXRlVG1wbFJlc3BvbnNlIiAKD0dldFRtcGxzUmVxdWVzdBINCgVhbGlhcxgBIAEoCSJxCghUZW1wbGF0ZRIMCgROYW1lGAEgASgJEioKBHZhcnMYAiADKAsyHC5maWxlcy52MS5UZW1wbGF0ZS5WYXJzRW50cnkaKwoJVmFyc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiNgoQR2V0VG1wbHNSZXNwb25zZRIiCgZ0ZW1wbHMYASADKAsyEi5maWxlcy52MS5UZW1wbGF0ZSJLCgtDb3B5UmVxdWVzdBIeCgZzb3VyY2UYASABKAsyDi5maWxlcy52MS5GaWxlEhwKBGRlc3QYAiABKAsyDi5maWxlcy52MS5GaWxlIg4KDENvcHlSZXNwb25zZSIbCgtMaXN0UmVxdWVzdBIMCgRwYXRoGAEgASgJIjIKDExpc3RSZXNwb25zZRIiCgdlbnRyaWVzGAEgAygLMhEuZmlsZXMudjEuRnNFbnRyeSIhCg1Gb3JtYXRSZXF1ZXN0EhAKCGZpbGVuYW1lGAEgASgJIiIKDkZvcm1hdFJlc3BvbnNlEhAKCGNvbnRlbnRzGAEgASgJIosBCgdGc0VudHJ5EhAKCGZpbGVuYW1lGAIgASgJEg0KBWlzRGlyGAMgASgIEiMKCHN1YkZpbGVzGAQgAygLMhEuZmlsZXMudjEuRnNFbnRyeRIRCglpc0ZldGNoZWQYBSABKAgSFwoPaXNDb21wb3NlRm9sZGVyGAYgASgJEg4KBnBpbm5lZBgHIAEoCCI2CgpSZW5hbWVGaWxlEhMKC29sZEZpbGVQYXRoGAEgASgJEhMKC25ld0ZpbGVQYXRoGAIgASgJIicKBEZpbGUSEAoIZmlsZW5hbWUYASABKAkSDQoFaXNEaXIYAiABKAgiBwoFRW1wdHkyhQQKC0ZpbGVTZXJ2aWNlEjcKBExpc3QSFS5maWxlcy52MS5MaXN0UmVxdWVzdBoWLmZpbGVzLnYxLkxpc3RSZXNwb25zZSIAEisKBkNyZWF0ZRIOLmZpbGVzLnYxLkZpbGUaDy5maWxlcy52MS5FbXB0eSIAEjcKBENvcHkSFS5maWxlcy52MS5Db3B5UmVxdWVzdBoWLmZpbGVzLnYxLkNvcHlSZXNwb25zZSIAEisKBkRlbGV0ZRIOLmZpbGVzLnYxLkZpbGUaDy5maWxlcy52MS5FbXB0eSIAEisKBkV4aXN0cxIOLmZpbGVzLnYxLkZpbGUaDy5maWxlcy52MS5FbXB0eSIAEjEKBlJlbmFtZRIULmZpbGVzLnYxLlJlbmFtZUZpbGUaDy5maWxlcy52MS5FbXB0eSIAEkMKCEdldFRtcGxzEhkuZmlsZXMudjEuR2V0VG1wbHNSZXF1ZXN0GhouZmlsZXMudjEuR2V0VG1wbHNSZXNwb25zZSIAEkYKCVdyaXRlVG1wbBIaLmZpbGVzLnYxLldyaXRlVG1wbFJlcXVlc3QaGy5maWxlcy52MS5Xcml0ZVRtcGxSZXNwb25zZSIAEj0KBkZvcm1hdBIXLmZpbGVzLnYxLkZvcm1hdFJlcXVlc3QaGC5maWxlcy52MS5Gb3JtYXRSZXNwb25zZSIAQogBCgxjb20uZmlsZXMudjFCCkZpbGVzUHJvdG9QAVorZ2l0aHViLmNvbS9SQTM0MS9kb2NrbWFuL2dlbmVyYXRlZC9maWxlcy92MaICA0ZYWKoCCEZpbGVzLlYxygIIRmlsZXNcVjHiAhRGaWxlc1xWMVxHUEJNZXRhZGF0YeoCCUZpbGVzOjpWMWIGcHJvdG8z"); /** * @generated from message files.v1.WriteTmplRequest @@ -236,6 +236,13 @@ export type FsEntry = Message<"files.v1.FsEntry"> & { * @generated from field: string isComposeFolder = 6; */ isComposeFolder: string; + + /** + * set when the entry's name is pinned in dockman.yml (pinnedFiles) + * + * @generated from field: bool pinned = 7; + */ + pinned: boolean; }; /** diff --git a/ui/src/hooks/button-action.ts b/ui/src/hooks/button-action.ts index 886420de..24c15262 100644 --- a/ui/src/hooks/button-action.ts +++ b/ui/src/hooks/button-action.ts @@ -4,11 +4,14 @@ function useButtonAction() { const [activeAction, setActiveAction] = useState('') const buttonAction = async (callback: () => Promise, actionName: string) => { setActiveAction(actionName) - await callback() - setActiveAction('') + try { + await callback() + } finally { + setActiveAction('') + } } return {activeAction, buttonAction} } -export default useButtonAction \ No newline at end of file +export default useButtonAction diff --git a/ui/src/hooks/container-freshness.ts b/ui/src/hooks/container-freshness.ts new file mode 100644 index 00000000..81d005c2 --- /dev/null +++ b/ui/src/hooks/container-freshness.ts @@ -0,0 +1,20 @@ +// Shared cadence policy for container list views: while something is +// settling (starting, health checks pending, just created or restarted), +// poll fast so the view tracks it second by second; once everything is +// stable the slow safety net is enough — docker events cover the rest. + +const TRANSITIONAL_STATES = new Set(['created', 'restarting', 'removing']); + +// a container that started less than this ago still gets the fast cadence, +// so its uptime/created column visibly counts up right after an action +const FRESH_WINDOW_MS = 60_000; + +export const FAST_POLL_MS = 3000; +export const IDLE_POLL_MS = 30000; + +export function isSettling(state: string, health: string, created: string): boolean { + if (TRANSITIONAL_STATES.has(state.toLowerCase())) return true; + if (health.toLowerCase() === 'starting') return true; + const createdMs = new Date(created).getTime(); + return Number.isFinite(createdMs) && Date.now() - createdMs < FRESH_WINDOW_MS; +} diff --git a/ui/src/hooks/docker-compose.ts b/ui/src/hooks/docker-compose.ts index 5679b805..ecfd5722 100644 --- a/ui/src/hooks/docker-compose.ts +++ b/ui/src/hooks/docker-compose.ts @@ -2,14 +2,18 @@ import {useCallback, useEffect, useState} from 'react' import {callRPC, useHostClient} from '../lib/api.ts' import {type ContainerList, DockerService} from '../gen/docker/v1/docker_pb.ts' import {useSnackbar} from "./snackbar.ts" +import {useDockerEvents} from "./docker-events.ts"; +import {FAST_POLL_MS, IDLE_POLL_MS, isSettling} from "./container-freshness.ts"; export function useDockerCompose(composeFile: string) { const dockerService = useHostClient(DockerService); const {showWarning} = useSnackbar() + // container lifecycle events drive the refresh; polling is a safety net + const eventBump = useDockerEvents() const [containers, setContainers] = useState([]) const [loading, setLoading] = useState(true) - const [refreshInterval, setRefreshInterval] = useState(2000) + const [refreshInterval, setRefreshInterval] = useState(30000) const fetchContainers = useCallback(async () => { if (!composeFile) { @@ -27,7 +31,7 @@ export function useDockerCompose(composeFile: string) { } setContainers(val?.list || []) - }, [composeFile, dockerService]) + }, [composeFile, dockerService, showWarning]) useEffect(() => { setLoading(true) @@ -36,12 +40,19 @@ export function useDockerCompose(composeFile: string) { }) }, [fetchContainers]) // run only once on page load + // fast cadence while containers settle (start, health checks, restarts), + // slow safety net once stable + useEffect(() => { + const fast = containers.some(c => isSettling(c.state, c.health, c.created)); + setRefreshInterval(fast ? FAST_POLL_MS : IDLE_POLL_MS); + }, [containers]) + // fetch without setting load useEffect(() => { fetchContainers().then() const intervalId = setInterval(fetchContainers, refreshInterval) return () => clearInterval(intervalId) - }, [fetchContainers, refreshInterval]) + }, [fetchContainers, refreshInterval, eventBump]) return {containers, loading, fetchContainers, refreshInterval, setRefreshInterval} -} \ No newline at end of file +} diff --git a/ui/src/hooks/docker-containers-stats.ts b/ui/src/hooks/docker-containers-stats.ts index dade3aca..68052788 100644 --- a/ui/src/hooks/docker-containers-stats.ts +++ b/ui/src/hooks/docker-containers-stats.ts @@ -1,8 +1,150 @@ import {useCallback, useEffect, useRef, useState} from 'react'; +import {create} from "@bufbuild/protobuf"; import {callRPC, useHostClient} from '../lib/api.ts'; -import {type ContainerStats, DockerService, ORDER, SORT_FIELD} from '../gen/docker/v1/docker_pb.ts'; +import {type ContainerStats, ContainerStatsSchema, DockerService, ORDER, SORT_FIELD} from '../gen/docker/v1/docker_pb.ts'; import {useSnackbar} from "./snackbar.ts"; import {useHostStore} from "../pages/compose/state/files.ts"; +import {useConfig} from "./config.ts"; + +// cpuUsage sentinel marking a row seeded from the container list whose +// one-shot metrics response has not arrived yet; the list is immediate. +export const METRICS_PENDING = -1; + +// Refresh cadence between two streaming cycles — 5s like Dockhand's stacks +// cards. CPU deltas are calculated server-side between these readings, so +// this interval is also the chart's stable sampling window. +const DEFAULT_REFRESH = 5000; + +// Rolling per-container metric history driving the sparklines: 20 points at +// the 5s cadence is ~100s of live history, same window as Dockhand's cards. +export interface StatHistory { + cpu: number[]; + mem: number[]; +} + +const HISTORY_CAP = 20; + +// Module-level on purpose: history must survive component remounts (a ref +// resets with its component, losing everything between two polls) and is +// shared between the host-wide stats page and per-stack stats tabs, which +// see the same containers. +const statHistories = new Map(); +let statHistoriesHost: string | null = null; + +// Aggregate header history, one point per COMPLETED cycle, keyed per view +// scope (host page vs each stack tab aggregate different container sets). +const aggHistories = new Map(); + +// One point per received container stat, Dockhand-style. History is keyed by +// container NAME, not id: names are unique per host and — unlike ids — +// survive a compose recreate, so the chart never restarts because the +// identifier flipped underneath it. +function recordStat(host: string, stat: ContainerStats) { + if (statHistoriesHost !== host) { + statHistories.clear(); + aggHistories.clear(); + statHistoriesHost = host; + } + + // IMMUTABLE append — fresh arrays AND a fresh entry object, exactly like + // Dockhand's Svelte code rebuilds its history on every point. The UI is + // compiled with the React Compiler, which memoizes render work by + // reference equality: pushing into the same array (or mutating the same + // entry object) leaves identities unchanged, so charts keep serving the + // geometry cached at their first render and never redraw, no matter how + // many points accumulate. + const prev = statHistories.get(stat.name) ?? {cpu: [], mem: []}; + const limit = Number(stat.memoryLimit); + const h: StatHistory = { + cpu: [...prev.cpu.slice(-(HISTORY_CAP - 1)), Math.max(stat.cpuUsage, 0)], + mem: [...prev.mem.slice(-(HISTORY_CAP - 1)), limit > 0 ? (Number(stat.memoryUsage) / limit) * 100 : 0], + }; + statHistories.set(stat.name, h); +} + +// AggregateSnapshot is the header's data: computed once per completed cycle +// from the cycle's final rows, so the totals never mix two cycles' values +// or wobble while results trickle in. +export interface AggregateSnapshot { + total: number; + running: number; + stopped: number; + paused: number; + restarting: number; + unhealthy: number; + cpu: number; + memUsed: number; + memLimit: number; + netRx: number; + netTx: number; + diskR: number; + diskW: number; + cpuHistory: number[]; + memHistory: number[]; +} + +function computeAggregates(scope: string, rows: ContainerStats[]): AggregateSnapshot { + const t = rows.reduce((acc, curr) => { + acc.cpu += Math.max(curr.cpuUsage, 0); + acc.memUsed += Number(curr.memoryUsage); + // containers without an explicit memory limit report the host's total + // RAM as their limit: summing would count the host once per container + // (4 containers on a 32GB host -> "128GB"), the max is the real ceiling + acc.memLimit = Math.max(acc.memLimit, Number(curr.memoryLimit)); + acc.netRx += Number(curr.networkRx); + acc.netTx += Number(curr.networkTx); + acc.diskR += Number(curr.blockRead); + acc.diskW += Number(curr.blockWrite); + switch (curr.state) { + case 'running': + acc.running++; + break; + case 'exited': + case 'dead': + case 'created': + acc.stopped++; + break; + case 'paused': + acc.paused++; + break; + case 'restarting': + acc.restarting++; + break; + } + // healthchecks only run on running containers: a stopped/paused + // container's health field is the daemon's stale last state, counting + // it would double-book the container as stopped AND unhealthy + if (curr.state === 'running' && curr.health === 'unhealthy') acc.unhealthy++; + return acc; + }, { + cpu: 0, memUsed: 0, memLimit: 0, netRx: 0, netTx: 0, diskR: 0, diskW: 0, + running: 0, stopped: 0, paused: 0, restarting: 0, unhealthy: 0, + }); + + const prev = aggHistories.get(scope) ?? {cpu: [], mem: []}; + const h: StatHistory = { + cpu: [...prev.cpu.slice(-(HISTORY_CAP - 1)), t.cpu], + mem: [...prev.mem.slice(-(HISTORY_CAP - 1)), t.memLimit > 0 ? (t.memUsed / t.memLimit) * 100 : 0], + }; + aggHistories.set(scope, h); + + return { + total: rows.length, + ...t, + cpuHistory: h.cpu, + memHistory: h.mem, + }; +} + +// drop history of containers that disappeared — only from a full host cycle: +// a stack-scoped cycle only sees its own containers and must not evict +// everyone else's history +function pruneHistory(seenNames: Set, fullListing: boolean) { + if (!fullListing) return; + for (const name of [...statHistories.keys()]) { + if (!seenNames.has(name)) statHistories.delete(name); + } +} // This map remains very useful for clean, client-side sorting. const sortFieldToKeyMap: Record = { @@ -13,107 +155,374 @@ const sortFieldToKeyMap: Record = { [SORT_FIELD.NETWORK_TX]: 'networkTx', [SORT_FIELD.DISK_R]: 'blockRead', [SORT_FIELD.DISK_W]: 'blockWrite', + [SORT_FIELD.STARTED]: 'startedAt', +}; + +// Sorting happens client-side on every merge (like Dockhand): the stream +// delivers containers in arrival order. +function sortRows(rows: ContainerStats[], field: SORT_FIELD, order: ORDER): ContainerStats[] { + const key = sortFieldToKeyMap[field]; + return [...rows].sort((a, b) => { + const valA = a[key]; + const valB = b[key]; + let comparison = 0; + + if (typeof valA === 'bigint' && typeof valB === 'bigint') { + if (valA < valB) comparison = -1; + if (valA > valB) comparison = 1; + } else if (typeof valA === 'number' && typeof valB === 'number') { + comparison = valA - valB; + } else { + comparison = String(valA).localeCompare(String(valB)); + } + + return order === ORDER.ASC ? comparison : -comparison; + }); +} + +// Maps a dockman.yml `stats.sort.field` string to a SORT_FIELD. Accepts the +// column labels and a few obvious aliases, case-insensitively. Falls back to +// MEM, preserving the historical default. +const configFieldToSortField = (field?: string): SORT_FIELD => { + switch ((field ?? '').trim().toLowerCase()) { + case 'name': + case 'container': + return SORT_FIELD.NAME; + case 'cpu': + case 'cpu usage': + return SORT_FIELD.CPU; + case 'mem': + case 'memory': + case 'memory usage': + return SORT_FIELD.MEM; + case 'network_rx': + case 'rx': + return SORT_FIELD.NETWORK_RX; + case 'network_tx': + case 'tx': + return SORT_FIELD.NETWORK_TX; + case 'disk_r': + case 'disk read': + return SORT_FIELD.DISK_R; + case 'disk_w': + case 'disk write': + return SORT_FIELD.DISK_W; + case 'started': + case 'uptime': + return SORT_FIELD.STARTED; + default: + return SORT_FIELD.MEM; + } }; +const configOrderToOrder = (order?: string): ORDER => + (order ?? '').trim().toLowerCase() === 'asc' ? ORDER.ASC : ORDER.DSC; + export function useDockerStats(selectedPage?: string) { const dockerService = useHostClient(DockerService) const {showError} = useSnackbar(); const selectedHost = useHostStore(state => state.host) + const {dockYaml} = useConfig(); const [rawContainers, setRawContainers] = useState([]); const [loading, setLoading] = useState(true); + // header totals, refreshed once per completed cycle + const [aggregates, setAggregates] = useState(null); + const gotStats = useRef(false); + // mirror of rawContainers so the stream can merge into the visible rows + // without depending on state identity + const rowsRef = useRef([]); const [sortField, setSortField] = useState(SORT_FIELD.MEM); const [sortOrder, setSortOrder] = useState(ORDER.DSC); - const [refreshInterval, setRefreshInterval] = useState(2500); + const [refreshInterval, setRefreshInterval] = useState(DEFAULT_REFRESH); const isInitialLoad = useRef(true); - const resort = useRef(false) + const loadingRef = useRef(true); + // sort settings exposed to the streaming loop without restarting it + const sortRef = useRef({field: sortField, order: sortOrder}); + sortRef.current = {field: sortField, order: sortOrder}; + // Once the user sorts by hand we stop applying the dockman.yml default so a + // late-arriving (or per-host) config never clobbers their choice. + const userSorted = useRef(false) + + const applyRows = useCallback((rows: ContainerStats[]) => { + rowsRef.current = rows; + setRawContainers(rows); + }, []); + + // Seed the sort from dockman.yml (stats.sort) until the user sorts manually. + useEffect(() => { + if (userSorted.current) return; + const cfg = dockYaml?.statsPage?.sort; + if (!cfg) return; + setSortField(configFieldToSortField(cfg.sortField)); + setSortOrder(configOrderToOrder(cfg.sortOrder)); + }, [dockYaml]); + + // re-sort the visible rows whenever the sort changes; the stream itself + // is sort-agnostic so this never restarts a cycle + useEffect(() => { + applyRows(sortRows(rowsRef.current, sortField, sortOrder)); + }, [applyRows, sortField, sortOrder]); useEffect(() => { let isCancelled = false; + let timer: ReturnType | null = null; + // sort settings are read through a ref so a sort change doesn't tear + // down the streaming cycle + // Coalesced paints: the stream delivers one message per container + // (identity wave + one per completed read), and sorting + re-rendering + // per message costs real CPU and allocation churn on busy hosts. A + // short flush window keeps the progressive-paint feel while cutting + // the render count by an order of magnitude. + let flushTimer: ReturnType | null = null; - const fetchData = async () => { - const {val, err} = await callRPC(() => dockerService.containerStats({ - sortBy: sortField, - order: sortOrder, - host: selectedHost, - file: selectedPage ? {filename: selectedPage} : undefined - })); + const tick = async () => { + const cycleStart = Date.now(); - if (isCancelled) return; + const merged = new Map(); - if (err) { - showError(err); - } else { - setRawContainers(val?.containers || []); - } + const flush = () => { + flushTimer = null; + if (isCancelled) return; + applyRows(sortRows([...merged.values()], sortRef.current.field, sortRef.current.order)); + if (loadingRef.current) { + setLoading(false); + loadingRef.current = false; + } + }; + const scheduleFlush = () => { + if (flushTimer === null) { + flushTimer = setTimeout(flush, 200); + } + }; + + try { + for (const c of rowsRef.current) { + merged.set(c.id, c); + } + const seen = new Set(); + const seenNames = new Set(); + + for await (const stat of dockerService.containerStatsStream({ + host: selectedHost, + file: selectedPage ? {filename: selectedPage} : undefined, + })) { + if (isCancelled) return; + + // identity-only row streamed ahead of its metrics reading so + // the view paints fast: never overwrite a row that + // already has real values, never record it as history + if (stat.cpuUsage < 0) { + if (!merged.has(stat.id)) { + merged.set(stat.id, stat); + scheduleFlush(); + } + continue; + } - if (isInitialLoad.current) { - setLoading(false); - isInitialLoad.current = false; + merged.set(stat.id, stat); + seen.add(stat.id); + seenNames.add(stat.name); + recordStat(selectedHost, stat); + scheduleFlush(); + } + + if (isCancelled) return; + if (flushTimer !== null) { + clearTimeout(flushTimer); + flushTimer = null; + } + + // cycle complete: drop containers that no longer exist, then + // refresh the header totals in one go — computing them from + // the cycle's final rows keeps them coherent instead of + // wobbling through mixed old/new values while results trickle + gotStats.current = true; + pruneHistory(seenNames, !selectedPage); + const finalRows = sortRows( + [...merged.values()].filter(c => seen.has(c.id)), + sortRef.current.field, sortRef.current.order, + ); + applyRows(finalRows); + setAggregates(computeAggregates(`${selectedHost}|${selectedPage || '*'}`, finalRows)); + } catch (e) { + if (!isCancelled) { + showError(String(e)); + } + } finally { + if (isInitialLoad.current) { + setLoading(false); + isInitialLoad.current = false; + } + if (!isCancelled) { + // fixed cadence between cycle starts, never overlapping + const elapsed = Date.now() - cycleStart; + timer = setTimeout(tick, Math.max(1000, refreshInterval - elapsed)); + } } }; - fetchData(); - - const intervalId = setInterval(fetchData, refreshInterval); + tick(); return () => { - clearInterval(intervalId); isCancelled = true; + if (timer !== null) clearTimeout(timer); + if (flushTimer !== null) clearTimeout(flushTimer); }; - }, [selectedHost, dockerService, selectedPage, sortField, sortOrder, refreshInterval]); + }, [applyRows, selectedHost, dockerService, selectedPage, refreshInterval, showError]); useEffect(() => { - // clear containers on host change - setRawContainers([]) + // clear containers on host change (history clears itself, keyed by host) + applyRows([]) setLoading(true) + loadingRef.current = true; isInitialLoad.current = true; - }, [selectedHost]); + gotStats.current = false; + // re-apply the configured default for the newly selected host + userSorted.current = false; + }, [applyRows, selectedHost]); - // Optimistic Client-Side Sorting - // This useMemo provides the INSTANT sort feedback to the UI. - // It runs immediately whenever `rawContainers` or the sort state changes. + // Instant first paint: seed the rows from the (immediate) container list + // while the first stats reads sample in the daemon; metrics cells render + // as pending and fill in as each container's stats arrive. Stack tabs are + // matched by the compose project name — conventionally the compose file's + // directory name; when the project was renamed the match finds nothing + // and the stream's identity wave paints the rows instead. useEffect(() => { - if (resort.current) { - // sort and let the server handle subsequent sorts until order is changed - resort.current = false - const key = sortFieldToKeyMap[sortField]; - const res = [...rawContainers].sort((a, b) => { - const valA = a[key]; - const valB = b[key]; - let comparison = 0; - - if (typeof valA === 'bigint' && typeof valB === 'bigint') { - if (valA < valB) comparison = -1; - if (valA > valB) comparison = 1; - } else if (typeof valA === 'number' && typeof valB === 'number') { - comparison = valA - valB; - } else { - comparison = String(valA).localeCompare(String(valB)); - } + let cancelled = false; - return sortOrder === ORDER.ASC ? comparison : -comparison; - }) - setRawContainers(res) - } - }, [rawContainers, sortField, sortOrder]); + const seed = async () => { + const {val} = await callRPC(() => dockerService.containerList({})); + // never overwrite real stats with placeholders + if (cancelled || gotStats.current || !val) return; + const project = selectedPage + ? (selectedPage.split('/').slice(-2, -1)[0] ?? '').toLowerCase() + : ''; + + const rows = val.list + .filter(c => !selectedPage || c.stackName.toLowerCase() === project) + .map(c => create(ContainerStatsSchema, { + id: c.id.substring(0, 12), + name: c.name, + image: c.imageName, + state: c.state, + health: c.health, + ipAddress: c.IPAddress, + cpuUsage: METRICS_PENDING, + })) + .sort((a, b) => a.name.localeCompare(b.name)); + if (rows.length === 0) return; + + // merge under any stats that already trickled in + const byId = new Map(rows.map(r => [r.id, r])); + for (const existing of rowsRef.current) { + byId.set(existing.id, existing); + } + applyRows([...byId.values()]); + setLoading(false); + loadingRef.current = false; + }; + + seed(); + return () => { + cancelled = true; + }; + }, [applyRows, selectedHost, dockerService, selectedPage]); const handleSortChange = useCallback((newField: SORT_FIELD, newOrderBy: ORDER) => { + userSorted.current = true setSortField(newField) setSortOrder(newOrderBy) - // immediate resort for ui - resort.current = true }, []); + const resetContainerStats = useCallback((names: string[]) => { + if (names.length === 0) return; + const reset = new Set(names); + for (const name of reset) statHistories.delete(name); + // Remove the old samples immediately. The streaming loop will seed a + // pending identity row, then replace it only with a fresh daemon read. + applyRows(rowsRef.current.filter(row => !reset.has(row.name))); + // The aggregate snapshot was computed from the same old samples. Do not + // display it as current while the next complete cycle is still pending. + setAggregates(null); + }, [applyRows]); + return { containers: rawContainers, + // fresh Map identity on every render: the module-level map mutates in + // place, and the React Compiler memoizes lookups like history.get(name) + // by reference — served the map itself, charts would freeze on their + // first geometry forever + history: new Map(statHistories), + aggregates, loading, sortField, sortOrder, handleSortChange, + resetContainerStats, setRefreshInterval, refreshInterval, }; } + +// real host-level usage for the general stats view (Dockhand-style): the +// backend reads /proc through the host's runner, so ssh hosts work too +export interface HostStatsView { + cpuPercent: number; + memUsed: number; + memTotal: number; + cpus: number; + cpuHistory: number[]; + memHistory: number[]; +} + +// survives remounts, keyed per host like the container histories +const hostHistories = new Map(); + +export function useHostStats(enabled: boolean): HostStatsView | null { + const dockerService = useHostClient(DockerService); + const selectedHost = useHostStore(state => state.host); + const [stats, setStats] = useState(null); + + useEffect(() => { + if (!enabled) { + setStats(null); + return; + } + + let cancelled = false; + const fetchStats = async () => { + const {val} = await callRPC(() => dockerService.hostStats({})); + if (cancelled || !val || val.memTotal === 0n) return; + + const memUsed = Number(val.memUsed); + const memTotal = Number(val.memTotal); + const prev = hostHistories.get(selectedHost) ?? {cpu: [], mem: []}; + const h: StatHistory = { + cpu: [...prev.cpu.slice(-(HISTORY_CAP - 1)), val.cpuPercent], + mem: [...prev.mem.slice(-(HISTORY_CAP - 1)), memTotal > 0 ? (memUsed / memTotal) * 100 : 0], + }; + hostHistories.set(selectedHost, h); + + setStats({ + cpuPercent: val.cpuPercent, + memUsed, + memTotal, + cpus: val.cpus, + cpuHistory: h.cpu, + memHistory: h.mem, + }); + }; + + void fetchStats(); + const id = setInterval(fetchStats, DEFAULT_REFRESH); + return () => { + cancelled = true; + clearInterval(id); + }; + }, [enabled, dockerService, selectedHost]); + + return stats; +} diff --git a/ui/src/hooks/docker-containers.ts b/ui/src/hooks/docker-containers.ts index 5bc653d6..0e2210f9 100644 --- a/ui/src/hooks/docker-containers.ts +++ b/ui/src/hooks/docker-containers.ts @@ -2,16 +2,18 @@ import {useCallback, useEffect, useState} from 'react' import {callRPC, useHostClient} from '../lib/api.ts' import {DockerService, type ListResponse} from '../gen/docker/v1/docker_pb.ts' import {useSnackbar} from "./snackbar.ts" -import {useHostStore} from "../pages/compose/state/files.ts"; +import {useDockerEvents} from "./docker-events.ts"; +import {FAST_POLL_MS, IDLE_POLL_MS, isSettling} from "./container-freshness.ts"; export function useDockerContainers() { const dockerService = useHostClient(DockerService) const {showWarning} = useSnackbar() - const selectedHost = useHostStore(state => state.host) + // container lifecycle events drive the refresh; polling is a safety net + const eventBump = useDockerEvents() const [containers, setContainers] = useState(null) const [loading, setLoading] = useState(true) - const [refreshInterval, setRefreshInterval] = useState(2000) + const [refreshInterval, setRefreshInterval] = useState(30000) const fetchContainers = useCallback(async () => { const {val, err} = await callRPC(() => dockerService.containerList({})) @@ -22,7 +24,7 @@ export function useDockerContainers() { } setContainers(val) - }, [dockerService, selectedHost]) + }, [dockerService, showWarning]) const refreshContainers = useCallback(() => { fetchContainers().finally(() => setLoading(false)) @@ -35,11 +37,18 @@ export function useDockerContainers() { }) }, [fetchContainers]) // run only once on page load + // fast cadence while containers settle (start, health checks, restarts), + // slow safety net once stable + useEffect(() => { + const fast = (containers?.list ?? []).some(c => isSettling(c.state, c.health, c.created)); + setRefreshInterval(fast ? FAST_POLL_MS : IDLE_POLL_MS); + }, [containers]) + useEffect(() => { fetchContainers().then() const intervalId = setInterval(fetchContainers, refreshInterval) return () => clearInterval(intervalId) - }, [fetchContainers, refreshInterval]) + }, [fetchContainers, refreshInterval, eventBump]) return {containers, loading, refreshContainers, fetchContainers, refreshInterval, setRefreshInterval} -} \ No newline at end of file +} diff --git a/ui/src/hooks/docker-events.ts b/ui/src/hooks/docker-events.ts new file mode 100644 index 00000000..415908d1 --- /dev/null +++ b/ui/src/hooks/docker-events.ts @@ -0,0 +1,91 @@ +import {useEffect, useSyncExternalStore} from 'react'; +import {DockerService} from '../gen/docker/v1/docker_pb.ts'; +import {useHostClient} from '../lib/api.ts'; +import {useHostStore} from '../pages/compose/state/files.ts'; + +// Minimal structural view of the connect client so the module-level stream +// runner doesn't depend on generated client types. +interface EventsClient { + containerEvents( + req: { host?: string }, + options?: { signal?: AbortSignal }, + ): AsyncIterable<{ action: string }>; +} + +// Module-level shared stream: however many views listen, a single events +// stream runs for the selected host (the server side already multiplexes one +// daemon subscription per host). Module scope survives component remounts. +let seq = 0; +const listeners = new Set<() => void>(); +let currentHost: string | null = null; +let abort: AbortController | null = null; +let notifyTimer: ReturnType | null = null; + +// coalesce bursts (a compose up emits one event per container) into a single +// refresh tick +function notify() { + if (notifyTimer !== null) return; + notifyTimer = setTimeout(() => { + notifyTimer = null; + seq++; + listeners.forEach(listener => listener()); + }, 300); +} + +async function run(client: EventsClient, host: string, signal: AbortSignal) { + let backoff = 1000; + while (!signal.aborted) { + try { + for await (const ev of client.containerEvents({host}, {signal})) { + if (signal.aborted) return; + backoff = 1000; + if (!ev.action) continue; // keepalive frame + notify(); + } + } catch { + // dropped stream: fall through to the backoff and resubscribe + } + if (signal.aborted) return; + await new Promise(resolve => setTimeout(resolve, backoff)); + backoff = Math.min(backoff * 2, 30000); + } +} + +function ensureStream(client: EventsClient, host: string) { + if (currentHost === host && abort !== null) return; + abort?.abort(); + abort = new AbortController(); + currentHost = host; + void run(client, host, abort.signal); +} + +function subscribe(callback: () => void): () => void { + listeners.add(callback); + return () => { + listeners.delete(callback); + if (listeners.size === 0) { + abort?.abort(); + abort = null; + currentHost = null; + } + }; +} + +/** + * Returns a counter bumped whenever a container lifecycle event (start, stop, + * die, health transition...) happens on the selected host, bursts coalesced. + * Add it to a fetch effect's dependencies to refetch reactively — and keep a + * slow polling interval as a safety net, not as the primary refresh. + */ +export function useDockerEvents(): number { + const client = useHostClient(DockerService); + const host = useHostStore(state => state.host); + + const bump = useSyncExternalStore(subscribe, () => seq); + + useEffect(() => { + ensureStream(client, host); + }, [client, host]); + + return bump; +} diff --git a/ui/src/hooks/upload-progress.ts b/ui/src/hooks/upload-progress.ts new file mode 100644 index 00000000..a9145956 --- /dev/null +++ b/ui/src/hooks/upload-progress.ts @@ -0,0 +1,31 @@ +import {create} from 'zustand'; + +// Tracks a single in-flight batch of file uploads so a progress toast can be +// rendered globally. Uploads fan out in parallel, so the caller aggregates the +// per-file byte counts and pushes the totals here. +interface UploadProgressState { + active: boolean; + fileCount: number; + doneCount: number; + totalBytes: number; + loadedBytes: number; + // start a new batch; resets progress counters + start: (fileCount: number, totalBytes: number) => void; + // report aggregate bytes sent and how many files have finished + update: (loadedBytes: number, doneCount: number) => void; + // batch finished (success or failure); the completion is surfaced via the + // regular snackbar, so the progress toast simply hides. + finish: () => void; +} + +export const useUploadProgress = create((set) => ({ + active: false, + fileCount: 0, + doneCount: 0, + totalBytes: 0, + loadedBytes: 0, + start: (fileCount, totalBytes) => + set({active: true, fileCount, totalBytes, loadedBytes: 0, doneCount: 0}), + update: (loadedBytes, doneCount) => set({loadedBytes, doneCount}), + finish: () => set({active: false}), +})); diff --git a/ui/src/lib/api.ts b/ui/src/lib/api.ts index 29dc80db..62eb1731 100644 --- a/ui/src/lib/api.ts +++ b/ui/src/lib/api.ts @@ -3,6 +3,7 @@ import {createConnectTransport} from "@connectrpc/connect-web"; import type {DescService} from "@bufbuild/protobuf"; import {useCallback, useEffect, useMemo, useRef, useState} from "react"; import {useParams} from "react-router-dom"; +import {debugLog, debugWarn} from "./debug.ts"; const mode = import.meta.env.MODE; export const API_BASE_URL = mode === 'development' || mode === 'electron' @@ -46,20 +47,41 @@ export function useContainerLogsWsUrl() { export function useContainerExecWsUrl() { const getBase = useHostUrl() - return useCallback((containerId: string, entrypoint: string, debuggerImage?: string) => { - let params: Record = { + return useCallback((containerId: string, entrypoint: string, debuggerImage?: string, user?: string) => { + const params: Record = { "cmd": entrypoint, } if (debuggerImage) { params["debug"] = "true" params["image"] = debuggerImage } + if (user) params["user"] = user const urlParam = new URLSearchParams(params) return getWSUrl(getBase(`/docker/exec/${containerId}?${urlParam.toString()}`)) }, [getBase]); } +export function useContainerExecOptionsUrl() { + const getBase = useHostUrl() + return useCallback((containerId: string) => getBase(`/docker/exec/${containerId}/options`), [getBase]) +} + +// interactive shell on the host itself (dockman container locally, ssh +// session for remote hosts); pass a compose filename to start the shell in +// that file's directory +export function useHostShellWsUrl() { + const getBase = useHostUrl() + return useCallback((file?: string) => { + const params = new URLSearchParams() + if (file) { + params.set("file", file) + } + const qs = params.toString() + return getWSUrl(getBase(`/docker/shell${qs ? `?${qs}` : ''}`)) + }, [getBase]); +} + export function withAuthAPI(url: string = "/") { return `${getBaseUrl('auth')}${url}` } @@ -80,7 +102,7 @@ export function useHostUrl() { }, [host]); } -console.log(`API url: ${API_BASE_URL} `) +debugLog("API URL", API_BASE_URL) export function useTransport(scope: ApiScope) { const {host} = useParams<{ host: string }>(); @@ -154,7 +176,6 @@ export async function callRPC(exec: () => Promise): Promise<{ val: T | nul return {val, err: ""} } catch (error: unknown) { if (error instanceof ConnectError) { - console.error(`Error: ${error.message}`); // todo maybe ????? // if (error.code == Code.Unauthenticated) { // nav("/") @@ -168,23 +189,21 @@ export async function callRPC(exec: () => Promise): Promise<{ val: T | nul export async function pingWithAuth() { try { - // console.log("Checking authentication status with server..."); const response = await fetch(withProtectedAPI("/ping"), { redirect: 'follow' }); if (response.status == 302) { const location = await response.text(); - console.log(`oidc is enabled redirecting to oidc auth: ${location}`); + debugLog("OIDC redirect", location); window.location.assign(location) return false } - // console.log(`Server response isOK: ${response.ok}`); return response.ok } catch (error) { - console.error("Authentication check failed:", error); + debugWarn("Authentication check failed", error); return false } } @@ -202,4 +221,3 @@ export function formatDate(timestamp: bigint | number | string) { minute: '2-digit' }); } - diff --git a/ui/src/lib/debug.ts b/ui/src/lib/debug.ts new file mode 100644 index 00000000..c9bb0648 --- /dev/null +++ b/ui/src/lib/debug.ts @@ -0,0 +1,16 @@ +// Vite replaces these flags at build time. Production builds stay silent by +// default; a diagnostic image can opt in with VITE_DEBUG=true during the UI +// build without scattering unconditional console calls through the app. +const debugEnabled = import.meta.env.DEV || import.meta.env.VITE_DEBUG === 'true'; + +export const debugLog = (...args: unknown[]) => { + if (debugEnabled) console.debug(...args); +}; + +export const debugWarn = (...args: unknown[]) => { + if (debugEnabled) console.warn(...args); +}; + +export const debugError = (...args: unknown[]) => { + if (debugEnabled) console.error(...args); +}; diff --git a/ui/src/lib/editor.ts b/ui/src/lib/editor.ts index 57247e29..328aeb16 100644 --- a/ui/src/lib/editor.ts +++ b/ui/src/lib/editor.ts @@ -2,6 +2,7 @@ import type {TabDetails} from "../context/tab-context.tsx"; import {useCallback} from "react"; import {useLocation} from "react-router-dom"; import {useAliasStore, useHostStore} from "../pages/compose/state/files.ts"; +import type {DockmanYaml} from "../gen/dockyaml/v1/dockyaml_pb.ts"; export const COMPOSE_EXTENSIONS = ['compose.yaml', 'compose.yml'] @@ -9,6 +10,22 @@ export function isComposeFile(filename: string): boolean { return COMPOSE_EXTENSIONS.some(ext => filename.endsWith(ext)) } +// Stack tab indexes by the names accepted in dockman.yml compose.defaultTab. +const STACK_TAB_INDEXES: Record = { + editor: 0, + deploy: 1, + stats: 2, +} + +// Resolves the tab a file should open on when none is selected yet, per the +// compose.defaultTab setting in dockman.yml. Non-compose files only have an +// editor tab; unknown values fall back to the editor. +export function stackDefaultTab(dockYaml: DockmanYaml | null | undefined, filename?: string): number { + if (!filename || !isComposeFile(filename)) return 0 + const name = dockYaml?.composePage?.defaultTab?.trim().toLowerCase() ?? '' + return STACK_TAB_INDEXES[name] ?? 0 +} + export const formatBytes = (bytes: number | bigint, decimals = 2) => { if (bytes === 0 || bytes === 0n) return '0 B' const k = 1024 @@ -48,6 +65,12 @@ export const useEditorUrl = () => { if (track === 0) { if (filename) { path = `/${host}/files/${filename}`; + if (tabDetail === undefined) { + // Opening a file without an explicit tab: drop the tab + // inherited from the previous file so the view falls back + // to the dockman.yml compose.defaultTab setting. + query.delete(tabKey); + } } else if (!pathname.includes("/files/")) { path = `/${host}/files/${prevAlias || "compose"}`; } diff --git a/ui/src/lib/table.ts b/ui/src/lib/table.ts index 7ba76a4a..626f10c1 100644 --- a/ui/src/lib/table.ts +++ b/ui/src/lib/table.ts @@ -1,4 +1,4 @@ -import {type JSX, useCallback, useEffect, useMemo, useState} from "react"; +import {type JSX, useCallback, useEffect, useMemo, useRef, useState} from "react"; import {callRPC, useHostClient} from "./api.ts"; import {type DockmanYaml, DockyamlService} from "../gen/dockyaml/v1/dockyaml_pb.ts"; @@ -63,8 +63,22 @@ export function useSelection( export const useSort = (initialField: string, initialSort: SortOrder = 'asc') => { const [sortField, setSortField] = useState(initialField); const [sortOrder, setSortOrder] = useState(initialSort); + // Stops the config from re-seeding once the user picks a column by hand. + const userSorted = useRef(false); + + // The dockman.yml config that feeds initialField/initialSort is fetched + // asynchronously, so it typically lands AFTER the first render — when + // useState has already locked in the fallback. Without this, the configured + // column/order (and its header sort arrow) never take effect until the user + // clicks a header. Re-seed from the config until they sort manually. + useEffect(() => { + if (userSorted.current) return; + setSortField(initialField); + setSortOrder(initialSort); + }, [initialField, initialSort]); const handleSort = (field: string) => { + userSorted.current = true; if (sortField === field) { setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc'); } else { @@ -126,8 +140,10 @@ const rtf = new Intl.RelativeTimeFormat('en', {numeric: 'always', style: "long", -export function formatTimeAgo(timestamp: Date) { - const diff = (new Date().getTime() - timestamp.getTime()) / 1000; +// now is an explicit input so memoized callers can tick a re-render and have +// the relative label actually recompute +export function formatTimeAgo(timestamp: Date, now: number = Date.now()) { + const diff = (now - timestamp.getTime()) / 1000; if (diff < 60) { return rtf.format(-Math.round(diff), 'second'); diff --git a/ui/src/pages/auth/auth-page.tsx b/ui/src/pages/auth/auth-page.tsx index d61d60b3..0c2c8f4a 100644 --- a/ui/src/pages/auth/auth-page.tsx +++ b/ui/src/pages/auth/auth-page.tsx @@ -11,7 +11,7 @@ import { TextField, Typography } from '@mui/material'; -import {LockOutlined, LoginOutlined, PersonOutline, PublicRounded} from '@mui/icons-material'; +import {LockOutlined, LoginOutlined, PersonOutlined, PublicRounded} from '@mui/icons-material'; import {useNavigate} from "react-router-dom"; import {callRPC, useAuthClient} from "../../lib/api.ts"; import {AuthService, type Config} from '../../gen/auth/v1/auth_pb.ts'; @@ -45,7 +45,7 @@ export function AuthPage() { } getConfig().then() - }, []); + }, [authClient, showError]); const handleLoginSubmit = async (event: React.FormEvent) => { event.preventDefault(); @@ -84,7 +84,12 @@ export function AuthPage() { }} > {/* Branded Header */} - + Dockman
- + Login @@ -117,7 +124,7 @@ export function AuthPage() { input: { startAdornment: ( - + ), } diff --git a/ui/src/pages/cleaner/cleaner.tsx b/ui/src/pages/cleaner/cleaner.tsx index 340d50d1..60267b0f 100644 --- a/ui/src/pages/cleaner/cleaner.tsx +++ b/ui/src/pages/cleaner/cleaner.tsx @@ -73,7 +73,12 @@ function DockerCleanerPage() { gap: 2 }}> - Loading cleaner + Loading cleaner config... ); @@ -98,9 +103,16 @@ function DockerCleanerPage() { borderBottom: '1px solid', borderColor: 'divider', bgcolor: 'background.paper', py: 2, px: 3, flexShrink: 0 }}> - + - + Docker Cleaner - + Automated pruning of unused resources @@ -141,12 +155,17 @@ function DockerCleanerPage() {
- - + @@ -154,7 +173,9 @@ function DockerCleanerPage() { - + - + Maintenance History diff --git a/ui/src/pages/cleaner/history.tsx b/ui/src/pages/cleaner/history.tsx index 144bf0fa..561f487b 100644 --- a/ui/src/pages/cleaner/history.tsx +++ b/ui/src/pages/cleaner/history.tsx @@ -152,7 +152,12 @@ const CleanerHistory = () => { {history.length === 0 ? ( - + No maintenance logs found @@ -182,7 +187,9 @@ const HeaderCell = ({label, icon}: { label: string, icon: React.ReactNode }) => py: 1.5, zIndex: 2 }}> - + {React.cloneElement(icon as React.ReactElement)} diff --git a/ui/src/pages/cleaner/state.ts b/ui/src/pages/cleaner/state.ts index ed1d7b1a..1f3c4949 100644 --- a/ui/src/pages/cleaner/state.ts +++ b/ui/src/pages/cleaner/state.ts @@ -2,6 +2,7 @@ import {CleanerService, type PruneConfig} from "../../gen/cleaner/v1/cleaner_pb. import {create} from "zustand"; import {callRPC} from "../../lib/api.ts"; import type {Client} from "@connectrpc/connect"; +import {debugWarn} from "../../lib/debug.ts"; export type CleanerConfig = Omit @@ -18,7 +19,7 @@ export const useCleanerConfig = create<{ isLoading: false, Save: async (client, showErr, onSuccess) => { if (!get().config) { - console.warn("No prev config found"); + debugWarn("Cleaner configuration is not loaded"); return; } diff --git a/ui/src/pages/cleaner/storage-inuse.tsx b/ui/src/pages/cleaner/storage-inuse.tsx index 35e27ff8..f81ad70e 100644 --- a/ui/src/pages/cleaner/storage-inuse.tsx +++ b/ui/src/pages/cleaner/storage-inuse.tsx @@ -30,29 +30,40 @@ function StorageInuse({refetch}: { refetch: boolean }) { const cleaner = useHostClient(CleanerService); const {showError} = useSnackbar(); - const spaceStatusRpc = useRPCRunner(() => cleaner.spaceStatus({})); + const {runner, val, loading, err} = useRPCRunner(() => cleaner.spaceStatus({})); - async function fetchStorage() { - await spaceStatusRpc.runner(); - if (spaceStatusRpc.err) showError(spaceStatusRpc.err); - } + const fetchStorage = useCallback(async () => { + await runner(); + }, [runner]); - const refetcher = useCallback(async () => { - await fetchStorage(); - }, [refetch]); + useEffect(() => { + if (err) showError(err); + }, [err, showError]); useEffect(() => { - refetcher().then(); - }, [refetcher]); + fetchStorage().then(); + }, [fetchStorage, refetch]); return ( - {(spaceStatusRpc.loading || !spaceStatusRpc.val) ? ( - + {(loading || !val) ? ( + - Calculating disk + Calculating disk usage... ) : ( @@ -60,23 +71,23 @@ function StorageInuse({refetch}: { refetch: boolean }) { } onClean={fetchStorage} - title="Containers" stat={spaceStatusRpc.val.Containers}/> + title="Containers" stat={val.Containers}/> } onClean={fetchStorage} title="Images" - stat={spaceStatusRpc.val.Images}/> + stat={val.Images}/> } onClean={fetchStorage} - title="Volumes" stat={spaceStatusRpc.val.Volumes}/> + title="Volumes" stat={val.Volumes}/> } onClean={fetchStorage} title="BuildCache" - stat={spaceStatusRpc.val.BuildCache}/> + stat={val.BuildCache}/> } onClean={fetchStorage} title="Networks" - stat={spaceStatusRpc.val.Network}/> + stat={val.Network}/> @@ -116,8 +127,15 @@ const SpaceStateDisplay = ({stat, title, icon, onClean}: { transition: 'all 0.2s', '&:hover': {borderColor: 'primary.main', boxShadow: '0 4px 12px rgba(0,0,0,0.05)'} }}> - - + + {icon} @@ -134,8 +152,13 @@ const SpaceStateDisplay = ({stat, title, icon, onClean}: { - + Reclaimable ( - {label} + {label} {value} @@ -186,4 +214,4 @@ type CleanerConfigWithoutNumbers = { [K in keyof CleanerConfig as CleanerConfig[K] extends number ? never : K]: CleanerConfig[K] }; -export default StorageInuse; \ No newline at end of file +export default StorageInuse; diff --git a/ui/src/pages/compose/components/action-sidebar.tsx b/ui/src/pages/compose/components/action-sidebar.tsx index 36b3487a..a662c43a 100644 --- a/ui/src/pages/compose/components/action-sidebar.tsx +++ b/ui/src/pages/compose/components/action-sidebar.tsx @@ -1,21 +1,69 @@ -import {useFileComponents, useTerminalAction} from "../state/terminal.tsx"; +import {useContainerExec, useFileComponents, useTerminalAction} from "../state/terminal.tsx"; import {Box, Divider, IconButton, Tooltip, Typography} from "@mui/material"; -import {EditRounded, Folder, TerminalOutlined} from "@mui/icons-material"; // Hub is a good default for "Alias/Connection" +import { + Add as AddIcon, + Cached as RefreshIcon, + DensityMedium as StandardIcon, + DensitySmall as CompactIcon, + EditRounded, + Folder, + PushPin as PushPinIcon, + PushPinOutlined as PushPinOutlinedIcon, + Search as SearchIcon, + Terminal, + TerminalOutlined, + VerticalSplit as PlacementIcon, +} from "@mui/icons-material"; import {useEffect} from "react"; -import {useSideBarAction} from "../state/files.ts"; +import {useCompactMode, usePinnedMode, useSideBarAction, useToolbarPlacement} from "../state/files.ts"; import {useAlias} from "../../../context/alias-context.tsx"; import {useNavigate} from "react-router-dom"; import {type FolderAlias} from "../../../gen/host/v1/host_pb.ts"; import {useAliasAddDialogState} from "./add-alias-dialog.tsx"; +import {useSidebarActions} from "../hooks/sidebar-actions.ts"; +import {YamlIcon} from "./file-icon.tsx"; +import {useHostShellWsUrl} from "../../../lib/api.ts"; + +// Shared style for the compact 40x40 rail buttons. +const railBtnSx = { + display: 'flex', + flexDirection: 'column', + borderRadius: '4px', + width: '40px', + height: '40px', + mb: 0, + color: 'rgba(255,255,255,0.7)', + '&:hover': {backgroundColor: 'rgba(255,255,255,0.15)', color: 'white'}, +} as const; const ActionSidebar = () => { const {isSidebarOpen, toggle: fileSideBarToggle} = useSideBarAction(state => state); const {isTerminalOpen, toggle: terminalToggle} = useTerminalAction(state => state); const {aliases} = useAlias(); const nav = useNavigate() - const {alias: activeAlias, host} = useFileComponents() + const {alias: activeAlias, host, filename} = useFileComponents() const openD = useAliasAddDialogState(state => state.setOpen) + const placement = useToolbarPlacement(state => state.placement) + const togglePlacement = useToolbarPlacement(state => state.toggle) + const pinnedMode = usePinnedMode(state => state.enabled) + const togglePinnedMode = usePinnedMode(state => state.toggle) + const compact = useCompactMode(state => state.enabled) + const toggleCompact = useCompactMode(state => state.toggle) + const {reload, showSearch, showFileAdd, showDockyaml} = useSidebarActions() + const onSide = placement === 'side' + + const createShellUrl = useHostShellWsUrl() + const execParams = useContainerExec(state => state.execParams) + + // shell on the current host, in the open compose file's folder when a + // file is being edited, otherwise in the runner user's home + const openHostShell = () => { + const dir = filename ? filename.split('/').slice(0, -1).pop() : '' + const title = dir ? `${dir} (shell)` : `${host} (shell)` + execParams(`shell:${host}/${filename || 'home'}`, title, createShellUrl(filename || undefined), true) + } + const handleAliasClick = (alias: FolderAlias) => { nav(`/${host}/files/${alias.alias}`) }; @@ -52,7 +100,7 @@ const ActionSidebar = () => { zIndex: 10, }} > - + {/* File Explorer Toggle */} @@ -72,6 +120,19 @@ const ActionSidebar = () => { + {/* Pinned-mode toggle, between the folder and the aliases */} + {onSide && ( + + + {pinnedMode ? : + } + + + )} + {/* Aliases List */} {aliases.map((alias, index) => ( @@ -83,7 +144,7 @@ const ActionSidebar = () => { borderRadius: '4px', width: '40px', height: '40px', - mb: 0.5, + mb: 0, color: 'rgba(255,255,255,0.7)', backgroundColor: 'rgba(255,255,255,0.05)', '&:hover': { @@ -119,7 +180,7 @@ const ActionSidebar = () => { borderRadius: '4px', width: '40px', height: '40px', - mb: 0.5, + mb: 0, color: 'rgba(255,255,255,0.7)', // backgroundColor: 'rgba(255,255,255,0.05)', '&:hover': { @@ -132,12 +193,80 @@ const ActionSidebar = () => { - + {/* File explorer actions, when placed on the side rail */} + {onSide && ( + <> + + + + + + + + + + + + + + + + + + + + + + + {compact ? : } + + + + + + + + + + )} + + {/* Bottom Section: Tools */} - + + + + + + + + + + + + + { }} > - + { - - - - ) + ); } export default AliasDialog \ No newline at end of file diff --git a/ui/src/pages/compose/components/compose-action-buttons.tsx b/ui/src/pages/compose/components/compose-action-buttons.tsx index 5327129b..eb410234 100644 --- a/ui/src/pages/compose/components/compose-action-buttons.tsx +++ b/ui/src/pages/compose/components/compose-action-buttons.tsx @@ -1,19 +1,29 @@ import {useHostClient} from "../../../lib/api.ts"; import {useFileComponents} from "../state/terminal.tsx"; -import {Box, Button, CircularProgress} from "@mui/material"; +import {Box, Button, ButtonGroup, CircularProgress, IconButton, Tooltip} from "@mui/material"; +import TerminalIcon from '@mui/icons-material/Terminal'; import {deployActionsConfig, useComposeAction} from "../state/compose.tsx"; import {DockerService} from "../../../gen/docker/v1/docker_pb.ts"; +import {useSnackbar} from "../../../hooks/snackbar.ts"; +import {DockerCommandButton} from "./docker-command-button.tsx"; export function ComposeActionHeaders({selectedServices, fetchContainers}: { selectedServices: string[]; fetchContainers: () => Promise }) { const dockerService = useHostClient(DockerService); + const {showSuccess, showError} = useSnackbar(); const runAction = useComposeAction(state => state.runAction) const activeAction = useComposeAction(state => state.activeAction) + const openOutput = useComposeAction(state => state.openOutput) const {filename} = useFileComponents(); const composeFile = filename! + const lastRun = useComposeAction(state => state.runs[composeFile]) + + // short stack label for toasts: the compose file's folder + const parts = composeFile.split('/'); + const stackLabel = parts.length > 1 ? parts[parts.length - 2] : composeFile; const handleComposeAction = ( name: typeof deployActionsConfig[number]['name'], @@ -25,27 +35,74 @@ export function ComposeActionHeaders({selectedServices, fetchContainers}: { dockerService[rpcName], name, selectedServices, - () => fetchContainers() + (error) => { + void fetchContainers() + if (error) { + showError(`${stackLabel}: ${name} failed`, { + duration: 10000, + action: ( + + ), + }); + } else { + showSuccess(`${stackLabel}: ${name} completed`); + } + } ) }; return ( - - {deployActionsConfig.map((action) => ( - - ))} + + + {deployActionsConfig.map((action) => ( + + ))} + + + {lastRun && ( + + openOutput(composeFile)} + sx={{color: lastRun.failed ? 'error.main' : 'text.secondary'}} + > + + + + )} ) -} \ No newline at end of file +} diff --git a/ui/src/pages/compose/components/container-info-table.tsx b/ui/src/pages/compose/components/container-info-table.tsx index a3aeb715..36f3a405 100644 --- a/ui/src/pages/compose/components/container-info-table.tsx +++ b/ui/src/pages/compose/components/container-info-table.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useState} from 'react' +import React, {type ReactNode, useEffect, useState} from 'react' import { Box, Checkbox, @@ -65,6 +65,22 @@ export function ContainerTable( if (!loading) setIsLoaded(true); }, [loading]); + // relative times ("28 seconds ago") are computed at render time; since + // data refreshes are event-driven (no fast polling anymore), tick a + // re-render so the labels keep counting between refreshes. `now` feeds + // formatTimeAgo explicitly so memoization recomputes on each tick. + const [now, setNow] = useState(() => Date.now()); + // rows created moments ago tick every 2s so their age counts smoothly + // right after an action; stable tables settle to a 10s tick + const hasFresh = containers.some(c => { + const age = now - new Date(c.created).getTime(); + return age >= 0 && age < 90_000; + }); + useEffect(() => { + const id = setInterval(() => setNow(Date.now()), hasFresh ? 2000 : 10000); + return () => clearInterval(id); + }, [hasFresh]); + const getContName = (c: ContainerList) => useContainerId ? c.id : c.serviceName; const {sortField, sortOrder, handleSort} = useSort( @@ -103,10 +119,14 @@ export function ContainerTable( ), cell: (c) => ( - + {c.name} - + ), cell: (c) => ( - + - - - - {formatTimeAgo(new Date(c.created))} - - + + + {formatTimeAgo(new Date(c.created), now)} + + {new Date(c.created).toLocaleDateString()}{' '} {new Date(c.created).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' - })} {new Date(c.created).toLocaleDateString()} + })} - + ) @@ -224,21 +243,29 @@ export function ContainerTable( ), cell: (c) => ( - + e.stopPropagation()} > - {c.imageName.split(':')[0]} + {c.imageName.split(':')[0]} {c.updateAvailable && } @@ -259,25 +286,31 @@ export function ContainerTable( ), cell: (c) => ( - - - - + + + + + + ) }, IP: { getValue: (c) => c.IPAddress.length, - header: (_) => ADDRESS, + header: () => ADDRESS, cell: (c) => ( {c.IPAddress ? - + {formatIPAddr(c.IPAddress)} - : + : } ) @@ -338,7 +371,12 @@ export function ContainerTable( onClick={() => handleRowClick(getContName(c))} selected={selectedServices.includes(getContName(c))} key={c.id} - sx={{cursor: 'pointer', '&.Mui-selected': {bgcolor: 'primary.lighter'}}} + sx={{ + cursor: 'pointer', + '&:nth-of-type(odd)': {bgcolor: 'rgba(255,255,255,0.015)'}, + '& td': {borderColor: 'rgba(255,255,255,0.06)'}, + '&.Mui-selected': {bgcolor: 'primary.lighter'}, + }} > {Object.values(tableInfo).map((col, idx) => {col.cell(c)})} @@ -361,18 +399,19 @@ const headerStyles = { zIndex: 2, }; -const ActionBtn = ({icon, title, onClick}: { icon: any, title: string, onClick: () => void }) => ( +const ActionBtn = ({icon, title, onClick}: { icon: ReactNode, title: string, onClick: () => void }) => ( {icon} @@ -428,7 +467,11 @@ const StatusChip = ({status, health}: { status: string; health: string }) => { }; const formatPorts = (ports: Port[]) => { - if (!ports?.length) return ; + if (!ports?.length) return ( + + ); return ports // .sort((a, b) => a.public - b.public) .map((p, i) => ( @@ -437,11 +480,12 @@ const formatPorts = (ports: Port[]) => { component="span" sx={{ bgcolor: 'action.hover', - px: 0.5, + px: 0.6, py: 0.1, - borderRadius: 0.5, - border: '1px solid', - borderColor: 'divider' + borderRadius: 0.75, + fontFamily: 'monospace', + fontSize: '0.72rem', + whiteSpace: 'nowrap', }} > @@ -450,7 +494,11 @@ const formatPorts = (ports: Port[]) => { }; const formatIPAddr = (addrs: string[]) => { - if (!addrs?.length) return ; + if (!addrs?.length) return ( + + ); return addrs.map((addr, i) => ( { component="span" sx={{ bgcolor: 'action.hover', - px: 0.5, + px: 0.6, py: 0.1, - borderRadius: 0.5, - border: '1px solid', - borderColor: 'divider' + borderRadius: 0.75, + fontFamily: 'monospace', + fontSize: '0.72rem', + whiteSpace: 'nowrap', }} > @@ -470,7 +519,11 @@ const formatIPAddr = (addrs: string[]) => { href={`http://${addr}`} target="_blank" rel="noopener noreferrer" - sx={{color: 'info.main', textDecoration: 'none', '&:hover': {textDecoration: 'underline'}}} + sx={{ + color: 'text.secondary', + textDecoration: 'none', + '&:hover': {color: 'primary.main', textDecoration: 'underline'}, + }} > {addr} diff --git a/ui/src/pages/compose/components/container-stat-chart.tsx b/ui/src/pages/compose/components/container-stat-chart.tsx index 8046b6f9..df1c1516 100644 --- a/ui/src/pages/compose/components/container-stat-chart.tsx +++ b/ui/src/pages/compose/components/container-stat-chart.tsx @@ -1,114 +1,126 @@ -import {Box, Divider, LinearProgress, Paper, Stack, Typography} from "@mui/material"; +import {Box, ButtonBase, Divider, Paper, Stack, Tooltip, Typography} from "@mui/material"; import { Dns as ContainerIcon, ImportExport as NetworkIcon, Memory as MemoryIcon, + Pause as PauseIcon, + PlayArrow as PlayArrowIcon, + RestartAlt as RestartIcon, Speed as CpuIcon, - Storage as StorageIcon + Stop as StopIcon, + Storage as StorageIcon, + WarningAmber as WarningIcon, } from "@mui/icons-material"; -import {type ContainerStats} from "../../../gen/docker/v1/docker_pb"; -import {formatBytes, getUsageColor} from "../../../lib/editor.ts"; -import type {ReactNode} from "react"; +import {formatBytes} from "../../../lib/editor.ts"; +import {type ReactNode} from "react"; +import {type AggregateSnapshot, type HostStatsView} from "../../../hooks/docker-containers-stats.ts"; +import Sparkline from "../../../components/sparkline.tsx"; +import {statsTheme as t} from "./stats-theme.ts"; + +// per-state container counts for the status strip +export interface StateCounts { + total: number; + running: number; + stopped: number; + paused: number; + restarting: number; + unhealthy: number; +} + +export type ContainerStateFilter = 'running' | 'stopped' | 'paused' | 'restarting' | 'unhealthy'; interface AggregateStatsProps { - containers: ContainerStats[]; - loading?: boolean; + // computed once per completed refresh cycle — null until the first + // cycle lands, so the header never renders half-updated totals + aggregates: AggregateSnapshot | null; + // when set (general view), CPU and memory show the real host usage + // Dockhand-style instead of summing container numbers; stack views + // keep the per-container aggregation + hostStats?: HostStatsView | null; + // authoritative state counts (from the event-driven container list); + // when provided they replace the cycle-based aggregate counts, which + // refresh more slowly + states?: StateCounts | null; + stateFilters?: ContainerStateFilter[]; + onStateFilterChange?: (filter: ContainerStateFilter | null, additive: boolean) => void; + // render without the outer card (the caller embeds the band in its own + // frame, e.g. merged with the toolbar) + bare?: boolean; } -function AggregateStats({containers}: AggregateStatsProps) { - const totals = containers.reduce((acc, curr) => { - acc.cpu += curr.cpuUsage; - acc.memUsed += Number(curr.memoryUsage); - acc.memLimit += Number(curr.memoryLimit); - acc.netRx += Number(curr.networkRx); - acc.netTx += Number(curr.networkTx); - acc.diskR += Number(curr.blockRead); - acc.diskW += Number(curr.blockWrite); - return acc; - }, { - cpu: 0, memUsed: 0, memLimit: 0, - netRx: 0, netTx: 0, diskR: 0, diskW: 0 - }); - - const memPercent = totals.memLimit > 0 ? (totals.memUsed / totals.memLimit) * 100 : 0; - const activeContainers = containers.length; +// load stays default-colored while calm, then warns +const cpuValueColor = (cpu: number) => + cpu < 50 ? t.text : cpu < 85 ? '#ffb74d' : '#ef5350'; + +function AggregateStats({aggregates, hostStats, states, stateFilters = [], onStateFilterChange, bare = false}: AggregateStatsProps) { + const memPercent = hostStats + ? (hostStats.memTotal > 0 ? (hostStats.memUsed / hostStats.memTotal) * 100 : 0) + : (aggregates && aggregates.memLimit > 0 ? (aggregates.memUsed / aggregates.memLimit) * 100 : 0); + const cpu = hostStats ? hostStats.cpuPercent : (aggregates?.cpu ?? 0); + const cpuReady = hostStats ? true : aggregates !== null; + const memUsed = hostStats ? hostStats.memUsed : aggregates?.memUsed; + const memCeil = hostStats ? hostStats.memTotal : aggregates?.memLimit; return ( } - sx={{width: '100%', overflowX: 'auto'}} + spacing={2.5} + divider={} + sx={{width: '100%', alignItems: 'stretch'}} > - {/* Container Count */} - } - label="Containers" - value={activeContainers.toString()} - subValue="Active Instances" - /> + {/* per-state breakdown, wraps onto more lines when narrow */} + - {/* Total CPU Load */} - } - label="Total CPU" - value={`${totals.cpu.toFixed(1)}%`} - subValue="Cumulative Load" + {/* charted tiles: value block + a chart that takes the room */} + } + label={hostStats ? "Host CPU" : "Total CPU"} + value={cpuReady ? `${cpu.toFixed(1)}%` : '–'} + valueColor={cpuValueColor(cpu)} + sub={hostStats && hostStats.cpus > 0 ? `${hostStats.cpus} cores` : ''} + data={hostStats ? hostStats.cpuHistory : aggregates?.cpuHistory ?? []} + color={t.cpuLine} /> - {/* Memory Aggregation */} - - - - - Aggregate Memory - - - - {formatBytes(totals.memUsed)} - - - - - {memPercent.toFixed(1)}% of total limits - - - + } + label={hostStats ? "Host Memory" : "Memory"} + value={memUsed !== undefined ? formatBytes(memUsed) : '–'} + sub={memCeil && memCeil > 0 + ? `${memPercent.toFixed(1)}% of ${formatBytes(memCeil)}` + : ''} + data={hostStats ? hostStats.memHistory : aggregates?.memHistory ?? []} + color={t.memLine} + /> - {/* Network Totals */} - } + } label="Network I/O" - value={formatBytes(totals.netRx + totals.netTx)} - subValue={`↓${formatBytes(totals.netRx)} ↑${formatBytes(totals.netTx)}`} + value={aggregates ? `↓ ${formatBytes(aggregates.netRx)}` : '–'} + sub={aggregates ? `↑ ${formatBytes(aggregates.netTx)}` : ''} + tooltip={aggregates ? `Total ${formatBytes(aggregates.netRx + aggregates.netTx)}` : ''} /> - {/* Disk Totals */} - } + } label="Block I/O" - value={formatBytes(totals.diskR + totals.diskW)} - subValue={`R: ${formatBytes(totals.diskR)} W: ${formatBytes(totals.diskW)}`} + value={aggregates ? `R ${formatBytes(aggregates.diskR)}` : '–'} + sub={aggregates ? `W ${formatBytes(aggregates.diskW)}` : ''} + tooltip={aggregates ? `Total ${formatBytes(aggregates.diskR + aggregates.diskW)}` : ''} /> @@ -117,30 +129,186 @@ function AggregateStats({containers}: AggregateStatsProps) { export default AggregateStats; -/** - * Reusable sub-component for individual statistics - */ -function StatItem({icon, label, value, subValue}: { +// Dockhand-style status strip over two fixed rows: totals and the common +// states first (total / running / stopped), the exceptional states below +// (paused / restarting / unhealthy). +function StateTile({counts, active, onFilter}: { + counts: StateCounts | null, + active: ContainerStateFilter[], + onFilter?: (filter: ContainerStateFilter | null, additive: boolean) => void, +}) { + const rows: { icon: ReactNode, count: number, color: string, title: string, filter: ContainerStateFilter | null }[][] = counts ? [ + [ + {icon: , count: counts.total, color: t.text, title: 'All containers', filter: null}, + {icon: , count: counts.running, color: '#66bb6a', title: 'Running', filter: 'running'}, + {icon: , count: counts.stopped, color: '#9e9e9e', title: 'Stopped', filter: 'stopped'}, + ], + [ + {icon: , count: counts.paused, color: '#ffb74d', title: 'Paused', filter: 'paused'}, + {icon: , count: counts.restarting, color: '#4db6ac', title: 'Restarting', filter: 'restarting'}, + {icon: , count: counts.unhealthy, color: '#ef5350', title: 'Unhealthy', filter: 'unhealthy'}, + ], + ] : []; + + return ( + + {counts ? ( + + {rows.map((row, i) => ( + + {row.map(e => ( + + onFilter?.(e.filter, event.ctrlKey || event.metaKey)} + sx={{ + display: 'flex', + gap: 0.25, + alignItems: 'center', + color: e.color, + px: 0.35, + py: 0.25, + mx: -0.35, + my: -0.25, + borderRadius: 0.75, + outline: e.filter !== null && active.includes(e.filter) ? `1px solid ${e.color}` : 'none', + bgcolor: e.filter !== null && active.includes(e.filter) ? 'rgba(255,255,255,0.08)' : 'transparent', + cursor: onFilter && (e.filter === null || e.count > 0) ? 'pointer' : 'default', + '&:hover': onFilter ? {bgcolor: 'rgba(255,255,255,0.1)'} : {}, + '&.Mui-disabled': {color: e.color, opacity: 0.38}, + }}> + {e.icon} + + {e.count} + + + + ))} + + ))} + + ) : ( + <> + } label="Containers"/> + + – + + + )} + + ); +} + +function TileLabel({icon, label}: { icon: ReactNode, label: string }) { + return ( + + {icon} + + {label} + + + ); +} + +// label + a single value line (optional dim inline detail); used for the +// counters that need no chart, kept narrow so charted tiles get the room +function CompactTile({icon, label, value, sub, tooltip, grow = true}: { icon: ReactNode, label: string, value: string, - subValue: string + sub?: string, + tooltip?: string, + grow?: boolean, }) { - return ( - - - {icon} - - {label} + const body = ( + + + + + {value} + {sub && ( + + {sub} + + )} - - {value} - - - {subValue} - ); + + return tooltip ? {body} : body; } +// label + value (and detail below it) with a chart filling the tile's +// remaining width — CPU and memory read at a glance, same size for both. +// The value block has a fixed width so both sparklines end up the same +// length regardless of how wide the numbers are. +function ChartTile({icon, label, value, valueColor, sub, data, color}: { + icon: ReactNode, + label: string, + value: string, + valueColor?: string, + sub: string, + data: number[], + color: string, +}) { + return ( + + + + + + {value} + + + {sub} + + + + + + + + ); +} diff --git a/ui/src/pages/compose/components/container-stat-table.tsx b/ui/src/pages/compose/components/container-stat-table.tsx index d93b9da5..3a121507 100644 --- a/ui/src/pages/compose/components/container-stat-table.tsx +++ b/ui/src/pages/compose/components/container-stat-table.tsx @@ -1,10 +1,10 @@ import { Box, + Button, CircularProgress, - Divider, - Fade, IconButton, Paper, + Popover, Skeleton, Stack, Table, @@ -14,35 +14,34 @@ import { TableHead, TableRow, TableSortLabel, + Tooltip, Typography } from "@mui/material" -import { - Article as ReadIcon, - Check as CheckIcon, - ContentCopy, - Edit as WriteIcon, - GetApp as DownloadIcon, - Publish as UploadIcon, - Terminal as TerminalIcon -} from "@mui/icons-material" -import {type ContainerStats, ORDER, SORT_FIELD} from "../../../gen/docker/v1/docker_pb" +import {Check as CheckIcon, ContentCopy, RestartAlt as RestartIcon} from "@mui/icons-material" +import {useState, useSyncExternalStore} from "react" +import {type ContainerStats, DockerService, ORDER, SORT_FIELD} from "../../../gen/docker/v1/docker_pb" import {formatBytes, getUsageColor} from "../../../lib/editor.ts"; import scrollbarStyles from "../../../components/scrollbar-style.tsx"; import {useCopyButton} from "../../../hooks/copy.ts"; +import {callRPC, useHostClient} from "../../../lib/api.ts"; +import {useSnackbar} from "../../../hooks/snackbar.ts"; +import {type StatHistory} from "../../../hooks/docker-containers-stats.ts"; +import Sparkline from "../../../components/sparkline.tsx"; +import {healthColors, stateBadges, statsTheme as t} from "./stats-theme.ts"; interface ContainersTableProps { activeSortField: SORT_FIELD order: ORDER onFieldClick: (field: SORT_FIELD, orderBy: ORDER) => void containers: ContainerStats[] + history: Map placeHolders?: number loading: boolean } export function ContainerStatTable({ - containers, onFieldClick, activeSortField, order, loading, placeHolders = 5 + containers, history, onFieldClick, activeSortField, order, loading, placeHolders = 5 }: ContainersTableProps) { - const {copiedId, handleCopy} = useCopyButton() const isEmpty = !loading && containers.length === 0 const handleSortRequest = (field: SORT_FIELD) => { @@ -51,31 +50,51 @@ export function ContainerStatTable({ onFieldClick(field, activeSortField !== field ? ORDER.DSC : (isAsc ? ORDER.DSC : ORDER.ASC)) } + const headerSx = { + py: 1.2, + bgcolor: t.header, + color: t.textDim, + borderBottom: `1px solid ${t.border}`, + whiteSpace: 'nowrap' as const, + zIndex: 2, + } + + const sortLabelSx = { + fontWeight: 700, + fontSize: '0.72rem', + textTransform: 'uppercase' as const, + letterSpacing: '0.06em', + color: `${t.textDim} !important`, + '&.Mui-active': {color: `${t.text} !important`}, + '& .MuiTableSortLabel-icon': {color: `${t.textDim} !important`}, + } + const createSortHeader = (field: SORT_FIELD, label: string, align: 'left' | 'center' | 'right' = 'left') => ( - + handleSortRequest(field)} - sx={{ - fontWeight: 700, - fontSize: '0.75rem', - textTransform: 'uppercase', - letterSpacing: '0.05em' - }} + sx={sortLabelSx} > {label} ) + const plainHeader = (label: string, align: 'left' | 'center' | 'right' = 'left') => ( + + + {label} + + + ) + return ( - +
- {createSortHeader(SORT_FIELD.NAME, 'Container')} - {createSortHeader(SORT_FIELD.CPU, 'CPU Usage', 'center')} + {createSortHeader(SORT_FIELD.NAME, 'Name')} + {plainHeader('State')} + {plainHeader('Health', 'center')} + {createSortHeader(SORT_FIELD.STARTED, 'Uptime')} + {plainHeader('Restarts', 'center')} + {createSortHeader(SORT_FIELD.CPU, 'CPU')} {createSortHeader(SORT_FIELD.MEM, 'Memory')} - - - NETWORK (RX/TX) - DISK (W/R) - - + {createSortHeader(SORT_FIELD.NETWORK_RX, 'Net I/O')} + {createSortHeader(SORT_FIELD.DISK_W, 'Disk I/O')} + {plainHeader('IP')} + {loading ? ( [...Array(placeHolders)].map((_, i) => ( - - - - - + + {[...Array(11)].map((_, j) => ( + + + + ))} )) ) : isEmpty ? ( - - No statistics available + + + No statistics available + ) : ( containers.map((container) => ( - - - - - - - - {container.name} - - - - {container.id.substring(0, 12)} - - handleCopy(container.id)} - sx={{p: 0.2}}> - {copiedId === container.id ? - : - } - - - - - - - - - - - }> - - - - - - + )) )} @@ -182,67 +161,346 @@ export function ContainerStatTable({ ) } -function CPUStat({value}: { value: number }) { - const color = getUsageColor(value); - const circleSize = 48; +// Rows redraw on every poll tick — they are text and a ~40-point SVG, cheap +// at a 2.5s cadence, and skipping renders froze the charts. +function StatRow({stat, hist}: { + stat: ContainerStats, + hist?: StatHistory, +}) { + const running = stat.state === 'running'; + // row seeded from the container list, metrics not read yet + const pending = stat.cpuUsage < 0; + const memLimit = Number(stat.memoryLimit); + const memUsage = Number(stat.memoryUsage); + const memPercent = memLimit > 0 ? (memUsage / memLimit) * 100 : 0; + + const cellSx = { + py: 1, + borderBottom: `1px solid ${t.border}`, + color: t.text, + }; + return ( - - - - - - {value.toFixed(0)}% + + + + + + + + + + + + + + + 0 ? t.diskWrite : t.textDim, + fontWeight: stat.restartCount > 0 ? 700 : 400, + }}> + {stat.restartCount > 0 ? stat.restartCount : '–'} - - - ) + + + + + + 0 ? `/ ${formatBytes(memLimit)}` : ''} + textColor={pending ? t.textDim : getUsageColor(memPercent)} + data={pending ? [] : hist?.mem} + lineColor={t.memLine} + /> + + + {pending ? ( + + ) : ( + + )} + + + {pending ? ( + + ) : ( + + )} + + + + + + + + + ); } -export function UsageBar({usage, limit}: { usage: number, limit: number }) { - const percent = limit > 0 ? (usage / limit) * 100 : 0 - const color = getUsageColor(percent) - +function NameCell({stat}: { stat: ContainerStats }) { + const {copiedId, handleCopy} = useCopyButton() return ( - - - - {percent.toFixed(1)}% - - - {formatBytes(usage)} + + + {stat.name} + + + + {stat.id.substring(0, 12)} + handleCopy(stat.id)} sx={{p: 0.2, color: t.textDim}}> + {copiedId === stat.id ? + : + } + + {stat.image && ( + + + {stat.image} + + + )} - - - - ) + ); } -const RWData = ({up, down, type}: { up: number; down: number, type: 'net' | 'disk' }) => { - const UpIcon = type === 'net' ? UploadIcon : ReadIcon; - const DownIcon = type === 'net' ? DownloadIcon : WriteIcon; +function StateBadge({state}: { state: string }) { + const badge = stateBadges[state] ?? {bg: 'rgba(71,85,105,0.55)', fg: '#cbd5e1', label: state || 'unknown'}; + return ( + + {state === 'running' ? '▶' : '■'} {badge.label} + + ); +} +// health is a colored dot only (like the stack list), with the status text in +// a tooltip — the column stays narrow +function HealthCell({health}: { health: string }) { + if (!health) { + return ; + } + const color = healthColors[health] ?? t.textDim; return ( - - - - - {formatBytes(down)} - - - - - - {formatBytes(up)} - - + + + + ); +} + +// MetricCell shows the live value above a small sparkline of its history, +// scaled like the Dockhand cards (zero-based, window max): a 3% CPU wiggle +// fills the chart, a container at 99% of its memory limit draws along the top. +function MetricCell({text, subText, textColor, data, lineColor}: { + text: string; + subText?: string; + textColor: string; + data?: number[]; + lineColor: string; +}) { + return ( + + + {text} + {subText && ( + + {' '}{subText} + + )} + + + + ); +} + +function PairCell({aLabel, aValue, aColor, bLabel, bValue, bColor}: { + aLabel: string; aValue: number; aColor: string; + bLabel: string; bValue: number; bColor: string; +}) { + return ( + + + {aLabel} + {' '}{formatBytes(aValue)} + + + {bLabel} + {' '}{formatBytes(bValue)} + - ) + ); +} + +function IPCell({ips}: { ips: string[] }) { + if (!ips || ips.length === 0) { + return ; + } + const [first, ...rest] = ips; + return ( + + + {first}{rest.length > 0 ? ` +${rest.length}` : ''} + + + ); +} + +// formatUptime renders how long ago a container started, from an RFC3339 string: +// "3d 4h", "5h 12m", "8m", "42s". Empty / zero-time (never started) -> "—". +// `now` is passed in so the value can tick live (see useNow). +function formatUptime(startedAt: string, now: number): string { + if (!startedAt || startedAt.startsWith('0001')) return '—'; + const start = Date.parse(startedAt); + if (isNaN(start)) return '—'; + let secs = Math.floor((now - start) / 1000); + if (secs < 0) secs = 0; + const d = Math.floor(secs / 86400); + const h = Math.floor((secs % 86400) / 3600); + const m = Math.floor((secs % 3600) / 60); + if (d > 0) return `${d}d ${h}h`; + if (h > 0) return `${h}h ${m}m`; + if (m > 0) return `${m}m`; + return `${secs}s`; +} + +// A single shared 1s ticker drives every uptime cell so they count up live, +// without re-rendering the rest of the table (only cells calling useNow update). +// The interval only runs while at least one cell is mounted. +let tickNow = Date.now(); +const tickListeners = new Set<() => void>(); +let tickTimer: ReturnType | null = null; + +function subscribeTick(cb: () => void): () => void { + tickListeners.add(cb); + if (tickTimer === null) { + tickTimer = setInterval(() => { + tickNow = Date.now(); + tickListeners.forEach((l) => l()); + }, 1000); + } + return () => { + tickListeners.delete(cb); + if (tickListeners.size === 0 && tickTimer !== null) { + clearInterval(tickTimer); + tickTimer = null; + } + }; +} + +function useNow(): number { + return useSyncExternalStore(subscribeTick, () => tickNow); +} + +function UptimeCell({startedAt}: { startedAt: string }) { + const now = useNow(); + const running = Boolean(startedAt) && !startedAt.startsWith('0001'); + const absolute = running ? new Date(startedAt).toLocaleString() : 'not running'; + return ( + + + {formatUptime(startedAt, now)} + + + ); +} + +// RestartButton restarts a single container, gated behind a small confirm popover +// so it can't fire on an accidental click while scanning the table. +function RestartButton({containerId, name}: { containerId: string; name: string }) { + const dockerService = useHostClient(DockerService); + const {showSuccess, showError} = useSnackbar(); + const [anchorEl, setAnchorEl] = useState(null); + const [busy, setBusy] = useState(false); + + const doRestart = async () => { + setAnchorEl(null); + setBusy(true); + const {err} = await callRPC(() => dockerService.containerRestart({containerIds: [containerId]})); + setBusy(false); + if (err) { + showError(`Failed to restart ${name}: ${err}`); + } else { + showSuccess(`Restarting ${name}`); + } + }; + + return ( + <> + + + setAnchorEl(e.currentTarget)}> + {busy ? : } + + + + setAnchorEl(null)} + anchorOrigin={{vertical: 'bottom', horizontal: 'right'}} + transformOrigin={{vertical: 'top', horizontal: 'right'}} + > + + + Restart {name}? + + + + + + + + + ); } diff --git a/ui/src/pages/compose/components/docker-command-button.tsx b/ui/src/pages/compose/components/docker-command-button.tsx new file mode 100644 index 00000000..80365342 --- /dev/null +++ b/ui/src/pages/compose/components/docker-command-button.tsx @@ -0,0 +1,105 @@ +import {useState} from "react"; +import {Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, TextField, Tooltip} from "@mui/material"; +import TerminalIcon from '@mui/icons-material/Terminal'; +import {useHostClient} from "../../../lib/api.ts"; +import {DockerService} from "../../../gen/docker/v1/docker_pb.ts"; +import {makeID, type TabTerminal, useTerminalAction, useTerminalTabs} from "../state/terminal.tsx"; + +const LAST_COMMAND_KEY = 'dockman-last-docker-command'; + +// Runs a one-off docker CLI command on the current host (quick container +// tests: docker run --rm ...); output streams into a bottom-panel terminal +export function DockerCommandButton() { + const dockerService = useHostClient(DockerService); + const [open, setOpen] = useState(false); + const [command, setCommand] = useState(() => localStorage.getItem(LAST_COMMAND_KEY) ?? "docker run --rm "); + + const runCommand = () => { + const cmd = command.trim(); + if (!cmd) return; + localStorage.setItem(LAST_COMMAND_KEY, cmd); + setOpen(false); + + const stream = dockerService.dockerCommand({command: cmd}); + + useTerminalAction.getState().open(); + const tabsStore = useTerminalTabs.getState(); + const key = `docker-command:${makeID(6)}`; + const shortTitle = cmd.length > 40 ? `${cmd.slice(0, 40)}…` : cmd; + + const tab: TabTerminal = { + id: makeID(), + title: shortTitle, + interactive: false, + onClose: () => { + }, + onTerminal: term => { + const consume = async () => { + try { + for await (const item of stream) { + term.write(item.message); + } + term.write('\r\n\x1b[32m*** command finished ***\x1b[0m\r\n'); + } catch (error: unknown) { + const err = error instanceof Error ? error.message : String(error); + term.write(`\r\n\x1b[31m${err}\x1b[0m\r\n`); + } + }; + void consume(); + }, + }; + tabsStore.addTab(key, tab); + }; + + return ( + <> + + + + + setOpen(false)} fullWidth maxWidth="md"> + Run a docker command + + + setCommand(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); + runCommand(); + } + }} + slotProps={{input: {sx: {fontFamily: 'monospace', fontSize: '0.9rem'}}}} + helperText="Only the docker binary is allowed; output opens in the bottom panel" + /> + + + + + + + + + ); +} diff --git a/ui/src/pages/compose/components/editor-commit-list-old.tsx b/ui/src/pages/compose/components/editor-commit-list-old.tsx index 6bdf9dfb..389cf0ba 100644 --- a/ui/src/pages/compose/components/editor-commit-list-old.tsx +++ b/ui/src/pages/compose/components/editor-commit-list-old.tsx @@ -61,7 +61,9 @@ export function GitCommitListOld({selectedFile, selectedCommit, chooseCommit}: C if (commits.length === 0) { return ( - No commit history. + No commit history. ); } diff --git a/ui/src/pages/compose/components/editor-commit-list.tsx b/ui/src/pages/compose/components/editor-commit-list.tsx index e30b8d4d..f742d098 100644 --- a/ui/src/pages/compose/components/editor-commit-list.tsx +++ b/ui/src/pages/compose/components/editor-commit-list.tsx @@ -46,7 +46,7 @@ export function EditorCommitList({selectedFile, selectedCommit, chooseCommit}: C setCommits(val?.commits ?? []) } setLoading(false) - }, [gitClient, selectedFile]) + }, [gitClient, selectedFile, showError]) useEffect(() => { fetchCommitCallback().then() @@ -104,9 +104,11 @@ export function EditorCommitList({selectedFile, selectedCommit, chooseCommit}: C if (Object.keys(groupedCommits).length === 0) { return ( - No commit history found for this file. + No commit history found for this file. - ) + ); } return ( @@ -170,4 +172,4 @@ export function EditorCommitList({selectedFile, selectedCommit, chooseCommit}: C ))} ) -} \ No newline at end of file +} diff --git a/ui/src/pages/compose/components/editor-common.tsx b/ui/src/pages/compose/components/editor-common.tsx index ff732c85..13200d0f 100644 --- a/ui/src/pages/compose/components/editor-common.tsx +++ b/ui/src/pages/compose/components/editor-common.tsx @@ -1,8 +1,8 @@ import {MonacoEditor} from "./editor.tsx"; -import {useEffect, useState} from "react"; +import {useCallback, useEffect, useRef, useState} from "react"; import {useSnackbar} from "../../../hooks/snackbar.ts"; import {Alert, AlertTitle, Box, Button, CircularProgress, Link, Typography} from '@mui/material'; -import {ErrorOutline, WarningAmber} from '@mui/icons-material'; +import {ErrorOutlined, WarningAmber} from '@mui/icons-material'; import {type SaveState, useSaveStatus} from "../hooks/status-hook.tsx"; import {ErrFileNotSupported} from "../../../context/file-context.tsx"; @@ -21,18 +21,18 @@ function EditorCommon({filename, setFileSaveStatus, saveFile, getFile}: TextEdit const [contents, setContents] = useState(""); const [loading, setLoading] = useState(true); const [err, setErr] = useState(""); + const loadRequest = useRef(0); const {status, handleContentChange} = useSaveStatus(500, filename); - const refreshFile = async () => { - await getFile(filename) - } - - const loadFile = async () => { + const loadFile = useCallback(async () => { + const request = ++loadRequest.current; setErr("") setLoading(true) const {contents, err} = await getFile(filename) + if (request !== loadRequest.current) return; + if (err) { setErr(err) } else { @@ -40,9 +40,9 @@ function EditorCommon({filename, setFileSaveStatus, saveFile, getFile}: TextEdit } setLoading(false); - }; + }, [filename, getFile]); - const saveContents = async (newContent: string): Promise => { + const saveContents = useCallback(async (newContent: string): Promise => { const err = await saveFile(filename, newContent); if (err) { showError(`Could not save contents: ${err}`); @@ -50,20 +50,20 @@ function EditorCommon({filename, setFileSaveStatus, saveFile, getFile}: TextEdit } else { return 'success' } - }; + }, [filename, saveFile, showError]); useEffect(() => { setFileSaveStatus(status) - }, [status]); + }, [setFileSaveStatus, status]); useEffect(() => { loadFile().then(); - }, []); + }, [loadFile]); - const onContentChange = (value: string | undefined) => { - if (!value) return; + const onContentChange = useCallback((value: string | undefined) => { + if (value === undefined) return; handleContentChange(value, saveContents) - } + }, [handleContentChange, saveContents]) if (loading) { return ( @@ -76,7 +76,9 @@ function EditorCommon({filename, setFileSaveStatus, saveFile, getFile}: TextEdit gap: 2 }}> - Loading {filename}... + Loading {filename}... ); } @@ -89,7 +91,7 @@ function EditorCommon({filename, setFileSaveStatus, saveFile, getFile}: TextEdit : } @@ -97,7 +99,10 @@ function EditorCommon({filename, setFileSaveStatus, saveFile, getFile}: TextEdit } return ( - + // clip Monaco overlays (e.g. the sticky scroll band, which keeps a + // stale width when the widget panel resizes the editor) so they can + // never paint over neighboring panels + void }) => { } + icon={} sx={{borderRadius: 2, bgcolor: 'background.paper'}} > @@ -150,7 +155,12 @@ const BinaryErrView = ({err}: { err: string }) => { Dockman has determined that this is not a valid text file. To prevent accidental corruption, editing binary files is not allowed. - + If you believe this file should be editable,{' '} { {err} - ) + ); } export default EditorCommon; diff --git a/ui/src/pages/compose/components/editor.tsx b/ui/src/pages/compose/components/editor.tsx index 8b9f5a3c..b3800e20 100644 --- a/ui/src/pages/compose/components/editor.tsx +++ b/ui/src/pages/compose/components/editor.tsx @@ -6,6 +6,7 @@ import {callRPC, useHostClient} from "../../../lib/api.ts"; import {useSnackbar} from "../../../hooks/snackbar.ts"; import {useTabs, useTabsStore} from "../../../context/tab-context.tsx"; import {FileService} from "../../../gen/files/v1/files_pb.ts"; +import {useConfig} from "../../../hooks/config.ts"; interface MonacoEditorProps { selectedFile: string; @@ -26,11 +27,49 @@ export function MonacoEditor( const saveLineNum = useSaveLineNum() const [mounted, setMounted] = useState(false); + // bumped on every editor instance creation: the component remounts per + // file (key={selectedFile}) while `mounted` stays true, so effects that + // must re-attach to the new instance depend on this counter instead + const [editorGen, setEditorGen] = useState(0); const {setTabDetails} = useTabs() + const {dockYaml} = useConfig() + const scrollPastEnd = dockYaml?.editorPage?.scrollPastEnd ?? false + + // dockman.yml editor.scrollPastEnd lets long files scroll half a viewport + // past their end (the last line stops at mid-view, not at the top), via a + // bottom padding applied only while the file is taller than the viewport: + // short files never scroll past their end + useEffect(() => { + const editor = editorRef.current; + if (!editor) return; + + if (!scrollPastEnd) { + editor.updateOptions({padding: {bottom: 0}}); + return; + } + + const apply = () => { + const lineHeight = editor.getOption(monacoEditor.editor.EditorOption.lineHeight); + const lineCount = editor.getModel()?.getLineCount() ?? 0; + const height = editor.getLayoutInfo().height; + const overflows = lineCount * lineHeight > height; + editor.updateOptions({padding: {bottom: overflows ? Math.round(height / 2) : 0}}); + }; + + apply(); + const contentSub = editor.onDidChangeModelContent(apply); + const layoutSub = editor.onDidLayoutChange(apply); + return () => { + contentSub.dispose(); + layoutSub.dispose(); + }; + }, [scrollPastEnd, editorGen]); + const handleEditorDidMount = (editor: monacoEditor.editor.IStandaloneCodeEditor, monaco: Monaco) => { editorRef.current = editor; setMounted(true); + setEditorGen(gen => gen + 1); editor.focus(); editor.addCommand( @@ -80,34 +119,36 @@ export function MonacoEditor( const model = editorRef.current.getModel(); if (!model) return; - // console.log("clearing stack for initial load"); model.pushStackElement(); model.setValue(fileContent); - model.onDidChangeContent(() => { + const contentSubscription = model.onDidChangeContent(() => { handleEditorChange(model.getValue()); }); const tab = useTabsStore.getState().allTabs[selectedFile]; - if (!tab) return; - const {row, col} = tab; - - // Clamp row/column to model size - const lineNumber = Math.min(row, model.getLineCount()); - const column = Math.min(col, model.getLineMaxColumn(lineNumber)); - - editorRef.current.setPosition({lineNumber, column}); - const padding = 5; - editorRef.current.revealRangeInCenter({ - startLineNumber: Math.max(1, lineNumber - padding), - startColumn: 1, - endLineNumber: lineNumber + padding, - endColumn: 1, - }); + if (tab) { + const {row, col} = tab; + + // Clamp row/column to model size + const lineNumber = Math.min(row, model.getLineCount()); + const column = Math.min(col, model.getLineMaxColumn(lineNumber)); + + editorRef.current.setPosition({lineNumber, column}); + const padding = 5; + editorRef.current.revealRangeInCenter({ + startLineNumber: Math.max(1, lineNumber - padding), + startColumn: 1, + endLineNumber: lineNumber + padding, + endColumn: 1, + }); + } + + return () => contentSubscription.dispose(); // do not add tabs as dependencies // it will mess with the editor typing // resetting cursor position when the tab - }, [fileContent, selectedFile, mounted]); + }, [editorGen, fileContent, handleEditorChange, mounted, selectedFile]); return ( ); @@ -139,7 +188,6 @@ function useSaveLineNum(debounceMs: number = 200) { debounceTimeout.current = setTimeout(() => { onSave(value); - // console.log("Saving cursor position: ", value) }, debounceMs); }, [debounceMs] diff --git a/ui/src/pages/compose/components/exec-terminal-panel.tsx b/ui/src/pages/compose/components/exec-terminal-panel.tsx new file mode 100644 index 00000000..adeae04f --- /dev/null +++ b/ui/src/pages/compose/components/exec-terminal-panel.tsx @@ -0,0 +1,136 @@ +import {Alert, Box, Button, CircularProgress, IconButton, Stack, TextField, Tooltip, Typography} from '@mui/material'; +import {Check, ContentCopy, DeleteSweep, Terminal as TerminalIcon} from '@mui/icons-material'; +import {FitAddon} from '@xterm/addon-fit'; +import type {Terminal} from '@xterm/xterm'; +import {useEffect, useMemo, useRef, useState} from 'react'; +import AppTerminal from './logs-terminal.tsx'; +import {createTab, type TabTerminal} from '../state/terminal.tsx'; +import {useContainerExecOptionsUrl, useContainerExecWsUrl} from '../../../lib/api.ts'; + +const sizes = [10, 12, 14, 16]; + +export default function ExecTerminalPanel({tab, isActive}: {tab: TabTerminal; isActive: boolean}) { + const fit = useRef(new FitAddon()); + const xterm = useRef(null); + const createExecUrl = useContainerExecWsUrl(); + const createOptionsUrl = useContainerExecOptionsUrl(); + const session = tab.execSession!; + const initialUserChoice = session.user === '' ? 'context' : ['root', 'nobody'].includes(session.user) ? session.user : 'other'; + const [shells, setShells] = useState(null); + const [shellError, setShellError] = useState(''); + const [shell, setShell] = useState(session.shell); + const [userChoice, setUserChoice] = useState(initialUserChoice); + const [otherUser, setOtherUser] = useState(initialUserChoice === 'other' ? session.user : ''); + const [connected, setConnected] = useState(true); + const [fontSize, setFontSize] = useState(() => Number(localStorage.getItem('dockman-exec-fontsize')) || 12); + const [copied, setCopied] = useState(false); + + useEffect(() => { + const controller = new AbortController(); + setShells(null); + setShellError(''); + fetch(createOptionsUrl(session.containerID), {signal: controller.signal}) + .then(async response => { + if (!response.ok) throw new Error(await response.text() || `HTTP ${response.status}`); + return response.json() as Promise<{shells?: string[]}>; + }) + .then(result => { + const available = result.shells ?? []; + setShells(available); + setShell(current => available.includes(current) ? current : available[0] ?? ''); + }) + .catch(error => { + if (error instanceof Error && error.name !== 'AbortError') { + setShellError(error.message); + setShells([]); + } + }); + return () => controller.abort(); + }, [createOptionsUrl, session.containerID]); + + const execUser = userChoice === 'context' ? '' : userChoice === 'other' ? otherUser.trim() : userChoice; + const terminal = useMemo(() => connected + ? createTab(createExecUrl(session.containerID, shell, undefined, execUser), tab.title, true) + : null, + [connected, createExecUrl, execUser, session.containerID, shell, tab.title]); + const controlled = useMemo(() => terminal ? ({ + ...terminal, + onTerminal: (term: Terminal) => { + xterm.current = term; + terminal.onTerminal(term); + }, + onClose: () => { + xterm.current = null; + terminal.onClose(); + }, + }) : null, [terminal]); + + const copy = async () => { + const term = xterm.current; + if (!term) return; + const selected = term.getSelection(); + const lines: string[] = []; + if (!selected) { + const buffer = term.buffer.active; + for (let i = 0; i < buffer.length; i++) lines.push(buffer.getLine(i)?.translateToString(true) ?? ''); + } + await navigator.clipboard.writeText(selected || lines.join('\n').replace(/\n+$/, '')); + setCopied(true); + setTimeout(() => setCopied(false), 1200); + }; + const changeFont = (value: number) => { + localStorage.setItem('dockman-exec-fontsize', String(value)); + setFontSize(value); + }; + const toggleConnection = () => { + if (connected) { + setConnected(false); + return; + } + setConnected(true); + }; + const fieldSx = { + '& .MuiInputBase-root': {height: 28, fontSize: '0.68rem'}, + '& .Mui-disabled': {WebkitTextFillColor: 'rgba(255,255,255,0.55)'}, + }; + + return + + + {session.containerID.slice(0, 12)} + {shells === null ? : setShell(event.target.value)} slotProps={{select: {native: true}}} sx={{width: 135, ...fieldSx}}> + {shells.map(value => )} + } + setUserChoice(event.target.value)} + slotProps={{select: {native: true}}} sx={{width: 145, ...fieldSx}}> + + + + {userChoice === 'other' && setOtherUser(event.target.value)} + placeholder="UID or user" sx={{width: 105, ...fieldSx}}/>} + changeFont(Number(event.target.value))} + slotProps={{select: {native: true}}} sx={{width: 70, ...fieldSx}}> + {sizes.map(value => )} + + + xterm.current?.clear()}> + void copy()}> + {copied ? : } + + + {connected ? 'Interactive session' : 'Disconnected'} + + + {shellError && {shellError}} + {shells?.length === 0 && !shellError && No supported shell is available.} + + {controlled ? + : Choose a shell and connect.} + + ; +} diff --git a/ui/src/pages/compose/components/file-alias-selector.tsx b/ui/src/pages/compose/components/file-alias-selector.tsx index 434d78f0..157b9883 100644 --- a/ui/src/pages/compose/components/file-alias-selector.tsx +++ b/ui/src/pages/compose/components/file-alias-selector.tsx @@ -40,7 +40,9 @@ const AliasSelector = () => { '&:hover': {opacity: 1} }} > - + {alias} @@ -82,7 +84,7 @@ const AliasSelector = () => { primary={displayAlias} secondary={file.fullpath} slotProps={{ - primary: {fontWeight: 500}, + primary: {sx: {fontWeight: 500}}, secondary: { sx: { overflow: 'hidden', @@ -102,4 +104,4 @@ const AliasSelector = () => { ); }; -export default AliasSelector; \ No newline at end of file +export default AliasSelector; diff --git a/ui/src/pages/compose/components/file-item.tsx b/ui/src/pages/compose/components/file-item.tsx index 69fd2431..2cf9573b 100644 --- a/ui/src/pages/compose/components/file-item.tsx +++ b/ui/src/pages/compose/components/file-item.tsx @@ -11,7 +11,7 @@ import { MenuItem, Tooltip } from "@mui/material"; import {useLocation, useNavigate} from 'react-router-dom' -import React, {type MouseEvent, useEffect, useState} from 'react' +import React, {type MouseEvent, useCallback, useEffect, useRef, useState} from 'react' import {ExpandLess, ExpandMore, Folder} from '@mui/icons-material' import {Link as RouterLink} from "react-router"; import FileIcon, {DockerFolderIcon} from "./file-icon.tsx"; @@ -24,7 +24,7 @@ import {useSnackbar} from "../../../hooks/snackbar.ts"; import {useFileCreate} from "../dialogs/file-create.tsx"; import {useFileDelete} from "../dialogs/file-delete.tsx"; import {useFileRename} from "../dialogs/file-rename.tsx"; -import {useAliasStore, useHostStore, useOpenFiles} from "../state/files.ts"; +import {useAliasStore, useCompactMode, useFileDrag, useHostStore, useOpenFiles} from "../state/files.ts"; import {useConfig} from "../../../hooks/config.ts"; import {useComposeFileState} from "../state/status.ts"; import {getContextKey} from "../../../context/tab-context.tsx"; @@ -35,10 +35,34 @@ import {stripQueryParams} from "../../../lib/strings.ts"; export const useFileDnD = (entry: FsEntry) => { const [isDragOver, setIsDragOver] = useState(false); const {renameFile, uploadFilesFromPC} = useFiles(); + const setDragging = useFileDrag(state => state.setDragging); const handleDragStart = (e: React.DragEvent) => { e.dataTransfer.setData("sourcePath", entry.filename); e.dataTransfer.effectAllowed = "move"; + + // Use a small, controlled drag image rather than the browser's snapshot + // of the row. In compact mode the row is very short, which made the + // native ghost render as a too-wide / overflowing block. + const label = entry.filename.split('/').pop() || entry.filename; + const ghost = document.createElement('div'); + ghost.textContent = label; + ghost.style.cssText = + 'position:fixed;top:-1000px;left:-1000px;padding:4px 10px;' + + 'background:#2b2b2b;color:#fff;border:1px solid rgba(255,255,255,0.15);' + + 'border-radius:4px;font:13px sans-serif;white-space:nowrap;pointer-events:none;'; + document.body.appendChild(ghost); + e.dataTransfer.setDragImage(ghost, 12, 12); + // Remove once the browser has captured the drag image. + setTimeout(() => ghost.remove(), 0); + + // Signal a drag is in progress so the transient "drop to root" banner appears. + setDragging(true); + }; + + const handleDragEnd = () => { + // Always clear the flag when the drag ends (dropped or cancelled). + setDragging(false); }; const handleDragOver = (e: React.DragEvent) => { @@ -89,6 +113,7 @@ export const useFileDnD = (entry: FsEntry) => { dndProps: { draggable: true, onDragStart: handleDragStart, + onDragEnd: handleDragEnd, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, @@ -103,21 +128,24 @@ export const FileItem = ({entry, index}: { entry: FsEntry; index: number }) => { : - + } ) }; -const FolderItemDisplay = ({entry, depthIndex}: { +const FolderItemDisplay = ({entry, depthIndex, depth}: { entry: FsEntry, depthIndex: number[], + depth: number, }) => { const openFiles = useOpenFiles(state => state.openFiles) const toggle = useOpenFiles(state => state.toggle) const {listFiles} = useFiles() const {dockYaml} = useConfig() + const compact = useCompactMode(state => state.enabled) const editorUrl = useEditorUrl() // Hook to get editor route helper const useComposeFolder = (dockYaml?.useComposeFolders ?? false) @@ -139,32 +167,43 @@ const FolderItemDisplay = ({entry, depthIndex}: { const closeComposeStatus = useComposeFileState(state => state.delete) - const handleToggle = (_e: React.MouseEvent) => { + const handleToggle = () => { // If it's a link, we want the navigation to happen, // but we ALSO want to toggle the folder visibility. toggle(entry.filename); } useEffect(() => { - if (!folderOpen && !isComposeFolder) { - closeComposeStatus(entry.filename) + if (!folderOpen) { + // Stop polling files nested inside the collapsed folder, but keep the + // folder's own stack status so its dot stays visible while collapsed. + closeComposeStatus(entry.filename, entry.isComposeFolder) } - }, [folderOpen]); + }, [closeComposeStatus, entry.filename, entry.isComposeFolder, folderOpen]); const [isFetchingMore, setIsFetchingMore] = useState(false) + const fetchingMore = useRef(false) + const depthPath = depthIndex.join(',') + + const fetchMore = useCallback(async () => { + if (entry.isFetched || fetchingMore.current) return - const fetchMore = async () => { + fetchingMore.current = true setIsFetchingMore(true) - if (entry.isFetched) return - await listFiles(name, depthIndex) - setIsFetchingMore(false) - } + try { + const currentDepthIndex = depthPath.split(',').map(Number) + await listFiles(name, currentDepthIndex) + } finally { + fetchingMore.current = false + setIsFetchingMore(false) + } + }, [depthPath, entry.isFetched, listFiles, name]) useEffect(() => { - if (folderOpen && !entry.isFetched && !isFetchingMore) { - fetchMore().then() + if (folderOpen && !entry.isFetched) { + void fetchMore() } - }, [folderOpen, entry.isFetched]) + }, [entry.isFetched, fetchMore, folderOpen]) const {contextMenu, closeCtxMenu, contextActions, handleContextMenu} = useFileMenuCtx(entry) @@ -174,10 +213,13 @@ const FolderItemDisplay = ({entry, depthIndex}: { const fileStatus = useComposeFileState(state => state.openFiles[getContextKey()]?.[entry.isComposeFolder]) useEffect(() => { - if (isComposeFolder) { + // Track the stack status for any folder that contains a compose file, + // regardless of the useComposeFolders display mode, so the status dot is + // shown even while the folder is collapsed. + if (entry.isComposeFolder) { trackComposeStatus(entry.isComposeFolder); } - }, [isComposeFolder, entry.isComposeFolder]); + }, [entry.isComposeFolder, trackComposeStatus]); const navigate = useNavigate() const createFileUrl = useEditorUrl() @@ -214,7 +256,11 @@ const FolderItemDisplay = ({entry, depthIndex}: { onClick={handleToggle} sx={{ - py: 1.25, + py: compact ? 0.25 : 1.25, + pl: 2 + depth * 4, + minWidth: '100%', + width: 'max-content', + whiteSpace: 'nowrap', backgroundColor: isDragOver ? 'action.hover' : 'transparent', outline: isDragOver ? '1px dashed primary.main' : 'none', outlineOffset: '-2px', @@ -230,13 +276,15 @@ const FolderItemDisplay = ({entry, depthIndex}: { - + {!entry.isFetched && isFetchingMore ? ( - + ) : ( @@ -274,8 +322,9 @@ const FolderItemDisplay = ({entry, depthIndex}: { : - + depthIndex={[...depthIndex, index]} + depth={depth + 1}/> : + )) )} @@ -297,10 +346,11 @@ const FolderItemDisplay = ({entry, depthIndex}: { ) } -const FileItemDisplay = ({entry}: { entry: FsEntry }) => { +const FileItemDisplay = ({entry, depth}: { entry: FsEntry, depth: number }) => { const filename = entry.filename const {isDragOver, dndProps} = useFileDnD(entry); + const compact = useCompactMode(state => state.enabled) const editorUrl = useEditorUrl() const filePath = editorUrl(filename) @@ -311,7 +361,7 @@ const FileItemDisplay = ({entry}: { entry: FsEntry }) => { if (isComposeFile(filename)) { trackComposeStatus(filename); } - }, [filename]); + }, [filename, trackComposeStatus]); const navigate = useNavigate() const createFileUrl = useEditorUrl() @@ -338,6 +388,11 @@ const FileItemDisplay = ({entry}: { entry: FsEntry }) => { { @@ -502,16 +558,21 @@ const StatusIndicator = ({fileStatus}: { fileStatus: Status }) => { return ((fileStatus) && @@ -522,14 +583,14 @@ const StatusIndicator = ({fileStatus}: { fileStatus: Status }) => { export default StatusIndicator; const getStatusTheme = (status: Status | undefined) => { + // Precedence: error > unhealthy > running > stopped. servicesDown carries the + // "in error" count (crashed / dead / restarting / exited non-zero). if (!status) { - return {color: 'text.disabled', label: ''}; + return {color: 'grey.500', label: 'Stopped', filled: false}; } - - if (status.servicesUnHealthy > 0) return {color: 'error.main', label: 'Unhealthy'}; - if (status.servicesDown > 0 && status.servicesUp > 0) return {color: 'warning.main', label: 'Partially Up'}; - if (status.servicesDown > 0 && status.servicesUp === 0) return {color: 'error.light', label: 'Down'}; - if (status.servicesHealthy > 0) return {color: 'success.main', label: 'Healthy'}; - if (status.servicesUp > 0) return {color: 'success.light', label: 'Running'}; - return {color: 'text.disabled', label: ''}; + if (status.servicesDown > 0) return {color: 'error.main', label: 'Error', filled: true}; + if (status.servicesUnHealthy > 0) return {color: 'warning.main', label: 'Unhealthy', filled: true}; + if (status.servicesUp > 0) return {color: 'success.main', label: 'Running', filled: true}; + // no running/failed/unhealthy container -> stack is stopped + return {color: 'grey.500', label: 'Stopped', filled: false}; }; diff --git a/ui/src/pages/compose/components/file-list.tsx b/ui/src/pages/compose/components/file-list.tsx index c2e45a49..efb98f93 100644 --- a/ui/src/pages/compose/components/file-list.tsx +++ b/ui/src/pages/compose/components/file-list.tsx @@ -1,6 +1,14 @@ -import {useCallback, useEffect, useRef} from 'react' -import {Box, CircularProgress, Divider, IconButton, List, Toolbar, Tooltip, Typography} from '@mui/material' -import {Add as AddIcon, Cached, Search as SearchIcon} from '@mui/icons-material' +import {useCallback, useEffect} from 'react' +import {Box, CircularProgress, Divider, IconButton, List, Tooltip, Typography} from '@mui/material' +import { + Add as AddIcon, + Cached, + DensityMedium as StandardIcon, + DensitySmall as CompactIcon, + PushPin as PushPinIcon, + PushPinOutlined as PushPinOutlinedIcon, + Search as SearchIcon +} from '@mui/icons-material' import {ShortcutFormatter} from "./shortcut-formatter.tsx" import {useFileComponents} from "../state/terminal.tsx"; import useResizeBar from "../hooks/resize-hook.ts"; @@ -8,14 +16,17 @@ import {FileItem} from "./file-item.tsx"; import {useFiles} from "../../../context/file-context.tsx" import {useFileSearch} from "../dialogs/file-search.tsx"; import {useFileCreate} from "../dialogs/file-create.tsx"; -import {useSideBarAction} from "../state/files.ts"; +import {useCompactMode, usePinnedMode, useSideBarAction, useToolbarPlacement} from "../state/files.ts"; import {YamlIcon} from "./file-icon.tsx"; +import {RootDropZone} from "./root-drop-zone.tsx"; +import {useDragAutoScroll} from "../hooks/drag-autoscroll.ts"; import {useNavigate} from "react-router-dom"; import {useEditorUrl} from "../../../lib/editor.ts"; import {formatDockyaml} from "./viewer-dockyml.tsx"; import {useComposeFileState} from "../state/status.ts"; import {callRPC, useHostClient} from "../../../lib/api.ts"; import {DockerService} from "../../../gen/docker/v1/docker_pb.ts"; +import {useDockerEvents} from "../../../hooks/docker-events.ts"; export function FileList() { const showSearch = useFileSearch(state => state.open) @@ -23,24 +34,29 @@ export function FileList() { const nav = useNavigate() const isSidebarCollapsed = useSideBarAction(state => state.isSidebarOpen) + const pinnedMode = usePinnedMode(state => state.enabled) + const togglePinnedMode = usePinnedMode(state => state.toggle) + const placement = useToolbarPlacement(state => state.placement) + const compact = useCompactMode(state => state.enabled) + const toggleCompact = useCompactMode(state => state.toggle) const {listFiles} = useFiles() const {host, alias} = useFileComponents() const showFileAdd = useCallback(() => { fileCreate(`${alias}`) - }, [alias]); + }, [alias, fileCreate]); const editUrl = useEditorUrl() - function showDockyaml() { + const showDockyaml = useCallback(() => { nav(editUrl(formatDockyaml(alias, host))) - } + }, [alias, editUrl, host, nav]) useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if ((event.altKey) && event.key === 'r') { - listFiles("", []).then() + void listFiles("", []) } if ((event.altKey) && event.key === 's') { event.preventDefault() @@ -59,8 +75,7 @@ export function FileList() { return () => { window.removeEventListener('keydown', handleKeyDown) } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) + }, [listFiles, showDockyaml, showFileAdd, showSearch]) const {panelSize, panelRef, handleMouseDown, isResizing} = useResizeBar('right') @@ -81,66 +96,90 @@ export function FileList() { overflow: 'hidden', // Keeps the header and resize handle fixed }} > - {/* HEADER AREA */} - - - + {/* HEADER AREA — slimmer when the actions live on the side rail; + also hosts the transient root-drop overlay */} + + + + {alias} - - }> - listFiles("", [])} - color="primary"> - - - - - }> - - - - - - }> - - - - - - {/* Added an extra icon just to match your snippet's count */} - }> - - - - - - + {placement === 'top' && ( + + }> + listFiles("", [])} color="primary"> + + + + + }> + + + + + + + + {pinnedMode ? : + } + + + + }> + + + + + + + + {compact ? : } + + + + }> + + + + + + )} + + {/* List area — FileListInner owns the scroll container(s) and the + drag auto-scroll refs. The root-drop overlay lives in the header. */} @@ -166,57 +205,119 @@ export function FileList() { )} - ) + ); } +const scrollSx = { + overflowY: 'auto', + overflowX: 'auto', + scrollbarGutter: 'stable', +} as const; + const FileListInner = () => { const {files, isLoading} = useFiles() const {host, alias} = useFileComponents() + const pinnedMode = usePinnedMode(state => state.enabled) - const openFiles = useComposeFileState(state => state.openFiles) + // Auto-scroll while dragging near a scroll area's edges. Two independent + // instances: the main list (single scroll area, or the "rest" pane in pinned + // mode) and the fixed pinned pane above it. + const autoScrollMain = useDragAutoScroll() + const autoScrollPinned = useDragAutoScroll() + + // ONLY the set of tracked file keys, as a stable string: depending on the + // whole openFiles object re-armed this effect on every status write, and + // each run fires a request whose response writes statuses — a feedback + // loop hammering ComposeFileStatus every 150ms. + const trackedKeys = useComposeFileState(state => + Object.keys(state.openFiles[`${host}/${alias}`] ?? {}).sort().join('|')) const setStatus = useComposeFileState(state => state.setStatus) const dockerSrv = useHostClient(DockerService) + // container lifecycle events refresh the stack dots instantly; the + // interval is only a safety net + const eventBump = useDockerEvents() - const openFilesRef = useRef(openFiles) useEffect(() => { - openFilesRef.current = openFiles - }, [openFiles]) + let cancelled = false - useEffect(() => { const refresh = async () => { - const currentElement = openFilesRef.current[`${host}/${alias}`]; - if (!currentElement) return; + const keys = trackedKeys ? trackedKeys.split('|') : [] + if (keys.length === 0) return; - const keys = Object.keys(currentElement) const {val} = await callRPC(() => dockerSrv.composeFileStatus({ files: keys })) - if (val) { + if (val && !cancelled) { setStatus(val.status) } } - refresh().then() - const interval = setInterval(refresh, 3000) - return () => clearInterval(interval) - }, []) + // Re-runs whenever the tracked file set changes (the tree registers + // its compose files progressively at mount — the dots must load as + // soon as the files are known, not at the next poll) and on every + // container event. The short delay coalesces those mount-time + // registrations into a single request. + const initial = setTimeout(refresh, 150) + const interval = setInterval(refresh, 30000) + return () => { + cancelled = true + clearTimeout(initial) + clearInterval(interval) + } + }, [trackedKeys, host, alias, dockerSrv, setStatus, eventBump]) + + if (isLoading && files.length < 1) { + return ( + + + + ); + } + // Pinned entries are always sorted first, so they form a contiguous prefix; + // the boundary is the first non-pinned entry (all-pinned -> everything). + const firstUnpinned = files.findIndex(f => !f.pinned) + const pinnedCount = firstUnpinned === -1 ? files.length : firstUnpinned + const hasPinned = pinnedCount > 0 + const hasRest = pinnedCount < files.length - return ( - <> - {isLoading && files.length < 1 ? ( - - + // Render a slice while preserving each entry's ORIGINAL index into `files` + // (used as the depthIndex that drives lazy-loading of nested folders). + const renderRange = (start: number, end: number) => + files.slice(start, end).map((ele, i) => ( + + )) + + // Pinned mode: pinned entries stay fixed at the top, only the rest scrolls. + if (pinnedMode && hasPinned) { + return ( + + + {renderRange(0, pinnedCount)} - ) : ( - - {files.map((ele, inde) => - - )} - - )} - - ); + + + {renderRange(pinnedCount, files.length)} + + + ) + } + + // Default: a single scroll area with a visual separator after the pinned run. + return ( + + + {renderRange(0, pinnedCount)} + {hasPinned && hasRest && ( + + )} + {renderRange(pinnedCount, files.length)} + + + ) }; diff --git a/ui/src/pages/compose/components/logs-panel.tsx b/ui/src/pages/compose/components/logs-panel.tsx index 8f4ce5b9..4e694081 100644 --- a/ui/src/pages/compose/components/logs-panel.tsx +++ b/ui/src/pages/compose/components/logs-panel.tsx @@ -1,5 +1,5 @@ -import {Box, Divider, IconButton, ListItemButton, Paper, Stack, Typography} from '@mui/material'; -import {Close, ExpandMore, TerminalRounded} from '@mui/icons-material'; +import {Box, Divider, IconButton, ListItemButton, Paper, Stack, Tooltip, Typography} from '@mui/material'; +import {ClearAll, Close, ExpandMore, PushPin, PushPinOutlined, TerminalRounded} from '@mui/icons-material'; import {useTerminalAction, useTerminalTabs} from "../state/terminal.tsx"; import useResizeBar from "../hooks/resize-hook.ts"; import scrollbarStyles from "../../../components/scrollbar-style.tsx"; @@ -7,34 +7,162 @@ import InsertDriveFile from '@mui/icons-material/InsertDriveFile'; import "@xterm/xterm/css/xterm.css"; import AppTerminal from "./logs-terminal.tsx"; -import {useRef} from "react"; +import LogsViewer from "../../../components/log-viewer/logs-viewer.tsx"; +import {useCallback, useEffect, useRef, useState} from "react"; import {FitAddon} from "@xterm/addon-fit"; +import {useFileComponents} from "../state/terminal.tsx"; +import ExecTerminalPanel from './exec-terminal-panel.tsx'; export function LogsPanel() { const {panelSize, panelRef, handleMouseDown, isResizing} = useResizeBar('top') const isTerminalOpen = useTerminalAction(state => state.isTerminalOpen); const toggle = useTerminalAction(state => state.toggle); + const closePanel = useTerminalAction(state => state.close); + const floatMode = useTerminalAction(state => state.floatMode); + const toggleFloat = useTerminalAction(state => state.toggleFloat); + const revealNonce = useTerminalAction(state => state.revealNonce); - const {tabs, activeTab, setActiveTab, close} = useTerminalTabs(); + const {tabs, activeTab, setActiveTab, close, clearAll} = useTerminalTabs(); const fitAddonRef = useRef(new FitAddon()); + // floating mode: only a slim bar stays docked; the body overlays the + // content above it while the pointer is over the bar or the body + const [hovered, setHovered] = useState(false); + const bodyVisible = isTerminalOpen && (!floatMode || hovered); + + // collapsing the instant the pointer slips out makes the overlay hard to + // use: give a grace period instead, and never collapse mid-drag — the + // pointer routinely exits the panel while resizing via the top handle + const collapseTimer = useRef | null>(null); + const pointerInside = useRef(false); + const cancelCollapse = useCallback(() => { + if (collapseTimer.current !== null) { + clearTimeout(collapseTimer.current); + collapseTimer.current = null; + } + }, []); + const scheduleCollapse = useCallback(() => { + cancelCollapse(); + collapseTimer.current = setTimeout(() => { + collapseTimer.current = null; + setHovered(false); + }, 500); + }, [cancelCollapse]); + useEffect(() => cancelCollapse, [cancelCollapse]); + useEffect(() => { + if (isResizing) { + cancelCollapse(); + } else if (!pointerInside.current) { + // drag ended with the pointer outside: collapse after the grace + // period, since no mouseleave will fire again + scheduleCollapse(); + } + }, [isResizing, cancelCollapse, scheduleCollapse]); + + // a freshly requested tab (logs, exec, last action, docker run…) must + // surface the floating body even though the pointer never touched the + // panel; the ref seeds with the mount-time nonce so switching views + // doesn't count as a new request + const seenReveal = useRef(revealNonce); + useEffect(() => { + if (revealNonce === seenReveal.current) return; + seenReveal.current = revealNonce; + if (!floatMode) return; + cancelCollapse(); + setHovered(true); + }, [revealNonce, floatMode, cancelCollapse]); + + // tabs hold container ids and socket urls scoped to one docker host: + // after a host switch they would query the wrong daemon, so drop them + const {host} = useFileComponents(); + const prevHost = useRef(host); + useEffect(() => { + if (prevHost.current !== host) { + prevHost.current = host; + clearAll(); + } + }, [host, clearAll]); + return ( - { + pointerInside.current = true; + cancelCollapse(); + setHovered(true); + }} + onMouseLeave={() => { + pointerInside.current = false; + if (isResizing) return; + scheduleCollapse(); + }} sx={{ - display: (isTerminalOpen) ? 'flex' : 'none', - height: `${panelSize}px`, - transition: isResizing ? 'none' : 'height 0.1s ease-in-out', - overflow: 'hidden', + display: isTerminalOpen ? 'block' : 'none', position: 'relative', - flexDirection: 'column', - bgcolor: '#000000', - border: '1px solid rgba(255, 255, 255, 0.2)', - borderRadius: '4px', flexShrink: 0, }} > + {floatMode && ( + + + + LOGS + + + · {tabs.size} + + + + + + + + + closePanel()}> + + + + + )} + + {/* Resize Handle */} { @@ -80,20 +208,54 @@ export function LogsPanel() { flex: 1, ...scrollbarStyles }}> - + { ev.stopPropagation() toggle() }} > - + - + LOGS + + + {floatMode + ? + : } + + + + clearAll()} + > + + + + + closePanel()} + > + + + @@ -178,19 +340,29 @@ export function LogsPanel() { flex: 1 }} > - + {v.logsContainers ? ( + + ) : v.execSession ? ( + + ) : ( + + )} ) }) )} - + + ) } @@ -207,9 +379,13 @@ function LogsEmpty() { color: 'rgba(255,255,255,0.3)' }} > - + - + No active terminals selected diff --git a/ui/src/pages/compose/components/logs-terminal.tsx b/ui/src/pages/compose/components/logs-terminal.tsx index 144c1155..282f0f8d 100644 --- a/ui/src/pages/compose/components/logs-terminal.tsx +++ b/ui/src/pages/compose/components/logs-terminal.tsx @@ -3,10 +3,11 @@ import {type ITerminalInitOnlyOptions, type ITerminalOptions, Terminal} from "@x import {FitAddon} from "@xterm/addon-fit"; import {Box} from "@mui/material"; import type {TabTerminal} from "../state/terminal.tsx"; +import {debugWarn} from "../../../lib/debug.ts"; const terminalConfig: ITerminalOptions & ITerminalInitOnlyOptions = { theme: { - background: '#1E1E1E', + background: '#09090b', foreground: '#CCCCCC' }, // theme: {background: '#1E1E1E', foreground: '#CCCCCC'}, @@ -29,9 +30,10 @@ const scrollbarStyles = ` type AppTerminalProps = TabTerminal & { fit?: RefObject; isActive: boolean; + fontSize?: number; }; -const AppTerminal = ({fit, interactive, onTerminal, isActive, onClose}: AppTerminalProps) => { +const AppTerminal = ({fit, interactive, onTerminal, isActive, onClose, fontSize}: AppTerminalProps) => { const terminalRef = useRef(null); const xtermRef = useRef(null); @@ -48,7 +50,7 @@ const AppTerminal = ({fit, interactive, onTerminal, isActive, onClose}: AppTermi try { fit.current.fit(); } catch (e) { - console.warn("Resize error", e); + debugWarn("Terminal resize failed", e); } }); @@ -94,17 +96,27 @@ const AppTerminal = ({fit, interactive, onTerminal, isActive, onClose}: AppTermi onTerminal(xtermRef.current) - setTimeout(() => { + const fitTimer = setTimeout(() => { fit?.current.fit(); }, 50); return () => { + clearTimeout(fitTimer); xtermRef.current?.dispose(); xtermRef.current = null; onClose() }; - // eslint-disable-next-line - }, []); + }, [fit, interactive, onClose, onTerminal]); + + useEffect(() => { + if (!fontSize || !xtermRef.current) return; + xtermRef.current.options.fontSize = fontSize; + try { + fit?.current.fit(); + } catch (e) { + debugWarn("Terminal font resize failed", e); + } + }, [fit, fontSize]); return ( @@ -116,7 +128,7 @@ const AppTerminal = ({fit, interactive, onTerminal, isActive, onClose}: AppTermi height: '100%', overflow: 'hidden', position: 'relative', - bgcolor: '#1E1E1E', + bgcolor: '#09090b', '& .xterm': { height: '100%', padding: '1px' @@ -145,4 +157,4 @@ const AppTerminal = ({fit, interactive, onTerminal, isActive, onClose}: AppTermi // ; }; -export default AppTerminal; \ No newline at end of file +export default AppTerminal; diff --git a/ui/src/pages/compose/components/root-drop-zone.tsx b/ui/src/pages/compose/components/root-drop-zone.tsx new file mode 100644 index 00000000..ceba4a56 --- /dev/null +++ b/ui/src/pages/compose/components/root-drop-zone.tsx @@ -0,0 +1,92 @@ +import React, {useState} from "react"; +import {Box, Typography} from "@mui/material"; +import {VerticalAlignTop as MoveToRootIcon} from "@mui/icons-material"; +import {useFiles} from "../../../context/file-context.tsx"; +import {useFileDrag} from "../state/files.ts"; +import {useFileComponents} from "../state/terminal.tsx"; + +// RootDropZone is a drop target that only exists while a file-tree entry is being +// dragged. It lets you move an entry back to the root of the tree — otherwise +// impossible when the root has no sibling file to drop onto. +// +// It is rendered as an ABSOLUTE overlay covering the file-list header (the alias +// bar), so it never overlaps a file row and — crucially — never shifts the list +// layout. Shifting the list during a drag moves the dragged row and makes the +// browser cancel the drag; an absolute overlay avoids that entirely. The parent +// header must be position:relative. +export function RootDropZone() { + const dragging = useFileDrag(state => state.dragging); + const setDragging = useFileDrag(state => state.setDragging); + const {renameFile} = useFiles(); + const {alias} = useFileComponents(); + const [isOver, setIsOver] = useState(false); + + // Nothing to show unless an internal drag is in progress. + if (!dragging) return null; + + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + setIsOver(true); + }; + + const handleDragLeave = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + setIsOver(false); + }; + + const handleDrop = async (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + setIsOver(false); + setDragging(false); + + const sourcePath = e.dataTransfer.getData("sourcePath"); + if (!sourcePath) return; + + // Tree paths are "/": the first segment is the alias (the + // tree root), NOT part of the file path. A root move therefore keeps the + // alias prefix and drops everything in between -> "/". + const parts = sourcePath.split("/").filter(Boolean); + // Already at the root (only "/") -> nothing to do. + if (parts.length <= 2) return; + + const newPath = `${parts[0]}/${parts[parts.length - 1]}`; + await renameFile(sourcePath, newPath); + }; + + return ( + + + + Move to {alias || 'root'} + + + ); +} + +export default RootDropZone; diff --git a/ui/src/pages/compose/components/shortcut-formatter.tsx b/ui/src/pages/compose/components/shortcut-formatter.tsx index fa23f6b3..3d22c6e8 100644 --- a/ui/src/pages/compose/components/shortcut-formatter.tsx +++ b/ui/src/pages/compose/components/shortcut-formatter.tsx @@ -4,10 +4,21 @@ import {KeyChar} from "../../../components/keychar.tsx"; export const ShortcutFormatter = ({title, keyCombo}: { title: string, keyCombo: string[] }) => { return ( - + {title !== "" && {title}} { - keyCombo.length > 0 && { + keyCombo.length > 0 && { keyCombo.map((key, index) => ( {key} diff --git a/ui/src/pages/compose/components/stats-theme.ts b/ui/src/pages/compose/components/stats-theme.ts new file mode 100644 index 00000000..90c16809 --- /dev/null +++ b/ui/src/pages/compose/components/stats-theme.ts @@ -0,0 +1,37 @@ +// Shared palette for the stats view. Kept on the app's original dark tones +// for now; only the metric line/badge accents are opinionated. +export const statsTheme = { + page: 'transparent', + panel: '#1e1e1e', + header: '#252525', + row: 'transparent', + rowHover: 'rgba(255,255,255,0.06)', + border: 'rgba(255,255,255,0.12)', + text: '#e6e6e6', + textDim: '#9aa0a6', + mono: '"JetBrains Mono", "Roboto Mono", "SFMono-Regular", Consolas, monospace', + + cpuLine: '#60a5fa', + memLine: '#a78bfa', + netDown: '#4ade80', + netUp: '#38bdf8', + diskRead: '#34d399', + diskWrite: '#fbbf24', +} as const; + +// State badge colors, Dockhand-style. +export const stateBadges: Record = { + running: {bg: 'rgba(16,124,73,0.9)', fg: '#d7f8e7', label: 'running'}, + exited: {bg: 'rgba(71,85,105,0.55)', fg: '#cbd5e1', label: 'exited'}, + created: {bg: 'rgba(51,88,138,0.55)', fg: '#cfe2f8', label: 'created'}, + paused: {bg: 'rgba(161,120,17,0.55)', fg: '#fde9b8', label: 'paused'}, + restarting: {bg: 'rgba(180,90,26,0.6)', fg: '#ffe1c7', label: 'restarting'}, + removing: {bg: 'rgba(148,68,68,0.55)', fg: '#ffd9d9', label: 'removing'}, + dead: {bg: 'rgba(153,27,45,0.7)', fg: '#ffd4dc', label: 'dead'}, +}; + +export const healthColors: Record = { + healthy: '#34d399', + unhealthy: '#f87171', + starting: '#fbbf24', +}; diff --git a/ui/src/pages/compose/components/viewer-dockyml.tsx b/ui/src/pages/compose/components/viewer-dockyml.tsx index 681fb40d..3f22dcbb 100644 --- a/ui/src/pages/compose/components/viewer-dockyml.tsx +++ b/ui/src/pages/compose/components/viewer-dockyml.tsx @@ -16,15 +16,12 @@ function stringToArrayBuffer(str: string): Uint8Array { return encoder.encode(str); } -function arrayBufferLikeToString(bufferLike?: ArrayBufferLike): string { - if (!bufferLike) { +function bytesToString(bytes?: Uint8Array): string { + if (!bytes) { return ""; } - // Ensure the input is an ArrayBuffer (or compatible TypedArray) - const uint8Array = new Uint8Array(bufferLike); - const decoder = new TextDecoder('utf-8'); // Specify encoding if needed, UTF-8 is default - return decoder.decode(uint8Array); + return new TextDecoder('utf-8').decode(bytes); } function DockyamlViewer({filename}: { filename: string }) { @@ -39,11 +36,9 @@ function DockyamlViewer({filename}: { filename: string }) { } const getFile = async (): Promise<{ contents: string; err: string }> => { - console.log("Testing ") - const {val, err} = await callRPC(() => dockYamlClient.get({})) return { - contents: arrayBufferLikeToString(val?.contents), + contents: bytesToString(val?.contents), err: err } }; diff --git a/ui/src/pages/compose/components/viewer-sqlite.tsx b/ui/src/pages/compose/components/viewer-sqlite.tsx index 895225fe..c259c51d 100644 --- a/ui/src/pages/compose/components/viewer-sqlite.tsx +++ b/ui/src/pages/compose/components/viewer-sqlite.tsx @@ -90,7 +90,13 @@ const ViewerSqlite = ({filename}: { filename: string }) => { }} /> ) : ( - + Connecting to sqlite web ui...
diff --git a/ui/src/pages/compose/components/viewer-text.tsx b/ui/src/pages/compose/components/viewer-text.tsx index 005dbcea..6a342069 100644 --- a/ui/src/pages/compose/components/viewer-text.tsx +++ b/ui/src/pages/compose/components/viewer-text.tsx @@ -1,33 +1,33 @@ -import React, {type ReactElement, useEffect, useMemo, useState} from 'react'; +import React, {type ReactElement, useCallback, useEffect, useMemo, useState} from 'react'; import {useNavigate, useSearchParams} from 'react-router-dom'; import {Box, Button, CircularProgress, Fade, Tab, Tabs, Tooltip} from '@mui/material'; -import {useFileComponents} from "../state/terminal.tsx"; import {callRPC, useHostClient} from "../../../lib/api.ts"; -import {isComposeFile, useEditorUrl} from "../../../lib/editor.ts"; +import {isComposeFile, stackDefaultTab, useEditorUrl} from "../../../lib/editor.ts"; +import {useConfig} from "../../../hooks/config.ts"; import TabEditor from "../tab-editor.tsx"; import {ShortcutFormatter} from "./shortcut-formatter.tsx"; import {TabDeploy} from "../tab-deploy.tsx"; import {TabStat} from "../tab-stats.tsx"; import CenteredMessage from "../../../components/centered-message.tsx"; -import {ErrorOutline} from "@mui/icons-material"; -import {useOpenFiles} from "../state/files.ts"; +import {ErrorOutlined} from "@mui/icons-material"; +import {useCompactMode, useOpenFiles} from "../state/files.ts"; import {FileService} from "../../../gen/files/v1/files_pb.ts"; import {indicatorMap, type SaveState} from "../hooks/status-hook.tsx"; -export enum TabType { +enum TabType { // noinspection JSUnusedGlobalSymbols EDITOR, DEPLOY, STATS, } -export function parseTabType(input: string | null): TabType { +function parseTabType(input: string | null): TabType { const tabValueInt = parseInt(input ?? '0', 10) const isValidTab = TabType[tabValueInt] !== undefined return isValidTab ? tabValueInt : TabType.EDITOR } -export interface TabDetails { +interface TabDetails { label: string; component: React.ReactElement; shortcut: React.ReactElement; @@ -43,41 +43,45 @@ function ViewerTextEditor({filename, track}: { filename: string, track: number } const fileService = useHostClient(FileService); const navigate = useNavigate(); + const {dockYaml} = useConfig() const [searchParams] = useSearchParams(); const tabKey = track === 0 ? 'tab' : 'splitTab'; - const selectedTab = parseTabType(searchParams.get(tabKey)) + // no tab selected in the url yet: open on the dockman.yml default tab + const selectedTab = parseTabType( + searchParams.get(tabKey) ?? String(stackDefaultTab(dockYaml, filename)) + ) const [isLoading, setIsLoading] = useState(true); const [fileError, setFileError] = useState(""); const recursiveOpen = useOpenFiles(state => state.recursiveOpen) - const {alias: activeAlias} = useFileComponents() - - useEffect(() => { - const checkExists = async () => { - setIsLoading(true); - setFileError(""); - - const {err} = await callRPC(() => fileService.exists({ - filename: filename, - })) - if (err) { - console.error("API error checking file existence:", err); - setFileError(`An API error occurred: ${err}`); - } - setIsLoading(false); - recursiveOpen(filename) + const compact = useCompactMode(state => state.enabled) + const tabMinHeight = compact ? '34px' : '48px' + + const checkExists = useCallback(async () => { + setIsLoading(true); + setFileError(""); + + const {err} = await callRPC(() => fileService.exists({ + filename: filename, + })) + if (err) { + setFileError(`An API error occurred: ${err}`); } + setIsLoading(false); + recursiveOpen(filename) + }, [fileService, filename, recursiveOpen]); + useEffect(() => { checkExists().then() - }, [filename, fileService, activeAlias]); + }, [checkExists]); const editorUrl = useEditorUrl() - const changeTab = (tabId: string) => { + const changeTab = useCallback((tabId: string) => { const url = editorUrl(filename, parseInt(tabId), track) navigate(url); - }; + }, [editorUrl, filename, navigate, track]); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { @@ -104,7 +108,7 @@ function ViewerTextEditor({filename, track}: { filename: string, track: number } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown) - }, [filename, navigate]); + }, [changeTab, filename]); const [saveStatus, setSaveStatus] = useState('idle') @@ -156,7 +160,6 @@ function ViewerTextEditor({filename, track}: { filename: string, track: number } // } return map; - // eslint-disable-next-line react-hooks/exhaustive-deps }, [filename]); const currentTab = selectedTab ?? 'editor'; @@ -168,7 +171,7 @@ function ViewerTextEditor({filename, track}: { filename: string, track: number } if (fileError) { return ( } + icon={} title={`Unable to load file: ${filename}`} message={fileError} /> @@ -187,7 +190,7 @@ function ViewerTextEditor({filename, track}: { filename: string, track: number } changeTab(value)} - sx={{minHeight: '48px'}} + sx={{minHeight: tabMinHeight}} variant="scrollable" scrollButtons="auto" slotProps={{ @@ -205,7 +208,7 @@ function ViewerTextEditor({filename, track}: { filename: string, track: number } value={key} sx={{ color: (key === 0) ? indicatorMap[saveStatus].color : "text.secondary", - minHeight: '48px' + minHeight: tabMinHeight }} label={ key === 0 ? ( @@ -255,4 +258,4 @@ function ViewerTextEditor({filename, track}: { filename: string, track: number } ); } -export default ViewerTextEditor; \ No newline at end of file +export default ViewerTextEditor; diff --git a/ui/src/pages/compose/compose-empty.tsx b/ui/src/pages/compose/compose-empty.tsx index 374448d6..69f145d0 100644 --- a/ui/src/pages/compose/compose-empty.tsx +++ b/ui/src/pages/compose/compose-empty.tsx @@ -55,7 +55,12 @@ export const InvalidAlias = () => { {isEmpty ? "No Aliases found" : "Invalid Directory Alias"}
- + {isEmpty ? ( "There are no aliases registered on this host. Dockman requires an alias to manage files." ) : ( @@ -101,7 +106,9 @@ export const InvalidAlias = () => { > {aliases.map((f) => ( - + {f.alias} @@ -115,10 +122,9 @@ export const InvalidAlias = () => {
- - ) + ); } function CoreComposeEmpty() { @@ -157,12 +163,21 @@ function CoreComposeEmpty() { height: '100%', }} > - + - + {selected.title} - + {selected.subtitle} diff --git a/ui/src/pages/compose/compose-page.tsx b/ui/src/pages/compose/compose-page.tsx index 79bddcfe..36a09730 100644 --- a/ui/src/pages/compose/compose-page.tsx +++ b/ui/src/pages/compose/compose-page.tsx @@ -1,30 +1,32 @@ import {type JSX, useEffect, useMemo, useRef, useState} from 'react'; -import {Navigate, Outlet, useLocation, useNavigate} from 'react-router-dom'; +import {Navigate, Outlet, useLocation, useNavigate, useParams} from 'react-router-dom'; import {Box, CircularProgress, IconButton, Tab, Tabs, Tooltip, Typography} from '@mui/material'; import {FileList} from "./components/file-list.tsx"; -import {Close} from '@mui/icons-material'; +import {ClearAll, Close} from '@mui/icons-material'; import ActionSidebar from "./components/action-sidebar.tsx"; import CoreComposeEmpty, {InvalidAlias} from "./compose-empty.tsx"; import {LogsPanel} from "./components/logs-panel.tsx"; -import {getExt} from "./components/file-icon.tsx"; +import FileIcon, {getExt} from "./components/file-icon.tsx"; import ViewerSqlite from "./components/viewer-sqlite.tsx"; import ViewerText from "./components/viewer-text.tsx"; import ViewerDockyaml, {formatDockyaml} from "./components/viewer-dockyml.tsx"; -import {useFileComponents, useTerminalTabs} from "./state/terminal.tsx"; +import {useFileComponents} from "./state/terminal.tsx"; import {TabsProvider, useTabs, useTabsStore} from "../../context/tab-context.tsx"; import FilesProvider from "../../context/file-context.tsx"; import FileSearch from "./dialogs/file-search.tsx"; import FileCreate from "./dialogs/file-create.tsx"; import FileDelete from "./dialogs/file-delete.tsx"; import FileRename from "./dialogs/file-rename.tsx"; -import {useAliasStore, useHostStore, useLastOpened} from "./state/files.ts"; +import {useAliasStore, useCompactMode, useLastOpened} from "./state/files.ts"; import AliasProvider, {useAlias} from "../../context/alias-context.tsx"; import AliasDialog from "./components/add-alias-dialog.tsx"; import useResizeBar from "./hooks/resize-hook.ts"; export function FilesLayout() { + const {host = 'local'} = useParams<{ host: string }>() + return ( - + @@ -35,19 +37,19 @@ export function FilesLayout() { function FileIndexRedirect() { const lastUrl = useLastOpened(state => state.lastEditorUrl) const {aliases} = useAlias() + const {host = 'local'} = useParams<{ host: string }>() - const path = lastUrl + const lastUrlHost = lastUrl.split('/')[1] + const path = lastUrl && lastUrlHost === host ? lastUrl - : aliases.at(0)?.alias ?? ''; - - console.log("last path", path, aliases.at(0)?.alias) + : aliases.at(0)?.alias + ? `/${host}/files/${aliases[0].alias}` + : ''; if (!path) { return } - console.log(`Nav to ${path}`) - return ; } @@ -60,7 +62,7 @@ export const ComposePage = () => { useEffect(() => { const fullPath = location.pathname + location.search + location.hash; setLast(fullPath) - }, [location.pathname, location.search, location.hash]); + }, [location.pathname, location.search, location.hash, setLast]); const {aliases, isLoading} = useAlias(); const {host, alias} = useFileComponents(); @@ -76,7 +78,13 @@ export const ComposePage = () => { height: '100vh', }}> - + Loading aliases...
@@ -116,13 +124,7 @@ export const ComposePageInner = () => { const setAlias = useAliasStore(state => state.setAlias) useEffect(() => { setAlias(alias) - }, [alias]); - - const clearTabs = useTerminalTabs(state => state.clearAll) - const host = useHostStore(state => state.host) - useEffect(() => { - clearTabs() - }, [host]); + }, [alias, setAlias]); const containerRef = useRef(null); const [containerWidth, setContainerWidth] = useState(1200); @@ -239,9 +241,41 @@ export const ComposePageInner = () => { ); }; -function getTabName(filename: string): string { - const s = filename.split("/").pop() ?? filename; - return s.slice(0, 19) // max name limit of 19 chars +interface TabLabel { + name: string; + // disambiguating parent folder, only set when several open tabs share + // the same file name (VS Code behavior) + hint: string; +} + +// buildTabLabels labels every tab with its file name, adding the parent +// folder as a hint when open tabs collide on the same name — and walking up +// the path while the hints themselves collide (bounded, most trees are flat). +function buildTabLabels(filenames: string[]): Map { + const byBase = new Map(); + for (const f of filenames) { + const base = f.split('/').pop() ?? f; + byBase.set(base, [...(byBase.get(base) ?? []), f]); + } + + const labels = new Map(); + for (const [base, paths] of byBase) { + if (paths.length === 1) { + labels.set(paths[0], {name: base, hint: ''}); + continue; + } + + let hints: string[] = []; + for (let depth = 1; depth <= 3; depth++) { + hints = paths.map(p => { + const parts = p.split('/'); + return parts.slice(Math.max(0, parts.length - 1 - depth), -1).join('/'); + }); + if (new Set(hints).size === paths.length) break; + } + paths.forEach((p, i) => labels.set(p, {name: base, hint: hints[i]})); + } + return labels; } const FileTabBar = ({track}: { track: number }) => { @@ -249,12 +283,17 @@ const FileTabBar = ({track}: { track: number }) => { const currentFilename = track === 0 ? filename : (splitFilename ?? '') const navigate = useNavigate(); - const {closeTab, onTabClick} = useTabs(); + const {closeTab, onTabClick, closeAllTabs} = useTabs(); + const reorderTab = useTabsStore(state => state.reorder); + // Chrome-style drag: the dragged tab slides into the hovered slot live + const [draggedTab, setDraggedTab] = useState(null); const contextKey = `${host}/${alias}` + const compact = useCompactMode(state => state.enabled) + const tabMinHeight = compact ? 34 : undefined - const contextTabs = useTabsStore(state => state.contextTabs)[contextKey] ?? {0: new Set(), 1: new Set()} - const tabs = contextTabs[track] ?? new Set() + const contextTabs = useTabsStore(state => state.contextTabs)[contextKey] + const tabs = useMemo(() => contextTabs?.[track] ?? new Set(), [contextTabs, track]) const activeTab = useTabsStore(state => state.lastOpened[track]) useEffect(() => { @@ -294,44 +333,167 @@ const FileTabBar = ({track}: { track: number }) => { return Array.from(tabs); }, [tabs]) + const tabLabels = useMemo(() => buildTabLabels(tablist), [tablist]) + return ( - + { + if (!draggedTab) return; + e.preventDefault(); + e.dataTransfer.dropEffect = 'move'; + }} + onDrop={(e) => e.preventDefault()} + > onTabClick(value as string, track)} variant="scrollable" scrollButtons="auto" + sx={{minHeight: tabMinHeight, flexGrow: 1, minWidth: 0}} + slotProps={{ + // the sliding underline chasing tabs mid-drag reads as + // the drop not having happened — freeze it while dragging + indicator: {sx: {transition: draggedTab ? 'none' : undefined}}, + }} > - {tablist.map((tabFilename) => ( - - - {getTabName(tabFilename)} - - { - e.stopPropagation(); - closeTab(tabFilename, track) - }} - sx={{ml: 1.5}} - > - - - - } - /> - ))} + {tablist.map((tabFilename) => { + const label = tabLabels.get(tabFilename) ?? { + name: tabFilename.split('/').pop() ?? tabFilename, + hint: '', + }; + return ( + { + e.dataTransfer.effectAllowed = 'move'; + setDraggedTab(tabFilename); + }} + onDragOver={(e) => { + if (!draggedTab) return; + // always accept the drop — releasing over an + // unaccepted zone (including the dragged tab + // itself) plays the browser's translucent + // snap-back-to-origin animation + e.preventDefault(); + e.dataTransfer.dropEffect = 'move'; + if (draggedTab === tabFilename) return; + // Only swap once the pointer crosses the middle of the + // hovered tab in the travel direction: tabs have + // variable widths, and swapping on first contact makes + // the swapped-in neighbor land under the pointer and + // swap straight back — a visible jitter loop. + const rect = e.currentTarget.getBoundingClientRect(); + const middle = rect.left + rect.width / 2; + const from = tablist.indexOf(draggedTab); + const to = tablist.indexOf(tabFilename); + if (to > from && e.clientX < middle) return; + if (to < from && e.clientX > middle) return; + reorderTab(draggedTab, to, track); + }} + onDrop={(e) => e.preventDefault()} + onDragEnd={() => setDraggedTab(null)} + sx={{ + textTransform: 'none', + p: 0.5, + minHeight: tabMinHeight, + maxWidth: 200, + opacity: draggedTab === tabFilename ? 0.4 : 1, + }} + label={ + + + + + + { + e.stopPropagation(); + closeTab(tabFilename, track) + }} + sx={{ + position: 'absolute', + inset: 0, + p: 0, + opacity: 0, + transition: 'opacity 0.1s', + }} + > + + + + + + + {label.name.slice(0, 19)} + + {label.hint && ( + + · {label.hint} + + )} + + + + } + /> + ); + })} + {tablist.length > 0 && ( + + closeAllTabs(track)} + sx={{ + mx: 0.5, + flexShrink: 0, + color: 'text.secondary', + '&:hover': {color: 'error.main', bgcolor: 'action.hover'}, + }} + > + + + + )} ); }; diff --git a/ui/src/pages/compose/dialogs/file-create.tsx b/ui/src/pages/compose/dialogs/file-create.tsx index 3c24edfd..4bac6810 100644 --- a/ui/src/pages/compose/dialogs/file-create.tsx +++ b/ui/src/pages/compose/dialogs/file-create.tsx @@ -18,17 +18,17 @@ import { Typography } from "@mui/material"; import { - AddCircleOutline, + AddCircleOutlined, ArrowBack, AutoAwesome, Cancel, - CheckCircleOutline, + CheckCircleOutlined, ChevronRight, ContentCopy, CreateNewFolder, DescriptionOutlined, Folder, - HelpOutline, + HelpOutlined, InsertDriveFile, SettingsSuggestOutlined, Terminal @@ -41,6 +41,7 @@ import {FileService, type Template} from "../../../gen/files/v1/files_pb.ts"; import scrollbarStyles from "../../../components/scrollbar-style.tsx"; import {useSnackbar} from "../../../hooks/snackbar.ts"; +import {debugError} from "../../../lib/debug.ts"; export type PresetType = 'file' | 'folder' | 'templates'; export type CreationStep = 'preset-selection' | 'name-input' | 'template-create'; @@ -145,7 +146,7 @@ function FileCreate({initialName = ""}: { initialName?: string }) { const trimmedName = name.trim(); if (!trimmedName) return; - let finalPath = rootPath ? `${rootPath.replace(/\/$/, '')}/${trimmedName}` : trimmedName; + const finalPath = rootPath ? `${rootPath.replace(/\/$/, '')}/${trimmedName}` : trimmedName; const selectedPreset = FILE_PRESETS[selectedPresetIndex].type; try { @@ -156,7 +157,7 @@ function FileCreate({initialName = ""}: { initialName?: string }) { } closeDialog(); } catch (err) { - console.error("File operation failed", err); + debugError("File operation failed", err); } }; @@ -193,7 +194,9 @@ function FileCreate({initialName = ""}: { initialName?: string }) { }} > - + {copyMode ? : step === 'template-create' ? : - } + } {copyMode ? 'Duplicate Item' : step === 'template-create' ? 'Select Template' : 'Create New'} - + Location: {rootPath || '/'} - {step === 'preset-selection' && ( @@ -237,13 +241,17 @@ function FileCreate({initialName = ""}: { initialName?: string }) { '&:hover': {borderColor: 'primary.main'} }} > - + {preset.icon} {preset.title} {preset.description} + sx={{ + color: "text.secondary" + }}>{preset.description} @@ -267,15 +275,17 @@ function FileCreate({initialName = ""}: { initialName?: string }) { value={name} onChange={(e) => setName(e.target.value)} sx={{mt: 0.5}} - InputProps={{ - startAdornment: ( - - {copyMode ? (isDir ? : - ) : - (currentPreset.type === 'folder' ? : - )} - - ), + slotProps={{ + input: { + startAdornment: ( + + {copyMode ? (isDir ? : + ) : + (currentPreset.type === 'folder' ? : + )} + + ), + }, }} />
@@ -305,10 +315,7 @@ function FileCreate({initialName = ""}: { initialName?: string }) {
)} - - - @@ -408,7 +422,7 @@ const TemplateCreate = ({rootPath, onClose}: { ); } - if (!runner.val?.templs || runner.val.templs.length === 0) { + if (!templateRunner.val?.templs || templateRunner.val.templs.length === 0) { return (
No Templates Found - + To use this feature, create a templates dir in your root and add some .tmpl template files. @@ -436,7 +456,7 @@ const TemplateCreate = ({rootPath, onClose}: { component={Link} href="https://dockman.radn.dev/docs/templates" target="_blank" - endIcon={} + endIcon={} > View Documentation @@ -472,8 +492,13 @@ const TemplateCreate = ({rootPath, onClose}: { ))} - - + @@ -481,7 +506,7 @@ const TemplateCreate = ({rootPath, onClose}: { variant="contained" onClick={handleConfirm} disabled={isSubmitting} - startIcon={isSubmitting ? : } + startIcon={isSubmitting ? : } sx={{borderRadius: 2, px: 4, fontWeight: 700}} > Create from Template @@ -498,7 +523,7 @@ const TemplateCreate = ({rootPath, onClose}: { - {runner.val.templs.map((tmpl, idx) => ( + {templateRunner.val.templs.map((tmpl, idx) => ( - + {tmpl.Name} - + {Object.keys(tmpl.vars).length} variables required diff --git a/ui/src/pages/compose/dialogs/file-rename.tsx b/ui/src/pages/compose/dialogs/file-rename.tsx index 130a4161..964ee640 100644 --- a/ui/src/pages/compose/dialogs/file-rename.tsx +++ b/ui/src/pages/compose/dialogs/file-rename.tsx @@ -84,7 +84,9 @@ function FileRename() { }} > - + Rename Item - + Original: {filename} - @@ -166,9 +169,7 @@ function FileRename() { )} - - + - Error @@ -130,7 +179,6 @@ export function TabDeploy({selectedPage}: DeployPageProps) { - Choose exec entrypoint @@ -147,9 +195,13 @@ export function TabDeploy({selectedPage}: DeployPageProps) { variant="outlined" size="small" slotProps={{ - inputLabel: {style: {color: '#aaa'}}, + ...params.slotProps, + inputLabel: { + ...params.slotProps.inputLabel, + style: {color: '#aaa'} + }, input: { - ...params.InputProps, + ...params.slotProps.input, style: {color: '#fff', backgroundColor: '#333'} } }} @@ -174,9 +226,13 @@ export function TabDeploy({selectedPage}: DeployPageProps) { variant="outlined" size="small" slotProps={{ - inputLabel: {style: {color: '#aaa'}}, + ...params.slotProps, + inputLabel: { + ...params.slotProps.inputLabel, + style: {color: '#aaa'} + }, input: { - ...params.InputProps, + ...params.slotProps.input, style: {color: '#fff', backgroundColor: '#333'} } }} diff --git a/ui/src/pages/compose/tab-editor.tsx b/ui/src/pages/compose/tab-editor.tsx index 474837f7..79dca4d0 100644 --- a/ui/src/pages/compose/tab-editor.tsx +++ b/ui/src/pages/compose/tab-editor.tsx @@ -1,5 +1,5 @@ import {Box, IconButton, Tooltip} from "@mui/material"; -import {type JSX, useMemo, useState} from "react"; +import {type JSX, useCallback, useMemo, useState} from "react"; import {callRPC, useHostClient} from "../../lib/api"; import {useSnackbar} from "../../hooks/snackbar.ts"; import {type SaveState} from "./hooks/status-hook.tsx"; @@ -32,24 +32,14 @@ function TabEditor({selectedPage, setFileSaveStatus}: EditorProps) { const [errors, setErrors] = useState([]) - const getFile = async (filename: string) => { + const getFile = useCallback(async (filename: string) => { const {file, err} = await downloadFile(filename) return {contents: file, err: err} - }; - - const saveFile = async (filename: string, contents: string) => { - const err = await uploadFile(filename, contents); - if (err) { - return err - } - - await validateFile(); - return "" - } + }, [downloadFile]); const [activeAction, setActiveAction] = useState(null); - async function validateFile() { + const validateFile = useCallback(async () => { if (isComposeFile(selectedPage)) { const {val: errs, err: err2} = await callRPC(() => dockerClient.composeValidate({ @@ -74,7 +64,17 @@ function TabEditor({selectedPage, setFileSaveStatus}: EditorProps) { }) } } - } + }, [dockerClient, selectedPage, showWarning]) + + const saveFile = useCallback(async (filename: string, contents: string) => { + const err = await uploadFile(filename, contents); + if (err) { + return err + } + + await validateFile(); + return "" + }, [uploadFile, validateFile]) const actions: Record = useMemo(() => { const baseActions: Record = { @@ -222,7 +222,7 @@ const SidebarContent = ( activeAction: string | null; actions: Record }) => { - const {panelSize, panelRef, handleMouseDown, isResizing} = useResizeBar('left', 450) + const {panelSize, panelRef, handleMouseDown, isResizing} = useResizeBar('left', 280) return ( state.host) + // the host-wide view reads the real host usage; stack views keep the + // per-container aggregation + const hostStats = useHostStats(!selectedPage) + + const isPage = variant === 'page'; + + // display-only filter: the aggregate band keeps totalling everything + const filteredContainers = useMemo(() => { + if (!isPage) return containers; + const query = search.trim().toLowerCase(); + if (!query) return containers; + return containers.filter(c => c.name.toLowerCase().includes(query)); + }, [containers, isPage, search]); return ( + {isPage && ( + + + + Stats + + + + on {host} + + + + + )} - + - ); -} \ No newline at end of file +} diff --git a/ui/src/pages/containers/components/container-action-button.tsx b/ui/src/pages/containers/components/container-action-button.tsx index 39ec65e7..1e878afa 100644 --- a/ui/src/pages/containers/components/container-action-button.tsx +++ b/ui/src/pages/containers/components/container-action-button.tsx @@ -4,6 +4,8 @@ import {useEffect, useState} from "react"; import {useSnackbar} from "../../../hooks/snackbar.ts"; import {DockerService} from "../../../gen/docker/v1/docker_pb.ts"; +type ContainerActionRpc = 'containerStart' | 'containerStop' | 'containerRestart' | 'containerRemove'; + export const useContainerAction = ({onActionComplete, containerId, removeRemoveAction = false}: { containerId?: string, removeRemoveAction?: boolean, @@ -19,8 +21,7 @@ export const useContainerAction = ({onActionComplete, containerId, removeRemoveA } }, [containerId]); - async function handleContainerAction(name: string, rpcName: keyof typeof dockerService, message: string) { - // @ts-ignore + async function handleContainerAction(name: string, rpcName: ContainerActionRpc, message: string) { const {err} = await callRPC(() => dockerService[rpcName]({containerIds: selectedContainers})); if (err) { showError(`Failed to ${name} Containers: ${err}`); diff --git a/ui/src/pages/containers/containers-loading.tsx b/ui/src/pages/containers/containers-loading.tsx index 62c2fe1c..cc68f402 100644 --- a/ui/src/pages/containers/containers-loading.tsx +++ b/ui/src/pages/containers/containers-loading.tsx @@ -9,10 +9,12 @@ export const ContainersLoading = () => { width: '100%', flex: 1 // Use flex: 1 instead of height: '100%' }}> - - - Loading containers... - - + + + Loading containers... + + ); }; diff --git a/ui/src/pages/containers/containers.tsx b/ui/src/pages/containers/containers.tsx index 8ba2f5bc..b29e7e49 100644 --- a/ui/src/pages/containers/containers.tsx +++ b/ui/src/pages/containers/containers.tsx @@ -1,16 +1,12 @@ import { Box, Chip, - CircularProgress, Divider, Fade, - IconButton, Paper, - Stack, - Tooltip, - Typography, } from '@mui/material'; -import {Delete, DnsOutlined, PlayArrow, Refresh, RestartAlt, Stop,} from '@mui/icons-material'; +import {Delete, DnsOutlined, PlayArrow, RestartAlt, Stop,} from '@mui/icons-material'; +import PageHeader, {RefreshButton} from "../../components/page-header.tsx"; import {ContainerTable} from '../compose/components/container-info-table'; import {useMemo, useState} from "react"; import {useDockerContainers} from "../../hooks/docker-containers.ts"; @@ -25,6 +21,8 @@ import {useNavigate} from "react-router-dom"; import {useHostStore} from "../compose/state/files.ts"; import {DockerService} from "../../gen/docker/v1/docker_pb.ts"; +type ContainerActionRpc = 'containerStart' | 'containerStop' | 'containerRestart' | 'containerRemove'; + function ContainersPage() { const dockerService = useHostClient(DockerService); const {containers, loading, refreshContainers, fetchContainers} = useDockerContainers(); @@ -83,8 +81,7 @@ function ContainersPage() { }, ]; - async function handleContainerAction(name: string, rpcName: keyof typeof dockerService, message: string) { - // @ts-ignore + async function handleContainerAction(name: string, rpcName: ContainerActionRpc, message: string) { const {err} = await callRPC(() => dockerService[rpcName]({containerIds: selectedContainers})); if (err) showError(`Failed to ${name} Containers: ${err}`); else showSuccess(`Successfully ${message} containers`); @@ -110,46 +107,23 @@ function ContainersPage() { ...scrollbarStyles }}> {/* Header Section */} - - - - - - Containers - - - - - Manage and monitor containers on {host} - - - - - - - {loading ? : } - - - - + } + title="Containers" + count={containers?.list.length} + host={host} + /> {/* Toolbar Card */} - + - - - - {selectedContainers.length > 0 && - {selectedContainers.length} SELECTED - } - + + + + {selectedContainers.length > 0 && ( + + )} diff --git a/ui/src/pages/containers/inspect-tab-exec.tsx b/ui/src/pages/containers/inspect-tab-exec.tsx index e9cc5f67..c914ec08 100644 --- a/ui/src/pages/containers/inspect-tab-exec.tsx +++ b/ui/src/pages/containers/inspect-tab-exec.tsx @@ -18,14 +18,13 @@ import "@xterm/xterm/css/xterm.css"; import TerminalIcon from '@mui/icons-material/Terminal'; import BugReportIcon from '@mui/icons-material/BugReport'; import PlayArrowIcon from '@mui/icons-material/PlayArrow'; -import HelpOutlineIcon from '@mui/icons-material/HelpOutline'; +import HelpOutlineIcon from '@mui/icons-material/HelpOutlined'; import SettingsIcon from '@mui/icons-material/Settings'; import {useSnackbar} from "../../hooks/snackbar.ts"; import {FitAddon} from "@xterm/addon-fit"; import {createTab} from "../compose/state/terminal.tsx"; import {useContainerExecWsUrl} from "../../lib/api.ts"; import AppTerminal from "../compose/components/logs-terminal.tsx"; -import {useHostStore} from "../compose/state/files.ts"; import {Close} from "@mui/icons-material"; const commandOptions = ["/bin/sh", "/bin/bash", "sh", "bash", "zsh"]; @@ -33,7 +32,6 @@ const debugImageOptions = ["nixery.dev/shell/fish", "nixery.dev/shell/bash", "ni const InspectTabExec = ({containerID}: { containerID: string; }) => { const {showError} = useSnackbar(); - const selectedHost = useHostStore(state => state.host) const fitAddonRef = useRef(new FitAddon()); const [selectedCmd, setSelectedCmd] = useState('/bin/sh'); @@ -56,7 +54,7 @@ const InspectTabExec = ({containerID}: { containerID: string; }) => { const setupExec = useCallback(() => { const url = createExecUrl(containerID, selectedCmd, debuggerImage) return createTab(url, `Exec: ${containerID}`, true) - }, [containerID, debuggerImage, selectedCmd, selectedHost]); + }, [containerID, createExecUrl, debuggerImage, selectedCmd]); const containerShortId = containerID.slice(0, 12); @@ -80,7 +78,9 @@ const InspectTabExec = ({containerID}: { containerID: string; }) => { bgcolor: 'background.default' }} > - + setIsConnected(false)}> @@ -89,7 +89,6 @@ const InspectTabExec = ({containerID}: { containerID: string; }) => { - { Execute Command - + Open an interactive shell session within container {containerShortId}. @@ -145,13 +146,25 @@ const InspectTabExec = ({containerID}: { containerID: string; }) => { /> - + OR - + Dockman Debug diff --git a/ui/src/pages/containers/inspect-tab-info.tsx b/ui/src/pages/containers/inspect-tab-info.tsx index 09028688..f3922d58 100644 --- a/ui/src/pages/containers/inspect-tab-info.tsx +++ b/ui/src/pages/containers/inspect-tab-info.tsx @@ -1,5 +1,5 @@ import {useHostClient, useRPCRunner} from "../../lib/api.ts"; -import {type ReactElement, type ReactNode, useEffect} from "react"; +import {type ElementType, type ReactElement, type ReactNode, useEffect} from "react"; import {DockerService} from "../../gen/docker/v1/docker_pb.ts"; import { @@ -62,10 +62,12 @@ function ContainerProcessList({containerId}: { containerId: string }) { > + sx={{ + alignItems: "center", + mb: 2, + width: '100%' + }}> }/> - - - {loading && !val ? ( - Interrogating container... + Interrogating container... ) : err ? ( @@ -131,7 +132,12 @@ function ContainerProcessList({containerId}: { containerId: string }) { ) : ( - + No process data available @@ -179,8 +185,15 @@ function InspectTabInfo({containerId}: { containerId: string }) { {/* 1. Header Section */} - - + + - {/* Metadata & Config Grid */} @@ -253,8 +265,8 @@ function InspectTabInfo({containerId}: { containerId: string }) { ( flexGrow: 1, minHeight: '120px' }}> - + {message} @@ -516,7 +533,13 @@ const tableHeaderStyle = { }; const SectionHeader = ({icon, title}: { icon: ReactNode, title: string }) => ( - + {icon} {title} @@ -525,13 +548,13 @@ const SectionHeader = ({icon, title}: { icon: ReactNode, title: string }) => ( ); const DetailRow = ({icon: Icon, label, value, mono}: { - icon?: any, label: string, value?: string | ReactElement, mono?: boolean + icon?: ElementType, label: string, value?: string | ReactElement, mono?: boolean }) => ( {label} - + {Icon && } {typeof value === 'string' ? { - const fitAddonRef = useRef(new FitAddon()); - const getLogUrl = useContainerLogsWsUrl() - - const getLogTab = useCallback(() => { - const url = getLogUrl(containerID) - return createTab(url, `Logs: ${containerID}`, false) - }, [containerID, getLogUrl]) - - return ( - - ); + return ; }; -export default InspectTabLog; \ No newline at end of file +export default InspectTabLog; diff --git a/ui/src/pages/containers/inspect.tsx b/ui/src/pages/containers/inspect.tsx index 94afe8fe..d83424d8 100644 --- a/ui/src/pages/containers/inspect.tsx +++ b/ui/src/pages/containers/inspect.tsx @@ -88,7 +88,9 @@ const ContainerInspect = () => { }} > - + navigate(`/${host}/containers`)} size="small" @@ -103,7 +105,12 @@ const ContainerInspect = () => { onClick={() => navigate(`/${host}/containers`)}> Containers - + Inspect @@ -144,7 +151,6 @@ const ContainerInspect = () => { - state.setHost) + const resetTabs = useTabsStore(state => state.reset) + const clearTerminalTabs = useTerminalTabs(state => state.clearAll) + const clearLastOpened = useLastOpened(state => state.clear) + const previousHost = useRef(host) useEffect(() => { + if (previousHost.current !== host) { + resetTabs() + clearTerminalTabs() + clearLastOpened() + previousHost.current = host + } setHost(host) - }, [host]); + }, [clearLastOpened, clearTerminalTabs, host, resetTabs, setHost]); const handleLogout = () => { logout(); @@ -43,13 +52,14 @@ export function RootLayout() { const navigationItems = useMemo(() => [ {title: 'Files', path: `/${host}/files`, icon: DockerFolderIcon}, + {title: 'Monitor', path: `/${host}/monitor`, icon: () => }, {title: 'Stats', path: `/${host}/stats`, icon: StatsIcon}, {title: 'Containers', path: `/${host}/containers`, icon: ContainerIcon}, {title: 'Images', path: `/${host}/images`, icon: ImagesIcon}, {title: 'Volumes', path: `/${host}/volumes`, icon: VolumeIcon}, {title: 'Networks', path: `/${host}/networks`, icon: NetworkIcon}, {title: 'Cleaner', path: `/${host}/cleaner`, icon: () => }, - ], [lastOpened, host, navigate]); + ], [host]); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { diff --git a/ui/src/pages/home/not-found.tsx b/ui/src/pages/home/not-found.tsx index a346481d..1e85532e 100644 --- a/ui/src/pages/home/not-found.tsx +++ b/ui/src/pages/home/not-found.tsx @@ -16,7 +16,9 @@ const NotFoundPage = () => { Page Not Found - + Page not in the sudoers file. This incident will be reported. @@ -29,7 +31,7 @@ const NotFoundPage = () => { - ) + ); } export default NotFoundPage \ No newline at end of file diff --git a/ui/src/pages/images/docker-images.ts b/ui/src/pages/images/docker-images.ts index 3d5f1a8f..744c82f5 100644 --- a/ui/src/pages/images/docker-images.ts +++ b/ui/src/pages/images/docker-images.ts @@ -2,7 +2,6 @@ import {useCallback, useEffect, useState} from 'react' import {callRPC, useHostClient} from "../../lib/api.ts"; import {DockerService, type Image} from "../../gen/docker/v1/docker_pb.ts"; import {useSnackbar} from "../../hooks/snackbar.ts"; -import {useHostStore} from "../compose/state/files.ts"; /** * Generates a clickable URL for a container image, pointing to its repository. @@ -63,7 +62,6 @@ export const getImageHomePageUrl = (imageName: string): string => { export function useDockerImages() { const dockerService = useHostClient(DockerService) const {showWarning} = useSnackbar() - const selectedHost = useHostStore(state => state.host) const [images, setImages] = useState([]) @@ -91,14 +89,14 @@ export function useDockerImages() { setUnusedContainerCount(val?.unusedImageCount ?? BigInt(0)) setImages(val?.images || []) - }, [dockerService, selectedHost]) + }, [dockerService, showWarning]) const refreshImages = useCallback(() => { fetchImages().finally(() => setLoading(false)) }, [fetchImages]); const pruneUnused = useCallback(async (all = false) => { - const {val, err} = await callRPC(() => dockerService.imagePruneUnused({ + const {err} = await callRPC(() => dockerService.imagePruneUnused({ pruneAll: all, })) if (err) { @@ -106,11 +104,10 @@ export function useDockerImages() { return } - console.info(val) fetchImages().finally(() => { setLoading(false) }) - }, [dockerService, fetchImages]) + }, [dockerService, fetchImages, showWarning]) const deleteImages = useCallback(async (images: string[]) => { const {err} = await callRPC(() => dockerService.imageRemove({ @@ -124,7 +121,7 @@ export function useDockerImages() { fetchImages().finally(() => { setLoading(false) }) - }, [dockerService, fetchImages]) + }, [dockerService, fetchImages, showWarning]) useEffect(() => { refreshImages() @@ -140,4 +137,4 @@ export function useDockerImages() { untagged, deleteImages, } -} \ No newline at end of file +} diff --git a/ui/src/pages/images/images-table.tsx b/ui/src/pages/images/images-table.tsx index bebe746f..7c4841be 100644 --- a/ui/src/pages/images/images-table.tsx +++ b/ui/src/pages/images/images-table.tsx @@ -88,7 +88,9 @@ export const ImageTable = ({images, selectedImages = [], onSelectionChange}: Ima ), cell: (image) => ( - + {image.repoTags.length > 0 ? ( )} - + {image.id.substring(0, 12)} @@ -233,7 +237,13 @@ export const ImageTable = ({images, selectedImages = [], onSelectionChange}: Ima ), cell: (image) => ( - + {formatDate(image.created)} diff --git a/ui/src/pages/images/images.tsx b/ui/src/pages/images/images.tsx index 79461bf1..63c1be91 100644 --- a/ui/src/pages/images/images.tsx +++ b/ui/src/pages/images/images.tsx @@ -1,6 +1,8 @@ import {useMemo, useState} from 'react'; -import {Box, Button, Card, CircularProgress, Fade, Link, Paper, Tooltip, Typography} from '@mui/material'; -import {CleaningServices, Delete, Refresh, Sanitizer, Storage} from '@mui/icons-material'; +import {Box, CircularProgress, Divider, Fade, Link, Paper, Typography} from '@mui/material'; +import {CleaningServices, Delete, Sanitizer, Storage} from '@mui/icons-material'; +import PageHeader, {RefreshButton} from "../../components/page-header.tsx"; +import {useHostStore} from "../compose/state/files.ts"; import {ImageTable} from './images-table.tsx'; import {formatBytes} from "../../lib/editor.ts"; import scrollbarStyles from "../../components/scrollbar-style.tsx"; @@ -23,13 +25,17 @@ const ImagesPage = () => { const {search, setSearch, searchInputRef} = useSearch(); const [selectedImages, setSelectedImages] = useState([]) + const host = useHostStore(state => state.host) const filteredImages = useMemo(() => { if (search) { + // untagged (dangling) images have no repoTags at all — indexing + // [0] blindly crashed the whole page on the first keystroke. + // Match any tag or the id, case-insensitively. + const query = search.toLowerCase(); return images.filter(image => - image.repoTags[0] - .toLowerCase() - .includes(search)) + image.repoTags.some(tag => tag.toLowerCase().includes(query)) || + image.id.toLowerCase().includes(query)) } return images; }, [images, search]); @@ -77,59 +83,46 @@ const ImagesPage = () => { overflow: 'hidden', ...scrollbarStyles }}> - } + title="Images" + count={images.length} + extra={formatBytes(totalImageSize) ?? '0B'} + host={host} + /> + + - {/* Title and Stats */} - - - Docker Images - + + - - - {images.length} images • {formatBytes(totalImageSize) ?? '0B'} - - - - - - - - + - {/* Spacer */} - - - - + + + + + {/* Table Container */} { flex: 1 }}> - + Loading images... @@ -198,12 +193,12 @@ const ImagesEmpty = ({searchTerm}: { searchTerm: string }) => { mb: 2, mx: 'auto' }}/> - {searchTerm ? 'No images found' : 'No images available'} - - + {searchTerm ? ( 'Try adjusting your search criteria.' ) : ( diff --git a/ui/src/pages/images/inspect.tsx b/ui/src/pages/images/inspect.tsx index e1a5950a..b765b409 100644 --- a/ui/src/pages/images/inspect.tsx +++ b/ui/src/pages/images/inspect.tsx @@ -25,7 +25,7 @@ import { import scrollbarStyles from "../../components/scrollbar-style.tsx"; import RefreshIcon from '@mui/icons-material/Refresh'; import ImageOutlinedIcon from '@mui/icons-material/ImageOutlined'; -import {ArrowBack, HistoryOutlined} from "@mui/icons-material"; +import {ArrowBack, HistoryOutlined, Inventory2Outlined} from "@mui/icons-material"; const ImageInspectPage = () => { const dockerService = useHostClient(DockerService); @@ -66,18 +66,26 @@ const ImageInspectPage = () => { py: 2, px: 3, flexShrink: 0 }} > - + navigate(-1)}> Images - Inspect + Inspect - + navigate(-1)} size="small" sx={{border: '1px solid', borderColor: 'divider'}}> @@ -95,7 +103,12 @@ const ImageInspectPage = () => { {inspect?.name?.split(':')[0] || 'Image Manifest'} - + {id?.substring(0, 12)} @@ -110,8 +123,13 @@ const ImageInspectPage = () => { - - + {loading ? ( { gap: 2 }}> - Analyzing image + Analyzing image layers... ) : err ? ( {err} ) : inspect && ( - <> + {/* Summary Info Cards */} {/* Unified Image Summary Card */} { }}> Specifications - + - SIZE + SIZE { - ARCHITECTURE + ARCHITECTURE { - CREATED + CREATED ON {inspect.createdIso ? new Date(inspect.createdIso).toLocaleDateString(undefined, {dateStyle: 'medium'}) : 'N/A'} @@ -212,6 +249,104 @@ const ImageInspectPage = () => { + {/* Containers using this image */} + + + + + Used By + + + + + {inspect.containers && inspect.containers.length > 0 ? ( + +
+ + + Container + State + Project + ID + + + + {inspect.containers.map((c) => ( + + + {c.name || '—'} + + + + + + {c.composeProject || '—'} + + + {c.id.substring(0, 12)} + + + ))} + +
+
+ ) : ( + + + No containers are using this image. + + + )} +
+ {/* Layers Table */} { justifyContent: 'space-between', bgcolor: 'background.paper' }}> - + History Layers { - + +
)}
@@ -296,6 +434,13 @@ const headerStyles = { py: 1.5 }; +const stateColor = (state: string): 'success' | 'error' | 'default' => { + const s = (state || '').toLowerCase(); + if (s === 'running') return 'success'; + if (s === 'exited' || s === 'dead') return 'error'; + return 'default'; +}; + const DetailRow = ({label, value, mono}: { label: string, value: string, mono?: boolean }) => ( {label} - + ; +type TabID = 'overview' | 'logs' | 'exec' | 'processes' | 'networks' | 'mounts' | 'environment' + | 'labels' | 'security' | 'resources' | 'health' | 'inspect'; + +interface Props { + open: boolean; + row: MonitorRow | null; + containers: ContainerList[]; + history?: {cpu: number[]; mem: number[]}; + busy?: RowAction; + stackBusy: boolean; + updateRun?: 'running' | 'failed' | 'done'; + onClose: () => void; + onAction: (row: MonitorRow, action: RowAction) => void; +} + +const tabs: {id: TabID, label: string, icon: ReactElement}[] = [ + {id: 'overview', label: 'Overview', icon: }, {id: 'logs', label: 'Logs', icon: }, + {id: 'exec', label: 'Exec', icon: }, + {id: 'processes', label: 'Processes', icon: }, {id: 'networks', label: 'Networks', icon: }, + {id: 'mounts', label: 'Mounts', icon: }, {id: 'environment', label: 'Environment', icon: }, + {id: 'labels', label: 'Labels', icon: }, {id: 'security', label: 'Security', icon: }, + {id: 'resources', label: 'Resources', icon: }, {id: 'health', label: 'Health', icon: }, + {id: 'inspect', label: 'Inspect JSON', icon: }, +]; + +const asObject = (value: unknown): JsonObject => value !== null && typeof value === 'object' && !Array.isArray(value) + ? value as JsonObject : {}; +const asArray = (value: unknown): unknown[] => Array.isArray(value) ? value : []; +const text = (value: unknown, fallback = '–'): string => { + if (value === null || value === undefined || value === '') return fallback; + if (typeof value === 'boolean') return value ? 'Yes' : 'No'; + if (typeof value === 'object') return JSON.stringify(value); + return String(value); +}; +const field = (object: unknown, key: string): unknown => asObject(object)[key]; +const list = (value: unknown): string[] => asArray(value).map(v => text(v, '')); +const fmtDate = (value: unknown) => { + const raw = text(value, ''); + if (!raw || raw.startsWith('0001-')) return '–'; + const date = new Date(raw); + return Number.isNaN(date.getTime()) ? raw : date.toLocaleString(); +}; +const fmtLimit = (value: unknown) => { + const n = Number(value ?? 0); + return n > 0 ? formatBytes(n) : 'Unlimited / default'; +}; +const fmtDuration = (value: unknown) => { + const ns = Number(value ?? 0); + if (!Number.isFinite(ns) || ns <= 0) return 'Default'; + if (ns >= 1e9) return `${ns / 1e9}s`; + if (ns >= 1e6) return `${ns / 1e6}ms`; + if (ns >= 1e3) return `${ns / 1e3}µs`; + return `${ns}ns`; +}; + +function Section({title, children, fill = false}: {title: string, children: ReactNode, fill?: boolean}) { + return + {title}{children} + ; +} + +function Details({rows}: {rows: [string, unknown][]}) { + return + {rows.map(([label, value]) => + {label} + {text(value)} + )} + ; +} + +function StringChips({values, empty = 'None'}: {values: string[], empty?: string}) { + if (values.length === 0) return {empty}; + return + {values.map((v, i) => )} + ; +} + +function KeyValueTable({value, maskSecrets = false, scrollable = false}: {value: unknown, maskSecrets?: boolean, scrollable?: boolean}) { + const [revealed, setRevealed] = useState(false); + const entries = useMemo(() => { + if (Array.isArray(value)) return value.map(v => { + const raw = text(v, ''); const idx = raw.indexOf('='); + return idx < 0 ? [raw, ''] : [raw.slice(0, idx), raw.slice(idx + 1)]; + }); + return Object.entries(asObject(value)).sort(([a], [b]) => a.localeCompare(b)); + }, [value]); + const sensitive = (key: string) => /pass|token|secret|credential|private|api.?key|cookie|auth/i.test(key); + return + {maskSecrets && } + NameValue + {entries.map(([key, val]) => + {String(key)} + + {maskSecrets && sensitive(String(key)) && !revealed ? '••••••••' : text(val, '')} + + )}
+ {entries.length === 0 && None} +
; +} + +function MetricCard({label, value, sub, data, color}: {label: string, value: string, sub?: string, data?: number[], color: string}) { + return + {label} + {value} + {sub && {sub}} + {data && } + ; +} + +function traefikEndpoints(labels: JsonObject, addresses: string[]): string[] { + const endpoints = new Set(addresses + .map(value => value.trim().toLowerCase()) + .filter(value => /[a-z]/i.test(value) && value.includes('.') && !value.includes(':'))); + const explicitlyDisabled = Object.entries(labels).some(([key, value]) => + key.toLowerCase() === 'traefik.enable' && text(value, '').trim().toLowerCase() === 'false'); + if (explicitlyDisabled) return [...endpoints].sort((a, b) => a.localeCompare(b)); + const hostFunction = /\bHost(?:SNI(?:Regexp)?|Regexp)?\s*\(([^)]*)\)/gi; + const quotedValue = /[`"']([^`"']+)[`"']/g; + for (const [key, raw] of Object.entries(labels)) { + if (!/^traefik\.(?:http|tcp|udp)\.routers\..+\.rule$/i.test(key)) continue; + for (const call of text(raw, '').matchAll(hostFunction)) { + for (const match of call[1].matchAll(quotedValue)) { + const endpoint = match[1].trim().replace(/\.$/, '').toLowerCase(); + if (endpoint && endpoint !== '*') endpoints.add(endpoint); + } + } + } + return [...endpoints].sort((a, b) => a.localeCompare(b)); +} + +function Dependencies({raw}: {raw: unknown}) { + const dependencies = text(raw, '').split(',').map(value => value.trim()).filter(Boolean).map(value => { + const parts = value.split(':'); + return { + service: parts[0] || value, + condition: parts[1]?.replace(/^service_/, '').replaceAll('_', ' ') || 'started', + restart: parts[2] === 'true', + }; + }); + if (dependencies.length === 0) return None; + return + {dependencies.map(dep => )} + ; +} + +function PortsView({value}: {value: unknown}) { + const ports = Object.entries(asObject(value)).sort(([a], [b]) => a.localeCompare(b, undefined, {numeric: true})); + if (ports.length === 0) return None; + return + Container portPublished on host + {ports.map(([containerPort, raw]) => { + const bindings = asArray(raw).map(asObject); + return + {containerPort} + + {bindings.length === 0 + ? + : bindings.map((binding, index) => { + const ip = text(binding.HostIp, '') || 'all interfaces'; + const port = text(binding.HostPort, 'dynamic'); + return ; + })} + + ; + })} +
; +} + +function Overview({row, inspect, history, processCount, onUpdate, updateRun}: { + row: MonitorRow, inspect: JsonObject, history?: {cpu: number[]; mem: number[]}, processCount: number | null, + onUpdate: () => void, updateRun?: 'running' | 'failed' | 'done', +}) { + const state = asObject(inspect.State); const config = asObject(inspect.Config); + const host = asObject(inspect.HostConfig); const network = asObject(inspect.NetworkSettings); + const endpoints = asObject(network.Networks); const stats = row.stats; + const restartPolicy = asObject(host.RestartPolicy); + const labels = asObject(config.Labels); + const portBindings = asObject(host.PortBindings); + const dns = traefikEndpoints(labels, row.info.IPAddress); + return + + } + sx={{alignItems: 'stretch', flexWrap: 'wrap'}}> + + + + + + + + +
+
+
+
+
+ Depends on + + + + + +
+
+ +
+ {dns.length > 0 && + {dns.map(domain => } label={domain} + sx={{fontFamily: t.mono, userSelect: 'text'}}/>)} + } +
{ + const ep = asObject(raw); return [ + [`${name} IP`, ep.IPAddress], [`${name} endpoint`, ep.EndpointID], [`${name} gateway`, ep.Gateway], + ] as [string, unknown][]; + }).concat([["Traefik endpoints", dns.join(', ')]])}/> +
+
+
+
; +} + +function Processes({active, containerID, onCount}: {active: boolean, containerID: string, onCount: (n: number | null) => void}) { + const client = useHostClient(DockerService); const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); const [titles, setTitles] = useState([]); const [rows, setRows] = useState([]); + const refresh = useCallback(async () => { + setLoading(true); const {val, err} = await callRPC(() => client.containerTop({containerId: containerID})); + setLoading(false); setError(err ?? ''); + const top = val?.top; const next = top?.proc.map(p => p.Processes) ?? []; + setTitles(top?.Titles ?? []); setRows(next); onCount(err ? null : next.length); + }, [client, containerID, onCount]); + useEffect(() => { if (!active) return; void refresh(); const id = setInterval(refresh, 5000); return () => clearInterval(id); }, [active, refresh]); + return
+ + + {error && {error}} + {titles.map(v => {v})} + {rows.map((r, i) => {r.map((v, j) => {v})})} +
+
; +} + +function friendlyNetworkMode(value: unknown, containers: ContainerList[]): string { + const mode = text(value, ''); + const match = /^container:(.+)$/.exec(mode); + if (!match) return mode || '–'; + + const reference = match[1]; + const target = containers.find(container => + container.id === reference || container.id.startsWith(reference) || reference.startsWith(container.id)); + return target ? `container:${target.name}` : mode; +} + +function Networks({containerID, containers, inspect, onChanged}: { + containerID: string, containers: ContainerList[], inspect: JsonObject, onChanged: () => void, +}) { + const client = useHostClient(DockerService); const {showError, showSuccess} = useSnackbar(); + const [networks, setNetworks] = useState([]); const [busy, setBusy] = useState(''); + const [confirm, setConfirm] = useState<{network: Network, action: 'connect' | 'disconnect'} | null>(null); + const load = useCallback(async () => { const {val, err} = await callRPC(() => client.networkList({})); if (err) showError(err); else setNetworks(val?.networks ?? []); }, [client, showError]); + useEffect(() => { void load(); }, [load]); + const settings = asObject(inspect.NetworkSettings); const mounted = asObject(settings.Networks); const host = asObject(inspect.HostConfig); + const run = async () => { + if (!confirm) return; const key = `${confirm.action}:${confirm.network.id}`; setBusy(key); + const request = {networkId: confirm.network.id, containerId: containerID}; + const result = confirm.action === 'connect' + ? await callRPC(() => client.networkConnectContainer(request)) + : await callRPC(() => client.networkDisconnectContainer(request)); + const {err} = result; + setBusy(''); setConfirm(null); + if (err) showError(`Network ${confirm.action} failed: ${err}`); else {showSuccess(`Network ${confirm.action}ed`); await load(); onChanged();} + }; + return +
+
+ {Object.entries(mounted).map(([name, raw]) => { const ep = asObject(raw); const network = networks.find(n => n.name === name); + return + {name} + IP {text(ep.IPAddress)} · gateway {text(ep.Gateway)} · MAC {text(ep.MacAddress)}
+ endpoint {text(ep.EndpointID)} · aliases {list(ep.Aliases).join(', ') || '–'}
+ {network && } +
; + })}{Object.keys(mounted).length === 0 && No mounted network} +
+
+ {networks.filter(n => !(n.name in mounted)).sort((a, b) => a.name.localeCompare(b.name)).map(n => + )} +
+
+ setConfirm(null)}>{confirm?.action === 'connect' ? 'Connect network?' : 'Disconnect network?'} + {confirm?.network.name} · {confirm?.network.driver} + + +
; +} + +function ExecTerminal({active, containerID, running}: {active: boolean, containerID: string, running: boolean}) { + const createExecUrl = useContainerExecWsUrl(); + const createOptionsUrl = useContainerExecOptionsUrl(); + const fitAddon = useRef(new FitAddon()); + const xterm = useRef(null); + const [shells, setShells] = useState(null); + const [shellError, setShellError] = useState(''); + const [shell, setShell] = useState(''); + const [userChoice, setUserChoice] = useState('context'); + const [otherUser, setOtherUser] = useState(''); + const [fontSize, setFontSize] = useState(() => Number(localStorage.getItem('dockman-exec-fontsize')) || 12); + const [connected, setConnected] = useState(false); + const {handleCopy, copiedId} = useCopyButton(); + useEffect(() => {setConnected(false); setShells(null); setShell(''); setShellError('');}, [containerID]); + useEffect(() => { + if (!active || !running) return; + const controller = new AbortController(); + setShells(null); setShellError(''); + fetch(createOptionsUrl(containerID), {signal: controller.signal}) + .then(async response => { + if (!response.ok) throw new Error(await response.text() || `HTTP ${response.status}`); + return response.json() as Promise<{shells?: string[]}>; + }) + .then(result => { + const available = result.shells ?? []; + setShells(available); + setShell(current => available.includes(current) ? current : available[0] ?? ''); + }) + .catch(error => {if (error instanceof Error && error.name !== 'AbortError') {setShellError(error.message); setShells([]);}}); + return () => controller.abort(); + }, [active, containerID, createOptionsUrl, running]); + const execUser = userChoice === 'context' ? '' : userChoice === 'other' ? otherUser.trim() : userChoice; + const terminal = useMemo(() => connected + ? createTab(createExecUrl(containerID, shell, undefined, execUser), `Exec: ${containerID.slice(0, 12)}`, true) + : null, [connected, containerID, createExecUrl, execUser, shell]); + const controlledTerminal = useMemo(() => terminal ? { + ...terminal, + onTerminal: (term: XTerm) => {xterm.current = term; terminal.onTerminal(term);}, + onClose: () => {xterm.current = null; terminal.onClose();}, + } : null, [terminal]); + const copyTerminal = () => { + const term = xterm.current; + if (!term) return; + const selected = term.getSelection(); + const lines: string[] = []; + if (!selected) { + const buffer = term.buffer.active; + for (let i = 0; i < buffer.length; i++) lines.push(buffer.getLine(i)?.translateToString(true) ?? ''); + } + handleCopy(selected || lines.join('\n').replace(/\n+$/, '')); + }; + const changeFontSize = (value: number) => {localStorage.setItem('dockman-exec-fontsize', String(value)); setFontSize(value);}; + if (!running) return Start or unpause the container to open an interactive terminal.; + return + + + {containerID.slice(0, 12)} + {shells === null ? : shells.length > 0 && setShell(event.target.value)} aria-label="Shell" slotProps={{select: {native: true}}} + sx={{width: 135, '& .MuiInputBase-root': {height: 28, fontFamily: t.mono, fontSize: '0.68rem'}}}> + {shells.map(value => )} + } + setUserChoice(event.target.value)} + aria-label="Exec user" slotProps={{select: {native: true}}} + sx={{width: 145, '& .MuiInputBase-root': {height: 28, fontSize: '0.68rem'}}}> + + + + {userChoice === 'other' && setOtherUser(event.target.value)} + aria-label="Custom exec user" placeholder="UID or user" sx={{width: 115, '& .MuiInputBase-root': {height: 28, fontFamily: t.mono, fontSize: '0.68rem'}}}/>} + changeFontSize(Number(event.target.value))} + aria-label="Terminal font size" slotProps={{select: {native: true}}} + sx={{width: 70, '& .MuiInputBase-root': {height: 28, fontSize: '0.68rem'}}}> + {[10, 12, 14, 16].map(value => )} + + + xterm.current?.clear()}> + + {copiedId ? : } + + + {connected ? 'Interactive session' : 'Disconnected'} + + + {shellError && {shellError}} + {shells?.length === 0 && !shellError && Exec is unavailable: no supported shell was found in this container.} + + {controlledTerminal ? + : Choose a shell and connect.} + + ; +} + +function Mounts({inspect}: {inspect: JsonObject}) { + const mounts = asArray(inspect.Mounts).map(asObject); + return
+ {['Type', 'Name', 'Source', 'Destination', 'Driver', 'Mode', 'RW', 'Propagation'].map(v => {v})} + {mounts.map((m, i) => + {[m.Type, m.Name, m.Source, m.Destination, m.Driver, m.Mode, m.RW, m.Propagation].map((v, j) => {text(v)})} + )}
{mounts.length === 0 && None}
; +} + +function Security({inspect}: {inspect: JsonObject}) { + const config = asObject(inspect.Config); const host = asObject(inspect.HostConfig); + return
v.includes('no-new-privileges'))], + ]}/>
+
+ +
+
+
+
+
; +} + +function Resources({inspect}: {inspect: JsonObject}) { + const h = asObject(inspect.HostConfig); const nano = Number(h.NanoCpus ?? 0); + return
0 ? `${nano / 1e9} CPU` : 'Unlimited / default'], ['CPU shares', h.CpuShares], + ['CPU quota / period', `${text(h.CpuQuota)} / ${text(h.CpuPeriod)}`], ['CPU set', h.CpusetCpus], ['NUMA memory nodes', h.CpusetMems], + ]}/>
0 ? h.PidsLimit : 'Unlimited / default'], + ]}/>
; +} + +function Health({inspect}: {inspect: JsonObject}) { + const config = asObject(inspect.Config); const healthConfig = asObject(config.Healthcheck); + const health = asObject(field(inspect.State, 'Health')); const logs = asArray(health.Log).map(asObject); + return
{logs.map((log, i) => +
+ )}{logs.length === 0 && No healthcheck log}
; +} + +function highlightJsonLine(line: string): ReactNode[] { + const tokens: ReactNode[] = []; + const pattern = /("(?:\\.|[^"\\])*")(\s*:)?|(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)|\b(true|false|null)\b/g; + let cursor = 0; + let key = 0; + for (const match of line.matchAll(pattern)) { + const start = match.index ?? 0; + if (start > cursor) tokens.push(line.slice(cursor, start)); + if (match[1]) { + tokens.push({match[1]}); + if (match[2]) tokens.push({match[2]}); + } else if (match[3]) { + tokens.push({match[3]}); + } else { + tokens.push({match[4]}); + } + cursor = start + match[0].length; + } + if (cursor < line.length) tokens.push(line.slice(cursor)); + return tokens; +} + +function JsonInspect({raw}: {raw: string}) { + const {handleCopy, copiedId} = useCopyButton(); + const formatted = useMemo(() => { + try { return JSON.stringify(JSON.parse(raw), null, 2); } catch { return raw; } + }, [raw]); + const lines = useMemo(() => formatted.split('\n'), [formatted]); + return + + Docker inspect + + {lines.length} lines + + + + {lines.map((line, index) => + {index + 1} + {highlightJsonLine(line)} + )} + + ; +} + +export default function ContainerDetailsDialog({open, row, containers, history, busy, stackBusy, updateRun, onClose, onAction}: Props) { + const client = useHostClient(DockerService); const [tab, setTab] = useState('overview'); + const [raw, setRaw] = useState(''); const [inspect, setInspect] = useState({}); + const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [processCount, setProcessCount] = useState(null); + const [removeAnchor, setRemoveAnchor] = useState(null); + const containerID = row?.info.id ?? ''; + const containerState = row?.info.state ?? ''; + const load = useCallback(async () => { + if (!containerID) return; setLoading(true); const {val, err} = await callRPC(() => client.containerInspect({containerID})); setLoading(false); + if (err || !val) {setError(err || 'Empty inspect response'); return;} setError(''); setRaw(val.rawJson); + try {setInspect(asObject(JSON.parse(val.rawJson)));} catch {setError('The daemon returned invalid inspect JSON');} + if (['running', 'restarting', 'paused'].includes(containerState)) { + const top = await callRPC(() => client.containerTop({containerId: containerID})); + setProcessCount(top.err ? null : top.val?.top?.proc.length ?? 0); + } else { + setProcessCount(0); + } + }, [client, containerID, containerState]); + useEffect(() => { if (open) {setTab('overview'); setProcessCount(null); void load();} }, [open, load]); + useEffect(() => { if (open && !row) onClose(); }, [open, row, onClose]); + if (!row) return null; + const state = row.info.state; const active = ['running', 'restarting', 'paused'].includes(state); const paused = state === 'paused'; + const processAvailable = state === 'running' || state === 'paused'; + const locked = !!busy || stackBusy; + const fixedContent = ['logs', 'exec', 'processes', 'mounts', 'environment', 'labels'].includes(tab); + return + + + + {row.info.name} + {row.info.id} + + onAction(row, active ? 'stop' : 'start')}> + {busy === 'start' || busy === 'stop' ? : active ? : } + onAction(row, 'restart')}>{busy === 'restart' ? : } + onAction(row, paused ? 'unpause' : 'pause')} + sx={{color: paused ? '#66bb6a' : '#ffb74d'}}>{paused ? : } + onAction(row, 'update')}> + setRemoveAnchor(event.currentTarget)}> + {loading ? : } + + + + + {stackBusy && Container actions are locked while its stack action is running.} + + + + setTab(value)} + variant="scrollable" sx={{py: 0.8, minHeight: '100%', '& .MuiTabs-indicator': {left: 0, right: 'auto', width: 3}}}> + {tabs.map(v => ) } + + + + {error && {error}} + + {loading && !raw ? : <> + + + + + + + + + + + + + } + + + + + setRemoveAnchor(null)} + anchorOrigin={{vertical: 'top', horizontal: 'center'}} transformOrigin={{vertical: 'bottom', horizontal: 'center'}} + slotProps={{paper: {sx: {bgcolor: t.header, border: `1px solid ${t.border}`, borderRadius: 1.5, px: 1.25, py: 1, maxWidth: 260}}}}> + Remove {row.info.name}? + + + + + + ; +} diff --git a/ui/src/pages/monitor/exec-launch-popover.tsx b/ui/src/pages/monitor/exec-launch-popover.tsx new file mode 100644 index 00000000..efbcb999 --- /dev/null +++ b/ui/src/pages/monitor/exec-launch-popover.tsx @@ -0,0 +1,105 @@ +import {Alert, Box, Button, CircularProgress, MenuItem, Popover, Stack, TextField, Typography} from '@mui/material'; +import {PersonOutlined, Terminal as TerminalIcon} from '@mui/icons-material'; +import {useEffect, useState} from 'react'; +import {useContainerExecOptionsUrl} from '../../lib/api.ts'; +import type {MonitorRow} from './monitor-table.tsx'; +import {statsTheme as t} from '../compose/components/stats-theme.ts'; + +export interface ExecLaunch { + anchor: HTMLElement; + row: MonitorRow; +} + +export default function ExecLaunchPopover({launch, onClose, onConnect}: { + launch: ExecLaunch | null; + onClose: () => void; + onConnect: (row: MonitorRow, shell: string, user: string) => void; +}) { + const optionsUrl = useContainerExecOptionsUrl(); + const [shells, setShells] = useState(null); + const [shell, setShell] = useState(''); + const [userChoice, setUserChoice] = useState('context'); + const [otherUser, setOtherUser] = useState(''); + const [error, setError] = useState(''); + useEffect(() => { + if (!launch) return; + const controller = new AbortController(); + setShells(null); + setShell(''); + setError(''); + setUserChoice('context'); + setOtherUser(''); + fetch(optionsUrl(launch.row.info.id), {signal: controller.signal}) + .then(async response => { + if (!response.ok) throw new Error(await response.text() || `HTTP ${response.status}`); + return response.json() as Promise<{shells?: string[]}>; + }) + .then(result => { + const values = result.shells ?? []; + setShells(values); + setShell(values[0] ?? ''); + }) + .catch(err => { + if (err instanceof Error && err.name !== 'AbortError') { + setError(err.message); + setShells([]); + } + }); + return () => controller.abort(); + }, [launch, optionsUrl]); + + const user = userChoice === 'context' ? '' : userChoice === 'other' ? otherUser.trim() : userChoice; + const fieldSx = { + '& .MuiInputBase-root': {height: 32, bgcolor: '#17191c', fontSize: '0.76rem'}, + '& .MuiOutlinedInput-notchedOutline': {borderColor: t.border}, + }; + + return + {launch && <> + + + {launch.row.info.name} + + {shells === null ? + : error ? + ! + Exec unavailable + {error} + : shells.length === 0 ? + ! + No shell available + This container has no supported shell installed. + : + Shell + setShell(event.target.value)} sx={fieldSx}> + {shells.map(value => {value})} + + User + setUserChoice(event.target.value)} sx={fieldSx} + slotProps={{input: {startAdornment: }}}> + Container context + root + nobody + Other… + + {userChoice === 'other' && setOtherUser(event.target.value)} placeholder="UID or user" sx={fieldSx}/>} + + } + } + ; +} diff --git a/ui/src/pages/monitor/monitor-page.tsx b/ui/src/pages/monitor/monitor-page.tsx new file mode 100644 index 00000000..433de219 --- /dev/null +++ b/ui/src/pages/monitor/monitor-page.tsx @@ -0,0 +1,899 @@ +import {Box, Button, Chip, Divider, Fade, Paper, Tooltip} from '@mui/material'; +import { + ArrowDownward, + ArrowUpward, + Delete, + Pause, + PlayArrow, + PlayCircleOutlined, + RestartAlt, + SpaceDashboardOutlined, + Stop, + UnfoldLess, + UnfoldMore, + Update, +} from '@mui/icons-material'; +import {useEffect, useMemo, useRef, useState} from 'react'; +import {useNavigate} from 'react-router-dom'; +import "@xterm/xterm/css/xterm.css"; +import PageHeader, {RefreshButton} from '../../components/page-header.tsx'; +import useSearch from '../../hooks/search.ts'; +import ActionButtons from '../../components/action-buttons.tsx'; +import scrollbarStyles from '../../components/scrollbar-style.tsx'; +import {useDockerContainers} from '../../hooks/docker-containers.ts'; +import {useDockerStats, useHostStats} from '../../hooks/docker-containers-stats.ts'; +import {useConfig} from '../../hooks/config.ts'; +import {callRPC, useContainerExecWsUrl, useHostClient} from '../../lib/api.ts'; +import {useSnackbar} from '../../hooks/snackbar.ts'; +import {useHostStore} from '../compose/state/files.ts'; +import {DockerService} from '../../gen/docker/v1/docker_pb.ts'; +import AggregateStats, {type ContainerStateFilter} from '../compose/components/container-stat-chart.tsx'; +import {LogsPanel} from '../compose/components/logs-panel.tsx'; +import {useContainerExec, useLogsPanel, useTerminalTabs} from '../compose/state/terminal.tsx'; +import {useComposeAction} from '../compose/state/compose.tsx'; +import {ContainersLoading} from '../containers/containers-loading.tsx'; +import { + MonitorTable, + type MonitorRow, + type MonitorSortField, + type RedeployOptions, + type RowAction, + type StackAction, + type StackGroup, + type StackStats, +} from './monitor-table.tsx'; +import {statsTheme as t} from '../compose/components/stats-theme.ts'; +import ContainerDetailsDialog from './container-details-dialog.tsx'; +import ExecLaunchPopover, {type ExecLaunch} from './exec-launch-popover.tsx'; + +type ContainerActionRpc = 'containerStart' | 'containerStop' | 'containerRestart' | 'containerPause' + | 'containerUnpause' | 'containerRemove'; + +// per-host view memory: expand/collapse choices and scroll offset survive +// navigating away and back (module-level on purpose — state resets with the +// component, this must not) +const monitorViewMemory = new Map, scroll: number }>(); + +function viewMemoryFor(host: string) { + const entry = monitorViewMemory.get(host) ?? {expanded: {}, scroll: 0}; + monitorViewMemory.set(host, entry); + return entry; +} + +// per-row sort key; -Number.MAX_VALUE sinks rows without a usable value. +// 'name' is compared as text in the comparators, not through this function. +function rowSortValue(r: MonitorRow, field: MonitorSortField): number { + const s = r.stats; + switch (field) { + case 'name': + return 0; + case 'cpu': + return s ? Math.max(s.cpuUsage, 0) : -Number.MAX_VALUE; + case 'mem': + return s ? Number(s.memoryUsage) : -Number.MAX_VALUE; + case 'net': + return s ? Number(s.networkRx) + Number(s.networkTx) : -Number.MAX_VALUE; + case 'uptime': { + if (r.info.state !== 'running' || !s?.startedAt) return -Number.MAX_VALUE; + const start = Date.parse(s.startedAt); + // older start = longer uptime = bigger value + return isNaN(start) ? -Number.MAX_VALUE : -start; + } + } +} + +// stack sort key: aggregate for the metric columns, best member for uptime +function groupSortValue(g: StackGroup, field: MonitorSortField): number { + switch (field) { + case 'name': + return 0; + case 'cpu': + return g.stats?.cpu ?? -Number.MAX_VALUE; + case 'mem': + return g.stats?.memUsed ?? -Number.MAX_VALUE; + case 'net': + return g.stats ? g.stats.netRx + g.stats.netTx : -Number.MAX_VALUE; + case 'uptime': + return Math.max(-Number.MAX_VALUE, ...g.rows.map(r => rowSortValue(r, 'uptime'))); + } +} + +// sums the member containers' live metrics and their history windows; +// sparklines scale to the window's shape, so a summed series keeps the +// aggregate's evolution readable +function aggregateStack(rows: MonitorRow[], history: Map): StackStats | null { + let cpu = 0, memUsed = 0, memLimit = 0, netRx = 0, netTx = 0, seen = 0; + for (const r of rows) { + const s = r.stats; + if (!s) continue; + seen++; + cpu += Math.max(s.cpuUsage, 0); + memUsed += Number(s.memoryUsage); + // same host-ceiling logic as the aggregate band: unlimited containers + // report the host total, summing would count it once per container + memLimit = Math.max(memLimit, Number(s.memoryLimit)); + netRx += Number(s.networkRx); + netTx += Number(s.networkTx); + } + if (seen === 0) return null; + + const cpuSeries: number[][] = []; + const memSeries: number[][] = []; + for (const r of rows) { + const h = history.get(r.info.name); + if (!h) continue; + cpuSeries.push(h.cpu); + memSeries.push(h.mem); + } + + return {cpu, memUsed, memLimit, netRx, netTx, cpuHist: sumSeries(cpuSeries), memHist: sumSeries(memSeries)}; +} + +// element-wise sum of series aligned on their most recent points +function sumSeries(series: number[][]): number[] { + const len = Math.max(0, ...series.map(s => s.length)); + const out: number[] = []; + for (let k = len; k >= 1; k--) { + let sum = 0; + for (const s of series) { + const v = s[s.length - k]; + if (v !== undefined) sum += v; + } + out.push(sum); + } + return out; +} + +// one view to run the host from: real host usage on top, every container +// grouped by stack below it, with per-row, per-stack and bulk controls plus +// the logs/exec bottom panel — no hopping between views. The existing Stats +// and Containers pages are left untouched. +function MonitorPage() { + const dockerService = useHostClient(DockerService); + const {containers, loading, fetchContainers} = useDockerContainers(); + const {history, containers: statContainers, aggregates, resetContainerStats} = useDockerStats(""); + const hostStats = useHostStats(true); + const {showSuccess, showError} = useSnackbar(); + const {search, setSearch, searchInputRef} = useSearch(); + const navigate = useNavigate(); + const host = useHostStore(state => state.host); + const {dockYaml} = useConfig(); + // dockman.yml → monitor.stackRows: "compact" drops the stack rows' charts + const compactStacks = (dockYaml?.monitorPage?.stackRows ?? '').trim().toLowerCase() === 'compact'; + + // stack and container selections are mutually exclusive; the toolbar + // switches to whichever kind is active + const [selectedContainers, setSelectedContainers] = useState([]); + const [selectedStacks, setSelectedStacks] = useState([]); + const [expanded, setExpanded] = useState>(() => viewMemoryFor(host).expanded); + const [sortField, setSortField] = useState(null); + const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc'); + const [stateFilters, setStateFilters] = useState([]); + const [now, setNow] = useState(() => Date.now()); + // container id → lifecycle action in flight: the row's buttons lock and + // the clicked one spins until the RPC and the list refetch settle + const [rowBusy, setRowBusy] = useState>({}); + const [detailsContainerID, setDetailsContainerID] = useState(''); + // container name → pre-action snapshot: the stats stream keeps serving + // the pre-action sample for a cycle or two, so these rows render pending + // metrics ('–') until fresh evidence arrives (see the pruning effect) + const [staleRows, setStaleRows] = useState>({}); + const scrollRef = useRef(null); + const scrollRestored = useRef(false); + + // remember the expand/collapse choices for this host + useEffect(() => { + viewMemoryFor(host).expanded = expanded; + }, [expanded, host]); + + // uptime column tick; freshness of the values themselves comes from the + // stats stream + useEffect(() => { + const id = setInterval(() => setNow(Date.now()), 10_000); + return () => clearInterval(id); + }, []); + + // the bottom panel tabs reference the previous host's containers + const clearTabs = useTerminalTabs(state => state.clearAll); + useEffect(() => { + clearTabs(); + setSelectedContainers([]); + setSelectedStacks([]); + setRowBusy({}); + setDetailsContainerID(''); + setStaleRows({}); + setStateFilters([]); + setExpanded(viewMemoryFor(host).expanded); + scrollRestored.current = false; + }, [clearTabs, host]); + + const createExecUrl = useContainerExecWsUrl(); + const execContainer = useContainerExec(state => state.execParams); + const openLogs = useLogsPanel(state => state.openLogs); + const runAction = useComposeAction(state => state.runAction); + const openOutput = useComposeAction(state => state.openOutput); + // string-valued selector: output appends leave it unchanged, so the page + // only re-renders when a stack action starts, finishes or flips outcome + const stackRunKeys = useComposeAction(state => + Object.entries(state.runs) + .map(([f, r]) => `${f}=${r.running ? 'running' : r.failed ? 'failed' : 'done'}`) + .sort().join('|')); + + // the stats stream and the container list agree on names (leading slash + // trimmed on both sides), while their id fields differ — join by name + const statsByName = useMemo(() => + new Map(statContainers.map(s => [s.name, s])), [statContainers]); + + const groups: StackGroup[] = useMemo(() => { + const query = search.trim().toLowerCase(); + const list = (containers?.list ?? []).filter(c => { + // This search lives in the NAME column, so only values displayed + // there participate (container/service and stack names). + const matchesSearch = !query || [c.name, c.stackName, c.serviceName] + .some(f => f.toLowerCase().includes(query)); + if (!matchesSearch) return false; + if (stateFilters.length === 0) return true; + return stateFilters.some(filter => { + switch (filter) { + case 'running': return c.state === 'running'; + case 'paused': return c.state === 'paused'; + case 'restarting': return c.state === 'restarting'; + case 'unhealthy': return c.state === 'running' && c.health === 'unhealthy'; + case 'stopped': return !['running', 'paused', 'restarting'].includes(c.state); + } + }); + }); + + const byStack = new Map(); + for (const c of list) { + const key = c.stackName; + const group = byStack.get(key) ?? {stack: key, servicePath: '', rows: [], stats: null}; + if (c.servicePath) group.servicePath = c.servicePath; + // a row frozen by a lifecycle action renders pending metrics + // instead of the stream's stale pre-action sample + const exposesMetrics = ['running', 'restarting', 'paused'].includes(c.state); + group.rows = [...group.rows, { + info: c, + stats: staleRows[c.name] || !exposesMetrics ? undefined : statsByName.get(c.name), + }]; + byStack.set(key, group); + } + + const dir = sortOrder === 'asc' ? 1 : -1; + return [...byStack.values()] + .map(g => { + const rows = [...g.rows].sort((a, b) => a.info.name.localeCompare(b.info.name)); + if (sortField === 'name') { + if (sortOrder === 'desc') rows.reverse(); + } else if (sortField) { + // stable metric sub-sort inside each stack (name breaks ties) + rows.sort((a, b) => (rowSortValue(a, sortField) - rowSortValue(b, sortField)) * dir); + } + return {...g, rows, stats: aggregateStack(g.rows, history)}; + }) + .sort((a, b) => { + // loose containers first (#standalone), then the sort order + if (!a.stack) return -1; + if (!b.stack) return 1; + if (sortField === 'name') { + return a.stack.localeCompare(b.stack) * dir; + } + if (sortField) { + const diff = (groupSortValue(a, sortField) - groupSortValue(b, sortField)) * dir; + if (diff !== 0) return diff; + } + return a.stack.localeCompare(b.stack); + }); + }, [containers, statsByName, history, search, stateFilters, sortField, sortOrder, staleRows]); + + // Resolve the dialog row from the unfiltered container list so an open + // details view is not accidentally closed by changing the monitor search. + const detailsRow: MonitorRow | null = useMemo(() => { + if (!detailsContainerID) return null; + const info = (containers?.list ?? []).find(c => c.id === detailsContainerID); + if (!info) return null; + const exposesMetrics = ['running', 'restarting', 'paused'].includes(info.state); + return {info, stats: staleRows[info.name] || !exposesMetrics ? undefined : statsByName.get(info.name)}; + }, [detailsContainerID, containers, staleRows, statsByName]); + + // a live search opens every matching stack so the hits are visible; + // the user's own expand/collapse choices come back once it clears + const effectiveExpanded = useMemo(() => { + if (!search.trim() && stateFilters.length === 0) return expanded; + const all: Record = {}; + for (const g of groups) all[g.stack] = true; + return all; + }, [search, stateFilters, expanded, groups]); + + const total = containers?.list.length ?? 0; + + // authoritative state counts from the (event-refreshed) container list — + // the band updates within seconds of a start/stop instead of waiting on + // a full stats cycle + const stateCounts = useMemo(() => { + const list = containers?.list ?? []; + const counts = {total: list.length, running: 0, stopped: 0, paused: 0, restarting: 0, unhealthy: 0}; + for (const c of list) { + switch (c.state) { + case 'running': + counts.running++; + break; + case 'paused': + counts.paused++; + break; + case 'restarting': + counts.restarting++; + break; + default: + counts.stopped++; + break; + } + // health only means something while the container runs; stale + // health on stopped/paused containers must not double-count + if (c.state === 'running' && c.health === 'unhealthy') counts.unhealthy++; + } + return counts; + }, [containers]); + useEffect(() => { + setStateFilters(current => current.filter(filter => stateCounts[filter] > 0)); + }, [stateCounts]); + const changeStateFilter = (filter: ContainerStateFilter | null, additive = false) => { + setStateFilters(current => { + if (filter === null) return []; + if (!additive) return [filter]; + return current.includes(filter) + ? current.filter(value => value !== filter) + : [...current, filter]; + }); + // Never leave hidden rows selected: bulk actions must only target + // containers the operator can currently see. + setSelectedContainers([]); + setSelectedStacks([]); + }; + // last (or current) action outcome per compose file, for the busy + // spinner and the last-action output button on stack rows + const stackRuns = useMemo(() => { + const map: Record = {}; + for (const entry of stackRunKeys.split('|')) { + if (!entry) continue; + const idx = entry.lastIndexOf('='); + map[entry.slice(0, idx)] = entry.slice(idx + 1) as 'running' | 'failed' | 'done'; + } + return map; + }, [stackRunKeys]); + const runningStacks = useMemo(() => { + const map: Record = {}; + for (const [file, status] of Object.entries(stackRuns)) { + if (status === 'running') map[file] = true; + } + return map; + }, [stackRuns]); + // per-container update runs, keyed by container name + const updateRuns = useMemo(() => { + const map: Record = {}; + for (const [key, status] of Object.entries(stackRuns)) { + if (key.startsWith('update:')) map[key.slice('update:'.length)] = status; + } + return map; + }, [stackRuns]); + + const allExpanded = groups.length > 0 && groups.every(g => effectiveExpanded[g.stack] ?? false); + + // the list is event-driven, so a manual refresh often changes nothing + // visible: give the button its own spinner so the fetch is observable + const [refreshing, setRefreshing] = useState(false); + const handleRefresh = async () => { + setRefreshing(true); + try { + await fetchContainers(); + } finally { + setRefreshing(false); + } + }; + + // restore the saved scroll offset once the table is mounted with data + useEffect(() => { + if (scrollRestored.current || loading) return; + const el = scrollRef.current; + if (!el) return; + el.scrollTop = viewMemoryFor(host).scroll; + scrollRestored.current = true; + }, [loading, groups.length, host]); + + const handleSortChange = (field: MonitorSortField) => { + if (sortField === field) { + setSortOrder(prev => prev === 'desc' ? 'asc' : 'desc'); + } else { + setSortField(field); + // names read naturally A→Z, metrics hottest-first + setSortOrder(field === 'name' ? 'asc' : 'desc'); + } + }; + + // ---- container actions ------------------------------------------------- + + // drop a frozen row as soon as the UI has fresh evidence of the new + // state: a moved startedAt for start/restart, the resting state for + // stop/pause/unpause, or the row vanishing. There is intentionally no + // timeout: an old uptime must never reappear merely because a refresh is + // slow; the row stays pending until fresh daemon evidence arrives. + useEffect(() => { + const names = Object.keys(staleRows); + if (names.length === 0) return; + const byName = new Map((containers?.list ?? []).map(c => [c.name, c])); + const next = {...staleRows}; + let changed = false; + for (const name of names) { + const m = staleRows[name]; + const listed = byName.get(name); + const sample = statsByName.get(name); + const settled = + m.action === 'start' || m.action === 'restart' + ? listed?.state === 'running' && sample !== undefined && (sample.startedAt ?? '') !== m.before + : m.action === 'stop' + ? listed !== undefined && listed.state !== 'running' + : m.action === 'pause' + ? listed?.state === 'paused' + : listed?.state === 'running'; // unpause: startedAt never moves + if (settled || listed === undefined) { + delete next[name]; + changed = true; + } + } + if (changed) setStaleRows(next); + }, [staleRows, containers, statsByName]); + + async function containerAction(action: Exclude, rpcName: ContainerActionRpc, message: string, ids: string[]) { + const named = (containers?.list ?? []).filter(c => ids.includes(c.id)); + setRowBusy(prev => { + const next = {...prev}; + for (const id of ids) next[id] = action; + return next; + }); + // snapshot the pre-action samples so the rows freeze to pending + // metrics until the stream visibly moves past them + if (action !== 'remove') { + setStaleRows(prev => { + const next = {...prev}; + for (const c of named) { + next[c.name] = {action, before: statsByName.get(c.name)?.startedAt ?? ''}; + } + return next; + }); + } + if (action === 'start' || action === 'stop' || action === 'restart') { + resetContainerStats(named.map(c => c.name)); + } + try { + const {err} = await callRPC(() => dockerService[rpcName]({containerIds: ids})); + if (err) { + showError(`Failed to ${action} containers: ${err}`); + // nothing changed on the daemon: unfreeze right away + setStaleRows(prev => { + const next = {...prev}; + for (const c of named) delete next[c.name]; + return next; + }); + } else { + showSuccess(`Successfully ${message} ${ids.length > 1 ? `${ids.length} containers` : 'container'}`); + } + setSelectedContainers(prev => prev.filter(id => !ids.includes(id))); + await fetchContainers(); + } finally { + setRowBusy(prev => { + const next = {...prev}; + for (const id of ids) delete next[id]; + return next; + }); + } + } + + const rowRpc: Record, { rpc: ContainerActionRpc, message: string }> = { + start: {rpc: 'containerStart', message: 'started'}, + stop: {rpc: 'containerStop', message: 'stopped'}, + restart: {rpc: 'containerRestart', message: 'restarted'}, + pause: {rpc: 'containerPause', message: 'paused'}, + unpause: {rpc: 'containerUnpause', message: 'unpaused'}, + remove: {rpc: 'containerRemove', message: 'removed'}, + }; + + // updates stream their progress (pull output, recreate steps) and run in + // the background like stack actions: one run per container, keyed + // update:, consultable through the output button + const startContainerUpdate = (id: string, name: string) => { + runAction( + `update:${name}`, + (_req, callOpts) => dockerService.containerUpdate({containerIds: [id]}, callOpts), + 'update', + [], + (error) => { + if (error) showError(`Update ${name} failed — ${error}`); + else showSuccess(`Update ${name} finished`); + void fetchContainers(); + }, + ); + }; + + const handleRowAction = (row: MonitorRow, action: RowAction) => { + if (row.info.servicePath && runningStacks[row.info.servicePath]) { + showError(`Stack ${row.info.stackName}: wait for the current stack action to finish`); + return; + } + if (action === 'update') { + startContainerUpdate(row.info.id, row.info.name); + return; + } + void containerAction(action, rowRpc[action].rpc, rowRpc[action].message, [row.info.id]); + }; + + // ---- stack actions ----------------------------------------------------- + + const stackRpc: Record = { + up: {rpc: 'composeUp', message: 'up'}, + down: {rpc: 'composeDown', message: 'down'}, + start: {rpc: 'composeStart', message: 'started'}, + stop: {rpc: 'composeStop', message: 'stopped'}, + restart: {rpc: 'composeRestart', message: 'restarted'}, + }; + + const runStack = (stackName: string, servicePath: string, action: StackAction) => { + const {rpc, message} = stackRpc[action]; + // A stack operation owns all its member containers until it settles. + // Drop any pre-existing container selection so the bulk toolbar cannot + // issue a conflicting command while Compose is changing the stack. + const memberIDs = new Set((containers?.list ?? []) + .filter(c => c.servicePath === servicePath) + .map(c => c.id)); + setSelectedContainers(prev => prev.filter(id => !memberIDs.has(id))); + runAction(servicePath, dockerService[rpc], action, [], (error) => { + if (error) showError(`Stack ${stackName}: ${action} failed — ${error}`); + else showSuccess(`Stack ${stackName} ${message}`); + void fetchContainers(); + }); + }; + + const handleStackAction = (group: StackGroup, action: StackAction) => + runStack(group.stack, group.servicePath, action); + + const handleStackRedeploy = (group: StackGroup, opts: RedeployOptions) => { + runAction( + group.servicePath, + (req, callOpts) => dockerService.composeRedeploy({ + file: {filename: req.filename, selectedServices: req.selectedServices}, + pull: opts.pull, + build: opts.build, + recreate: opts.recreate, + }, callOpts), + 'redeploy', + [], + (error) => { + if (error) showError(`Stack ${group.stack}: redeploy failed — ${error}`); + else showSuccess(`Stack ${group.stack} redeployed`); + void fetchContainers(); + }, + ); + }; + + // async to satisfy the shared ActionButtons handler contract; the stack + // runs themselves are fire-and-forget background actions + const bulkStackAction = async (action: StackAction) => { + for (const stackName of selectedStacks) { + const group = groups.find(g => g.stack === stackName); + if (group?.servicePath) runStack(group.stack, group.servicePath, action); + } + setSelectedStacks([]); + }; + + // ---- panel openers ----------------------------------------------------- + + const handleRowLogs = (row: MonitorRow) => + openLogs(`logs:${host}/monitor#${row.info.id}`, + row.info.stackName ? `${row.info.stackName}/${row.info.name}` : row.info.name, + [{id: row.info.id, name: row.info.name}]); + + const handleStackLogs = (group: StackGroup) => + openLogs(`logs:${host}/monitor#stack:${group.stack || 'standalone'}`, + `${group.stack || 'standalone'}: stack logs`, + group.rows.map(r => ({id: r.info.id, name: r.info.name}))); + + const [execLaunch, setExecLaunch] = useState(null); + const handleRowExec = (row: MonitorRow, anchor: HTMLElement) => setExecLaunch({row, anchor}); + const connectRowExec = (row: MonitorRow, shell: string, user: string) => { + execContainer(`exec:${host}/monitor#${row.info.id}`, + `${row.info.stackName ? `${row.info.stackName}/` : ''}${row.info.name} (exec)`, + createExecUrl(row.info.id, shell, undefined, user), + true, + {containerID: row.info.id, shell, user}); + setExecLaunch(null); + }; + + // ?tab=0 pins the EDITOR tab regardless of the compose.defaultTab setting + const handleStackEdit = (group: StackGroup) => + navigate(`/${host}/files/${group.servicePath}?tab=0`); + + // ---- selection + toolbar ---------------------------------------------- + + const toggleContainers = (ids: string[], on: boolean) => { + setSelectedStacks([]); + setSelectedContainers(prev => + on ? [...new Set([...prev, ...ids])] : prev.filter(id => !ids.includes(id))); + }; + + const toggleStack = (stack: string, on: boolean) => { + setSelectedContainers([]); + setSelectedStacks(prev => + on ? [...new Set([...prev, stack])] : prev.filter(s => s !== stack)); + }; + + const toggleAllStacks = (stacks: string[], on: boolean) => { + setSelectedContainers([]); + setSelectedStacks(on ? stacks : []); + }; + + const stacksMode = selectedStacks.length > 0; + const selectedContainersBlocked = (containers?.list ?? []).some(c => + selectedContainers.includes(c.id) && c.servicePath !== '' && runningStacks[c.servicePath]); + + const containerBulkActions = [ + { + action: 'start', buttonText: 'Start', icon: , + disabled: selectedContainers.length === 0 || selectedContainersBlocked, + handler: () => containerAction('start', 'containerStart', 'started', selectedContainers), + tooltip: '', + }, + { + action: 'stop', buttonText: 'Stop', icon: , + disabled: selectedContainers.length === 0 || selectedContainersBlocked, + handler: () => containerAction('stop', 'containerStop', 'stopped', selectedContainers), + tooltip: '', + }, + { + action: 'restart', buttonText: 'Restart', icon: , + disabled: selectedContainers.length === 0 || selectedContainersBlocked, + handler: () => containerAction('restart', 'containerRestart', 'restarted', selectedContainers), + tooltip: '', + }, + { + action: 'pause', buttonText: 'Pause', icon: , + disabled: selectedContainers.length === 0 || selectedContainersBlocked, + handler: () => containerAction('pause', 'containerPause', 'paused', selectedContainers), + tooltip: '', + }, + { + action: 'unpause', buttonText: 'Unpause', icon: , + disabled: selectedContainers.length === 0 || selectedContainersBlocked, + handler: () => containerAction('unpause', 'containerUnpause', 'unpaused', selectedContainers), + tooltip: '', + }, + { + action: 'update', buttonText: 'Update', icon: , + disabled: selectedContainers.length === 0 || selectedContainersBlocked, + handler: async () => { + const list = containers?.list ?? []; + for (const id of selectedContainers) { + const c = list.find(x => x.id === id); + if (c) startContainerUpdate(c.id, c.name); + } + setSelectedContainers([]); + }, + tooltip: 'Pull the image and recreate when a newer one exists', + }, + { + action: 'remove', buttonText: 'Remove', icon: , + disabled: selectedContainers.length === 0 || selectedContainersBlocked, + handler: () => containerAction('remove', 'containerRemove', 'removed', selectedContainers), + tooltip: '', + confirm: `Remove ${selectedContainers.length} selected container${selectedContainers.length > 1 ? 's' : ''}?`, + }, + ]; + + const stackBulkActions = [ + { + action: 'up', buttonText: 'Up', icon: , + disabled: false, handler: () => bulkStackAction('up'), tooltip: '', + }, + { + action: 'down', buttonText: 'Down', icon: , + disabled: false, handler: () => bulkStackAction('down'), tooltip: '', + }, + { + action: 'start', buttonText: 'Start', icon: , + disabled: false, handler: () => bulkStackAction('start'), tooltip: '', + }, + { + action: 'stop', buttonText: 'Stop', icon: , + disabled: false, handler: () => bulkStackAction('stop'), tooltip: '', + }, + { + action: 'restart', buttonText: 'Restart', icon: , + disabled: false, handler: () => bulkStackAction('restart'), tooltip: '', + }, + ]; + + return ( + + + } + title="Monitor" + count={total} + host={host} + compact + /> + + {/* host band and toolbar share one frame, split by an inner rule */} + + + + + + + + + {stateFilters.map(filter => changeStateFilter(filter, true)} + sx={{height: 27, color: t.text, borderColor: t.border, fontWeight: 700, textTransform: 'none'}} + />)} + + + + + + {(stacksMode || selectedContainers.length > 0) && ( + <> + + 1 ? 's' : ''} selected` + : `${selectedContainers.length} container${selectedContainers.length > 1 ? 's' : ''} selected`} + sx={{fontWeight: 700}} + /> + + )} + + + + + {loading ? ( + + ) : ( + + + setExpanded(prev => + ({...prev, [stack]: !(prev[stack] ?? false)}))} + sortField={sortField} + sortOrder={sortOrder} + onSortChange={handleSortChange} + nameSearch={search} + onNameSearchChange={setSearch} + nameSearchInputRef={searchInputRef} + scrollRef={scrollRef} + onScroll={(top) => { + viewMemoryFor(host).scroll = top; + }} + now={now} + stackRowsCompact={compactStacks} + runningStacks={runningStacks} + stackRuns={stackRuns} + onStackOutput={(group) => openOutput(group.servicePath)} + updateRuns={updateRuns} + onUpdateOutput={(row) => openOutput(`update:${row.info.name}`)} + onRowAction={handleRowAction} + rowBusy={rowBusy} + onRowLogs={handleRowLogs} + onRowExec={handleRowExec} + onRowDetails={(row) => setDetailsContainerID(row.info.id)} + onStackAction={handleStackAction} + onStackRedeploy={handleStackRedeploy} + onStackLogs={handleStackLogs} + onStackEdit={handleStackEdit} + /> + + + )} + + + + setDetailsContainerID('')} + onAction={handleRowAction} + /> + + setExecLaunch(null)} + onConnect={connectRowExec} + /> + + + + ); +} + +export default MonitorPage; diff --git a/ui/src/pages/monitor/monitor-table.tsx b/ui/src/pages/monitor/monitor-table.tsx new file mode 100644 index 00000000..477e2b6f --- /dev/null +++ b/ui/src/pages/monitor/monitor-table.tsx @@ -0,0 +1,813 @@ +import { + Box, + Button, + Checkbox, + CircularProgress, + IconButton, + Link, + ListItemIcon, + ListItemText, + Menu, + MenuItem, + InputBase, + Popover, + Stack, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TableSortLabel, + Tooltip, + Typography, +} from '@mui/material'; +import { + ArrowDownward, + ArrowUpward, + Build, + CloudDownload, + Delete, + EditNote, + ExpandLess, + ExpandMore, + InfoOutlined, + Pause, + PlayArrow, + PlayCircleOutlined, + ReceiptLong, + RestartAlt, + RocketLaunch, + Search, + Stop, + Subject, + Terminal, + Update, + Upgrade, + WarningAmber, +} from '@mui/icons-material'; +import {Fragment, type MouseEvent, type ReactNode, type Ref, useState} from 'react'; +import type {ContainerList, ContainerStats} from '../../gen/docker/v1/docker_pb.ts'; +import {statsTheme as t} from '../compose/components/stats-theme.ts'; +import Sparkline from '../../components/sparkline.tsx'; +import {formatBytes, getUsageColor} from '../../lib/editor.ts'; +import {ContainerInfoPort} from '../compose/components/container-info-port.tsx'; + +export interface MonitorRow { + info: ContainerList; + stats?: ContainerStats; +} + +// per-stack aggregation of the member containers' live metrics +export interface StackStats { + cpu: number; + memUsed: number; + memLimit: number; + netRx: number; + netTx: number; + cpuHist: number[]; + memHist: number[]; +} + +// sortable columns; stacks sort by their aggregate (or name) and their +// member containers sub-sort by the same field +export type MonitorSortField = 'name' | 'uptime' | 'cpu' | 'mem' | 'net'; + +export interface StackGroup { + // display name; empty for containers outside any compose stack + stack: string; + // compose file in dockman's namespace, empty when unknown — stack-level + // actions and the editor shortcut need it + servicePath: string; + rows: MonitorRow[]; + // null until at least one member delivered real stats + stats: StackStats | null; +} + +export type RowAction = 'start' | 'stop' | 'restart' | 'pause' | 'unpause' | 'update' | 'remove'; +export type StackAction = 'up' | 'down' | 'start' | 'stop' | 'restart'; + +export interface RedeployOptions { + pull: boolean; + build: boolean; + recreate: boolean; +} + +interface MonitorTableProps { + groups: StackGroup[]; + history: Map; + // stack and container selections are mutually exclusive: picking one + // kind clears the other, and the toolbar actions adapt to the kind + selectedContainers: string[]; + selectedStacks: string[]; + onToggleContainers: (ids: string[], on: boolean) => void; + onToggleStack: (stack: string, on: boolean) => void; + onToggleAllStacks: (stacks: string[], on: boolean) => void; + expanded: Record; + onToggleExpand: (stack: string) => void; + sortField: MonitorSortField | null; + sortOrder: 'asc' | 'desc'; + onSortChange: (field: MonitorSortField) => void; + nameSearch: string; + onNameSearchChange: (value: string) => void; + nameSearchInputRef: Ref; + // scroll position persistence across navigations + scrollRef: Ref; + onScroll: (top: number) => void; + // explicit clock so the memoized rows re-render on tick + now: number; + // dockman.yml monitor.stackRows: compact stack rows drop the charts + stackRowsCompact: boolean; + runningStacks: Record; + // last action outcome per compose file — drives the output button + stackRuns: Record; + onStackOutput: (group: StackGroup) => void; + // per-container update runs (keyed by name): busy indicator + output + updateRuns: Record; + onUpdateOutput: (row: MonitorRow) => void; + onRowAction: (row: MonitorRow, action: RowAction) => void; + // container id → lifecycle action in flight: locks the row's buttons + // and spins the one that launched the action + rowBusy: Record; + onRowLogs: (row: MonitorRow) => void; + onRowExec: (row: MonitorRow, anchor: HTMLElement) => void; + onRowDetails: (row: MonitorRow) => void; + onStackAction: (group: StackGroup, action: StackAction) => void; + onStackRedeploy: (group: StackGroup, opts: RedeployOptions) => void; + onStackLogs: (group: StackGroup) => void; + onStackEdit: (group: StackGroup) => void; +} + +const headCell = { + bgcolor: t.header, + color: t.textDim, + borderColor: t.border, + fontWeight: 700, + fontSize: '0.68rem', + letterSpacing: '0.08em', + whiteSpace: 'nowrap' as const, + py: 0.75, +}; + +const bodyCell = { + borderColor: t.border, + color: t.text, + py: 0.5, +}; + +const disabledIcon = {color: 'rgba(255,255,255,0.15)'}; + +// per-state icon + color, shared by the state column +const stateVisual: Record = { + running: {icon: , color: '#66bb6a'}, + restarting: {icon: , color: '#4db6ac'}, + paused: {icon: , color: '#ffb74d'}, + exited: {icon: , color: '#9e9e9e'}, + created: {icon: , color: '#64748b'}, + dead: {icon: , color: '#ef5350'}, + removing: {icon: , color: '#ef5350'}, +}; + +export function MonitorTable(props: MonitorTableProps) { + const { + groups, selectedStacks, selectedContainers, onToggleAllStacks, + sortField, sortOrder, onSortChange, nameSearch, + onNameSearchChange, nameSearchInputRef, scrollRef, onScroll, + } = props; + + const sortLabelSx = { + color: `${t.textDim} !important`, + '&.Mui-active': {color: `${t.text} !important`}, + '& .MuiTableSortLabel-icon': {color: `${t.textDim} !important`}, + }; + + const sortableHead = (field: MonitorSortField, label: string) => ( + onSortChange(field)} + sx={sortLabelSx} + > + {label} + + ); + + // header checkbox drives stack selection (containers are picked row by + // row); standalone containers have no stack to select + const selectableStacks = groups.filter(g => g.stack && g.servicePath).map(g => g.stack); + const allStacksSelected = selectableStacks.length > 0 && selectableStacks.every(s => selectedStacks.includes(s)); + const someStacksSelected = selectableStacks.some(s => selectedStacks.includes(s)); + + return ( + onScroll((e.target as HTMLDivElement).scrollTop)} + sx={{height: '100%', bgcolor: t.panel}} + > + + + + + + + 0} + onChange={e => onToggleAllStacks(selectableStacks, e.target.checked)} + sx={{color: t.textDim, p: 0.5}} + /> + + + + + + + {sortableHead('name', 'NAME')} + + + onNameSearchChange(event.target.value)} + placeholder="Filter name" + inputProps={{'aria-label': 'Filter by container or stack name'}} + sx={{ + minWidth: 0, + flex: 1, + color: t.text, + fontSize: '0.72rem', + '& input': {p: 0}, + '& input::placeholder': {color: t.textDim, opacity: 0.8}, + }} + /> + + + + STATE + {sortableHead('uptime', 'UPTIME')} + {sortableHead('cpu', 'CPU')} + {sortableHead('mem', 'MEMORY')} + {sortableHead('net', 'NET I/O')} + PORTS + ACTIONS + + + + {groups.map(group => ( + + + {(props.expanded[group.stack] ?? false) && group.rows.map(row => ( + + ))} + + ))} + +
+
+ ); +} + +function StackRow(props: MonitorTableProps & { group: StackGroup }) { + const { + group, expanded, onToggleExpand, selectedStacks, selectedContainers, + onToggleStack, onToggleContainers, runningStacks, stackRuns, + onStackAction, onStackRedeploy, onStackLogs, onStackEdit, onStackOutput, + } = props; + + const isExpanded = expanded[group.stack] ?? false; + const isStack = group.stack !== ''; + const hasFile = group.servicePath !== ''; + const busy = runningStacks[group.servicePath] ?? false; + const running = group.rows.filter(r => r.info.state === 'running').length; + // paused/restarting containers still count as an "active" stack: down is + // the meaningful direction, up only once everything is stopped + const active = group.rows.some(r => ['running', 'restarting', 'paused'].includes(r.info.state)); + + const ids = group.rows.map(r => r.info.id); + // selecting a stack checks the stack AND (visually) all its containers; + // the two selection kinds never mix, so the other kind's boxes disable + const checked = isStack && hasFile + ? selectedStacks.includes(group.stack) + : ids.length > 0 && ids.every(id => selectedContainers.includes(id)); + const indeterminate = !(isStack && hasFile) + && !checked && ids.some(id => selectedContainers.includes(id)); + const disabled = isStack && hasFile + ? selectedContainers.length > 0 + : selectedStacks.length > 0; + + const s = group.stats; + const memPercent = s && s.memLimit > 0 ? (s.memUsed / s.memLimit) * 100 : 0; + const lastRun = hasFile ? stackRuns[group.servicePath] : undefined; + + return ( + // the whole row toggles expand/collapse; the checkbox and the action + // cluster opt out via stopPropagation + onToggleExpand(group.stack)} + sx={{bgcolor: t.header, cursor: 'pointer'}}> + e.stopPropagation()} + sx={{...bodyCell, borderLeft: '3px solid #4db6ac', cursor: 'default', width: 40, minWidth: 40, maxWidth: 40, px: 0.5}}> + + + isStack && hasFile + ? onToggleStack(group.stack, e.target.checked) + : onToggleContainers(ids, e.target.checked)} + sx={{color: t.textDim, p: 0.5}} + /> + + + + + + + + {isExpanded ? : } + + + {group.stack || '#standalone'} + + + {running}/{group.rows.length} running + + {busy && } + + + + 0 ? `/ ${formatBytes(s.memLimit)}` : ''} + textColor={s ? getUsageColor(memPercent) : t.textDim} + data={s?.memHist} + lineColor={t.memLine} + chart={!props.stackRowsCompact} + /> + + e.stopPropagation()} + sx={{...bodyCell, py: 0.25, whiteSpace: 'nowrap', cursor: 'default'}}> + {hasFile && ( + <> + onStackAction(group, active ? 'down' : 'up')} + icon={active ? : } + /> + 0 ? 'Stack stop' : 'Stack start'} + disabled={busy} + onClick={() => onStackAction(group, running > 0 ? 'stop' : 'start')} + icon={running > 0 ? : } + /> + onStackAction(group, 'restart')} + icon={} + /> + onStackRedeploy(group, opts)}/> + {lastRun && ( + + onStackOutput(group)} + sx={{ + color: lastRun === 'failed' ? '#ef5350' : t.textDim, + '&:hover': {color: lastRun === 'failed' ? '#ef5350' : t.text}, + }}> + + + + )} + + )} + onStackLogs(group)} + icon={} + /> + {hasFile && ( + onStackEdit(group)} + icon={} + /> + )} + + + ); +} + +function StackActionButton({title, icon, onClick, disabled}: { + title: string, + icon: ReactNode, + onClick: () => void, + disabled?: boolean, +}) { + return ( + + + + {icon} + + + + ); +} + +// redeploy = compose up -d with a forced option, picked from a small menu +function RedeployMenuButton({disabled, onPick}: { + disabled?: boolean, + onPick: (opts: RedeployOptions) => void, +}) { + const [anchor, setAnchor] = useState(null); + + const pick = (opts: RedeployOptions) => { + setAnchor(null); + onPick(opts); + }; + + return ( + <> + + + ) => setAnchor(e.currentTarget)} + sx={{color: t.textDim, '&:hover': {color: t.text}, '&.Mui-disabled': disabledIcon}}> + + + + + setAnchor(null)}> + pick({pull: true, build: false, recreate: false})}> + + + + pick({pull: false, build: true, recreate: false})}> + + + + pick({pull: false, build: false, recreate: true})}> + + + + + + ); +} + +function ContainerRow(props: MonitorTableProps & { row: MonitorRow }) { + const { + row, history, selectedContainers, selectedStacks, onToggleContainers, + now, onRowAction, rowBusy, onRowLogs, onRowExec, onRowDetails, updateRuns, onUpdateOutput, + } = props; + const c = row.info; + const s = row.stats; + const hist = s ? history.get(c.name) : undefined; + const isRunning = c.state === 'running'; + const isPaused = c.state === 'paused'; + const isActive = ['running', 'restarting', 'paused'].includes(c.state); + const busy = rowBusy[c.id]; + const stackBusy = c.servicePath !== '' && (props.runningStacks[c.servicePath] ?? false); + // deleting is destructive: a small popover above the button asks first + const [confirmEl, setConfirmEl] = useState(null); + const spinner = ; + // a selected stack shows all its members checked; while stacks are + // selected, individual container boxes are frozen (kinds never mix) + const stackSelected = c.stackName !== '' && selectedStacks.includes(c.stackName); + const isChecked = stackSelected || selectedContainers.includes(c.id); + const updRun = updateRuns[c.name]; + + const memPercent = s && Number(s.memoryLimit) > 0 + ? (Number(s.memoryUsage) / Number(s.memoryLimit)) * 100 : 0; + + const portsList = c.ports + .filter(p => p.public > 0) + .filter((p, i, arr) => + arr.findIndex(q => q.public === p.public && q.private === p.private && q.type === p.type) === i); + // traefik-declared hostnames land in the address list next to plain ips; + // anything with letters and a dot (and no ipv6 colon) reads as a domain. + // several router rules can declare the same host (priorities): distinct. + const domains = [...new Set(c.IPAddress.filter(a => /[a-z]/i.test(a) && a.includes('.') && !a.includes(':')))]; + + return ( + + + 0 || stackBusy} + onChange={e => onToggleContainers([c.id], e.target.checked)} + sx={{color: t.textDim, p: 0.5}} + /> + + + + onRowDetails(row)} + sx={{color: '#64b5f6', '&:hover': {color: '#90caf9'}}}> + + + + + + + + {c.name} + + {c.updateAvailable && ( + + + + )} + + + {c.imageName} + + + + + {isRunning && s ? formatUptime(s.startedAt, now) : '–'} + + + 0 ? `/ ${formatBytes(Number(s.memoryLimit))}` : ''} + textColor={s ? getUsageColor(memPercent) : t.textDim} + data={hist?.mem} + lineColor={t.memLine} + /> + + {s ? <>↓ {formatBytes(Number(s.networkRx))}
↑ {formatBytes(Number(s.networkTx))} : '–'} +
+ + {portsList.length === 0 && domains.length === 0 ? ( + + ) : ( + // one entry per line, ports then hostnames + ( + {portsList.map((p, i) => ( + + + + ))} + {domains.map(d => ( + + + + {d} + + + + ))} + ) + )} + + + + + onRowAction(row, isActive ? 'stop' : 'start')} + sx={{color: isActive ? '#ef5350' : '#66bb6a', '&.Mui-disabled': disabledIcon}}> + {busy === 'start' || busy === 'stop' ? spinner + : isActive ? : } + + + + + + onRowAction(row, 'restart')} + sx={{color: '#4db6ac', '&.Mui-disabled': disabledIcon}}> + {busy === 'restart' ? spinner : } + + + + + + onRowAction(row, isPaused ? 'unpause' : 'pause')} + sx={{color: isPaused ? '#66bb6a' : '#ffb74d', '&:hover': {color: t.text}, '&.Mui-disabled': disabledIcon}}> + {busy === 'pause' || busy === 'unpause' ? spinner + : isPaused ? : } + + + + + + onRowAction(row, 'update')} + sx={{color: c.updateAvailable ? '#4db6ac' : t.textDim, '&:hover': {color: t.text}, '&.Mui-disabled': disabledIcon}}> + {updRun === 'running' ? spinner : } + + + + {updRun && ( + + onUpdateOutput(row)} + sx={{ + color: updRun === 'failed' ? '#ef5350' : t.textDim, + '&:hover': {color: updRun === 'failed' ? '#ef5350' : t.text}, + }}> + + + + )} + + + setConfirmEl(e.currentTarget)} + sx={{color: t.textDim, '&:hover': {color: '#ef5350'}, '&.Mui-disabled': disabledIcon}}> + {busy === 'remove' ? spinner : } + + + + setConfirmEl(null)} + anchorOrigin={{vertical: 'top', horizontal: 'center'}} + transformOrigin={{vertical: 'bottom', horizontal: 'center'}} + slotProps={{ + paper: { + sx: { + bgcolor: t.header, + border: `1px solid ${t.border}`, + borderRadius: 1.5, + px: 1.25, py: 1, + maxWidth: 260, + }, + }, + }} + > + + Remove {c.name}? + + + + + + + + onRowLogs(row)} + sx={{color: t.textDim, '&:hover': {color: t.text}}}> + + + + + + onRowExec(row, event.currentTarget)} + sx={{color: t.textDim, '&:hover': {color: t.text}, '&.Mui-disabled': disabledIcon}}> + + + + + +
+ ); +} + +// state as a colored icon (tooltip carries the word), health spelled out +// next to it when the container has a healthcheck. Health only means +// something while the container runs: the daemon keeps serving the last +// health state on stopped/paused containers, which would wrongly paint +// them unhealthy. +function StateCell({state, health}: { state: string, health: string }) { + const isRunning = state === 'running'; + const visual = (isRunning && health === 'unhealthy') + ? {icon: , color: '#ef5350'} + : stateVisual[state] ?? {icon: , color: t.textDim}; + + return ( + + + + + {visual.icon} + + + {health && isRunning && ( + + {health} + + )} + + + ); +} + +// same recipe as the edit view's STATS tab: the live value (usage-colored) +// sits top-left above a small sparkline of its history; chart=false keeps +// only the value line (compact stack rows) +function MetricCell({text, subText, textColor, data, lineColor, chart = true}: { + text: string; + subText?: string; + textColor: string; + data?: number[]; + lineColor: string; + chart?: boolean; +}) { + return ( + + + + {text} + {subText && ( + + {' '}{subText} + + )} + + {chart && } + + + ); +} + +// how long the container has been up, from the stats' RFC3339 started_at: +// "3d 4h", "5h 12m", "8m", "42s" +function formatUptime(startedAt: string, now: number): string { + if (!startedAt || startedAt.startsWith('0001')) return '–'; + const start = Date.parse(startedAt); + if (isNaN(start)) return '–'; + let secs = Math.floor((now - start) / 1000); + if (secs < 0) secs = 0; + const d = Math.floor(secs / 86400); + const h = Math.floor((secs % 86400) / 3600); + const m = Math.floor((secs % 3600) / 60); + if (d > 0) return `${d}d ${h}h`; + if (h > 0) return `${h}h ${m}m`; + if (m > 0) return `${m}m`; + return `${secs}s`; +} diff --git a/ui/src/pages/networks/docker-hook-networks.ts b/ui/src/pages/networks/docker-hook-networks.ts index 82d7baa8..b3202047 100644 --- a/ui/src/pages/networks/docker-hook-networks.ts +++ b/ui/src/pages/networks/docker-hook-networks.ts @@ -2,12 +2,10 @@ import {useCallback, useEffect, useState} from 'react' import {callRPC, useHostClient} from "../../lib/api.ts"; import {DockerService, type Network} from "../../gen/docker/v1/docker_pb.ts"; import {useSnackbar} from "../../hooks/snackbar.ts"; -import {useHostStore} from "../compose/state/files.ts"; export function useDockerNetwork() { const dockerService = useHostClient(DockerService) const {showWarning} = useSnackbar() - const selectedHost = useHostStore(state => state.host) const [networks, setNetworks] = useState([]) const [loading, setLoading] = useState(true) @@ -23,7 +21,7 @@ export function useDockerNetwork() { } setNetworks(val?.networks || []) - }, [dockerService, selectedHost]) + }, [dockerService, showWarning]) const deleteSelected = async (networkIDs: string[]) => { const {err} = await callRPC(() => dockerService.networkDelete({ @@ -57,4 +55,4 @@ export function useDockerNetwork() { }, [loadNetworks]) return {networks, loading, loadNetworks, networkPrune, deleteSelected} -} \ No newline at end of file +} diff --git a/ui/src/pages/networks/networks-empty.tsx b/ui/src/pages/networks/networks-empty.tsx index 37e189cc..d2591b97 100644 --- a/ui/src/pages/networks/networks-empty.tsx +++ b/ui/src/pages/networks/networks-empty.tsx @@ -12,10 +12,14 @@ const NetworksEmpty = () => { textAlign: 'center', gap: 2 }}> - + No networks found - + Better start networking or run some containers let them do it for you
diff --git a/ui/src/pages/networks/networks-inspect.tsx b/ui/src/pages/networks/networks-inspect.tsx index e1f6aa8c..47760c1e 100644 --- a/ui/src/pages/networks/networks-inspect.tsx +++ b/ui/src/pages/networks/networks-inspect.tsx @@ -23,7 +23,7 @@ import { import {ArrowBack, ContentCopy} from "@mui/icons-material"; import HubIcon from "@mui/icons-material/Hub"; import RefreshIcon from "@mui/icons-material/Refresh"; -import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline"; +import ErrorOutlineIcon from "@mui/icons-material/ErrorOutlined"; const NetworksInspect = () => { const dockerService = useHostClient(DockerService) @@ -99,7 +99,6 @@ const NetworksInspect = () => { - { gap: 2 }}> - + Loading... @@ -161,11 +162,18 @@ const NetworksInspect = () => { {/* Summary Header */} - + {inspect.net.name || "Unnamed Network"} - + {inspect.net.id} { - + Driver - + Scope @@ -208,7 +228,13 @@ const NetworksInspect = () => { {inspect.net.composeProject && ( - + Compose Project @@ -217,7 +243,13 @@ const NetworksInspect = () => { )} - + Created @@ -234,7 +266,13 @@ const NetworksInspect = () => { - + Subnet @@ -242,7 +280,13 @@ const NetworksInspect = () => { - + IPv4 Enabled { /> - + IPv6 Enabled { - + Internal { /> - + Attachable { bgcolor: 'background.default', borderRadius: 1 }}> - + No containers connected to this network @@ -370,4 +434,4 @@ const NetworksInspect = () => { ); }; -export default NetworksInspect; \ No newline at end of file +export default NetworksInspect; diff --git a/ui/src/pages/networks/networks-table.tsx b/ui/src/pages/networks/networks-table.tsx index af2a1b59..2a2bd23a 100644 --- a/ui/src/pages/networks/networks-table.tsx +++ b/ui/src/pages/networks/networks-table.tsx @@ -57,7 +57,7 @@ export const NetworkTable = ({networks, selectedNetworks = [], onSelectionChange }; const {sortField, sortOrder, handleSort} = useSort( - dockYaml?.networkPage?.sort?.sortField ?? 'Name', + dockYaml?.networkPage?.sort?.sortField ?? 'Network Name', (dockYaml?.networkPage?.sort?.sortOrder as SortOrder) ?? 'asc' ); @@ -79,22 +79,29 @@ export const NetworkTable = ({networks, selectedNetworks = [], onSelectionChange ) }, - Name: { + // Key must match the dockman.yml field name ("Network Name", the backend + // default) so a configured sort lights up the header arrow; the label is + // hardcoded to keep the column header short, like the columns below. + "Network Name": { getValue: (n) => n.name, header: (label) => ( handleSort(label)}> - {label} + Name ), cell: (n) => ( - + - + {n.name} {(n.name === "host" || n.name === "bridge" || n.name === "none") && ( @@ -107,7 +114,9 @@ export const NetworkTable = ({networks, selectedNetworks = [], onSelectionChange }}/> )} - + {n.id.substring(0, 12)} @@ -236,7 +245,13 @@ export const NetworkTable = ({networks, selectedNetworks = [], onSelectionChange ), cell: (n) => ( - + {formatDate(n.createdAt)} diff --git a/ui/src/pages/networks/networks.tsx b/ui/src/pages/networks/networks.tsx index 16313c57..e4c84d75 100644 --- a/ui/src/pages/networks/networks.tsx +++ b/ui/src/pages/networks/networks.tsx @@ -1,6 +1,8 @@ import {useMemo, useState} from 'react'; -import {Box, Button, Card, CircularProgress, Fade, Tooltip, Typography} from '@mui/material'; -import {Delete, DryCleaning, Refresh} from '@mui/icons-material'; +import {Box, Divider, Fade, Paper} from '@mui/material'; +import {Delete, DryCleaning, Lan as NetworkIcon} from '@mui/icons-material'; +import PageHeader, {RefreshButton} from "../../components/page-header.tsx"; +import {useHostStore} from "../compose/state/files.ts"; import scrollbarStyles from "../../components/scrollbar-style.tsx"; import NetworksLoading from "./networks-loading.tsx"; import NetworksEmpty from "./networks-empty.tsx"; @@ -14,6 +16,7 @@ const NetworksPage = () => { const {loading, networks, loadNetworks, networkPrune, deleteSelected} = useDockerNetwork(); const {search, setSearch, searchInputRef} = useSearch(); + const host = useHostStore(state => state.host); const [selectedNetworks, setSelectedNetworks] = useState([]); @@ -62,59 +65,45 @@ const NetworksPage = () => { overflow: 'hidden', ...scrollbarStyles }}> - } + title="Networks" + count={networks.length} + host={host} + /> + + - {/* Title and Stats */} - - - Docker Networks - - - - - - {networks.length} Networks - + + - + - - - - - {/* Spacer */} - - - - + + + + + {/* Table Container */} { fetchAliases().then(); @@ -102,7 +102,6 @@ function HostAliasManager({hostname, hostId}: { hostname: string, hostId: number return ( } title="Manage Path Aliases"/> - ADD NEW ALIAS @@ -153,16 +152,20 @@ function HostAliasManager({hostname, hostId}: { hostname: string, hostId: number - - {/* LIST */} {loading && } {!loading && aliases.length === 0 && ( - + No aliases configured for this node. )} @@ -176,7 +179,9 @@ function HostAliasManager({hostname, hostId}: { hostname: string, hostId: number borderColor: isEditing ? 'primary.main' : 'divider' }}> {isEditing ? ( - + setEditData({...editData, alias: e.target.value})} @@ -208,7 +213,9 @@ function HostAliasManager({hostname, hostId}: { hostname: string, hostId: number setEditingId(null)} size="small"> ) : ( - + handleDelete(a.alias)}> + onClick={() => handleDelete(a.alias)}> )} ); })} - {/* SHARED FOLDER PICKER */} - + + Remote Browser - + Node: {hostname} @@ -144,7 +153,6 @@ function FolderPickerDialog({open, onClose, onSelect, hostname, initialPath = "/ - {/* Navigation Bar */} + ) : err ? ( @@ -204,7 +216,12 @@ function FolderPickerDialog({open, onClose, onSelect, hostname, initialPath = "/ {entries.length === 0 ? ( - + This directory is empty @@ -242,14 +259,14 @@ function FolderPickerDialog({open, onClose, onSelect, hostname, initialPath = "/ )} - - Selected Path - + + {isEditMode ? 'Host Settings' : 'Add New Node'} - + {isEditMode ? `Name: ${host?.name}` : 'Configure a new Docker environment'} @@ -135,7 +143,6 @@ function HostWizardDialog({open, onClose, host, onSuccess}: { /> - {tabValue === 0 ? ( @@ -209,8 +216,13 @@ function HostWizardDialog({open, onClose, host, onSuccess}: { {(form.sshOptions.usePublicKeyAuth ? publicKeyHelperText : passwordHelperText).map((t, i) => ( - • {t} + • {t} ))} @@ -223,9 +235,7 @@ function HostWizardDialog({open, onClose, host, onSuccess}: { )} - - @@ -246,8 +256,14 @@ function HostWizardDialog({open, onClose, host, onSuccess}: { ); } -export const SectionHeader = ({icon, title}: { icon: any, title: string }) => ( - +export const SectionHeader = ({icon, title}: { icon: React.ReactElement<{ sx?: object }>, title: string }) => ( + {React.cloneElement(icon, {sx: {fontSize: 16, color: 'primary.main'}})} {title} diff --git a/ui/src/pages/settings/settings-page.tsx b/ui/src/pages/settings/settings-page.tsx index eeae1551..ecd38ebe 100644 --- a/ui/src/pages/settings/settings-page.tsx +++ b/ui/src/pages/settings/settings-page.tsx @@ -2,6 +2,7 @@ import React from 'react'; import {Box, Tab, Tabs} from "@mui/material"; import {useSearchParams} from 'react-router-dom'; import TabDockerHosts from "./tab-host.tsx"; +import TabDockman from "./tab-dockman.tsx"; interface TabConfig { label: string; @@ -13,6 +14,10 @@ const tabConfigurations: TabConfig[] = [ label: "Docker Hosts", component: }, + { + label: "Dockman", + component: + }, ]; interface TabPanelProps { diff --git a/ui/src/pages/settings/tab-dockman.tsx b/ui/src/pages/settings/tab-dockman.tsx new file mode 100644 index 00000000..fe30e6d0 --- /dev/null +++ b/ui/src/pages/settings/tab-dockman.tsx @@ -0,0 +1,107 @@ +import {Box, Button, CircularProgress, Stack, Typography} from "@mui/material"; +import {RestartAlt, SystemUpdateAlt} from "@mui/icons-material"; +import {useState} from "react"; +import {getBaseUrl} from "../../lib/api.ts"; +import {useSnackbar} from "../../hooks/snackbar.ts"; + +export default function TabDockman() { + const {showError, showSuccess} = useSnackbar(); + const [updating, setUpdating] = useState(false); + const [restarting, setRestarting] = useState(false); + const busy = updating || restarting; + + const handleUpdate = async () => { + const ok = window.confirm( + "Pull the latest Dockman image and recreate the container?\n\n" + + "Dockman will briefly go offline while it restarts." + ); + if (!ok) return; + + setUpdating(true); + try { + const res = await fetch(`${getBaseUrl("host", "local")}/docker/update/dockman`, { + method: "POST", + }); + if (!res.ok) { + showError(`Update failed: ${res.status} ${await res.text()}`); + return; + } + showSuccess("Update started — Dockman will restart shortly."); + } catch (e) { + showError(`Update failed: ${(e as Error).message}`); + } finally { + setUpdating(false); + } + }; + + const handleRestart = async () => { + const ok = window.confirm( + "Restart the Dockman container now?\n\n" + + "Dockman will briefly go offline. No image will be pulled." + ); + if (!ok) return; + + setRestarting(true); + try { + const res = await fetch(`${getBaseUrl("host", "local")}/docker/restart/dockman`, { + method: "POST", + }); + if (!res.ok) { + showError(`Restart failed: ${res.status} ${await res.text()}`); + return; + } + showSuccess("Restart scheduled — Dockman will be back shortly."); + } catch (e) { + showError(`Restart failed: ${(e as Error).message}`); + } finally { + setRestarting(false); + } + }; + + return ( + + + Dockman maintenance + + Restart the current container, or pull the latest Dockman image and + recreate it through a short-lived helper. In both cases Dockman is + briefly unavailable and your compose configuration is reused as-is. + + + + + + + + ); +} diff --git a/ui/src/pages/settings/tab-host-empty.tsx b/ui/src/pages/settings/tab-host-empty.tsx index fd550860..8cc5eddb 100644 --- a/ui/src/pages/settings/tab-host-empty.tsx +++ b/ui/src/pages/settings/tab-host-empty.tsx @@ -33,15 +33,18 @@ function EmptyHostDisplay({onAdd}: { onAdd: () => void }) { > - No Hosts Detected - - + No Docker hosts found. Add hosts to get started. - - {err && } - {loading && hosts.length === 0 ? ( ) : hosts.length > 0 ? ( @@ -138,7 +144,6 @@ function TabDockerHosts() { ) : ( setDialogOpen(true)}/> )} - - - + + - + {host.name} @@ -231,7 +249,9 @@ function HostCard({host, onEdit, onDelete, onToggle}: { )} - + } @@ -242,7 +262,12 @@ function HostCard({host, onEdit, onDelete, onToggle}: { - + @@ -254,7 +279,7 @@ function HostCard({host, onEdit, onDelete, onToggle}: { event.stopPropagation() onDelete() }}> - + diff --git a/ui/src/pages/stats/stats-page.tsx b/ui/src/pages/stats/stats-page.tsx index 93f309fe..2342921d 100644 --- a/ui/src/pages/stats/stats-page.tsx +++ b/ui/src/pages/stats/stats-page.tsx @@ -1,76 +1,7 @@ -import {Box, Paper, Stack, Typography} from "@mui/material"; -import {BarChart as StatsIcon} from "@mui/icons-material"; import {TabStat} from "../compose/tab-stats.tsx"; -import {useHostStore} from "../compose/state/files.ts"; -const StatsPage = () => { - const host = useHostStore(state => state.host); - - return ( - - - - - - - - - - - System Resources - - - Resource usage for node: {host} - - - - - - - - {/* --- Stats Content --- */} - - - - - - ); -}; +// the stats view is TabStat in page mode: it brings the uniform view header +// (title, count, host, search) on top of the aggregate band and the table +const StatsPage = () => ; export default StatsPage; diff --git a/ui/src/pages/volumes/docker-volumes.ts b/ui/src/pages/volumes/docker-volumes.ts index 0a1c9abd..c9696823 100644 --- a/ui/src/pages/volumes/docker-volumes.ts +++ b/ui/src/pages/volumes/docker-volumes.ts @@ -24,7 +24,7 @@ export function useDockerVolumes() { } setVolumes(val?.volumes || []) - }, [dockerService, selectedHost]) + }, [dockerService, showWarning]) const loadVolumes = useCallback(() => { fetchVolumes().finally(() => setLoading(false)) @@ -62,4 +62,4 @@ export function useDockerVolumes() { }, [loadVolumes]) return {volumes, loadVolumes, loading, deleteUnunsed, deleteSelected, deleteAnonynomous} -} \ No newline at end of file +} diff --git a/ui/src/pages/volumes/volumes-empty.tsx b/ui/src/pages/volumes/volumes-empty.tsx index dbbdb6b3..27ec53fb 100644 --- a/ui/src/pages/volumes/volumes-empty.tsx +++ b/ui/src/pages/volumes/volumes-empty.tsx @@ -12,10 +12,14 @@ const VolumesEmpty = () => { textAlign: 'center', gap: 2 }}> - + No volumes found - + Create some Docker volumes to see them here diff --git a/ui/src/pages/volumes/volumes-inspect.tsx b/ui/src/pages/volumes/volumes-inspect.tsx new file mode 100644 index 00000000..e90ce580 --- /dev/null +++ b/ui/src/pages/volumes/volumes-inspect.tsx @@ -0,0 +1,288 @@ +import {callRPC, useHostClient} from "../../lib/api.ts"; +import {DockerService, type VolumeInspectInfo} from "../../gen/docker/v1/docker_pb.ts"; +import {useParams} from "react-router-dom"; +import {type ReactNode, useCallback, useEffect, useState} from "react"; +import { + Alert, + Box, + Button, + Chip, + CircularProgress, + Divider, + IconButton, + Paper, + Stack, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography +} from "@mui/material"; +import {ArrowBack, ContentCopy} from "@mui/icons-material"; +import StorageIcon from "@mui/icons-material/Storage"; +import RefreshIcon from "@mui/icons-material/Refresh"; +import ErrorOutlineIcon from "@mui/icons-material/ErrorOutlined"; +import {formatBytes} from "../../lib/editor.ts"; +import {formatDate} from "../../lib/api.ts"; + +const VolumesInspect = () => { + const dockerService = useHostClient(DockerService) + const {id} = useParams() + const volumeName = id ?? "" + + const [inspect, setInspect] = useState(null) + const [err, setErr] = useState("") + const [loading, setLoading] = useState(false) + + const fetchData = useCallback(async () => { + setLoading(true) + setErr("") + + const {val, err} = await callRPC(() => dockerService.volumeInspect({volumeName})) + if (err) { + setErr(err) + } else { + setInspect(val?.inspect ?? null) + } + + setLoading(false) + }, [dockerService, volumeName]); + + useEffect(() => { + fetchData().then() + }, [fetchData]); + + const handleCopy = (text: string) => { + navigator.clipboard.writeText(text).then(); + }; + + const containers = inspect?.containers ?? [] + + return ( + + {/* --- Header Section --- */} + + + history.back()} title="Back to Volumes"> + + + + + Inspect Volume + + + + + + + + {loading && ( + + + Loading... + + )} + + {!loading && err && ( + + Retry} + > + Error: {err} + + + )} + + {!loading && !err && !inspect?.vol && ( + + + No volume info found + + )} + + {!loading && !err && inspect?.vol && ( + + {/* Summary Header */} + + + {inspect.vol.name || "Unnamed Volume"} + + + + {inspect.vol.mountPoint || 'N/A'} + + {inspect.vol.mountPoint && ( + handleCopy(inspect.vol!.mountPoint)} + title="Copy Mount Point"> + + + )} + + + + + + {/* Volume Details */} + + + Volume Details + + + + + + + {formatBytes(inspect.vol.size)} + + + + 0 ? "In Use" : "Unused"} + size="small" + color={containers.length > 0 ? "success" : "default"} + variant="outlined" + /> + + + + + + + + {inspect.vol.composeProjectName || inspect.vol.labels || '—'} + + + + + {inspect.vol.createdAt ? formatDate(inspect.vol.createdAt) : 'N/A'} + + + + + + + + + + {/* Containers using this volume */} + + + Used By ({containers.length}) + + {containers.length > 0 ? ( + + + + + Container + Mount Path + Access + Project + ID + + + + {containers.map((c, idx) => ( + + {c.name || 'N/A'} + + {c.destination || 'N/A'} + + + + + {c.composeProject || '—'} + + + + {c.id ? c.id.substring(0, 12) : 'N/A'} + + {c.id && ( + handleCopy(c.id)} + title="Copy Container ID"> + + + )} + + + + ))} + +
+
+ ) : ( + + + No containers are using this volume + + + )} +
+
+ )} +
+
+ ); +}; + +const Detail = ({label, children}: { label: string; children: ReactNode }) => ( + + + {label} + + {children} + +); + +export default VolumesInspect; diff --git a/ui/src/pages/volumes/volumes-table.tsx b/ui/src/pages/volumes/volumes-table.tsx index 4b7ede7f..e1c8d66c 100644 --- a/ui/src/pages/volumes/volumes-table.tsx +++ b/ui/src/pages/volumes/volumes-table.tsx @@ -3,6 +3,7 @@ import { Box, Checkbox, Chip, + IconButton, Paper, Stack, Table, @@ -12,9 +13,15 @@ import { TableHead, TableRow, TableSortLabel, + Tooltip, Typography } from '@mui/material'; -import {CalendarToday as CalendarIcon, FolderOpen as FolderIcon} from '@mui/icons-material'; +import { + CalendarToday as CalendarIcon, + FolderOpen as FolderIcon, + InfoOutlined as InspectIcon +} from '@mui/icons-material'; +import {useNavigate} from "react-router-dom"; import scrollbarStyles from "../../components/scrollbar-style.tsx"; import type {Volume} from "../../gen/docker/v1/docker_pb.ts"; import {formatBytes} from "../../lib/editor.ts"; @@ -38,6 +45,7 @@ export const VolumeTable = ({ }: VolumeTableProps) => { const {handleCopy, copiedId} = useCopyButton(); const {dockYaml} = useConfig(); + const nav = useNavigate(); const handleRowSelection = (volumeName: string) => { if (!onSelectionChange) return; @@ -87,7 +95,9 @@ export const VolumeTable = ({ ), cell: (volume) => ( - + ) }, + Actions: { + getValue: () => 0, + header: () => ACTIONS, + cell: (volume) => ( + + + { + e.stopPropagation(); + nav(`inspect/${encodeURIComponent(volume.name)}`); + }} + sx={{border: '1px solid', borderColor: 'divider', borderRadius: 1.5, p: 0.5}} + > + + + + + ) + }, Project: { getValue: (volume) => volume.composeProjectName || '', header: (label) => ( @@ -151,14 +182,18 @@ export const VolumeTable = ({ cell: (volume) => ( {volume.composeProjectName ? ( - + ) : ( - + )} ) @@ -193,7 +228,9 @@ export const VolumeTable = ({ ), cell: (volume) => ( - + @@ -221,7 +258,13 @@ export const VolumeTable = ({ ), cell: (volume) => ( - + {formatDate(volume.createdAt)} diff --git a/ui/src/pages/volumes/volumes.tsx b/ui/src/pages/volumes/volumes.tsx index df6d9816..8f9e492d 100644 --- a/ui/src/pages/volumes/volumes.tsx +++ b/ui/src/pages/volumes/volumes.tsx @@ -1,6 +1,8 @@ import {useMemo, useState} from 'react'; -import {Box, Button, Card, CircularProgress, Fade, Tooltip, Typography} from '@mui/material'; -import {CleaningServices, Delete, DryCleaning, Refresh} from '@mui/icons-material'; +import {Box, Divider, Fade, Paper} from '@mui/material'; +import {CleaningServices, Delete, DryCleaning, Storage as VolumeIcon} from '@mui/icons-material'; +import PageHeader, {RefreshButton} from "../../components/page-header.tsx"; +import {useHostStore} from "../compose/state/files.ts"; import {VolumeTable} from './volumes-table.tsx'; import scrollbarStyles from "../../components/scrollbar-style.tsx"; import VolumesLoading from "./volumes-loading.tsx"; @@ -15,6 +17,7 @@ const VolumesPage = () => { const [selectedVolumes, setSelectedVolumes] = useState([]); const {search, setSearch, searchInputRef} = useSearch(); + const host = useHostStore(state => state.host); const filteredVolumes = useMemo(() => { if (search) { @@ -68,59 +71,45 @@ const VolumesPage = () => { overflow: 'hidden', ...scrollbarStyles }}> - } + title="Volumes" + count={volumes.length} + host={host} + /> + + - {/* Title and Stats */} - - - Docker Volumes - - - - - - {volumes.length} volumes - + + - + - - - - - {/* Spacer */} - - - - + + + + + {/* Table Container */} { @@ -7,10 +8,9 @@ export default defineConfig(({mode}) => { return { plugins: [ - react({ - babel: { - plugins: ['babel-plugin-react-compiler'], - }, + react(), + babel({ + presets: [reactCompilerPreset()], }), ], } diff --git a/website/docs/install/env.md b/website/docs/install/env.md index 7ead91c3..c485efac 100644 --- a/website/docs/install/env.md +++ b/website/docs/install/env.md @@ -31,6 +31,20 @@ dockman: - DOCKMAN_AUTH_ENABLE=true ``` +## Temporarily allowing Exec into Dockman + +Interactive Exec sessions into a container built from the Dockman image are blocked by default. This protects the +configuration and credentials mounted inside Dockman. To enable self-Exec temporarily for troubleshooting, add: + +```yaml title="docker-compose.yaml" +dockman: + environment: + DOCKMAN_ALLOW_SELF_EXEC: "true" +``` + +Recreate the Dockman container for the setting to take effect. Remove the variable (or set it to `false`) and recreate +the container again as soon as troubleshooting is complete. Exec into other managed containers is unaffected. + ### **Environment File:** ```bash title="dockman.env"