[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\ntrim_trailing_whitespace = false\nmax_line_length = 120\nij_markdown_wrap_text_if_long = true\nij_markdown_format_tables = true\n\n[*.java]\nindent_style = tab\nindent_size = 4\nij_java_space_before_for_left_brace = true\nij_java_keep_multiple_expressions_in_one_line = true\nij_java_keep_simple_blocks_in_one_line = true\nij_java_keep_simple_classes_in_one_line = true\nij_java_else_on_new_line = true\nij_java_catch_on_new_line = true\nij_java_finally_on_new_line = true\nij_java_align_multiline_method_parentheses = false\nij_java_keep_blank_lines_before_right_brace = 1\nij_java_blank_lines_after_class_header = 0\nij_java_doc_enable_formatting = false\nij_java_class_count_to_use_import_on_demand = 100\nij_java_names_count_to_use_import_on_demand = 100\nij_java_imports_layout = |, java.**, |, jakarta.**, *, tools.**, |, de.codecentric.boot.admin.**, |, $*\nij_java_layout_static_imports_separately = true\n\n[*.xml]\nindent_style = space\nindent_size = 4\n\n[*.{js,ts,vue}]\nindent_style = space\nindent_size = 2\n"
  },
  {
    "path": ".gitattributes",
    "content": "# All text files should have the \"lf\" (Unix) line endings\n* text eol=lf\n# windows cmd shoud have the \"crlf\" (Win32) line endings\n*.cmd eol=crlf\n\n# Explicitly declare text files you want to always be normalized and converted\n# to native line endings on checkout.\n*.java text\n*.js text\n*.css text\n*.html text\n*.properties text\n*.xml text\n*.yml text\n\n# Denote all files that are truly binary and should not be modified.\n*.png binary\n*.jpg binary\n*.jar binary\n*.ttf binary\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "* @codecentric/spring-boot-admins\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/spring-boot-admin-bug.md",
    "content": "---\nname: Bug\nabout: Spring Boot Admin issue template for reporting bugs\ntitle: ''\nlabels: bug, waiting-for-triage\nassignees: ''\n\n---\n\n## Spring Boot Admin Server information\n\n- **Version**:\n  <!-- Please specify the version of Spring Boot Admin server here -->\n\n- **Spring Boot version**:\n  <!-- Please specify the underlying Spring Boot version -->\n\n- **Configured Security**:\n  <!-- basic auth, LDAP, client-certificate, ... -->\n\n- **Webflux or Servlet application**:\n  <!-- Please specify whether your Admin server is as webflux or servlet application ... -->\n\n## Client information\n\n- **Spring Boot versions**:\n  <!-- Please specify the Spring Boot version of the monitored instance(s) -->\n\n- **Used discovery mechanism**:\n  <!-- self registration, kubernetes, eureka, ... -->\n\n- **Webflux or Servlet application**:\n  <!-- Please specify whether your client application is as webflux or servlet application ... -->\n\n## Description\n\n\n\n<!--\nThanks for raising a Spring Boot Admin issue. Please take the time to review the following\ncategories as some of them do not apply here.\n\n** Question **\n🛑 STOP!! Please ask questions about how to use something, or to understand why something isn't\nworking as you expect it to, on Stack Overflow using the spring-boot-admin tag.\n\n** Bug report **\n🪳 Please provide details of the problem, including the version of Spring Boot Admin and Spring Boot that you are using. If possible, please provide a test case or sample application that reproduces\nthe problem. This makes it much easier for us to diagnose the problem and to verify that\nwe have fixed it.\n-->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/spring-boot-admin-enhancement.md",
    "content": "---\nname: Enhancement / Feature Request\nabout: Spring Boot Admin issue template for proposing Enhancements or making feature\n  requests\ntitle: ''\nlabels: enhancement, waiting-for-triage\nassignees: ''\n\n---\n\n<!--\nThanks for raising a Spring Boot Admin issue. Please take the time to review the following\ncategories as some of them do not apply here.\n\n** Question **\nSTOP!! Please ask questions about how to use something, or to understand why something isn't\nworking as you expect it to, on Stack Overflow using the spring-boot-admin tag.\n\n** Enhancement **\nPlease start by describing the problem that you are trying to solve. There may already\nbe a solution, or there may be a way to solve it that you hadn't considered.\n-->\n"
  },
  {
    "path": ".github/copilot-instructions.md",
    "content": "# Spring Boot Admin\nSpring Boot Admin is a multi-module Maven project providing an admin interface for Spring Boot applications that expose actuator endpoints. It consists of a Vue.js frontend and Java backend components with 19 Maven modules.\n\nAlways reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.\n\n## Working Effectively\n\n### Prerequisites and Environment Setup\n- Install Java 17 (OpenJDK Temurin 17.0.16+ recommended) - project requires exactly Java 17\n- Install Node.js 22.18.0 exactly (project specifies this in .nvmrc and package.json)\n- Download Node.js 22.18.0: `curl -fsSL https://nodejs.org/dist/v22.18.0/node-v22.18.0-linux-x64.tar.xz -o /tmp/node.tar.xz`\n- Extract and configure PATH: `cd /tmp && tar -xf node.tar.xz && export PATH=\"/tmp/node-v22.18.0-linux-x64/bin:$PATH\"`\n- Verify versions: `java -version` (should show 17.x) and `node --version` (should show v22.18.0)\n\n### Building the Project\n- **NEVER CANCEL builds - they take time but will complete successfully**\n- **CRITICAL**: Set timeout to 20+ minutes for builds, 60+ minutes for tests\n- Clean compile: `export PATH=\"/tmp/node-v22.18.0-linux-x64/bin:$PATH\" && ./mvnw clean compile -B --no-transfer-progress -DskipTests` -- takes ~10.5 minutes\n- Full package: `export PATH=\"/tmp/node-v22.18.0-linux-x64/bin:$PATH\" && ./mvnw package -B --no-transfer-progress -DskipTests` -- takes ~4 minutes  \n- Install to local repository: `export PATH=\"/tmp/node-v22.18.0-linux-x64/bin:$PATH\" && ./mvnw install -B --no-transfer-progress -DskipTests` -- takes ~1.5 minutes\n\n### Testing\n- **NEVER CANCEL test runs - set 60+ minute timeouts**\n- Full test suite: `export PATH=\"/tmp/node-v22.18.0-linux-x64/bin:$PATH\" && ./mvnw test -B --no-transfer-progress` -- takes 20-40 minutes\n- UI tests only: `cd spring-boot-admin-server-ui && export PATH=\"/tmp/node-v22.18.0-linux-x64/bin:$PATH\" && npm run test` -- takes ~44 seconds\n- UI build only: `cd spring-boot-admin-server-ui && export PATH=\"/tmp/node-v22.18.0-linux-x64/bin:$PATH\" && npm run build` -- takes ~16 seconds\n\n### Code Quality and Formatting\n- Format Java code: `./mvnw spring-javaformat:apply`\n- Check Java formatting: `./mvnw spring-javaformat:validate`\n- Lint UI code: `cd spring-boot-admin-server-ui && npm run lint`\n- Format UI code: `cd spring-boot-admin-server-ui && npm run format:fix`\n- **ALWAYS** run `./mvnw spring-javaformat:apply` and UI formatting before committing\n- **ALWAYS** run `./mvnw checkstyle:check` to verify compliance\n\n### Running Sample Applications\n- Build and install all modules first: `export PATH=\"/tmp/node-v22.18.0-linux-x64/bin:$PATH\" && ./mvnw install -B --no-transfer-progress -DskipTests`\n- Run servlet sample: `cd spring-boot-admin-samples/spring-boot-admin-sample-servlet && export PATH=\"/tmp/node-v22.18.0-linux-x64/bin:$PATH\" && ../../mvnw spring-boot:run -Dspring-boot.run.profiles=dev,insecure`\n- Access UI at: `http://localhost:8080` (username: user, password shown in logs or use insecure profile)\n- Application starts in ~3 seconds and shows \"all up\" status with registered instance\n\n### UI Development Mode\n- For UI development: `cd spring-boot-admin-server-ui && npm run build:watch` (builds on file changes)\n- Configure Spring Boot Admin Server with: \n  ```\n  spring.boot.admin.ui.cache.no-cache: true\n  spring.boot.admin.ui.resource-locations: file:../../spring-boot-admin-server-ui/target/dist/\n  spring.boot.admin.ui.template-location: file:../../spring-boot-admin-server-ui/target/dist/\n  spring.boot.admin.ui.cache-templates: false\n  ```\n\n## Validation Scenarios\n- **MANUAL VALIDATION REQUIRED**: After building and running, always test actual functionality\n- Launch sample application and verify Spring Boot Admin UI loads at `http://localhost:8080`\n- Verify application registration: Should show \"spring-boot-admin-sample-servlet\" with UP status\n- Test navigation: Click on Applications, Wallboard, and instance details\n- Check endpoints: Verify actuator endpoints are detected and accessible\n- Test monitoring features: Instance details should show health, metrics, loggers, etc.\n\n## Key Modules and Locations\n\n### Primary Components\n- **Root**: `/` - Main Maven reactor project (pom.xml)\n- **Server Backend**: `spring-boot-admin-server/` - Core Spring Boot Admin server functionality\n- **UI Frontend**: `spring-boot-admin-server-ui/` - Vue.js 3 web interface with Vite build\n- **Client**: `spring-boot-admin-client/` - Client library for connecting applications\n- **Documentation**: `spring-boot-admin-docs/` - Asciidoc documentation\n\n### Starter Modules\n- **Server Starter**: `spring-boot-admin-starter-server/` - Auto-configuration for servers\n- **Client Starter**: `spring-boot-admin-starter-client/` - Auto-configuration for clients\n\n### Sample Applications (in spring-boot-admin-samples/)\n- **servlet**: Standard servlet-based sample (recommended for testing)\n- **reactive**: WebFlux reactive sample\n- **eureka**: Eureka discovery integration\n- **consul**: Consul discovery integration\n- **hazelcast**: Hazelcast session management\n- **war**: WAR deployment sample\n- **zookeeper**: Zookeeper discovery integration\n\n### Infrastructure\n- **Dependencies**: `spring-boot-admin-dependencies/` - Dependency management BOM\n- **Build**: `spring-boot-admin-build/` - Build configuration and tooling\n- **Server Cloud**: `spring-boot-admin-server-cloud/` - Spring Cloud integrations\n\n## Common Issues and Solutions\n\n### Build Issues\n- Node.js version mismatch: Always use exact PATH override with v22.18.0\n- Missing dependencies: Run `./mvnw install` to install all modules to local Maven repository\n- Checkstyle violations: Run `./mvnw spring-javaformat:apply` to auto-fix formatting\n- UI build failures: Verify Node.js version and run `npm install` in spring-boot-admin-server-ui/\n- \"Command timed out\": Increase timeout - builds legitimately take 10+ minutes\n\n### Runtime Issues\n- Sample app dependency resolution: Must run `./mvnw install` first to populate local repository\n- Config server connection refused: Normal warning, config server is optional in dev mode\n- Security warnings: Use `insecure` profile for development to bypass authentication\n\n## Development Workflow\n1. **Setup**: Configure Node.js 22.18.0 in PATH: `export PATH=\"/tmp/node-v22.18.0-linux-x64/bin:$PATH\"`\n2. **Initial Build**: Run `./mvnw clean compile` to verify everything builds (~10.5 minutes - be patient)\n3. **Install Dependencies**: Run `./mvnw install -DskipTests` for sample app dependencies (~1.5 minutes)\n4. **Make Changes**: Edit code in appropriate modules\n5. **Format Code**: Run `./mvnw spring-javaformat:apply` and UI `npm run format:fix`\n6. **Test Build**: Run build commands to verify changes don't break anything\n7. **Manual Validation**: Start sample application and test UI functionality\n8. **Final Check**: Ensure formatting and linting pass before committing\n\n## Critical Timing Information\n- **NEVER CANCEL**: Build commands take significant time but complete successfully\n- Clean compile: ~10.5 minutes (normal, expected)\n- Package build: ~4 minutes\n- Install build: ~1.5 minutes\n- Full test suite: 20-40 minutes (set 60+ minute timeout)\n- UI tests only: ~44 seconds\n- UI build only: ~16 seconds\n- Application startup: ~3 seconds\n\n## Technology Stack\n- **Backend**: Spring Boot 3.5, Java 17, Maven multi-module\n- **Frontend**: Vue.js 3, Vite, TypeScript, Tailwind CSS\n- **Testing**: JUnit 5 (Java), Vitest (UI), Playwright integration\n- **Build**: Maven 3.9+, Node.js 22.18.0, npm\n- **Code Quality**: Spring Java Format, Checkstyle, ESLint, Prettier\n\n## Repository Structure Overview\n```\nspring-boot-admin/                    # Root project (19 modules)\n├── .github/                         # GitHub configuration\n├── spring-boot-admin-build/         # Build configuration\n├── spring-boot-admin-client/        # Client library\n├── spring-boot-admin-dependencies/  # Dependency management\n├── spring-boot-admin-docs/          # Documentation (Asciidoc)\n├── spring-boot-admin-samples/       # Sample applications\n│   ├── spring-boot-admin-sample-servlet/    # Main sample (recommended)\n│   ├── spring-boot-admin-sample-reactive/   # WebFlux sample\n│   └── [other samples]/            # Various integration samples\n├── spring-boot-admin-server/        # Core server implementation\n├── spring-boot-admin-server-cloud/  # Spring Cloud integrations\n├── spring-boot-admin-server-ui/     # Vue.js frontend\n│   ├── src/main/frontend/          # Vue.js source code\n│   ├── package.json                # Node.js dependencies\n│   └── .nvmrc                      # Node.js version specification\n├── spring-boot-admin-starter-*/     # Spring Boot auto-configuration\n├── mvnw                            # Maven wrapper\n└── pom.xml                         # Root Maven configuration\n```"
  },
  {
    "path": ".github/milestones.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nshow_help() {\n  cat <<EOF\nUsage:\n  GITHUB_TOKEN=ghp_xxx ./create_milestone_for_tag.sh [--dry-run] owner repo tag [base]\n\nPositional arguments:\n  owner     GitHub owner/org (required)\n  repo      GitHub repository name (required)\n  tag       Tag to create milestone for (required)\n  base      Optional base tag/branch/sha to compute commits from (optional)\n\nFlags:\n  --dry-run  Print what would be done, do not call APIs that modify data.\n  --help     Show this help and exit.\n\nExamples:\n  GITHUB_TOKEN=ghp_xxx ./create_milestone_for_tag.sh octocat Hello-World v1.2.0\n  ./create_milestone_for_tag.sh --dry-run octocat Hello-World v1.2.0\n  ./create_milestone_for_tag.sh octocat Hello-World v1.2.0 previous-tag --dry-run\nEOF\n}\n\n# Parse args: flags can be anywhere\nDRYRUN=0\nPOSITIONAL=()\nfor arg in \"$@\"; do\n  case \"$arg\" in\n    --dry-run) DRYRUN=1; shift || true ;;\n    --help) show_help; exit 0 ;;\n    *) POSITIONAL+=(\"$arg\"); shift || true ;;\n  esac\ndone\n\n# After parsing, positional args are in POSITIONAL array\nif [[ ${#POSITIONAL[@]} -lt 3 ]]; then\n  echo \"ERROR: owner, repo and tag are required.\"\n  show_help\n  exit 1\nfi\n\nOWNER=\"${POSITIONAL[0]}\"\nREPO=\"${POSITIONAL[1]}\"\nTAG=\"${POSITIONAL[2]}\"\nBASE=\"${POSITIONAL[3]:-}\"  # optional\n\nif [[ $DRYRUN -eq 1 ]]; then\n  echo \"⚠️  Dry-run mode enabled — no changes will be made.\"\nfi\n\nif [[ -z \"${GITHUB_TOKEN:-}\" ]]; then\n  echo \"ERROR: GITHUB_TOKEN environment variable must be set (PAT with repo permissions).\"\n  exit 1\nfi\n\nAPI=\"https://api.github.com\"\nAUTH=\"Authorization: token ${GITHUB_TOKEN}\"\nACCEPT=\"Accept: application/vnd.github.groot-preview+json\"\n\nif ! command -v jq >/dev/null 2>&1; then\n  echo \"ERROR: jq is required. Install jq and try again.\"\n  exit 1\nfi\nif ! command -v git >/dev/null 2>&1; then\n  echo \"ERROR: git is required. Install git and try again.\"\n  exit 1\nfi\n\n# If base not provided, attempt to find previous tag ordered by creation date (descending)\nif [[ -z \"$BASE\" ]]; then\n  if git rev-parse --verify \"$TAG\" >/dev/null 2>&1; then\n    mapfile -t TAGS < <(git tag --sort=-creatordate)\n    PREV_TAG=\"\"\n    found=0\n    for i in \"${!TAGS[@]}\"; do\n      if [[ \"${TAGS[$i]}\" == \"$TAG\" ]]; then\n        found=1\n        if [[ $((i+1)) -lt ${#TAGS[@]} ]]; then\n          PREV_TAG=\"${TAGS[$((i+1))]}\"\n        fi\n        break\n      fi\n    done\n    if [[ $found -eq 0 ]]; then\n      echo \"Warning: tag '$TAG' not found locally. You may need to 'git fetch --tags'.\"\n      BASE=\"\"\n    else\n      if [[ -n \"$PREV_TAG\" ]]; then\n        echo \"Detected previous tag: $PREV_TAG\"\n        BASE=\"$PREV_TAG\"\n      else\n        echo \"No previous tag found (this looks like the oldest tag). Continuing with single tag commit.\"\n        BASE=\"\"\n      fi\n    fi\n  else\n    echo \"Warning: tag '$TAG' not found locally. Continuing; will attempt API-based fallback if needed.\"\n    BASE=\"\"\n  fi\nfi\n\n# Build commit list: if BASE is empty, use the single tag's commit; otherwise range base..tag\nCOMMITS=()\nif [[ -n \"$BASE\" ]]; then\n  # verify both reachable locally; if not present, try to fetch tags/branches from remote\n  if ! git rev-parse --verify \"$BASE\" >/dev/null 2>&1 || ! git rev-parse --verify \"$TAG\" >/dev/null 2>&1; then\n    echo \"One of base or tag is not present locally. Attempting to fetch tags/heads from origin...\"\n    git fetch --tags --prune --no-recurse-submodules --quiet || true\n  fi\n\n  # Re-check\n  git rev-parse --verify \"$BASE\" >/dev/null 2>&1 || { echo \"ERROR: base '$BASE' not found locally after fetch\"; exit 1; }\n  git rev-parse --verify \"$TAG\" >/dev/null 2>&1 || { echo \"ERROR: tag '$TAG' not found locally after fetch\"; exit 1; }\n\n  # list commits from base (exclusive) to tag (inclusive)\n  while IFS= read -r sha; do\n    COMMITS+=(\"$sha\")\n  done < <(git rev-list --reverse \"${BASE}..${TAG}\")\nelse\n  # try to get tag commit locally; if not present, fall back to GitHub compare API to get commits\n  if git rev-parse --verify \"$TAG\" >/dev/null 2>&1; then\n    TAG_SHA=\"$(git rev-list -n 1 \"$TAG\")\"\n    COMMITS+=(\"$TAG_SHA\")\n  else\n    echo \"Tag not present locally. Falling back to GitHub compare API to get commits for tag ${TAG}...\"\n    # We attempt to compare default branch...tag to get commits — best-effort\n    default_branch=\"$(curl -sSL -H \"$AUTH\" \"${API}/repos/${OWNER}/${REPO}\" | jq -r '.default_branch')\"\n    if [[ -z \"$default_branch\" || \"$default_branch\" == \"null\" ]]; then\n      echo \"ERROR: could not determine default branch via API.\"\n      exit 1\n    fi\n    # Use compare endpoint: default_branch...tag\n    cmp=$(curl -sSL -H \"$AUTH\" \"${API}/repos/${OWNER}/${REPO}/compare/${default_branch}...${TAG}\")\n    if echo \"$cmp\" | jq -e 'has(\"commits\")' >/dev/null 2>&1; then\n      mapfile -t commits_from_api < <(echo \"$cmp\" | jq -r '.commits[]?.sha')\n      COMMITS+=(\"${commits_from_api[@]}\")\n    else\n      echo \"API compare did not return commits. Response:\"\n      echo \"$cmp\" | jq -C .\n      exit 1\n    fi\n  fi\nfi\n\necho \"Found ${#COMMITS[@]} commit(s).\"\n\n# For each commit, call GitHub API to get associated PRs\ndeclare -A PRS_MAP=()\nfor sha in \"${COMMITS[@]}\"; do\n  echo \"Querying PRs for commit $sha...\"\n  resp=\"$(curl -sSL -H \"$AUTH\" -H \"$ACCEPT\" \\\n    \"${API}/repos/${OWNER}/${REPO}/commits/${sha}/pulls\")\"\n\n  if echo \"$resp\" | jq -e 'has(\"message\")' >/dev/null 2>&1; then\n    msg=$(echo \"$resp\" | jq -r '.message // empty')\n    if [[ -n \"$msg\" ]]; then\n      echo \"GitHub API error for commit $sha: $msg\"\n      echo \"Full response:\"\n      echo \"$resp\" | jq -C .\n      exit 1\n    fi\n  fi\n\n  pr_numbers=$(echo \"$resp\" | jq -r '.[]?.number' || true)\n  if [[ -n \"$pr_numbers\" ]]; then\n    while IFS= read -r pr; do\n      if [[ -n \"$pr\" ]]; then\n        PRS_MAP[\"$pr\"]=1\n        echo \"  -> PR #$pr\"\n      fi\n    done <<<\"$pr_numbers\"\n  fi\ndone\n\nif [[ ${#PRS_MAP[@]} -eq 0 ]]; then\n  echo \"No PRs associated with commits found. Exiting.\"\n  exit 0\nfi\n\nPR_LIST=()\nfor k in \"${!PRS_MAP[@]}\"; do PR_LIST+=(\"$k\"); done\necho \"Total unique PRs to update: ${#PR_LIST[@]}\"\n\n# Check existing milestones for a matching title\necho \"Checking for existing milestone named '$TAG'...\"\nMILESTONES_RESP=\"$(curl -sSL -H \"$AUTH\" \"${API}/repos/${OWNER}/${REPO}/milestones?state=all&per_page=100\")\"\nMILESTONE_NUMBER=\"$(echo \"$MILESTONES_RESP\" | jq -r --arg TITLE \"$TAG\" '.[] | select(.title==$TITLE) | .number' | head -n1 || true)\"\n\nif [[ -n \"$MILESTONE_NUMBER\" && \"$MILESTONE_NUMBER\" != \"null\" ]]; then\n  echo \"Found existing milestone '$TAG' (number: $MILESTONE_NUMBER).\"\nelse\n  if [[ $DRYRUN -eq 1 ]]; then\n    echo \"Would create milestone '$TAG' (dry-run).\"\n    MILESTONE_NUMBER=\"DUMMY_ID\"\n  else\n    echo \"Creating milestone '$TAG'...\"\n    create_resp=\"$(curl -sSL -H \"$AUTH\" -H \"Content-Type: application/json\" \\\n      -d \"{\\\"title\\\": \\\"${TAG}\\\", \\\"description\\\": \\\"Auto-created milestone for tag ${TAG}\\\"}\" \\\n      \"${API}/repos/${OWNER}/${REPO}/milestones\")\"\n\n    if echo \"$create_resp\" | jq -e 'has(\"message\")' >/dev/null 2>&1; then\n      err=$(echo \"$create_resp\" | jq -r '.message // empty')\n      echo \"Error creating milestone: $err\"\n      echo \"Response: $create_resp\" | jq -C .\n      exit 1\n    fi\n\n    MILESTONE_NUMBER=\"$(echo \"$create_resp\" | jq -r '.number')\"\n    if [[ -z \"$MILESTONE_NUMBER\" || \"$MILESTONE_NUMBER\" == \"null\" ]]; then\n      echo \"Failed to create milestone. Response: $create_resp\"\n      exit 1\n    fi\n    echo \"Created milestone '${TAG}' (number: $MILESTONE_NUMBER).\"\n  fi\nfi\n\n# Patch each PR (issues endpoint) to set milestone\nfor prnum in \"${PR_LIST[@]}\"; do\n  if [[ $DRYRUN -eq 1 ]]; then\n    echo \"Would update PR #${prnum} -> milestone ${MILESTONE_NUMBER}\"\n    continue\n  fi\n\n  echo \"Updating PR #${prnum} -> milestone ${MILESTONE_NUMBER}...\"\n  patch_resp=\"$(curl -sSL -X PATCH -H \"$AUTH\" -H \"Content-Type: application/json\" \\\n    -d \"{\\\"milestone\\\": ${MILESTONE_NUMBER}}\" \\\n    \"${API}/repos/${OWNER}/${REPO}/issues/${prnum}\")\"\n\n  if echo \"$patch_resp\" | jq -e 'has(\"message\")' >/dev/null 2>&1; then\n    err=$(echo \"$patch_resp\" | jq -r '.message // empty')\n    echo \"Warning: failed to update PR #${prnum}: $err\"\n    echo \"Response: $patch_resp\" | jq -C .\n    # continue so we attempt remaining PRs\n    continue\n  fi\n\n  echo \"PR #${prnum} updated.\"\ndone\n\necho \"Done.\"\nexit 0\n"
  },
  {
    "path": ".github/release.yml",
    "content": "changelog:\n  categories:\n    - title: Features\n      labels:\n        - '*'\n      exclude:\n        labels:\n          - fix\n          - breaking-change\n          - dependencies\n          - bot\n    - title: Bug Fixes\n      labels:\n        - fix\n    - title: Breaking Changes\n      labels:\n        - breaking-change\n    - title: Dependencies\n      labels:\n        - dependencies\n"
  },
  {
    "path": ".github/workflows/build-feature.yml",
    "content": "name: Build Pull Request\n\non:\n  push:\n    branches-ignore:\n      - master\n      - 1.*\n      - 2.*\n  pull_request:\n\njobs:\n  build:\n    strategy:\n      matrix:\n        os: [ubuntu-latest]\n\n    runs-on: ${{ matrix.os }}\n    steps:\n\n      - name: free disk space\n        continue-on-error: true\n        run: |\n          df -h\n          sudo swapoff -a\n          sudo rm -f /swapfile\n          sudo apt clean\n          if [ -n \"$(docker image ls -q)\" ]; then\n            docker rmi -f $(docker image ls -aq) || true\n          fi\n          df -h\n\n      - uses: actions/checkout@v6\n\n      - name: Set up JDK\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'temurin'\n          java-version: '17'\n          cache: 'maven'\n          cache-dependency-path: '**/pom.xml'\n\n      - name: Set up Node\n        uses: actions/setup-node@v6\n        with:\n          node-version: 'lts/*'\n          cache: 'npm'\n          cache-dependency-path: '**/package-lock.json'\n\n      - name: Build with Maven\n        run: ./mvnw -B --no-transfer-progress install -P coverage\n\n      - name: Upload surefire reports\n        if: always()\n        uses: actions/upload-artifact@v7\n        with:\n          name: surefire-reports\n          path: |\n            **/target/surefire-reports/*.xml\n            **/target/surefire-reports/*.txt\n            **/target/surefire-reports/*.dump*\n            **/target/surefire-reports/*.out\n            **/target/surefire-reports/*.err\n          if-no-files-found: warn\n          retention-days: 14\n"
  },
  {
    "path": ".github/workflows/build-main.yml",
    "content": "name: Build Main / Version Branch\n\non:\n  push:\n    branches:\n      - master\n      - 1.*\n      - 2.*\n      - 3.*\n\njobs:\n  build:\n    strategy:\n      matrix:\n        os: [ ubuntu-latest ]\n\n    runs-on: ${{ matrix.os }}\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Set up JDK\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'temurin'\n          java-version: '17'\n          cache: 'maven'\n          cache-dependency-path: '**/pom.xml'\n\n      - name: Set up Node\n        uses: actions/setup-node@v6\n        with:\n          node-version: 'lts/*'\n          cache: 'npm'\n          cache-dependency-path: '**/package-lock.json'\n\n      - name: Build with Maven\n        run: ./mvnw -B --no-transfer-progress install -P coverage\n\n      - name: Upload surefire reports\n        if: always()\n        uses: actions/upload-artifact@v7\n        with:\n          name: surefire-reports\n          path: |\n            **/target/surefire-reports/*.xml\n            **/target/surefire-reports/*.txt\n            **/target/surefire-reports/*.dump*\n            **/target/surefire-reports/*.out\n            **/target/surefire-reports/*.err\n          if-no-files-found: warn\n          retention-days: 14\n\n      - name: Upload coverage to Codecov\n        uses: codecov/codecov-action@v5\n\n  publish-snapshot:\n    needs: build\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Set up JDK\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'temurin'\n          java-version: '17'\n          cache: 'maven'\n          cache-dependency-path: '**/pom.xml'\n\n      - name: Set up Node\n        uses: actions/setup-node@v6\n        with:\n          node-version: 'lts/*'\n          cache: 'npm'\n          cache-dependency-path: '**/package-lock.json'\n\n      - name: Publish SNAPSHOT version to GitHub Packages (we can skip tests, since we only deploy, if the build workflow succeeded)\n        run: ./mvnw -B deploy --no-transfer-progress -DskipTests\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Extract Maven project version for Asciidoc GitHub Pages directory naming\n        run: echo ::set-output name=version::$(./mvnw -q -Dexec.executable=echo -Dexec.args='${project.version}' --non-recursive exec:exec)\n        id: project\n\n      - name: Show extracted Maven project version\n        run: echo ${{ steps.project.outputs.version }}\n\n      - name: Build documentation with Maven\n        run: ./mvnw -B --no-transfer-progress site\n\n      - name: Deploy documentation to GitHub Pages\n        uses: JamesIves/github-pages-deploy-action@v4.8.0\n        with:\n          branch: gh-pages\n          folder: spring-boot-admin-docs/target/generated-docs/build\n          target-folder: ${{ steps.project.outputs.version }}\n          clean: true # Automatically remove deleted files from the deploy branch\n"
  },
  {
    "path": ".github/workflows/deploy-documentation.yml",
    "content": "name: Deploy Documentation\n\non:\n  workflow_dispatch:\n    inputs:\n      releaseversion:\n        description: 'Version to publish the documentation for. This should be a tag that exists in the repository.'\n        type: string\n        required: true\n      copyDocsToCurrent:\n        description: \"Mark docs as 'latest'? This will create a redirect from /current to the version being published. This should only be set for the latest version of the documentation.\"\n        required: true\n        type: boolean\n        default: false\n\nenv:\n  VERSION: ${{ github.event.inputs.releaseversion }}\n\njobs:\n  deploy-documentation:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          ref: refs/tags/${{ github.event.inputs.releaseversion }}\n\n      - name: Set up JDK\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'temurin'\n          java-version: '17'\n          cache: 'maven'\n          cache-dependency-path: '**/pom.xml'\n\n      - name: Set up Node\n        uses: actions/setup-node@v6\n        with:\n          node-version: 'lts/*'\n          cache: 'npm'\n          cache-dependency-path: '**/package-lock.json'\n\n      - name: Set projects Maven version to GitHub Action GUI set version\n        run: ./mvnw versions:set \"-DnewVersion=${{ github.event.inputs.releaseversion }}\" --no-transfer-progress\n\n      - name: Build with Maven\n        run: ./mvnw -B --no-transfer-progress package -DskipTests\n\n      - name: Build documentation with Maven\n        run: ./mvnw -B --no-transfer-progress  -pl spring-boot-admin-docs site\n\n      - name: Deploy documentation to GitHub Pages\n        uses: JamesIves/github-pages-deploy-action@v4.8.0\n        with:\n          branch: gh-pages\n          folder: spring-boot-admin-docs/target/generated-docs/build\n          target-folder: ${{ github.event.inputs.releaseversion }}\n          clean: true\n\n      - name: Deploy redirect for /current to /${{ github.event.inputs.releaseversion }}\n        uses: JamesIves/github-pages-deploy-action@v4.8.0\n        if: github.event.inputs.copyDocsToCurrent == 'true'\n        with:\n          branch: gh-pages\n          folder: spring-boot-admin-docs/target/generated-docs/build/current\n          target-folder: /current\n          clean: true\n\n      - name: Deploy deeplink redirect for /current/* to /${{ github.event.inputs.releaseversion }}/*\n        uses: JamesIves/github-pages-deploy-action@v4.8.0\n        if: github.event.inputs.copyDocsToCurrent == 'true'\n        with:\n          branch: gh-pages\n          folder: spring-boot-admin-docs/target/generated-docs/build/current\n          target-folder: /\n          clean: false\n"
  },
  {
    "path": ".github/workflows/issue-metrics.yml",
    "content": "name: Monthly issue metrics\non:\n  workflow_dispatch:\n    inputs:\n      start_date:\n        description: 'Start date (YYYY-MM-DD)'\n        required: false\n        type: string\n      end_date:\n        description: 'End date (YYYY-MM-DD)'\n        required: false\n        type: string\n  schedule:\n    - cron: \"3 2 1 * *\"\n\npermissions:\n  contents: read\n\njobs:\n  build:\n    name: issue metrics\n    runs-on: ubuntu-latest\n    permissions:\n      issues: write\n      pull-requests: read\n    steps:\n      - name: Get dates for last month\n        shell: bash\n        run: |\n          # Use input dates if provided, otherwise calculate the previous month\n          if [ -n \"${{ inputs.start_date }}\" ] && [ -n \"${{ inputs.end_date }}\" ]; then\n            first_day=\"${{ inputs.start_date }}\"\n            last_day=\"${{ inputs.end_date }}\"\n          else\n            # Calculate the first day of the previous month\n            first_day=$(date -d \"last month\" +%Y-%m-01)\n\n            # Calculate the last day of the previous month\n            last_day=$(date -d \"$first_day +1 month -1 day\" +%Y-%m-%d)\n          fi\n\n          #Set an environment variable with the date range\n          echo \"$first_day..$last_day\"\n          echo \"last_month=$first_day..$last_day\" >> \"$GITHUB_ENV\"\n\n      - name: Run issue-metrics tool\n        uses: github/issue-metrics@v3\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          SEARCH_QUERY: 'spring-boot-admin:codecentric/spring-boot-admin is:issue created:${{ env.last_month }} -reason:\"not planned\"'\n"
  },
  {
    "path": ".github/workflows/release-to-maven-central.yml",
    "content": "name: Release To Maven Central\n\non:\n  workflow_dispatch:\n    inputs:\n      releaseversion:\n        description: \"Version to release and publish to maven central.\"\n        required: true\n      copyDocsToCurrent:\n        description: \"Mark docs as 'latest'? This will create a redirect from /current to the version being published. This should only be set for the latest version of the documentation.\"\n        required: true\n        type: boolean\n        default: false\n\nenv:\n  VERSION: ${{ github.event.inputs.releaseversion }}\n\njobs:\n  publish-central-and-pages:\n    runs-on: ubuntu-latest\n    steps:\n      - run: echo \"Will start a Maven Central upload with version ${{ github.event.inputs.releaseversion }}\"\n\n      - name: free disk space\n        continue-on-error: true\n        run: |\n          df -h\n          sudo swapoff -a\n          sudo rm -f /swapfile\n          sudo apt clean\n          if [ -n \"$(docker image ls -q)\" ]; then\n            docker rmi -f $(docker image ls -aq) || true\n          fi\n          df -h\n\n      - uses: actions/checkout@v6\n\n      - name: Set up settings.xml for Maven Central Repository\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'temurin'\n          java-version: '17'\n          server-id: central\n          server-username: MAVEN_USERNAME\n          server-password: MAVEN_PASSWORD\n          gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }}\n          gpg-passphrase: MAVEN_GPG_PASSPHRASE\n          cache: 'maven'\n        env:\n          MAVEN_USERNAME: ${{ secrets.OSS_SONATYPE_USERNAME }}\n          MAVEN_PASSWORD: ${{ secrets.OSS_SONATYPE_PASSWORD }}\n          MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}\n\n      - name: Cache node modules\n        uses: actions/cache@v5\n        env:\n          cache-name: cache-node-modules\n        with:\n          path: ~/.npm\n          key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}\n          restore-keys: |\n            ${{ runner.os }}-build-${{ env.cache-name }}-\n\n      - name: Set projects Maven version to GitHub Action GUI set version\n        run: ./mvnw versions:set \"-DnewVersion=${{ github.event.inputs.releaseversion }}\" --no-transfer-progress\n\n      - name: Publish package\n        run: ./mvnw -B deploy --no-transfer-progress -P central-deploy -DskipTests\n        env:\n          #TODO: This is a workaround for NEXUS-27902 which uses XStream that fails on JVM >16\n          JDK_JAVA_OPTIONS: \"--add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED\"\n          MAVEN_USERNAME: ${{ secrets.OSS_SONATYPE_USERNAME }}\n          MAVEN_PASSWORD: ${{ secrets.OSS_SONATYPE_PASSWORD }}\n          MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}\n\n      - name: Build documentation with Maven\n        run: ./mvnw -B --no-transfer-progress  -pl spring-boot-admin-docs site\n\n      - name: Deploy documentation to GitHub Pages for version ${{ github.event.inputs.releaseversion }}\n        uses: JamesIves/github-pages-deploy-action@v4.8.0\n        with:\n          branch: gh-pages\n          folder: spring-boot-admin-docs/target/generated-docs/build\n          target-folder: ${{ github.event.inputs.releaseversion }}\n          clean: true\n\n      - name: Deploy redirect for /current to /${{ github.event.inputs.releaseversion }}\n        uses: JamesIves/github-pages-deploy-action@v4.8.0\n        if: github.event.inputs.copyDocsToCurrent == 'true'\n        with:\n          branch: gh-pages\n          folder: spring-boot-admin-docs/target/generated-docs/build/current\n          target-folder: /current\n          clean: true\n\n      - name: Deploy deeplink redirect for /current/* to /${{ github.event.inputs.releaseversion }}/*\n        uses: JamesIves/github-pages-deploy-action@v4.8.0\n        if: github.event.inputs.copyDocsToCurrent == 'true'\n        with:\n          branch: gh-pages\n          folder: spring-boot-admin-docs/target/generated-docs/build/current\n          target-folder: /\n          clean: false\n\n  publish-github-release:\n    needs: publish-central-and-pages\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: free disk space\n        continue-on-error: true\n        run: |\n          df -h\n          sudo swapoff -a\n          sudo rm -f /swapfile\n          sudo apt clean\n          if [ -n \"$(docker image ls -q)\" ]; then\n            docker rmi -f $(docker image ls -aq) || true\n          fi\n          df -h\n\n      - name: Generate changelog\n        id: changelog\n        uses: metcalfc/changelog-generator@v4.6.2\n        with:\n          myToken: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Create GitHub Release\n        id: create_release\n        uses: softprops/action-gh-release@v2\n        with:\n          tag_name: ${{ github.event.inputs.releaseversion }}\n          token: ${{ secrets.GITHUB_TOKEN }}\n          draft: false\n          prerelease: ${{ contains(github.event.inputs.releaseversion, '-') }}\n\n\n"
  },
  {
    "path": ".github/workflows/vulnerability-scan.yml",
    "content": "name: Vulnerability Scan\n\non:\n  workflow_dispatch: {}\n\njobs:\n  semgrep:\n    name: semgrep/ci\n    runs-on: ubuntu-latest\n\n    container:\n      image: returntocorp/semgrep\n\n    if: (github.actor != 'renovate')\n\n    steps:\n      - uses: actions/checkout@v6\n      - run: semgrep ci\n        env:\n          SEMGREP_RULES: p/default\n"
  },
  {
    "path": ".gitignore",
    "content": "# Maven\ntarget/\n\n# Eclipse\n.settings/\n.classpath\n.project\n.factorypath\n.apt_generated/\n\n# Intellij\n.idea/\n*.iml\n*.iws\n\n#vscode\n.vscode/\n\n# gnupg keyring\n/.gnupg\n\n#nodejs\nnode_modules/\nnode/\n\n#flattened POMs\n.flattened-pom.xml\n\n.DS_Store\nmockServiceWorker.js\n/.github/agents/\n"
  },
  {
    "path": ".mvn/wrapper/maven-wrapper.properties",
    "content": "distributionType=only-script\ndistributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.14/apache-maven-3.9.14-bin.zip\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to Spring Boot Admin\nContributions are highly welcome. Feel free to submit Pull Requests. Maybe watch out for tickets tagged with `ideal-for-contribution`, these tickets should always be a good starting point for contributing.\n\nYou can find some hints for starting development in the [README](spring-boot-admin-server-ui/README.md) of `spring-boot-admin-server-ui`.\n\n## Coding Conventions\n### Java / Server\nWe try to satisfy the [Code Style of Spring Framework](https://github.com/spring-projects/spring-framework/wiki/Code-Style).\n\nThe [Spring Java Format Plugin](https://github.com/spring-io/spring-javaformat) is added to the build. Checkstyle will enforce the consistency of the code. Nevertheless, there are some disabled rules, due to backward compatibility. You can find these disabled rules in a comment in `src/checkstyle/checkstyle.xml`.\n\nHowever, you can always run `./mvnw spring-javaformat:apply` to fix some basic errors, like indentation.\n\n### JavaScript / Client\nThe Vue frontend implements basic prettier rules as well as Vue3 recommended rules.\nTo check if there are any violations run `npm run lint` and `npm run format`.\nAppend `:fix` to let eslint or prettier auto-solve most issues.\n\n## Working with the code\n### IntelliJ\nThe IntelliJ settings are based on the IntelliJ-IDEA-Editor-Settings from spring, but have been adapted slightly, you can find the original settings [here](https://github.com/spring-projects/spring-framework/wiki/IntelliJ-IDEA-Editor-Settings).\nThe custom settings are stored in `.editorconfig` and are imported automatically by IntelliJ.\nIf you are using IntelliJ, there is also a [formatter-plugin provided by Spring](https://github.com/spring-io/spring-javaformat#intellij-idea).\n(i) Plugin version x did not work in IntelliJ IDEA Ultimate 2020.3.\n\n#### Checkstyle Plugin\nThis plugin scans Java files with the project's custom CheckStyle rules from within IDEA.\nInstall and configure the Checkstyle Plugin, and enable the configuration file.\n\n##### Configuration\nBefore configuration, add the  `spring-javaformat-checkstyle` JAR to the Third-Party checks.\n1. Preferences > Tools > Checkstyle > Third-Party Checks\n2. Add `~/.m2/repository/io/spring/javaformat/spring-javaformat-checkstyle/0.0.26/spring-javaformat-checkstyle-0.0.26.jar`\n\n##### Configuration File\nAdd the configuration file and enabled it:\n1. Preferences > Tools > Checkstyle > Configuration File > +\n2. Add a Name, ex. Spring Boot Admin\n3. Use a local Checkstyle File, Browse to `src/checkstyle/checkstyle.xml` and click Next\n4. Enter the full path to the checkstyle header file: `<git repo>/src/checkstyle/checkstyle-header.txt`, click Finish\n5. Select the new configuration file to enable it\n\n#### Prettier Plugin\nThis plugin is able to run Prettier from within IntelliJ. It can even be configured to run on \"Reformat Code\" action. It comes bundles with the IDE but needs to be enabled.\n\n##### Configuration\n1. Preferences > Languages & Frameworks > JavaScript > Prettier\n2. Manual Prettier configuration\n   1. Prettier package: `<git repo>/spring-boot-admin-server-ui/node_modules/prettier`\n   2. Enable \"Run on 'Reformat Code' action\"\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "                               Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright {yyyy} {name of copyright owner}\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# Spring Boot Admin by [codecentric](https://codecentric.de)\n[![Apache License 2](https://img.shields.io/badge/license-ASF2-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0.txt)\n![Build Status](https://github.com/codecentric/spring-boot-admin/actions/workflows/build-main.yml/badge.svg?branch=master)\n[![codecov](https://codecov.io/gh/codecentric/spring-boot-admin/branch/master/graph/badge.svg?token=u5SWsZpj5S)](https://codecov.io/gh/codecentric/spring-boot-admin)\n![Maven Central Version](https://img.shields.io/maven-central/v/de.codecentric/spring-boot-admin)\n\nThis community project provides an admin interface for [Spring Boot <sup>®</sup>](http://projects.spring.io/spring-boot/ \"Official Spring-Boot website\") web applications that expose actuator endpoints.\n\nMonitoring Python applications is available using [Pyctuator](https://github.com/SolarEdgeTech/pyctuator).\n\n## Compatibility Matrix\nIn the Spring Boot Admin Server App, the Spring Boot Admin's version matches the major and minor versions of Spring Boot.\n\n| Spring Boot Version | Spring Boot Admin |\n|---------------------|-------------------|\n| 2.7                 | 2.7.Y             |\n| 3.0                 | 3.0.Y             |\n| 4.0                 | 4.0.Y             |\n\nNevertheless, it is possible to monitor any version of a Spring Boot service independently of the underlying Spring Boot version in the service.\nHence, it is possible to run Spring Boot Admin Server version 2.6 and monitor a service that is running on Spring Boot 2.3 using Spring Boot Admin Client version 2.3.\n\n## Getting Started\n\n[A quick guide](https://docs.spring-boot-admin.com/current) to get started can be found in our docs.\n\nThere are introductory talks available on YouTube:\n\n<a href=\"https://youtu.be/Ql1Gnz4L_-c\" target=\"_blank\"><img src=\"https://i.ytimg.com/vi/Ql1Gnz4L_-c/maxresdefault.jpg\"\nalt=\"Cloud Native Spring Boot® Admin by Johannes Edmeier @ Spring I/O 2019\" width=\"240\" height=\"135\" border=\"10\" /></a><br>\n**Cloud Native Spring Boot® Admin by Johannes Edmeier @ Spring I/O 2019**\n\n<a href=\"https://youtu.be/__zkypwjSMs\" target=\"_blank\"><img src=\"https://i.ytimg.com/vi/__zkypwjSMs/maxresdefault.jpg\"\nalt=\"Monitoring Spring Boot® Applications with Spring Boot Admin @ Spring I/O 2018\" width=\"240\" height=\"135\" border=\"10\" /></a><br>\n**Monitoring Spring Boot® Applications with Spring Boot Admin @ Spring I/O 2018**\n\n<a href=\"https://goo.gl/2tRiUi\" target=\"_blank\"><img src=\"https://i.ytimg.com/vi/PWd9Q8_4OFo/maxresdefault.jpg\"\nalt=\"Spring Boot® Admin - Monitoring and Configuring Spring Boot Applications at Runtime\" width=\"240\" height=\"135\" border=\"10\" /></a><br>\n**Spring Boot® Admin - Monitoring and Configuring Spring Boot Applications at Runtime**\n\n## Getting Help\n\nHaving trouble with codecentric's Spring Boot Admin? We’d like to help!\n\n * Check the [reference documentation](http://codecentric.github.io/spring-boot-admin/current/).\n\n * Ask a question on [stackoverflow.com](http://stackoverflow.com/questions/tagged/spring-boot-admin) - we monitor questions tagged with `spring-boot-admin`.\n\n * Ask for help in our [spring-boot-admin Gitter chat](https://gitter.im/codecentric/spring-boot-admin)\n\n * Report bugs at http://github.com/codecentric/spring-boot-admin/issues.\n\n## Reference Guide\n### Translated versions\nThe following reference guides have been translated by users of Spring Boot Admin and are not part of the official bundle.\nThe maintainers of Spring Boot Admin will not update and maintain the guides mentioned below.\n\n[Version 2.6.6 (Chinese translated by @qq253498229)](https://consolelog.gitee.io/docs-spring-boot-admin-docs-chinese/)\n\n## Trademarks and licenses\nThe source code of codecentric's Spring Boot Admin is licensed under [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0)\n\nSpring, Spring Boot and Spring Cloud are trademarks of [Pivotal Software, Inc.](https://pivotal.io/) in the U.S. and other countries.\n\n## Snapshot builds\nYou can access snapshot builds from the github snapshot repository by adding the following to your `repositories`:\n```xml\n<repository>\n\t<id>sba-snapshot</id>\n\t<name>Spring Boot Admin Snapshots</name>\n\t<url>https://maven.pkg.github.com/codecentric/spring-boot-admin</url>\n\t<snapshots>\n\t\t<enabled>true</enabled>\n\t</snapshots>\n\t<releases>\n\t\t<enabled>false</enabled>\n\t</releases>\n</repository>\n```\n\n## Contributing\nSee [CONTRIBUTING.md](CONTRIBUTING.md) file.\n"
  },
  {
    "path": "lombok.config",
    "content": "lombok.noArgsConstructor.extraPrivate = false\n"
  },
  {
    "path": "mvnw",
    "content": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Software Foundation (ASF) under one\n# or more contributor license agreements.  See the NOTICE file\n# distributed with this work for additional information\n# regarding copyright ownership.  The ASF licenses this file\n# to you under the Apache License, Version 2.0 (the\n# \"License\"); you may not use this file except in compliance\n# with the License.  You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing,\n# software distributed under the License is distributed on an\n# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n# KIND, either express or implied.  See the License for the\n# specific language governing permissions and limitations\n# under the License.\n# ----------------------------------------------------------------------------\n\n# ----------------------------------------------------------------------------\n# Apache Maven Wrapper startup batch script, version 3.3.4\n#\n# Optional ENV vars\n# -----------------\n#   JAVA_HOME - location of a JDK home dir, required when download maven via java source\n#   MVNW_REPOURL - repo url base for downloading maven distribution\n#   MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven\n#   MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output\n# ----------------------------------------------------------------------------\n\nset -euf\n[ \"${MVNW_VERBOSE-}\" != debug ] || set -x\n\n# OS specific support.\nnative_path() { printf %s\\\\n \"$1\"; }\ncase \"$(uname)\" in\nCYGWIN* | MINGW*)\n  [ -z \"${JAVA_HOME-}\" ] || JAVA_HOME=\"$(cygpath --unix \"$JAVA_HOME\")\"\n  native_path() { cygpath --path --windows \"$1\"; }\n  ;;\nesac\n\n# set JAVACMD and JAVACCMD\nset_java_home() {\n  # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched\n  if [ -n \"${JAVA_HOME-}\" ]; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ]; then\n      # IBM's JDK on AIX uses strange locations for the executables\n      JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n      JAVACCMD=\"$JAVA_HOME/jre/sh/javac\"\n    else\n      JAVACMD=\"$JAVA_HOME/bin/java\"\n      JAVACCMD=\"$JAVA_HOME/bin/javac\"\n\n      if [ ! -x \"$JAVACMD\" ] || [ ! -x \"$JAVACCMD\" ]; then\n        echo \"The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run.\" >&2\n        echo \"JAVA_HOME is set to \\\"$JAVA_HOME\\\", but \\\"\\$JAVA_HOME/bin/java\\\" or \\\"\\$JAVA_HOME/bin/javac\\\" does not exist.\" >&2\n        return 1\n      fi\n    fi\n  else\n    JAVACMD=\"$(\n      'set' +e\n      'unset' -f command 2>/dev/null\n      'command' -v java\n    )\" || :\n    JAVACCMD=\"$(\n      'set' +e\n      'unset' -f command 2>/dev/null\n      'command' -v javac\n    )\" || :\n\n    if [ ! -x \"${JAVACMD-}\" ] || [ ! -x \"${JAVACCMD-}\" ]; then\n      echo \"The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run.\" >&2\n      return 1\n    fi\n  fi\n}\n\n# hash string like Java String::hashCode\nhash_string() {\n  str=\"${1:-}\" h=0\n  while [ -n \"$str\" ]; do\n    char=\"${str%\"${str#?}\"}\"\n    h=$(((h * 31 + $(LC_CTYPE=C printf %d \"'$char\")) % 4294967296))\n    str=\"${str#?}\"\n  done\n  printf %x\\\\n $h\n}\n\nverbose() { :; }\n[ \"${MVNW_VERBOSE-}\" != true ] || verbose() { printf %s\\\\n \"${1-}\"; }\n\ndie() {\n  printf %s\\\\n \"$1\" >&2\n  exit 1\n}\n\ntrim() {\n  # MWRAPPER-139:\n  #   Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.\n  #   Needed for removing poorly interpreted newline sequences when running in more\n  #   exotic environments such as mingw bash on Windows.\n  printf \"%s\" \"${1}\" | tr -d '[:space:]'\n}\n\nscriptDir=\"$(dirname \"$0\")\"\nscriptName=\"$(basename \"$0\")\"\n\n# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties\nwhile IFS=\"=\" read -r key value; do\n  case \"${key-}\" in\n  distributionUrl) distributionUrl=$(trim \"${value-}\") ;;\n  distributionSha256Sum) distributionSha256Sum=$(trim \"${value-}\") ;;\n  esac\ndone <\"$scriptDir/.mvn/wrapper/maven-wrapper.properties\"\n[ -n \"${distributionUrl-}\" ] || die \"cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties\"\n\ncase \"${distributionUrl##*/}\" in\nmaven-mvnd-*bin.*)\n  MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/\n  case \"${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)\" in\n  *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;\n  :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;\n  :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;\n  :Linux*x86_64*) distributionPlatform=linux-amd64 ;;\n  *)\n    echo \"Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version\" >&2\n    distributionPlatform=linux-amd64\n    ;;\n  esac\n  distributionUrl=\"${distributionUrl%-bin.*}-$distributionPlatform.zip\"\n  ;;\nmaven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;\n*) MVN_CMD=\"mvn${scriptName#mvnw}\" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;\nesac\n\n# apply MVNW_REPOURL and calculate MAVEN_HOME\n# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>\n[ -z \"${MVNW_REPOURL-}\" ] || distributionUrl=\"$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*\"$_MVNW_REPO_PATTERN\"}\"\ndistributionUrlName=\"${distributionUrl##*/}\"\ndistributionUrlNameMain=\"${distributionUrlName%.*}\"\ndistributionUrlNameMain=\"${distributionUrlNameMain%-bin}\"\nMAVEN_USER_HOME=\"${MAVEN_USER_HOME:-${HOME}/.m2}\"\nMAVEN_HOME=\"${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string \"$distributionUrl\")\"\n\nexec_maven() {\n  unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :\n  exec \"$MAVEN_HOME/bin/$MVN_CMD\" \"$@\" || die \"cannot exec $MAVEN_HOME/bin/$MVN_CMD\"\n}\n\nif [ -d \"$MAVEN_HOME\" ]; then\n  verbose \"found existing MAVEN_HOME at $MAVEN_HOME\"\n  exec_maven \"$@\"\nfi\n\ncase \"${distributionUrl-}\" in\n*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;\n*) die \"distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'\" ;;\nesac\n\n# prepare tmp dir\nif TMP_DOWNLOAD_DIR=\"$(mktemp -d)\" && [ -d \"$TMP_DOWNLOAD_DIR\" ]; then\n  clean() { rm -rf -- \"$TMP_DOWNLOAD_DIR\"; }\n  trap clean HUP INT TERM EXIT\nelse\n  die \"cannot create temp dir\"\nfi\n\nmkdir -p -- \"${MAVEN_HOME%/*}\"\n\n# Download and Install Apache Maven\nverbose \"Couldn't find MAVEN_HOME, downloading and installing it ...\"\nverbose \"Downloading from: $distributionUrl\"\nverbose \"Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName\"\n\n# select .zip or .tar.gz\nif ! command -v unzip >/dev/null; then\n  distributionUrl=\"${distributionUrl%.zip}.tar.gz\"\n  distributionUrlName=\"${distributionUrl##*/}\"\nfi\n\n# verbose opt\n__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''\n[ \"${MVNW_VERBOSE-}\" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v\n\n# normalize http auth\ncase \"${MVNW_PASSWORD:+has-password}\" in\n'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;\nhas-password) [ -n \"${MVNW_USERNAME-}\" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;\nesac\n\nif [ -z \"${MVNW_USERNAME-}\" ] && command -v wget >/dev/null; then\n  verbose \"Found wget ... using wget\"\n  wget ${__MVNW_QUIET_WGET:+\"$__MVNW_QUIET_WGET\"} \"$distributionUrl\" -O \"$TMP_DOWNLOAD_DIR/$distributionUrlName\" || die \"wget: Failed to fetch $distributionUrl\"\nelif [ -z \"${MVNW_USERNAME-}\" ] && command -v curl >/dev/null; then\n  verbose \"Found curl ... using curl\"\n  curl ${__MVNW_QUIET_CURL:+\"$__MVNW_QUIET_CURL\"} -f -L -o \"$TMP_DOWNLOAD_DIR/$distributionUrlName\" \"$distributionUrl\" || die \"curl: Failed to fetch $distributionUrl\"\nelif set_java_home; then\n  verbose \"Falling back to use Java to download\"\n  javaSource=\"$TMP_DOWNLOAD_DIR/Downloader.java\"\n  targetZip=\"$TMP_DOWNLOAD_DIR/$distributionUrlName\"\n  cat >\"$javaSource\" <<-END\n\tpublic class Downloader extends java.net.Authenticator\n\t{\n\t  protected java.net.PasswordAuthentication getPasswordAuthentication()\n\t  {\n\t    return new java.net.PasswordAuthentication( System.getenv( \"MVNW_USERNAME\" ), System.getenv( \"MVNW_PASSWORD\" ).toCharArray() );\n\t  }\n\t  public static void main( String[] args ) throws Exception\n\t  {\n\t    setDefault( new Downloader() );\n\t    java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );\n\t  }\n\t}\n\tEND\n  # For Cygwin/MinGW, switch paths to Windows format before running javac and java\n  verbose \" - Compiling Downloader.java ...\"\n  \"$(native_path \"$JAVACCMD\")\" \"$(native_path \"$javaSource\")\" || die \"Failed to compile Downloader.java\"\n  verbose \" - Running Downloader.java ...\"\n  \"$(native_path \"$JAVACMD\")\" -cp \"$(native_path \"$TMP_DOWNLOAD_DIR\")\" Downloader \"$distributionUrl\" \"$(native_path \"$targetZip\")\"\nfi\n\n# If specified, validate the SHA-256 sum of the Maven distribution zip file\nif [ -n \"${distributionSha256Sum-}\" ]; then\n  distributionSha256Result=false\n  if [ \"$MVN_CMD\" = mvnd.sh ]; then\n    echo \"Checksum validation is not supported for maven-mvnd.\" >&2\n    echo \"Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties.\" >&2\n    exit 1\n  elif command -v sha256sum >/dev/null; then\n    if echo \"$distributionSha256Sum  $TMP_DOWNLOAD_DIR/$distributionUrlName\" | sha256sum -c - >/dev/null 2>&1; then\n      distributionSha256Result=true\n    fi\n  elif command -v shasum >/dev/null; then\n    if echo \"$distributionSha256Sum  $TMP_DOWNLOAD_DIR/$distributionUrlName\" | shasum -a 256 -c >/dev/null 2>&1; then\n      distributionSha256Result=true\n    fi\n  else\n    echo \"Checksum validation was requested but neither 'sha256sum' or 'shasum' are available.\" >&2\n    echo \"Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties.\" >&2\n    exit 1\n  fi\n  if [ $distributionSha256Result = false ]; then\n    echo \"Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised.\" >&2\n    echo \"If you updated your Maven version, you need to update the specified distributionSha256Sum property.\" >&2\n    exit 1\n  fi\nfi\n\n# unzip and move\nif command -v unzip >/dev/null; then\n  unzip ${__MVNW_QUIET_UNZIP:+\"$__MVNW_QUIET_UNZIP\"} \"$TMP_DOWNLOAD_DIR/$distributionUrlName\" -d \"$TMP_DOWNLOAD_DIR\" || die \"failed to unzip\"\nelse\n  tar xzf${__MVNW_QUIET_TAR:+\"$__MVNW_QUIET_TAR\"} \"$TMP_DOWNLOAD_DIR/$distributionUrlName\" -C \"$TMP_DOWNLOAD_DIR\" || die \"failed to untar\"\nfi\n\n# Find the actual extracted directory name (handles snapshots where filename != directory name)\nactualDistributionDir=\"\"\n\n# First try the expected directory name (for regular distributions)\nif [ -d \"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain\" ]; then\n  if [ -f \"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD\" ]; then\n    actualDistributionDir=\"$distributionUrlNameMain\"\n  fi\nfi\n\n# If not found, search for any directory with the Maven executable (for snapshots)\nif [ -z \"$actualDistributionDir\" ]; then\n  # enable globbing to iterate over items\n  set +f\n  for dir in \"$TMP_DOWNLOAD_DIR\"/*; do\n    if [ -d \"$dir\" ]; then\n      if [ -f \"$dir/bin/$MVN_CMD\" ]; then\n        actualDistributionDir=\"$(basename \"$dir\")\"\n        break\n      fi\n    fi\n  done\n  set -f\nfi\n\nif [ -z \"$actualDistributionDir\" ]; then\n  verbose \"Contents of $TMP_DOWNLOAD_DIR:\"\n  verbose \"$(ls -la \"$TMP_DOWNLOAD_DIR\")\"\n  die \"Could not find Maven distribution directory in extracted archive\"\nfi\n\nverbose \"Found extracted Maven distribution directory: $actualDistributionDir\"\nprintf %s\\\\n \"$distributionUrl\" >\"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url\"\nmv -- \"$TMP_DOWNLOAD_DIR/$actualDistributionDir\" \"$MAVEN_HOME\" || [ -d \"$MAVEN_HOME\" ] || die \"fail to move MAVEN_HOME\"\n\nclean || :\nexec_maven \"$@\"\n"
  },
  {
    "path": "mvnw.cmd",
    "content": "<# : batch portion\r\n@REM ----------------------------------------------------------------------------\r\n@REM Licensed to the Apache Software Foundation (ASF) under one\r\n@REM or more contributor license agreements.  See the NOTICE file\r\n@REM distributed with this work for additional information\r\n@REM regarding copyright ownership.  The ASF licenses this file\r\n@REM to you under the Apache License, Version 2.0 (the\r\n@REM \"License\"); you may not use this file except in compliance\r\n@REM with the License.  You may obtain a copy of the License at\r\n@REM\r\n@REM    http://www.apache.org/licenses/LICENSE-2.0\r\n@REM\r\n@REM Unless required by applicable law or agreed to in writing,\r\n@REM software distributed under the License is distributed on an\r\n@REM \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\r\n@REM KIND, either express or implied.  See the License for the\r\n@REM specific language governing permissions and limitations\r\n@REM under the License.\r\n@REM ----------------------------------------------------------------------------\r\n\r\n@REM ----------------------------------------------------------------------------\r\n@REM Apache Maven Wrapper startup batch script, version 3.3.4\r\n@REM\r\n@REM Optional ENV vars\r\n@REM   MVNW_REPOURL - repo url base for downloading maven distribution\r\n@REM   MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven\r\n@REM   MVNW_VERBOSE - true: enable verbose log; others: silence the output\r\n@REM ----------------------------------------------------------------------------\r\n\r\n@IF \"%__MVNW_ARG0_NAME__%\"==\"\" (SET __MVNW_ARG0_NAME__=%~nx0)\r\n@SET __MVNW_CMD__=\r\n@SET __MVNW_ERROR__=\r\n@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%\r\n@SET PSModulePath=\r\n@FOR /F \"usebackq tokens=1* delims==\" %%A IN (`powershell -noprofile \"& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}\"`) DO @(\r\n  IF \"%%A\"==\"MVN_CMD\" (set __MVNW_CMD__=%%B) ELSE IF \"%%B\"==\"\" (echo %%A) ELSE (echo %%A=%%B)\r\n)\r\n@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%\r\n@SET __MVNW_PSMODULEP_SAVE=\r\n@SET __MVNW_ARG0_NAME__=\r\n@SET MVNW_USERNAME=\r\n@SET MVNW_PASSWORD=\r\n@IF NOT \"%__MVNW_CMD__%\"==\"\" (\"%__MVNW_CMD__%\" %*)\r\n@echo Cannot start maven from wrapper >&2 && exit /b 1\r\n@GOTO :EOF\r\n: end batch / begin powershell #>\r\n\r\n$ErrorActionPreference = \"Stop\"\r\nif ($env:MVNW_VERBOSE -eq \"true\") {\r\n  $VerbosePreference = \"Continue\"\r\n}\r\n\r\n# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties\r\n$distributionUrl = (Get-Content -Raw \"$scriptDir/.mvn/wrapper/maven-wrapper.properties\" | ConvertFrom-StringData).distributionUrl\r\nif (!$distributionUrl) {\r\n  Write-Error \"cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties\"\r\n}\r\n\r\nswitch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {\r\n  \"maven-mvnd-*\" {\r\n    $USE_MVND = $true\r\n    $distributionUrl = $distributionUrl -replace '-bin\\.[^.]*$',\"-windows-amd64.zip\"\r\n    $MVN_CMD = \"mvnd.cmd\"\r\n    break\r\n  }\r\n  default {\r\n    $USE_MVND = $false\r\n    $MVN_CMD = $script -replace '^mvnw','mvn'\r\n    break\r\n  }\r\n}\r\n\r\n# apply MVNW_REPOURL and calculate MAVEN_HOME\r\n# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>\r\nif ($env:MVNW_REPOURL) {\r\n  $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { \"/org/apache/maven/\" } else { \"/maven/mvnd/\" }\r\n  $distributionUrl = \"$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace \"^.*$MVNW_REPO_PATTERN\",'')\"\r\n}\r\n$distributionUrlName = $distributionUrl -replace '^.*/',''\r\n$distributionUrlNameMain = $distributionUrlName -replace '\\.[^.]*$','' -replace '-bin$',''\r\n\r\n$MAVEN_M2_PATH = \"$HOME/.m2\"\r\nif ($env:MAVEN_USER_HOME) {\r\n  $MAVEN_M2_PATH = \"$env:MAVEN_USER_HOME\"\r\n}\r\n\r\nif (-not (Test-Path -Path $MAVEN_M2_PATH)) {\r\n    New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null\r\n}\r\n\r\n$MAVEN_WRAPPER_DISTS = $null\r\nif ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {\r\n  $MAVEN_WRAPPER_DISTS = \"$MAVEN_M2_PATH/wrapper/dists\"\r\n} else {\r\n  $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + \"/wrapper/dists\"\r\n}\r\n\r\n$MAVEN_HOME_PARENT = \"$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain\"\r\n$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString(\"x2\")}) -join ''\r\n$MAVEN_HOME = \"$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME\"\r\n\r\nif (Test-Path -Path \"$MAVEN_HOME\" -PathType Container) {\r\n  Write-Verbose \"found existing MAVEN_HOME at $MAVEN_HOME\"\r\n  Write-Output \"MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD\"\r\n  exit $?\r\n}\r\n\r\nif (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {\r\n  Write-Error \"distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl\"\r\n}\r\n\r\n# prepare tmp dir\r\n$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile\r\n$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path \"$TMP_DOWNLOAD_DIR_HOLDER.dir\"\r\n$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null\r\ntrap {\r\n  if ($TMP_DOWNLOAD_DIR.Exists) {\r\n    try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }\r\n    catch { Write-Warning \"Cannot remove $TMP_DOWNLOAD_DIR\" }\r\n  }\r\n}\r\n\r\nNew-Item -Itemtype Directory -Path \"$MAVEN_HOME_PARENT\" -Force | Out-Null\r\n\r\n# Download and Install Apache Maven\r\nWrite-Verbose \"Couldn't find MAVEN_HOME, downloading and installing it ...\"\r\nWrite-Verbose \"Downloading from: $distributionUrl\"\r\nWrite-Verbose \"Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName\"\r\n\r\n$webclient = New-Object System.Net.WebClient\r\nif ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {\r\n  $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)\r\n}\r\n[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12\r\n$webclient.DownloadFile($distributionUrl, \"$TMP_DOWNLOAD_DIR/$distributionUrlName\") | Out-Null\r\n\r\n# If specified, validate the SHA-256 sum of the Maven distribution zip file\r\n$distributionSha256Sum = (Get-Content -Raw \"$scriptDir/.mvn/wrapper/maven-wrapper.properties\" | ConvertFrom-StringData).distributionSha256Sum\r\nif ($distributionSha256Sum) {\r\n  if ($USE_MVND) {\r\n    Write-Error \"Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties.\"\r\n  }\r\n  Import-Module $PSHOME\\Modules\\Microsoft.PowerShell.Utility -Function Get-FileHash\r\n  if ((Get-FileHash \"$TMP_DOWNLOAD_DIR/$distributionUrlName\" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {\r\n    Write-Error \"Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property.\"\r\n  }\r\n}\r\n\r\n# unzip and move\r\nExpand-Archive \"$TMP_DOWNLOAD_DIR/$distributionUrlName\" -DestinationPath \"$TMP_DOWNLOAD_DIR\" | Out-Null\r\n\r\n# Find the actual extracted directory name (handles snapshots where filename != directory name)\r\n$actualDistributionDir = \"\"\r\n\r\n# First try the expected directory name (for regular distributions)\r\n$expectedPath = Join-Path \"$TMP_DOWNLOAD_DIR\" \"$distributionUrlNameMain\"\r\n$expectedMvnPath = Join-Path \"$expectedPath\" \"bin/$MVN_CMD\"\r\nif ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {\r\n  $actualDistributionDir = $distributionUrlNameMain\r\n}\r\n\r\n# If not found, search for any directory with the Maven executable (for snapshots)\r\nif (!$actualDistributionDir) {\r\n  Get-ChildItem -Path \"$TMP_DOWNLOAD_DIR\" -Directory | ForEach-Object {\r\n    $testPath = Join-Path $_.FullName \"bin/$MVN_CMD\"\r\n    if (Test-Path -Path $testPath -PathType Leaf) {\r\n      $actualDistributionDir = $_.Name\r\n    }\r\n  }\r\n}\r\n\r\nif (!$actualDistributionDir) {\r\n  Write-Error \"Could not find Maven distribution directory in extracted archive\"\r\n}\r\n\r\nWrite-Verbose \"Found extracted Maven distribution directory: $actualDistributionDir\"\r\nRename-Item -Path \"$TMP_DOWNLOAD_DIR/$actualDistributionDir\" -NewName $MAVEN_HOME_NAME | Out-Null\r\ntry {\r\n  Move-Item -Path \"$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME\" -Destination $MAVEN_HOME_PARENT | Out-Null\r\n} catch {\r\n  if (! (Test-Path -Path \"$MAVEN_HOME\" -PathType Container)) {\r\n    Write-Error \"fail to move MAVEN_HOME\"\r\n  }\r\n} finally {\r\n  try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }\r\n  catch { Write-Warning \"Cannot remove $TMP_DOWNLOAD_DIR\" }\r\n}\r\n\r\nWrite-Output \"MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD\"\r\n"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright 2014-2020 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>de.codecentric</groupId>\n    <artifactId>spring-boot-admin</artifactId>\n    <version>${revision}</version>\n\n    <packaging>pom</packaging>\n\n    <name>Spring Boot Admin</name>\n    <description>Spring Boot Admin</description>\n    <url>https://github.com/codecentric/spring-boot-admin/</url>\n\n    <properties>\n        <revision>4.1.0-SNAPSHOT</revision>\n\n        <java.version>17</java.version>\n        <node.version>v22.12.0</node.version>\n\n        <require.maven.version>3.9</require.maven.version>\n\n        <maven.compiler.source>${java.version}</maven.compiler.source>\n        <maven.compiler.target>${java.version}</maven.compiler.target>\n        <resource.delimiter>@</resource.delimiter>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n\n        <!-- disable default maven site generation, but allow docusarus to hook into site lifecycle -->\n        <maven.site.skip>true</maven.site.skip>\n\n        <!-- used dependencies versions -->\n        <spring-boot.version>4.0.3</spring-boot.version>\n        <spring-cloud.version>2025.1.1</spring-cloud.version>\n\n        <jolokia-support-springboot.version>2.5.1</jolokia-support-springboot.version>\n\n        <checkstyle.version>12.3.1</checkstyle.version>\n        <findbugs-jsr305.version>3.0.2</findbugs-jsr305.version>\n\n        <wiremock.version>3.13.2</wiremock.version>\n        <hazelcast.version>5.6.0</hazelcast.version>\n        <awaitility.version>4.3.0</awaitility.version>\n        <jetty.version>12.1.7</jetty.version>\n\n        <!-- plugin versions -->\n        <build-helper-maven-plugin.version>3.6.1</build-helper-maven-plugin.version>\n        <maven-compiler-plugin.version>3.15.0</maven-compiler-plugin.version>\n        <!-- because of https://github.com/mojohaus/versions/issues/855 -->\n        <versions-maven-plugin.version>2.21.0</versions-maven-plugin.version>\n        <maven-clean-plugin.version>3.5.0</maven-clean-plugin.version>\n        <maven-dependency-plugin.version>3.10.0</maven-dependency-plugin.version>\n        <maven-deploy-plugin.version>3.1.4</maven-deploy-plugin.version>\n        <maven-enforcer-plugin.version>3.6.2</maven-enforcer-plugin.version>\n        <maven-surefire-plugin.version>3.5.5</maven-surefire-plugin.version>\n        <maven-failsafe-plugin.version>3.5.5</maven-failsafe-plugin.version>\n        <maven-install-plugin.version>3.1.4</maven-install-plugin.version>\n        <maven-jar-plugin.version>3.5.0</maven-jar-plugin.version>\n        <maven-javadoc-plugin.version>3.12.0</maven-javadoc-plugin.version>\n        <maven-resources-plugin.version>3.5.0</maven-resources-plugin.version>\n        <maven-source-plugin.version>3.4.0</maven-source-plugin.version>\n        <maven-war-plugin.version>3.5.1</maven-war-plugin.version>\n        <maven-gpg-plugin.version>3.2.8</maven-gpg-plugin.version>\n        <frontend-maven-plugin.version>2.0.0</frontend-maven-plugin.version>\n        <jacoco-maven-plugin.version>0.8.14</jacoco-maven-plugin.version>\n        <git-commit-id-maven-plugin.version>4.9.10</git-commit-id-maven-plugin.version>\n        <flatten-maven-plugin.version>1.7.3</flatten-maven-plugin.version>\n        <cyclonedx-maven-plugin.version>2.9.1</cyclonedx-maven-plugin.version>\n        <maven-checkstyle-plugin.version>3.6.0</maven-checkstyle-plugin.version>\n        <spring-javaformat-maven-plugin.version>0.0.47</spring-javaformat-maven-plugin.version>\n        <central-publishing-maven-plugin.version>0.10.0</central-publishing-maven-plugin.version>\n        <spring-conf-prop-documenter-maven-plugin.version>0.7.2</spring-conf-prop-documenter-maven-plugin.version>\n    </properties>\n\n    <modules>\n        <module>spring-boot-admin-dependencies</module>\n        <module>spring-boot-admin-build</module>\n        <module>spring-boot-admin-server</module>\n        <module>spring-boot-admin-server-ui</module>\n        <module>spring-boot-admin-client</module>\n        <module>spring-boot-admin-docs</module>\n        <module>spring-boot-admin-starter-server</module>\n        <module>spring-boot-admin-starter-client</module>\n        <module>spring-boot-admin-samples</module>\n    </modules>\n\n    <organization>\n        <name>codecentric AG</name>\n        <url>https://www.codecentric.de</url>\n    </organization>\n\n    <licenses>\n        <license>\n            <name>Apache License, Version 2.0</name>\n            <url>https://www.apache.org/licenses/LICENSE-2.0</url>\n        </license>\n    </licenses>\n\n    <scm>\n        <connection>scm:git:git://github.com/codecentric/spring-boot-admin.git</connection>\n        <developerConnection>scm:git:ssh://git@github.com/codecentric/spring-boot-admin.git</developerConnection>\n        <url>https://github.com/codecentric/spring-boot-admin</url>\n    </scm>\n\n    <developers>\n        <developer>\n            <name>codecentric AG</name>\n            <email>spring-boot-admin@codecentric.de</email>\n            <url>https://www.codecentric.de/</url>\n        </developer>\n    </developers>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-source-plugin</artifactId>\n                <executions>\n                    <execution>\n                        <id>attach-sources</id>\n                        <goals>\n                            <goal>jar-no-fork</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-javadoc-plugin</artifactId>\n                <configuration>\n                    <source>${java.version}</source>\n                    <encoding>${project.build.sourceEncoding}</encoding>\n                    <docencoding>${project.reporting.outputEncoding}</docencoding>\n                    <charset>${project.reporting.outputEncoding}</charset>\n                    <quiet>true</quiet>\n                </configuration>\n                <executions>\n                    <execution>\n                        <id>attach-javadocs</id>\n                        <goals>\n                            <goal>jar</goal>\n                        </goals>\n                        <inherited>true</inherited>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-checkstyle-plugin</artifactId>\n                <configuration>\n                    <configLocation>src/checkstyle/checkstyle.xml</configLocation>\n                    <headerLocation>src/checkstyle/checkstyle-header.txt</headerLocation>\n                    <consoleOutput>true</consoleOutput>\n                    <failOnViolation>true</failOnViolation>\n                    <includeTestSourceDirectory>true</includeTestSourceDirectory>\n                </configuration>\n                <executions>\n                    <execution>\n                        <id>checkstyle-validation</id>\n                        <phase>validate</phase>\n                        <goals>\n                            <goal>check</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <!-- this fix is needed for the eclipse-plugin to get the right java-version -->\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <configuration>\n                    <parameters>true</parameters>\n                    <source>${maven.compiler.source}</source>\n                    <target>${maven.compiler.target}</target>\n                    <annotationProcessorPaths>\n                        <path>\n                            <groupId>org.projectlombok</groupId>\n                            <artifactId>lombok</artifactId>\n                        </path>\n                        <path>\n                            <groupId>org.springframework.boot</groupId>\n                            <artifactId>spring-boot-configuration-processor</artifactId>\n                        </path>\n                    </annotationProcessorPaths>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.codehaus.mojo</groupId>\n                <artifactId>flatten-maven-plugin</artifactId>\n                <configuration>\n                    <updatePomFile>true</updatePomFile>\n                    <flattenMode>oss</flattenMode>\n                    <embedBuildProfileDependencies>true</embedBuildProfileDependencies>\n                    <pomElements>\n                        <distributionManagement>remove</distributionManagement>\n                        <repositories>remove</repositories>\n                    </pomElements>\n                </configuration>\n                <executions>\n                    <execution>\n                        <id>flatten</id>\n                        <phase>process-resources</phase>\n                        <goals>\n                            <goal>flatten</goal>\n                        </goals>\n                    </execution>\n                    <execution>\n                        <id>flatten-clean</id>\n                        <phase>clean</phase>\n                        <goals>\n                            <goal>clean</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-enforcer-plugin</artifactId>\n                <executions>\n                    <execution>\n                        <id>enforce-maven</id>\n                        <goals>\n                            <goal>enforce</goal>\n                        </goals>\n                        <configuration>\n                            <rules>\n                                <requireMavenVersion>\n                                    <version>${require.maven.version}</version>\n                                </requireMavenVersion>\n                            </rules>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>io.spring.javaformat</groupId>\n                <artifactId>spring-javaformat-maven-plugin</artifactId>\n                <version>${spring-javaformat-maven-plugin.version}</version>\n                <executions>\n                    <execution>\n                        <phase>validate</phase>\n                        <inherited>true</inherited>\n                        <goals>\n                            <goal>validate</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n\n        <pluginManagement>\n            <plugins>\n                <plugin>\n                    <groupId>org.codehaus.mojo</groupId>\n                    <artifactId>versions-maven-plugin</artifactId>\n                    <version>${versions-maven-plugin.version}</version>\n                </plugin>\n                <plugin>\n                    <groupId>org.apache.maven.plugins</groupId>\n                    <artifactId>maven-compiler-plugin</artifactId>\n                    <version>${maven-compiler-plugin.version}</version>\n                </plugin>\n                <plugin>\n                    <groupId>org.apache.maven.plugins</groupId>\n                    <artifactId>maven-clean-plugin</artifactId>\n                    <version>${maven-clean-plugin.version}</version>\n                </plugin>\n                <plugin>\n                    <groupId>org.apache.maven.plugins</groupId>\n                    <artifactId>maven-source-plugin</artifactId>\n                    <version>${maven-source-plugin.version}</version>\n                </plugin>\n                <plugin>\n                    <groupId>org.apache.maven.plugins</groupId>\n                    <artifactId>maven-jar-plugin</artifactId>\n                    <version>${maven-jar-plugin.version}</version>\n                </plugin>\n                <plugin>\n                    <groupId>org.apache.maven.plugins</groupId>\n                    <artifactId>maven-javadoc-plugin</artifactId>\n                    <version>${maven-javadoc-plugin.version}</version>\n                </plugin>\n                <plugin>\n                    <groupId>org.apache.maven.plugins</groupId>\n                    <artifactId>maven-deploy-plugin</artifactId>\n                    <version>${maven-deploy-plugin.version}</version>\n                </plugin>\n                <plugin>\n                    <groupId>org.apache.maven.plugins</groupId>\n                    <artifactId>maven-dependency-plugin</artifactId>\n                    <version>${maven-dependency-plugin.version}</version>\n                </plugin>\n                <plugin>\n                    <groupId>org.apache.maven.plugins</groupId>\n                    <artifactId>maven-enforcer-plugin</artifactId>\n                    <version>${maven-enforcer-plugin.version}</version>\n                </plugin>\n                <plugin>\n                    <groupId>org.apache.maven.plugins</groupId>\n                    <artifactId>maven-install-plugin</artifactId>\n                    <version>${maven-install-plugin.version}</version>\n                </plugin>\n                <plugin>\n                    <groupId>org.apache.maven.plugins</groupId>\n                    <artifactId>maven-war-plugin</artifactId>\n                    <version>${maven-war-plugin.version}</version>\n                </plugin>\n                <plugin>\n                    <groupId>org.springframework.boot</groupId>\n                    <artifactId>spring-boot-maven-plugin</artifactId>\n                    <version>${spring-boot.version}</version>\n                </plugin>\n                <plugin>\n                    <groupId>org.codehaus.mojo</groupId>\n                    <artifactId>build-helper-maven-plugin</artifactId>\n                    <version>${build-helper-maven-plugin.version}</version>\n                </plugin>\n                <plugin>\n                    <groupId>org.codehaus.mojo</groupId>\n                    <artifactId>flatten-maven-plugin</artifactId>\n                    <version>${flatten-maven-plugin.version}</version>\n                </plugin>\n                <plugin>\n                    <groupId>org.cyclonedx</groupId>\n                    <artifactId>cyclonedx-maven-plugin</artifactId>\n                    <version>${cyclonedx-maven-plugin.version}</version>\n                </plugin>\n                <plugin>\n                    <groupId>org.apache.maven.plugins</groupId>\n                    <artifactId>maven-failsafe-plugin</artifactId>\n                    <version>${maven-failsafe-plugin.version}</version>\n                    <executions>\n                        <execution>\n                            <goals>\n                                <goal>integration-test</goal>\n                                <goal>verify</goal>\n                            </goals>\n                        </execution>\n                    </executions>\n                </plugin>\n                <plugin>\n                    <groupId>org.apache.maven.plugins</groupId>\n                    <artifactId>maven-surefire-plugin</artifactId>\n                    <version>${maven-surefire-plugin.version}</version>\n                    <configuration>\n                        <parallel>classesAndMethods</parallel>\n                        <useUnlimitedThreads>false</useUnlimitedThreads>\n                        <redirectTestOutputToFile>true</redirectTestOutputToFile>\n                        <rerunFailingTestsCount>2</rerunFailingTestsCount>\n                        <includes>\n                            <include>**/*Tests.java</include>\n                            <include>**/*Test.java</include>\n                        </includes>\n                        <excludes>\n                            <exclude>**/Abstract*.java</exclude>\n                        </excludes>\n                    </configuration>\n                </plugin>\n                <plugin>\n                    <groupId>org.apache.maven.plugins</groupId>\n                    <artifactId>maven-resources-plugin</artifactId>\n                    <version>${maven-resources-plugin.version}</version>\n                    <configuration>\n                        <delimiters>\n                            <delimiter>${resource.delimiter}</delimiter>\n                        </delimiters>\n                        <useDefaultDelimiters>false</useDefaultDelimiters>\n                    </configuration>\n                </plugin>\n                <plugin>\n                    <groupId>org.apache.maven.plugins</groupId>\n                    <artifactId>maven-gpg-plugin</artifactId>\n                    <version>${maven-gpg-plugin.version}</version>\n                </plugin>\n                <plugin>\n                    <groupId>org.apache.maven.plugins</groupId>\n                    <artifactId>maven-checkstyle-plugin</artifactId>\n                    <version>${maven-checkstyle-plugin.version}</version>\n                    <dependencies>\n                        <dependency>\n                            <groupId>com.puppycrawl.tools</groupId>\n                            <artifactId>checkstyle</artifactId>\n                            <version>${checkstyle.version}</version>\n                        </dependency>\n                        <dependency>\n                            <groupId>io.spring.javaformat</groupId>\n                            <artifactId>spring-javaformat-checkstyle</artifactId>\n                            <version>${spring-javaformat-maven-plugin.version}</version>\n                        </dependency>\n                    </dependencies>\n                </plugin>\n                <plugin>\n                    <groupId>com.github.eirslett</groupId>\n                    <artifactId>frontend-maven-plugin</artifactId>\n                    <version>${frontend-maven-plugin.version}</version>\n                </plugin>\n                <plugin>\n                    <groupId>pl.project13.maven</groupId>\n                    <artifactId>git-commit-id-plugin</artifactId>\n                    <version>${git-commit-id-maven-plugin.version}</version>\n                </plugin>\n                <plugin>\n                    <groupId>org.jacoco</groupId>\n                    <artifactId>jacoco-maven-plugin</artifactId>\n                    <version>${jacoco-maven-plugin.version}</version>\n                </plugin>\n                <plugin>\n                    <groupId>org.sonatype.central</groupId>\n                    <artifactId>central-publishing-maven-plugin</artifactId>\n                    <version>${central-publishing-maven-plugin.version}</version>\n                </plugin>\n                <plugin>\n                    <groupId>org.rodnansol</groupId>\n                    <artifactId>spring-configuration-property-documenter-maven-plugin</artifactId>\n                    <version>${spring-conf-prop-documenter-maven-plugin.version}</version>\n                </plugin>\n            </plugins>\n        </pluginManagement>\n    </build>\n\n    <profiles>\n        <profile>\n            <id>include-cloud</id>\n            <activation>\n                <property>\n                    <name>!excludeSpringCloud</name>\n                </property>\n            </activation>\n            <modules>\n                <module>spring-boot-admin-server-cloud</module>\n            </modules>\n        </profile>\n        <profile>\n            <id>coverage</id>\n            <build>\n                <plugins>\n                    <plugin>\n                        <groupId>org.jacoco</groupId>\n                        <artifactId>jacoco-maven-plugin</artifactId>\n                        <executions>\n                            <!-- Prepares the property pointing to the JaCoCo runtime agent which is passed as\n                                 VM argument when Maven the Surefire plugin is executed. -->\n                            <execution>\n                                <id>pre-unit-test</id>\n                                <goals>\n                                    <goal>prepare-agent</goal>\n                                </goals>\n                            </execution>\n                            <!-- Ensures that the code coverage report for unit tests is created after unit tests have\n                                 been run. -->\n                            <execution>\n                                <id>post-unit-test</id>\n                                <goals>\n                                    <goal>report</goal>\n                                </goals>\n                            </execution>\n                        </executions>\n                    </plugin>\n                </plugins>\n            </build>\n        </profile>\n        <profile>\n            <id>central-deploy</id>\n            <build>\n                <plugins>\n                    <plugin>\n                        <groupId>org.apache.maven.plugins</groupId>\n                        <artifactId>maven-gpg-plugin</artifactId>\n                        <executions>\n                            <execution>\n                                <id>sign-artifacts</id>\n                                <phase>verify</phase>\n                                <goals>\n                                    <goal>sign</goal>\n                                </goals>\n                                <configuration>\n                                    <!-- This is necessary for gpg to not try to use the pinentry programs -->\n                                    <gpgArguments>\n                                        <arg>--pinentry-mode</arg>\n                                        <arg>loopback</arg>\n                                    </gpgArguments>\n                                </configuration>\n                            </execution>\n                        </executions>\n                    </plugin>\n\n                    <plugin>\n                        <groupId>org.sonatype.central</groupId>\n                        <artifactId>central-publishing-maven-plugin</artifactId>\n                        <extensions>true</extensions>\n                        <configuration>\n                            <publishingServerId>central</publishingServerId>\n                            <autoPublish>true</autoPublish>\n                        </configuration>\n                    </plugin>\n                </plugins>\n            </build>\n        </profile>\n        <profile>\n            <id>spring-repo</id>\n            <activation>\n                <property>\n                    <name>!disableSpringSnapshots</name>\n                </property>\n            </activation>\n            <repositories>\n                <repository>\n                    <id>spring-milestone</id>\n                    <snapshots>\n                        <enabled>false</enabled>\n                    </snapshots>\n                    <url>https://repo.spring.io/milestone</url>\n                </repository>\n                <repository>\n                    <id>spring-snapshot</id>\n                    <snapshots>\n                        <enabled>true</enabled>\n                    </snapshots>\n                    <url>https://repo.spring.io/snapshot</url>\n                </repository>\n                <!-- Required for Eureka RC -->\n                <repository>\n                    <id>netflix-candidates</id>\n                    <name>Netflix Candidates</name>\n                    <url>https://artifactory-oss.prod.netflix.net/artifactory/maven-oss-candidates</url>\n                    <snapshots>\n                        <enabled>false</enabled>\n                    </snapshots>\n                </repository>\n            </repositories>\n            <pluginRepositories>\n                <pluginRepository>\n                    <id>spring-milestone</id>\n                    <snapshots>\n                        <enabled>false</enabled>\n                    </snapshots>\n                    <url>https://repo.spring.io/milestone</url>\n                </pluginRepository>\n                <pluginRepository>\n                    <id>spring-snapshot</id>\n                    <snapshots>\n                        <enabled>true</enabled>\n                    </snapshots>\n                    <url>https://repo.spring.io/snapshot</url>\n                </pluginRepository>\n            </pluginRepositories>\n        </profile>\n        <!--\n        Profile: noNpm\n        Set this profile to skip any frontend related build step in this project.\n        Skipping frontend tests can be achieved by using default flag -D skipTests.\n        -->\n        <profile>\n            <id>noNpm</id>\n            <build>\n                <plugins>\n                    <plugin>\n                        <groupId>com.github.eirslett</groupId>\n                        <artifactId>frontend-maven-plugin</artifactId>\n                        <configuration>\n                            <skip>true</skip>\n                        </configuration>\n                    </plugin>\n                </plugins>\n            </build>\n        </profile>\n    </profiles>\n\n    <distributionManagement>\n        <snapshotRepository>\n            <id>github</id>\n            <name>GitHub Packages</name>\n            <url>https://maven.pkg.github.com/codecentric/spring-boot-admin</url>\n        </snapshotRepository>\n    </distributionManagement>\n</project>\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"extends\": [\n    \"config:recommended\"\n  ],\n  \"labels\": [\n    \"bot\",\n    \"dependencies\"\n  ],\n  \"minimumReleaseAge\": \"5 days\",\n  \"internalChecksFilter\": \"strict\",\n  \"packageRules\": [\n    {\n      \"description\": \"Automatically merge minor and patch-level updates when checks pass, creates a PR otherwise\",\n      \"matchUpdateTypes\": [\n        \"minor\",\n        \"patch\",\n        \"digest\"\n      ],\n      \"automerge\": true,\n      \"automergeType\": \"pr\",\n      \"platformAutomerge\": true\n    },\n    {\n      \"matchPackageNames\": [\n        \"org.apache.maven.plugins:maven-site-plugin\",\n        \"spring-boot-admin\"\n      ],\n      \"enabled\": false\n    },\n    {\n      \"description\": \"Checkstyle 13+ requires Java 21, stay on 12.x\",\n      \"matchPackageNames\": [\n        \"com.puppycrawl.tools:checkstyle\"\n      ],\n      \"allowedVersions\": \"<13\"\n    }\n  ]\n}\n"
  },
  {
    "path": "spring-boot-admin-build/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright 2014-2019 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>spring-boot-admin-build</artifactId>\n\n    <packaging>pom</packaging>\n\n    <name>Spring Boot Admin Build</name>\n    <description>Spring Boot Admin Build</description>\n\n    <parent>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-dependencies</artifactId>\n        <version>${revision}</version>\n        <relativePath>../spring-boot-admin-dependencies</relativePath>\n    </parent>\n\n    <dependencyManagement>\n        <dependencies>\n            <dependency>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-dependencies</artifactId>\n                <version>${spring-boot.version}</version>\n                <type>pom</type>\n                <scope>import</scope>\n            </dependency>\n            <dependency>\n                <groupId>org.springframework.cloud</groupId>\n                <artifactId>spring-cloud-dependencies</artifactId>\n                <version>${spring-cloud.version}</version>\n                <type>pom</type>\n                <scope>import</scope>\n            </dependency>\n            <dependency>\n                <groupId>org.jolokia</groupId>\n                <artifactId>jolokia-support-springboot</artifactId>\n                <version>${jolokia-support-springboot.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.wiremock</groupId>\n                <artifactId>wiremock-standalone</artifactId>\n                <version>${wiremock.version}</version>\n                <scope>test</scope>\n            </dependency>\n            <dependency>\n                <groupId>com.hazelcast</groupId>\n                <artifactId>hazelcast</artifactId>\n                <version>${hazelcast.version}</version>\n                <classifier>tests</classifier>\n                <scope>test</scope>\n            </dependency>\n            <dependency>\n                <groupId>com.google.code.findbugs</groupId>\n                <artifactId>jsr305</artifactId>\n                <version>${findbugs-jsr305.version}</version>\n                <scope>provided</scope>\n            </dependency>\n            <dependency>\n                <groupId>org.eclipse.jetty</groupId>\n                <artifactId>jetty-alpn-server</artifactId>\n                <version>${jetty.version}</version>\n                <scope>test</scope>\n            </dependency>\n            <dependency>\n                <groupId>org.awaitility</groupId>\n                <artifactId>awaitility</artifactId>\n                <version>${awaitility.version}</version>\n                <scope>test</scope>\n            </dependency>\n        </dependencies>\n    </dependencyManagement>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.codehaus.mojo</groupId>\n                <artifactId>flatten-maven-plugin</artifactId>\n                <inherited>true</inherited>\n                <executions>\n                    <execution>\n                        <!-- Tidy up all POMs before they are published -->\n                        <id>flatten</id>\n                        <phase>process-resources</phase>\n                        <goals>\n                            <goal>flatten</goal>\n                        </goals>\n                        <configuration>\n                            <updatePomFile>true</updatePomFile>\n                            <flattenMode>oss</flattenMode>\n                            <embedBuildProfileDependencies>true</embedBuildProfileDependencies>\n                            <pomElements>\n                                <parent>expand</parent>\n                                <distributionManagement>remove</distributionManagement>\n                                <repositories>remove</repositories>\n                            </pomElements>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.codehaus.mojo</groupId>\n                <artifactId>build-helper-maven-plugin</artifactId>\n                <executions>\n                    <execution>\n                        <id>generate-automatic-module-name</id>\n                        <goals>\n                            <goal>regex-property</goal>\n                        </goals>\n                        <configuration>\n                            <name>automatic-module-name</name>\n                            <value>${project.groupId}.${project.artifactId}</value>\n                            <regex>[^a-zA-Z0-9]+</regex>\n                            <replacement>.</replacement>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-jar-plugin</artifactId>\n                <configuration>\n                    <archive>\n                        <manifest>\n                            <addDefaultImplementationEntries>true</addDefaultImplementationEntries>\n                        </manifest>\n                        <manifestEntries>\n                            <Automatic-Module-Name>${automatic-module-name}</Automatic-Module-Name>\n                        </manifestEntries>\n                    </archive>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "spring-boot-admin-client/pom.xml",
    "content": "<?xml version=\"1.0\"?>\n<!--\n  ~ Copyright 2014-2019 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>spring-boot-admin-client</artifactId>\n\n    <name>Spring Boot Admin Client</name>\n    <description>Spring Boot Admin Client</description>\n\n    <parent>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-build</artifactId>\n        <version>${revision}</version>\n        <relativePath>../spring-boot-admin-build</relativePath>\n    </parent>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-actuator</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-restclient</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-webflux</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-webmvc</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-autoconfigure-processor</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-configuration-processor</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.wiremock</groupId>\n            <artifactId>wiremock-standalone</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.awaitility</groupId>\n            <artifactId>awaitility</artifactId>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/java/de/codecentric/boot/admin/client/config/ClientProperties.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.config;\n\nimport java.time.Duration;\nimport java.time.temporal.ChronoUnit;\n\nimport org.jspecify.annotations.Nullable;\nimport org.springframework.boot.cloud.CloudPlatform;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.boot.convert.DurationUnit;\nimport org.springframework.core.env.Environment;\n\n@lombok.Data\n@ConfigurationProperties(prefix = \"spring.boot.admin.client\")\npublic class ClientProperties {\n\n\t/**\n\t * The admin server urls to register at\n\t */\n\tprivate String[] url = new String[] {};\n\n\t/**\n\t * The admin rest-apis path.\n\t */\n\tprivate String apiPath = \"instances\";\n\n\t/**\n\t * Time interval the registration is repeated\n\t */\n\t@DurationUnit(ChronoUnit.MILLIS)\n\tprivate Duration period = Duration.ofMillis(10_000L);\n\n\t/**\n\t * Connect timeout for the registration.\n\t */\n\t@DurationUnit(ChronoUnit.MILLIS)\n\tprivate Duration connectTimeout = Duration.ofMillis(5_000L);\n\n\t/**\n\t * Read timeout (in ms) for the registration.\n\t */\n\t@DurationUnit(ChronoUnit.MILLIS)\n\tprivate Duration readTimeout = Duration.ofMillis(5_000L);\n\n\t/**\n\t * Username for basic authentication on admin server\n\t */\n\t@Nullable private String username;\n\n\t/**\n\t * Password for basic authentication on admin server\n\t */\n\t@Nullable private String password;\n\n\t/**\n\t * Enable automatic deregistration on shutdown If not set it defaults to true if an\n\t * active {@link CloudPlatform} is present;\n\t */\n\t@Nullable private Boolean autoDeregistration = null;\n\n\t/**\n\t * Enable automatic registration when the application is ready.\n\t */\n\tprivate boolean autoRegistration = true;\n\n\t/**\n\t * Enable registration against one or all admin servers\n\t */\n\tprivate boolean registerOnce = true;\n\n\t/**\n\t * Enable Spring Boot Admin Client.\n\t */\n\tprivate boolean enabled = true;\n\n\tpublic String[] getAdminUrl() {\n\t\tString[] adminUrls = this.url.clone();\n\t\tfor (int i = 0; i < adminUrls.length; i++) {\n\t\t\tadminUrls[i] += \"/\" + this.apiPath;\n\t\t}\n\t\treturn adminUrls;\n\t}\n\n\tpublic boolean isAutoDeregistration(Environment environment) {\n\t\treturn (this.autoDeregistration != null) ? this.autoDeregistration\n\t\t\t\t: (CloudPlatform.getActive(environment) != null);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/java/de/codecentric/boot/admin/client/config/ClientRuntimeHints.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.config;\n\nimport lombok.SneakyThrows;\nimport org.springframework.aot.hint.ExecutableMode;\nimport org.springframework.aot.hint.MemberCategory;\nimport org.springframework.aot.hint.RuntimeHints;\nimport org.springframework.aot.hint.RuntimeHintsRegistrar;\nimport org.springframework.boot.web.server.context.WebServerInitializedEvent;\nimport org.springframework.context.annotation.Configuration;\n\nimport de.codecentric.boot.admin.client.registration.Application;\nimport de.codecentric.boot.admin.client.registration.DefaultApplicationFactory;\n\n@Configuration\npublic class ClientRuntimeHints implements RuntimeHintsRegistrar {\n\n\t@Override\n\tpublic void registerHints(RuntimeHints hints, ClassLoader classLoader) {\n\t\tregisterReflectionHints(hints);\n\t}\n\n\t@SneakyThrows\n\tprivate static void registerReflectionHints(RuntimeHints hints) {\n\t\thints.reflection()\n\t\t\t.registerType(Application.Builder.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(Application.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerConstructor(Application.Builder.class.getDeclaredConstructor(), ExecutableMode.INVOKE)\n\t\t\t.registerMethod(Application.Builder.class.getMethod(\"build\"), ExecutableMode.INVOKE)\n\t\t\t.registerMethod(Application.class.getMethod(\"builder\"), ExecutableMode.INVOKE)\n\t\t\t.registerMethod(DefaultApplicationFactory.class.getMethod(\"onWebServerInitialized\",\n\t\t\t\t\tWebServerInitializedEvent.class), ExecutableMode.INVOKE);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/java/de/codecentric/boot/admin/client/config/CloudFoundryApplicationProperties.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.config;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.jspecify.annotations.Nullable;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\n\n@lombok.Data\n@ConfigurationProperties(\"vcap.application\")\npublic class CloudFoundryApplicationProperties {\n\n\t@Nullable private String applicationId;\n\n\t@Nullable private String instanceIndex;\n\n\tprivate List<String> uris = new ArrayList<>();\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/java/de/codecentric/boot/admin/client/config/InstanceProperties.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.config;\n\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\nimport org.jspecify.annotations.Nullable;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\n\n@lombok.Data\n@ConfigurationProperties(prefix = \"spring.boot.admin.client.instance\")\npublic class InstanceProperties {\n\n\t/**\n\t * Management-url to register with. Inferred at runtime, can be overridden in case the\n\t * reachable URL is different (e.g. Docker).\n\t */\n\t@Nullable private String managementUrl;\n\n\t/**\n\t * Base url for computing the management-url to register with. The path is inferred at\n\t * runtime, and appended to the base url.\n\t */\n\t@Nullable private String managementBaseUrl;\n\n\t/**\n\t * Client-service-URL register with. Inferred at runtime, can be overridden in case\n\t * the reachable URL is different (e.g. Docker).\n\t */\n\t@Nullable private String serviceUrl;\n\n\t/**\n\t * Base url for computing the service-url to register with. The path is inferred at\n\t * runtime, and appended to the base url.\n\t */\n\t@Nullable private String serviceBaseUrl;\n\n\t/**\n\t * Path for computing the service-url to register with. If not specified, defaults to\n\t * \"/\"\n\t */\n\t@Nullable private String servicePath;\n\n\t/**\n\t * Client-health-URL to register with. Inferred at runtime, can be overridden in case\n\t * the reachable URL is different (e.g. Docker). Must be unique all services registry.\n\t */\n\t@Nullable private String healthUrl;\n\n\t/**\n\t * Name to register with. Defaults to ${spring.application.name}\n\t */\n\t@Value(\"${spring.application.name:spring-boot-application}\")\n\tprivate String name = \"spring-boot-application\";\n\n\t/**\n\t * Should the registered urls be built with server.address or with hostname.\n\t * @deprecated Use serviceHostType instead.\n\t */\n\t@Deprecated\n\tprivate boolean preferIp = false;\n\n\t/**\n\t * Should the registered urls be built with server.address or with hostname.\n\t */\n\tprivate ServiceHostType serviceHostType = ServiceHostType.CANONICAL_HOST_NAME;\n\n\t/**\n\t * Metadata that should be associated with this application\n\t */\n\tprivate Map<String, String> metadata = new LinkedHashMap<>();\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/java/de/codecentric/boot/admin/client/config/ServiceHostType.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.config;\n\npublic enum ServiceHostType {\n\n\tIP, HOST_NAME, CANONICAL_HOST_NAME,\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/java/de/codecentric/boot/admin/client/config/SpringBootAdminClientAutoConfiguration.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.config;\n\nimport java.util.Collections;\nimport java.util.List;\n\nimport jakarta.servlet.ServletContext;\nimport org.springframework.beans.factory.ObjectProvider;\nimport org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;\nimport org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;\nimport org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;\nimport org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;\nimport org.springframework.boot.autoconfigure.AutoConfigureAfter;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;\nimport org.springframework.boot.http.client.HttpClientSettings;\nimport org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration;\nimport org.springframework.boot.web.server.autoconfigure.ServerProperties;\nimport org.springframework.boot.webflux.autoconfigure.WebFluxProperties;\nimport org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration;\nimport org.springframework.boot.webmvc.autoconfigure.DispatcherServletPath;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Conditional;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Lazy;\nimport org.springframework.core.env.Environment;\nimport org.springframework.http.client.support.BasicAuthenticationInterceptor;\nimport org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;\nimport org.springframework.web.client.RestClient;\nimport tools.jackson.databind.json.JsonMapper;\n\nimport de.codecentric.boot.admin.client.registration.ApplicationFactory;\nimport de.codecentric.boot.admin.client.registration.ApplicationRegistrator;\nimport de.codecentric.boot.admin.client.registration.DefaultApplicationRegistrator;\nimport de.codecentric.boot.admin.client.registration.ReactiveApplicationFactory;\nimport de.codecentric.boot.admin.client.registration.RegistrationApplicationListener;\nimport de.codecentric.boot.admin.client.registration.RegistrationClient;\nimport de.codecentric.boot.admin.client.registration.RestClientRegistrationClient;\nimport de.codecentric.boot.admin.client.registration.ServletApplicationFactory;\nimport de.codecentric.boot.admin.client.registration.metadata.CompositeMetadataContributor;\nimport de.codecentric.boot.admin.client.registration.metadata.MetadataContributor;\nimport de.codecentric.boot.admin.client.registration.metadata.StartupDateMetadataContributor;\n\n@Configuration(proxyBeanMethods = false)\n@ConditionalOnWebApplication\n@Conditional(SpringBootAdminClientEnabledCondition.class)\n@AutoConfigureAfter({ WebEndpointAutoConfiguration.class, RestClientAutoConfiguration.class })\n@EnableConfigurationProperties({ ClientProperties.class, InstanceProperties.class, ServerProperties.class,\n\t\tManagementServerProperties.class })\npublic class SpringBootAdminClientAutoConfiguration {\n\n\t@Bean\n\t@ConditionalOnMissingBean\n\tpublic ApplicationRegistrator registrator(RegistrationClient registrationClient, ClientProperties client,\n\t\t\tApplicationFactory applicationFactory) {\n\t\treturn new DefaultApplicationRegistrator(applicationFactory, registrationClient, client.getAdminUrl(),\n\t\t\t\tclient.isRegisterOnce());\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean\n\tpublic RegistrationApplicationListener registrationListener(ClientProperties client,\n\t\t\tApplicationRegistrator registrator, Environment environment) {\n\t\tRegistrationApplicationListener listener = new RegistrationApplicationListener(registrator);\n\t\tlistener.setAutoRegister(client.isAutoRegistration());\n\t\tlistener.setAutoDeregister(client.isAutoDeregistration(environment));\n\t\tlistener.setRegisterPeriod(client.getPeriod());\n\t\treturn listener;\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean\n\tpublic StartupDateMetadataContributor startupDateMetadataContributor() {\n\t\treturn new StartupDateMetadataContributor();\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnWebApplication(type = Type.SERVLET)\n\t@AutoConfigureAfter(DispatcherServletAutoConfiguration.class)\n\tpublic static class ServletConfiguration {\n\n\t\t@Bean\n\t\t@Lazy(false)\n\t\t@ConditionalOnMissingBean\n\t\tpublic ApplicationFactory applicationFactory(InstanceProperties instance, ManagementServerProperties management,\n\t\t\t\tServerProperties server, ServletContext servletContext, PathMappedEndpoints pathMappedEndpoints,\n\t\t\t\tWebEndpointProperties webEndpoint, ObjectProvider<List<MetadataContributor>> metadataContributors,\n\t\t\t\tDispatcherServletPath dispatcherServletPath) {\n\t\t\treturn new ServletApplicationFactory(instance, management, server, servletContext, pathMappedEndpoints,\n\t\t\t\t\twebEndpoint,\n\t\t\t\t\tnew CompositeMetadataContributor(metadataContributors.getIfAvailable(Collections::emptyList)),\n\t\t\t\t\tdispatcherServletPath);\n\t\t}\n\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnWebApplication(type = Type.REACTIVE)\n\tpublic static class ReactiveConfiguration {\n\n\t\t@Bean\n\t\t@Lazy(false)\n\t\t@ConditionalOnMissingBean\n\t\tpublic ApplicationFactory applicationFactory(InstanceProperties instance, ManagementServerProperties management,\n\t\t\t\tServerProperties server, PathMappedEndpoints pathMappedEndpoints, WebEndpointProperties webEndpoint,\n\t\t\t\tObjectProvider<List<MetadataContributor>> metadataContributors, WebFluxProperties webFluxProperties) {\n\t\t\treturn new ReactiveApplicationFactory(instance, management, server, pathMappedEndpoints, webEndpoint,\n\t\t\t\t\tnew CompositeMetadataContributor(metadataContributors.getIfAvailable(Collections::emptyList)),\n\t\t\t\t\twebFluxProperties);\n\t\t}\n\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnBean(RestClient.Builder.class)\n\tpublic static class RestClientRegistrationClientConfig {\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\tpublic RegistrationClient registrationClient(ClientProperties client, RestClient.Builder restClientBuilder,\n\t\t\t\tObjectProvider<JsonMapper> objectMapper) {\n\t\t\tvar factorySettings = HttpClientSettings.defaults()\n\t\t\t\t.withConnectTimeout(client.getConnectTimeout())\n\t\t\t\t.withReadTimeout(client.getReadTimeout());\n\n\t\t\tvar clientHttpRequestFactory = ClientHttpRequestFactoryBuilder.detect().build(factorySettings);\n\n\t\t\trestClientBuilder.requestFactory(clientHttpRequestFactory);\n\n\t\t\tobjectMapper.ifAvailable((mapper) -> restClientBuilder.messageConverters((configurer) -> {\n\t\t\t\tconfigurer.removeIf(JacksonJsonHttpMessageConverter.class::isInstance);\n\t\t\t\tconfigurer.add(new JacksonJsonHttpMessageConverter(mapper));\n\t\t\t}));\n\n\t\t\tif (client.getUsername() != null && client.getPassword() != null) {\n\t\t\t\trestClientBuilder\n\t\t\t\t\t.requestInterceptor(new BasicAuthenticationInterceptor(client.getUsername(), client.getPassword()));\n\t\t\t}\n\n\t\t\tvar restClient = restClientBuilder.build();\n\t\t\treturn new RestClientRegistrationClient(restClient);\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/java/de/codecentric/boot/admin/client/config/SpringBootAdminClientCloudFoundryAutoConfiguration.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.config;\n\nimport java.util.Collections;\nimport java.util.List;\n\nimport org.springframework.beans.factory.ObjectProvider;\nimport org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;\nimport org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;\nimport org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;\nimport org.springframework.boot.autoconfigure.AutoConfigureBefore;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;\nimport org.springframework.boot.cloud.CloudPlatform;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.boot.web.server.autoconfigure.ServerProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Conditional;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Lazy;\n\nimport de.codecentric.boot.admin.client.registration.CloudFoundryApplicationFactory;\nimport de.codecentric.boot.admin.client.registration.metadata.CloudFoundryMetadataContributor;\nimport de.codecentric.boot.admin.client.registration.metadata.CompositeMetadataContributor;\nimport de.codecentric.boot.admin.client.registration.metadata.MetadataContributor;\n\n@Configuration(proxyBeanMethods = false)\n@ConditionalOnWebApplication\n@ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY)\n@Conditional(SpringBootAdminClientEnabledCondition.class)\n@EnableConfigurationProperties(CloudFoundryApplicationProperties.class)\n@AutoConfigureBefore({ SpringBootAdminClientAutoConfiguration.class })\npublic class SpringBootAdminClientCloudFoundryAutoConfiguration {\n\n\t@Bean\n\t@ConditionalOnMissingBean\n\tpublic CloudFoundryMetadataContributor cloudFoundryMetadataContributor(\n\t\t\tCloudFoundryApplicationProperties cloudFoundryApplicationProperties) {\n\t\treturn new CloudFoundryMetadataContributor(cloudFoundryApplicationProperties);\n\t}\n\n\t@Bean\n\t@Lazy(false)\n\t@ConditionalOnMissingBean\n\tpublic CloudFoundryApplicationFactory applicationFactory(InstanceProperties instance,\n\t\t\tManagementServerProperties management, ServerProperties server, PathMappedEndpoints pathMappedEndpoints,\n\t\t\tWebEndpointProperties webEndpoint, ObjectProvider<List<MetadataContributor>> metadataContributors,\n\t\t\tCloudFoundryApplicationProperties cfApplicationProperties) {\n\t\treturn new CloudFoundryApplicationFactory(instance, management, server, pathMappedEndpoints, webEndpoint,\n\t\t\t\tnew CompositeMetadataContributor(metadataContributors.getIfAvailable(Collections::emptyList)),\n\t\t\t\tcfApplicationProperties);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/java/de/codecentric/boot/admin/client/config/SpringBootAdminClientEnabledCondition.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.config;\n\nimport org.springframework.boot.autoconfigure.condition.ConditionOutcome;\nimport org.springframework.boot.autoconfigure.condition.SpringBootCondition;\nimport org.springframework.boot.context.properties.bind.Bindable;\nimport org.springframework.boot.context.properties.bind.Binder;\nimport org.springframework.context.annotation.ConditionContext;\nimport org.springframework.core.type.AnnotatedTypeMetadata;\n\n/**\n * This condition checks if the client should be enabled. Two properties are checked:\n * spring.boot.admin.client.enabled and spring.boot.admin.client.url. The following table\n * shows under which conditions the client is active. <pre>\n *           | enabled: false | enabled: true (default) |\n * --------- | -------------- | ----------------------- |\n * url empty | inactive       | inactive                |\n * (default) |                |                         |\n * --------- | -------------- | ----------------------- |\n * url set   | inactive       | active                  |\n * </pre>\n */\npublic class SpringBootAdminClientEnabledCondition extends SpringBootCondition {\n\n\t@Override\n\tpublic ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata annotatedTypeMetadata) {\n\t\tClientProperties clientProperties = getClientProperties(context);\n\n\t\tif (!clientProperties.isEnabled()) {\n\t\t\treturn ConditionOutcome\n\t\t\t\t.noMatch(\"Spring Boot Client is disabled, because 'spring.boot.admin.client.enabled' is false.\");\n\t\t}\n\n\t\tif (clientProperties.getUrl().length == 0) {\n\t\t\treturn ConditionOutcome\n\t\t\t\t.noMatch(\"Spring Boot Client is disabled, because 'spring.boot.admin.client.url' is empty.\");\n\t\t}\n\n\t\treturn ConditionOutcome.match();\n\t}\n\n\tprivate ClientProperties getClientProperties(ConditionContext context) {\n\t\tClientProperties clientProperties = new ClientProperties();\n\t\tBinder.get(context.getEnvironment()).bind(\"spring.boot.admin.client\", Bindable.ofInstance(clientProperties));\n\t\treturn clientProperties;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/java/de/codecentric/boot/admin/client/config/SpringNativeClientAutoConfiguration.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.config;\n\nimport org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;\nimport org.springframework.boot.autoconfigure.AutoConfigureAfter;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;\nimport org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration;\nimport org.springframework.context.annotation.Conditional;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.ImportRuntimeHints;\n\n@Configuration(proxyBeanMethods = false)\n@ConditionalOnWebApplication\n@Conditional(SpringBootAdminClientEnabledCondition.class)\n@AutoConfigureAfter({ WebEndpointAutoConfiguration.class, RestClientAutoConfiguration.class })\n@ImportRuntimeHints({ ClientRuntimeHints.class })\npublic class SpringNativeClientAutoConfiguration {\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/java/de/codecentric/boot/admin/client/config/package-info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n@NullMarked\npackage de.codecentric.boot.admin.client.config;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/java/de/codecentric/boot/admin/client/registration/Application.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.registration;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.springframework.util.Assert;\n\n/**\n * Contains all information which is used when this application is registered.\n *\n * @author Johannes Edmeier\n */\n@lombok.Data\n@lombok.ToString(exclude = \"metadata\")\npublic class Application {\n\n\tprivate final String name;\n\n\tprivate final String managementUrl;\n\n\tprivate final String healthUrl;\n\n\tprivate final String serviceUrl;\n\n\tprivate final Map<String, String> metadata;\n\n\t@lombok.Builder(builderClassName = \"Builder\")\n\tprotected Application(String name, String managementUrl, String healthUrl, String serviceUrl,\n\t\t\t@lombok.Singular(\"metadata\") Map<String, String> metadata) {\n\t\tAssert.hasText(name, \"name must not be empty!\");\n\t\tAssert.hasText(healthUrl, \"healthUrl must not be empty!\");\n\t\tthis.name = name;\n\t\tthis.managementUrl = managementUrl;\n\t\tthis.healthUrl = healthUrl;\n\t\tthis.serviceUrl = serviceUrl;\n\t\tthis.metadata = new HashMap<>(metadata);\n\t}\n\n\tpublic static Builder create(String name) {\n\t\treturn Application.builder().name(name);\n\t}\n\n\tpublic Map<String, String> getMetadata() {\n\t\treturn Collections.unmodifiableMap(metadata);\n\t}\n\n\tpublic static class Builder {\n\n\t\t// Will be generated by lombok\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/java/de/codecentric/boot/admin/client/registration/ApplicationFactory.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.registration;\n\n/**\n * Classes implementing this interface are responsible for creating an {@link Application}\n * class which is used to register at the admin server.\n *\n * @author Johannes Edmeier\n */\npublic interface ApplicationFactory {\n\n\t/**\n\t * @return {@link Application} instance;\n\t */\n\tApplication createApplication();\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/java/de/codecentric/boot/admin/client/registration/ApplicationRegistrator.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.registration;\n\n/**\n * Interface for client application registration at spring-boot-admin-server\n */\npublic interface ApplicationRegistrator {\n\n\t/**\n\t * Registers the client application at spring-boot-admin-server.\n\t * @return true if successful registration on at least one admin server\n\t */\n\tboolean register();\n\n\t/**\n\t * Tries to deregister currently registered application\n\t */\n\tvoid deregister();\n\n\t/**\n\t * @return the id of this client as given by the admin server. Returns null if the\n\t * client has not registered against the admin server yet.\n\t */\n\tString getRegisteredId();\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/java/de/codecentric/boot/admin/client/registration/CloudFoundryApplicationFactory.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.registration;\n\nimport org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;\nimport org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;\nimport org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;\nimport org.springframework.boot.web.server.autoconfigure.ServerProperties;\nimport org.springframework.util.StringUtils;\n\nimport de.codecentric.boot.admin.client.config.CloudFoundryApplicationProperties;\nimport de.codecentric.boot.admin.client.config.InstanceProperties;\nimport de.codecentric.boot.admin.client.registration.metadata.MetadataContributor;\n\npublic class CloudFoundryApplicationFactory extends DefaultApplicationFactory {\n\n\tprivate final CloudFoundryApplicationProperties cfApplicationProperties;\n\n\tprivate final InstanceProperties instance;\n\n\tpublic CloudFoundryApplicationFactory(InstanceProperties instance, ManagementServerProperties management,\n\t\t\tServerProperties server, PathMappedEndpoints pathMappedEndpoints, WebEndpointProperties webEndpoint,\n\t\t\tMetadataContributor metadataContributor, CloudFoundryApplicationProperties cfApplicationProperties) {\n\t\tsuper(instance, management, server, pathMappedEndpoints, webEndpoint, metadataContributor);\n\t\tthis.cfApplicationProperties = cfApplicationProperties;\n\t\tthis.instance = instance;\n\t}\n\n\t@Override\n\tprotected String getServiceBaseUrl() {\n\t\tString baseUrl = this.instance.getServiceBaseUrl();\n\t\tif (StringUtils.hasText(baseUrl)) {\n\t\t\treturn baseUrl;\n\t\t}\n\n\t\tif (this.cfApplicationProperties.getUris().isEmpty()) {\n\t\t\treturn super.getServiceBaseUrl();\n\t\t}\n\n\t\treturn \"http://\" + this.cfApplicationProperties.getUris().get(0);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/java/de/codecentric/boot/admin/client/registration/DefaultApplicationFactory.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.registration;\n\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\nimport org.jspecify.annotations.Nullable;\nimport org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;\nimport org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;\nimport org.springframework.boot.actuate.endpoint.EndpointId;\nimport org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;\nimport org.springframework.boot.web.server.Ssl;\nimport org.springframework.boot.web.server.autoconfigure.ServerProperties;\nimport org.springframework.boot.web.server.context.WebServerInitializedEvent;\nimport org.springframework.context.event.EventListener;\nimport org.springframework.util.StringUtils;\nimport org.springframework.web.util.UriComponentsBuilder;\n\nimport de.codecentric.boot.admin.client.config.InstanceProperties;\nimport de.codecentric.boot.admin.client.registration.metadata.MetadataContributor;\n\n/**\n * Default implementation for creating the {@link Application} instance which gets\n * registered at the admin server.\n *\n * @author Johannes Edmeier\n * @author Rene Felgenträger\n */\npublic class DefaultApplicationFactory implements ApplicationFactory {\n\n\tprivate final InstanceProperties instance;\n\n\tprivate final ServerProperties server;\n\n\tprivate final ManagementServerProperties management;\n\n\tprivate final PathMappedEndpoints pathMappedEndpoints;\n\n\tprivate final WebEndpointProperties webEndpoint;\n\n\tprivate final MetadataContributor metadataContributor;\n\n\t@Nullable private Integer localServerPort;\n\n\t@Nullable private Integer localManagementPort;\n\n\tpublic DefaultApplicationFactory(InstanceProperties instance, ManagementServerProperties management,\n\t\t\tServerProperties server, PathMappedEndpoints pathMappedEndpoints, WebEndpointProperties webEndpoint,\n\t\t\tMetadataContributor metadataContributor) {\n\t\tthis.instance = instance;\n\t\tthis.management = management;\n\t\tthis.server = server;\n\t\tthis.pathMappedEndpoints = pathMappedEndpoints;\n\t\tthis.webEndpoint = webEndpoint;\n\t\tthis.metadataContributor = metadataContributor;\n\t}\n\n\t@Override\n\tpublic Application createApplication() {\n\t\treturn Application.create(getName())\n\t\t\t.healthUrl(getHealthUrl())\n\t\t\t.managementUrl(getManagementUrl())\n\t\t\t.serviceUrl(getServiceUrl())\n\t\t\t.metadata(getMetadata())\n\t\t\t.build();\n\t}\n\n\tprotected String getName() {\n\t\treturn this.instance.getName();\n\t}\n\n\tprotected String getServiceUrl() {\n\t\tif (this.instance.getServiceUrl() != null) {\n\t\t\treturn this.instance.getServiceUrl();\n\t\t}\n\n\t\treturn UriComponentsBuilder.fromUriString(getServiceBaseUrl()).path(getServicePath()).toUriString();\n\t}\n\n\tprotected String getServiceBaseUrl() {\n\t\tString baseUrl = this.instance.getServiceBaseUrl();\n\n\t\tif (StringUtils.hasText(baseUrl)) {\n\t\t\treturn baseUrl;\n\t\t}\n\n\t\treturn UriComponentsBuilder.newInstance()\n\t\t\t.scheme(getScheme(this.server.getSsl()))\n\t\t\t.host(getServiceHost())\n\t\t\t.port(getLocalServerPort())\n\t\t\t.toUriString();\n\t}\n\n\tprotected String getServicePath() {\n\t\tString path = this.instance.getServicePath();\n\n\t\tif (StringUtils.hasText(path)) {\n\t\t\treturn path;\n\t\t}\n\n\t\treturn \"/\";\n\t}\n\n\tprotected String getManagementUrl() {\n\t\tif (this.instance.getManagementUrl() != null) {\n\t\t\treturn this.instance.getManagementUrl();\n\t\t}\n\n\t\treturn UriComponentsBuilder.fromUriString(getManagementBaseUrl())\n\t\t\t.path(\"/\")\n\t\t\t.path(getEndpointsWebPath())\n\t\t\t.toUriString();\n\t}\n\n\tprotected String getManagementBaseUrl() {\n\t\tString baseUrl = this.instance.getManagementBaseUrl();\n\n\t\tif (StringUtils.hasText(baseUrl)) {\n\t\t\treturn baseUrl;\n\t\t}\n\n\t\tif (isManagementPortEqual()) {\n\t\t\treturn this.getServiceUrl();\n\t\t}\n\n\t\tSsl ssl = (this.management.getSsl() != null) ? this.management.getSsl() : this.server.getSsl();\n\t\treturn UriComponentsBuilder.newInstance()\n\t\t\t.scheme(getScheme(ssl))\n\t\t\t.host(getManagementHost())\n\t\t\t.port(getLocalManagementPort())\n\t\t\t.toUriString();\n\t}\n\n\tprotected boolean isManagementPortEqual() {\n\t\treturn this.localManagementPort == null || this.localManagementPort.equals(this.localServerPort);\n\t}\n\n\tprotected String getEndpointsWebPath() {\n\t\treturn this.webEndpoint.getBasePath();\n\t}\n\n\tprotected String getHealthUrl() {\n\t\tif (this.instance.getHealthUrl() != null) {\n\t\t\treturn this.instance.getHealthUrl();\n\t\t}\n\t\treturn UriComponentsBuilder.fromUriString(getManagementBaseUrl())\n\t\t\t.path(\"/\")\n\t\t\t.path(getHealthEndpointPath())\n\t\t\t.toUriString();\n\t}\n\n\tprotected Map<String, String> getMetadata() {\n\t\tMap<String, String> metadata = new LinkedHashMap<>();\n\t\tmetadata.putAll(this.metadataContributor.getMetadata());\n\t\tmetadata.putAll(this.instance.getMetadata());\n\t\treturn metadata;\n\t}\n\n\tprotected String getServiceHost() {\n\t\tInetAddress address = this.server.getAddress();\n\t\tif (address == null) {\n\t\t\taddress = getLocalHost();\n\t\t}\n\t\treturn getHost(address);\n\t}\n\n\tprotected String getManagementHost() {\n\t\tInetAddress address = this.management.getAddress();\n\t\tif (address != null) {\n\t\t\treturn getHost(address);\n\t\t}\n\t\treturn getServiceHost();\n\t}\n\n\tprotected InetAddress getLocalHost() {\n\t\ttry {\n\t\t\treturn InetAddress.getLocalHost();\n\t\t}\n\t\tcatch (UnknownHostException ex) {\n\t\t\tthrow new IllegalArgumentException(ex.getMessage(), ex);\n\t\t}\n\t}\n\n\tprotected Integer getLocalServerPort() {\n\t\tif (this.localServerPort == null) {\n\t\t\tthrow new IllegalStateException(\n\t\t\t\t\t\"couldn't determine local port. Please set spring.boot.admin.client.instance.service-base-url.\");\n\t\t}\n\t\treturn this.localServerPort;\n\t}\n\n\tprotected Integer getLocalManagementPort() {\n\t\tif (this.localManagementPort == null) {\n\t\t\treturn this.getLocalServerPort();\n\t\t}\n\t\treturn this.localManagementPort;\n\t}\n\n\tprotected String getHealthEndpointPath() {\n\t\tString health = this.pathMappedEndpoints.getPath(EndpointId.of(\"health\"));\n\t\tif (StringUtils.hasText(health)) {\n\t\t\treturn health;\n\t\t}\n\t\tString status = this.pathMappedEndpoints.getPath(EndpointId.of(\"status\"));\n\t\tif (StringUtils.hasText(status)) {\n\t\t\treturn status;\n\t\t}\n\t\tthrow new IllegalStateException(\"Either health or status endpoint must be enabled!\");\n\t}\n\n\tprotected String getScheme(@Nullable Ssl ssl) {\n\t\treturn ((ssl != null) && ssl.isEnabled()) ? \"https\" : \"http\";\n\t}\n\n\tprotected String getHost(InetAddress address) {\n\t\tif (this.instance.isPreferIp()) {\n\t\t\treturn address.getHostAddress();\n\t\t}\n\n\t\treturn switch (this.instance.getServiceHostType()) {\n\t\t\tcase IP -> address.getHostAddress();\n\t\t\tcase HOST_NAME -> address.getHostName();\n\t\t\tdefault -> address.getCanonicalHostName();\n\t\t};\n\t}\n\n\t@EventListener\n\tpublic void onWebServerInitialized(WebServerInitializedEvent event) {\n\t\tString name = event.getApplicationContext().getServerNamespace();\n\t\tif (\"server\".equals(name) || !StringUtils.hasText(name)) {\n\t\t\tthis.localServerPort = event.getWebServer().getPort();\n\t\t}\n\t\telse if (\"management\".equals(name)) {\n\t\t\tthis.localManagementPort = event.getWebServer().getPort();\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/java/de/codecentric/boot/admin/client/registration/DefaultApplicationRegistrator.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.registration;\n\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.concurrent.atomic.LongAdder;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\npublic class DefaultApplicationRegistrator implements ApplicationRegistrator {\n\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(DefaultApplicationRegistrator.class);\n\n\tprivate final ConcurrentHashMap<String, LongAdder> attempts = new ConcurrentHashMap<>();\n\n\tprivate final AtomicReference<String> registeredId = new AtomicReference<>();\n\n\tprivate final ApplicationFactory applicationFactory;\n\n\tprivate final String[] adminUrls;\n\n\tprivate final boolean registerOnce;\n\n\tprivate final RegistrationClient registrationClient;\n\n\tpublic DefaultApplicationRegistrator(ApplicationFactory applicationFactory, RegistrationClient registrationClient,\n\t\t\tString[] adminUrls, boolean registerOnce) {\n\t\tthis.applicationFactory = applicationFactory;\n\t\tthis.adminUrls = adminUrls;\n\t\tthis.registerOnce = registerOnce;\n\t\tthis.registrationClient = registrationClient;\n\t}\n\n\t/**\n\t * Registers the client application at spring-boot-admin-server.\n\t * @return true if successful registration on at least one admin server\n\t */\n\t@Override\n\tpublic boolean register() {\n\t\tApplication application = this.applicationFactory.createApplication();\n\t\tboolean isRegistrationSuccessful = false;\n\n\t\tfor (String adminUrl : this.adminUrls) {\n\t\t\tLongAdder attempt = this.attempts.computeIfAbsent(adminUrl, (k) -> new LongAdder());\n\t\t\tboolean successful = register(application, adminUrl, attempt.intValue() == 0);\n\n\t\t\tif (!successful) {\n\t\t\t\tattempt.increment();\n\t\t\t}\n\t\t\telse {\n\t\t\t\tattempt.reset();\n\t\t\t\tisRegistrationSuccessful = true;\n\t\t\t\tif (this.registerOnce) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn isRegistrationSuccessful;\n\t}\n\n\tprotected boolean register(Application application, String adminUrl, boolean firstAttempt) {\n\t\ttry {\n\t\t\tString id = this.registrationClient.register(adminUrl, application);\n\t\t\tif (this.registeredId.compareAndSet(null, id)) {\n\t\t\t\tLOGGER.info(\"Application registered itself as {}\", id);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tLOGGER.debug(\"Application refreshed itself as {}\", id);\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\tcatch (Exception ex) {\n\t\t\tif (firstAttempt) {\n\t\t\t\tLOGGER.warn(\n\t\t\t\t\t\t\"Failed to register application as {} at spring-boot-admin ({}): {}. Further attempts are logged on DEBUG level\",\n\t\t\t\t\t\tapplication, this.adminUrls, ex.getMessage(), ex);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tLOGGER.debug(\"Failed to register application as {} at spring-boot-admin ({}): {}\", application,\n\t\t\t\t\t\tthis.adminUrls, ex.getMessage(), ex);\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t@Override\n\tpublic void deregister() {\n\t\tString id = this.registeredId.get();\n\t\tif (id == null) {\n\t\t\treturn;\n\t\t}\n\n\t\tfor (String adminUrl : this.adminUrls) {\n\t\t\ttry {\n\t\t\t\tthis.registrationClient.deregister(adminUrl, id);\n\t\t\t\tthis.registeredId.compareAndSet(id, null);\n\t\t\t\tif (this.registerOnce) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tcatch (Exception ex) {\n\t\t\t\tLOGGER.warn(\"Failed to deregister application (id={}) at spring-boot-admin ({}): {}\", id, adminUrl,\n\t\t\t\t\t\tex.getMessage());\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic String getRegisteredId() {\n\t\treturn this.registeredId.get();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/java/de/codecentric/boot/admin/client/registration/ReactiveApplicationFactory.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.registration;\n\nimport org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;\nimport org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;\nimport org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;\nimport org.springframework.boot.web.server.Ssl;\nimport org.springframework.boot.web.server.autoconfigure.ServerProperties;\nimport org.springframework.boot.webflux.autoconfigure.WebFluxProperties;\nimport org.springframework.util.StringUtils;\nimport org.springframework.web.util.UriComponentsBuilder;\n\nimport de.codecentric.boot.admin.client.config.InstanceProperties;\nimport de.codecentric.boot.admin.client.registration.metadata.MetadataContributor;\n\npublic class ReactiveApplicationFactory extends DefaultApplicationFactory {\n\n\tprivate final ManagementServerProperties management;\n\n\tprivate final ServerProperties server;\n\n\tprivate final WebFluxProperties webflux;\n\n\tprivate final InstanceProperties instance;\n\n\tpublic ReactiveApplicationFactory(InstanceProperties instance, ManagementServerProperties management,\n\t\t\tServerProperties server, PathMappedEndpoints pathMappedEndpoints, WebEndpointProperties webEndpoint,\n\t\t\tMetadataContributor metadataContributor, WebFluxProperties webFluxProperties) {\n\t\tsuper(instance, management, server, pathMappedEndpoints, webEndpoint, metadataContributor);\n\t\tthis.management = management;\n\t\tthis.server = server;\n\t\tthis.webflux = webFluxProperties;\n\t\tthis.instance = instance;\n\t}\n\n\t@Override\n\tprotected String getServiceUrl() {\n\t\tif (instance.getServiceUrl() != null) {\n\t\t\treturn instance.getServiceUrl();\n\t\t}\n\n\t\treturn UriComponentsBuilder.fromUriString(getServiceBaseUrl())\n\t\t\t.path(getServicePath())\n\t\t\t.path(getWebfluxBasePath())\n\t\t\t.toUriString();\n\t}\n\n\t@Override\n\tprotected String getManagementBaseUrl() {\n\t\tString baseUrl = this.instance.getManagementBaseUrl();\n\n\t\tif (StringUtils.hasText(baseUrl)) {\n\t\t\treturn baseUrl;\n\t\t}\n\n\t\tif (isManagementPortEqual()) {\n\t\t\treturn this.getServiceUrl();\n\t\t}\n\n\t\tSsl ssl = (this.management.getSsl() != null) ? this.management.getSsl() : this.server.getSsl();\n\t\treturn UriComponentsBuilder.newInstance()\n\t\t\t.scheme(getScheme(ssl))\n\t\t\t.host(getManagementHost())\n\t\t\t.port(getLocalManagementPort())\n\t\t\t.path(getManagementContextPath())\n\t\t\t.toUriString();\n\t}\n\n\tprotected String getManagementContextPath() {\n\t\treturn management.getBasePath();\n\t}\n\n\tprotected String getWebfluxBasePath() {\n\t\treturn webflux.getBasePath();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/java/de/codecentric/boot/admin/client/registration/RegistrationApplicationListener.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.registration;\n\nimport java.time.Duration;\nimport java.util.concurrent.ScheduledFuture;\n\nimport org.jspecify.annotations.Nullable;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.DisposableBean;\nimport org.springframework.beans.factory.InitializingBean;\nimport org.springframework.boot.context.event.ApplicationReadyEvent;\nimport org.springframework.context.event.ContextClosedEvent;\nimport org.springframework.context.event.EventListener;\nimport org.springframework.core.Ordered;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;\n\n/**\n * Listener responsible for starting and stopping the registration task when the\n * application is ready.\n *\n * @author Johannes Edmeier\n */\npublic class RegistrationApplicationListener implements InitializingBean, DisposableBean {\n\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(RegistrationApplicationListener.class);\n\n\tprivate final ApplicationRegistrator registrator;\n\n\tprivate final ThreadPoolTaskScheduler taskScheduler;\n\n\tprivate boolean autoDeregister = false;\n\n\tprivate boolean autoRegister = true;\n\n\tprivate Duration registerPeriod = Duration.ofSeconds(10);\n\n\t@Nullable private volatile ScheduledFuture<?> scheduledTask;\n\n\tpublic RegistrationApplicationListener(ApplicationRegistrator registrator) {\n\t\tthis(registrator, registrationTaskScheduler());\n\t}\n\n\tRegistrationApplicationListener(ApplicationRegistrator registrator, ThreadPoolTaskScheduler taskScheduler) {\n\t\tthis.registrator = registrator;\n\t\tthis.taskScheduler = taskScheduler;\n\t}\n\n\tprivate static ThreadPoolTaskScheduler registrationTaskScheduler() {\n\t\tThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();\n\t\ttaskScheduler.setPoolSize(1);\n\t\ttaskScheduler.setRemoveOnCancelPolicy(true);\n\t\ttaskScheduler.setThreadNamePrefix(\"registrationTask\");\n\t\treturn taskScheduler;\n\t}\n\n\t@EventListener\n\t@Order(Ordered.LOWEST_PRECEDENCE)\n\tpublic void onApplicationReady(ApplicationReadyEvent event) {\n\t\tif (autoRegister) {\n\t\t\tstartRegisterTask();\n\t\t}\n\t}\n\n\t@EventListener\n\t@Order(Ordered.LOWEST_PRECEDENCE)\n\tpublic void onClosedContext(ContextClosedEvent event) {\n\t\tif (event.getApplicationContext().getParent() == null\n\t\t\t\t|| \"bootstrap\".equals(event.getApplicationContext().getParent().getId())) {\n\t\t\tstopRegisterTask();\n\n\t\t\tif (autoDeregister) {\n\t\t\t\tregistrator.deregister();\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic void startRegisterTask() {\n\t\tif (scheduledTask != null && !scheduledTask.isDone()) {\n\t\t\treturn;\n\t\t}\n\n\t\tscheduledTask = taskScheduler.scheduleAtFixedRate(registrator::register, registerPeriod);\n\t\tLOGGER.debug(\"Scheduled registration task for every {}ms\", registerPeriod.toMillis());\n\t}\n\n\tpublic void stopRegisterTask() {\n\t\tif (scheduledTask != null && !scheduledTask.isDone()) {\n\t\t\tscheduledTask.cancel(true);\n\t\t\tLOGGER.debug(\"Canceled registration task\");\n\t\t}\n\t}\n\n\tpublic void setAutoDeregister(boolean autoDeregister) {\n\t\tthis.autoDeregister = autoDeregister;\n\t}\n\n\tpublic void setAutoRegister(boolean autoRegister) {\n\t\tthis.autoRegister = autoRegister;\n\t}\n\n\tpublic void setRegisterPeriod(Duration registerPeriod) {\n\t\tthis.registerPeriod = registerPeriod;\n\t}\n\n\t@Override\n\tpublic void afterPropertiesSet() {\n\t\ttaskScheduler.afterPropertiesSet();\n\t}\n\n\t@Override\n\tpublic void destroy() {\n\t\ttaskScheduler.destroy();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/java/de/codecentric/boot/admin/client/registration/RegistrationClient.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.registration;\n\npublic interface RegistrationClient {\n\n\tString register(String adminUrl, Application self);\n\n\tvoid deregister(String adminUrl, String id);\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/java/de/codecentric/boot/admin/client/registration/RestClientRegistrationClient.java",
    "content": "/*\n * Copyright 2014-2024 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.registration;\n\nimport java.util.Collections;\nimport java.util.Map;\n\nimport org.springframework.core.ParameterizedTypeReference;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.MediaType;\nimport org.springframework.web.client.RestClient;\n\npublic class RestClientRegistrationClient implements RegistrationClient {\n\n\tprivate static final ParameterizedTypeReference<Map<String, Object>> RESPONSE_TYPE = new ParameterizedTypeReference<>() {\n\t};\n\n\tprivate final RestClient restClient;\n\n\tpublic RestClientRegistrationClient(RestClient restClient) {\n\t\tthis.restClient = restClient;\n\t}\n\n\t@Override\n\tpublic String register(String adminUrl, Application application) {\n\t\tMap<String, Object> response = this.restClient.post()\n\t\t\t.uri(adminUrl)\n\t\t\t.headers(this::setRequestHeaders)\n\t\t\t.body(application)\n\t\t\t.retrieve()\n\t\t\t.body(RESPONSE_TYPE);\n\t\treturn response.get(\"id\").toString();\n\t}\n\n\t@Override\n\tpublic void deregister(String adminUrl, String id) {\n\t\tthis.restClient.delete().uri(adminUrl + '/' + id).retrieve().toBodilessEntity();\n\t}\n\n\tprotected void setRequestHeaders(HttpHeaders headers) {\n\t\theaders.setContentType(MediaType.APPLICATION_JSON);\n\t\theaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/java/de/codecentric/boot/admin/client/registration/ServletApplicationFactory.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.registration;\n\nimport jakarta.servlet.ServletContext;\nimport org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;\nimport org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;\nimport org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;\nimport org.springframework.boot.web.server.Ssl;\nimport org.springframework.boot.web.server.autoconfigure.ServerProperties;\nimport org.springframework.boot.webmvc.autoconfigure.DispatcherServletPath;\nimport org.springframework.util.StringUtils;\nimport org.springframework.web.util.UriComponentsBuilder;\n\nimport de.codecentric.boot.admin.client.config.InstanceProperties;\nimport de.codecentric.boot.admin.client.registration.metadata.MetadataContributor;\n\npublic class ServletApplicationFactory extends DefaultApplicationFactory {\n\n\tprivate final ServletContext servletContext;\n\n\tprivate final ServerProperties server;\n\n\tprivate final ManagementServerProperties management;\n\n\tprivate final InstanceProperties instance;\n\n\tprivate final DispatcherServletPath dispatcherServletPath;\n\n\tpublic ServletApplicationFactory(InstanceProperties instance, ManagementServerProperties management,\n\t\t\tServerProperties server, ServletContext servletContext, PathMappedEndpoints pathMappedEndpoints,\n\t\t\tWebEndpointProperties webEndpoint, MetadataContributor metadataContributor,\n\t\t\tDispatcherServletPath dispatcherServletPath) {\n\t\tsuper(instance, management, server, pathMappedEndpoints, webEndpoint, metadataContributor);\n\t\tthis.servletContext = servletContext;\n\t\tthis.server = server;\n\t\tthis.management = management;\n\t\tthis.instance = instance;\n\t\tthis.dispatcherServletPath = dispatcherServletPath;\n\t}\n\n\t@Override\n\tprotected String getServiceUrl() {\n\t\tif (instance.getServiceUrl() != null) {\n\t\t\treturn instance.getServiceUrl();\n\t\t}\n\n\t\treturn UriComponentsBuilder.fromUriString(getServiceBaseUrl())\n\t\t\t.path(getServicePath())\n\t\t\t.path(getServerContextPath())\n\t\t\t.toUriString();\n\t}\n\n\t@Override\n\tprotected String getManagementBaseUrl() {\n\t\tString baseUrl = instance.getManagementBaseUrl();\n\n\t\tif (StringUtils.hasText(baseUrl)) {\n\t\t\treturn baseUrl;\n\t\t}\n\n\t\tif (isManagementPortEqual()) {\n\t\t\treturn UriComponentsBuilder.fromUriString(getServiceUrl())\n\t\t\t\t.path(\"/\")\n\t\t\t\t.path(getDispatcherServletPrefix())\n\t\t\t\t.path(getManagementContextPath())\n\t\t\t\t.toUriString();\n\t\t}\n\n\t\tSsl ssl = (management.getSsl() != null) ? management.getSsl() : server.getSsl();\n\t\treturn UriComponentsBuilder.newInstance()\n\t\t\t.scheme(getScheme(ssl))\n\t\t\t.host(getManagementHost())\n\t\t\t.port(getLocalManagementPort())\n\t\t\t.path(getManagementContextPath())\n\t\t\t.toUriString();\n\t}\n\n\tprotected String getManagementContextPath() {\n\t\treturn management.getBasePath();\n\t}\n\n\tprotected String getServerContextPath() {\n\t\treturn servletContext.getContextPath();\n\t}\n\n\tprotected String getDispatcherServletPrefix() {\n\t\treturn this.dispatcherServletPath.getPrefix();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/java/de/codecentric/boot/admin/client/registration/metadata/CloudFoundryMetadataContributor.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.registration.metadata;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.springframework.util.StringUtils;\n\nimport de.codecentric.boot.admin.client.config.CloudFoundryApplicationProperties;\n\npublic class CloudFoundryMetadataContributor implements MetadataContributor {\n\n\tprivate final CloudFoundryApplicationProperties cfApplicationProperties;\n\n\tpublic CloudFoundryMetadataContributor(CloudFoundryApplicationProperties cfApplicationProperties) {\n\t\tthis.cfApplicationProperties = cfApplicationProperties;\n\t}\n\n\t@Override\n\tpublic Map<String, String> getMetadata() {\n\t\tif (StringUtils.hasText(this.cfApplicationProperties.getApplicationId())\n\t\t\t\t&& StringUtils.hasText(this.cfApplicationProperties.getInstanceIndex())) {\n\t\t\tMap<String, String> map = new HashMap<>();\n\t\t\tmap.put(\"applicationId\", this.cfApplicationProperties.getApplicationId());\n\t\t\tmap.put(\"instanceId\", this.cfApplicationProperties.getInstanceIndex());\n\t\t\treturn map;\n\t\t}\n\t\treturn Collections.emptyMap();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/java/de/codecentric/boot/admin/client/registration/metadata/CompositeMetadataContributor.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.registration.metadata;\n\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class CompositeMetadataContributor implements MetadataContributor {\n\n\tprivate final List<MetadataContributor> delegates;\n\n\tpublic CompositeMetadataContributor(List<MetadataContributor> delegates) {\n\t\tthis.delegates = delegates;\n\t}\n\n\t@Override\n\tpublic Map<String, String> getMetadata() {\n\t\tMap<String, String> metadata = new LinkedHashMap<>();\n\t\tdelegates.forEach((delegate) -> metadata.putAll(delegate.getMetadata()));\n\t\treturn metadata;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/java/de/codecentric/boot/admin/client/registration/metadata/MetadataContributor.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.registration.metadata;\n\nimport java.util.Map;\n\n@FunctionalInterface\npublic interface MetadataContributor {\n\n\tMap<String, String> getMetadata();\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/java/de/codecentric/boot/admin/client/registration/metadata/StartupDateMetadataContributor.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.registration.metadata;\n\nimport java.time.OffsetDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.util.Map;\n\nimport static java.util.Collections.singletonMap;\n\npublic class StartupDateMetadataContributor implements MetadataContributor {\n\n\tprivate final OffsetDateTime timestamp = OffsetDateTime.now();\n\n\t@Override\n\tpublic Map<String, String> getMetadata() {\n\t\treturn singletonMap(\"startup\", this.timestamp.format(DateTimeFormatter.ISO_DATE_TIME));\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/java/de/codecentric/boot/admin/client/registration/metadata/package-info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n@NullMarked\npackage de.codecentric.boot.admin.client.registration.metadata;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/java/de/codecentric/boot/admin/client/registration/package-info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n@NullMarked\npackage de.codecentric.boot.admin.client.registration;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/resources/META-INF/additional-spring-configuration-metadata.json",
    "content": "{\"groups\": [\n\n],\"properties\": [\n  {\n    \"name\": \"spring.boot.admin.client.enabled\",\n    \"type\": \"java.lang.Boolean\",\n    \"description\": \"Enable Spring Admin Client.\",\n    \"defaultValue\": \"true\"\n  }\n]}\n"
  },
  {
    "path": "spring-boot-admin-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration\nde.codecentric.boot.admin.client.config.SpringBootAdminClientCloudFoundryAutoConfiguration\nde.codecentric.boot.admin.client.config.SpringNativeClientAutoConfiguration\n"
  },
  {
    "path": "spring-boot-admin-client/src/test/java/de/codecentric/boot/admin/client/AbstractClientApplicationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client;\n\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\nimport java.util.Arrays;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.stream.Stream;\n\nimport com.github.tomakehurst.wiremock.WireMockServer;\nimport com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;\nimport com.github.tomakehurst.wiremock.common.ConsoleNotifier;\nimport com.github.tomakehurst.wiremock.matching.RequestPatternBuilder;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.SpringBootConfiguration;\nimport org.springframework.boot.WebApplicationType;\nimport org.springframework.boot.autoconfigure.EnableAutoConfiguration;\nimport org.springframework.boot.context.event.ApplicationReadyEvent;\nimport org.springframework.context.ConfigurableApplicationContext;\nimport org.springframework.context.event.EventListener;\n\nimport de.codecentric.boot.admin.client.registration.ApplicationRegistrator;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.created;\nimport static com.github.tomakehurst.wiremock.client.WireMock.equalTo;\nimport static com.github.tomakehurst.wiremock.client.WireMock.matching;\nimport static com.github.tomakehurst.wiremock.client.WireMock.matchingJsonPath;\nimport static com.github.tomakehurst.wiremock.client.WireMock.post;\nimport static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;\nimport static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;\nimport static org.awaitility.Awaitility.await;\n\npublic abstract class AbstractClientApplicationTest {\n\n\tprivate final WireMockServer wireMock = new WireMockServer(\n\t\t\toptions().dynamicPort().notifier(new ConsoleNotifier(true)));\n\n\tprivate SpringApplication application;\n\n\tprivate ConfigurableApplicationContext instance;\n\n\tprivate static final CountDownLatch cdl = new CountDownLatch(1);\n\n\tprotected void setUp(WebApplicationType type) {\n\t\tsetUpWiremock();\n\t\tsetUpApplication(type);\n\t}\n\n\tprivate void setUpWiremock() {\n\t\twireMock.start();\n\t\tResponseDefinitionBuilder response = created().withHeader(\"Content-Type\", \"application/json\")\n\t\t\t.withHeader(\"Connection\", \"close\")\n\t\t\t.withHeader(\"Location\", wireMock.url(\"/instances/abcdef\"))\n\t\t\t.withBody(\"{ \\\"id\\\" : \\\"abcdef\\\" }\");\n\t\twireMock.stubFor(post(urlEqualTo(\"/instances\")).willReturn(response));\n\t}\n\n\tprivate void setUpApplication(WebApplicationType type) {\n\t\tapplication = new SpringApplication(TestClientApplication.class);\n\t\tapplication.setWebApplicationType(type);\n\t}\n\n\tprivate void setUpApplicationContext(String... additionalArgs) {\n\t\tStream<String> defaultArgs = Stream.of(\"--spring.application.name=Test-Client\", \"--server.port=0\",\n\t\t\t\t\"--management.endpoints.web.base-path=/mgmt\", \"--endpoints.health.enabled=true\",\n\t\t\t\t\"--spring.boot.admin.client.url=\" + wireMock.url(\"/\"));\n\n\t\tString[] args = Stream.concat(defaultArgs, Arrays.stream(additionalArgs)).toArray(String[]::new);\n\n\t\tthis.instance = application.run(args);\n\t}\n\n\t@AfterEach\n\tvoid tearDown() {\n\t\twireMock.stop();\n\t\tif (instance != null) {\n\t\t\tinstance.close();\n\t\t}\n\t}\n\n\t@Test\n\tpublic void test_context() throws InterruptedException, UnknownHostException {\n\t\tsetUpApplicationContext();\n\n\t\tString hostName = InetAddress.getLocalHost().getCanonicalHostName();\n\t\tString serviceHost = \"http://\" + hostName + \":\" + getServerPort();\n\t\tString managementHost = \"http://\" + hostName + \":\" + getManagementPort();\n\t\tRequestPatternBuilder request = postRequestedFor(urlEqualTo(\"/instances\"));\n\t\trequest.withHeader(\"Content-Type\", equalTo(\"application/json\"))\n\t\t\t.withRequestBody(matchingJsonPath(\"$.name\", equalTo(\"Test-Client\")))\n\t\t\t.withRequestBody(matchingJsonPath(\"$.healthUrl\", equalTo(managementHost + \"/mgmt/health\")))\n\t\t\t.withRequestBody(matchingJsonPath(\"$.managementUrl\", equalTo(managementHost + \"/mgmt\")))\n\t\t\t.withRequestBody(matchingJsonPath(\"$.serviceUrl\", equalTo(serviceHost + \"/\")))\n\t\t\t.withRequestBody(matchingJsonPath(\"$.metadata.startup\", matching(\".+\")));\n\n\t\tcdl.await();\n\t\tawait().untilAsserted(() -> wireMock.verify(request));\n\t}\n\n\t@Test\n\tpublic void test_context_with_snake_case() throws InterruptedException, UnknownHostException {\n\t\tsetUpApplicationContext(\"--spring.jackson.property-naming-strategy=SNAKE_CASE\");\n\n\t\tString hostName = InetAddress.getLocalHost().getCanonicalHostName();\n\t\tString serviceHost = \"http://\" + hostName + \":\" + getServerPort();\n\t\tString managementHost = \"http://\" + hostName + \":\" + getManagementPort();\n\t\tRequestPatternBuilder request = postRequestedFor(urlEqualTo(\"/instances\"));\n\t\trequest.withHeader(\"Content-Type\", equalTo(\"application/json\"))\n\t\t\t.withRequestBody(matchingJsonPath(\"$.name\", equalTo(\"Test-Client\")))\n\t\t\t.withRequestBody(matchingJsonPath(\"$.health_url\", equalTo(managementHost + \"/mgmt/health\")))\n\t\t\t.withRequestBody(matchingJsonPath(\"$.management_url\", equalTo(managementHost + \"/mgmt\")))\n\t\t\t.withRequestBody(matchingJsonPath(\"$.service_url\", equalTo(serviceHost + \"/\")))\n\t\t\t.withRequestBody(matchingJsonPath(\"$.metadata.startup\", matching(\".+\")));\n\n\t\tcdl.await();\n\t\tawait().untilAsserted(() -> wireMock.verify(request));\n\t}\n\n\tprivate int getServerPort() {\n\t\treturn instance.getEnvironment().getProperty(\"local.server.port\", Integer.class, 0);\n\t}\n\n\tprivate int getManagementPort() {\n\t\treturn instance.getEnvironment().getProperty(\"local.management.port\", Integer.class, 0);\n\t}\n\n\t@SpringBootConfiguration\n\t@EnableAutoConfiguration\n\tpublic static class TestClientApplication {\n\n\t\t@Autowired\n\t\tprivate ApplicationRegistrator registrator;\n\n\t\t@EventListener\n\t\tpublic void ping(ApplicationReadyEvent ev) {\n\t\t\tnew Thread(() -> {\n\t\t\t\tawait().until(() -> registrator.getRegisteredId() != null);\n\t\t\t\tcdl.countDown();\n\t\t\t}).start();\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/test/java/de/codecentric/boot/admin/client/ClientReactiveApplicationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.springframework.boot.WebApplicationType;\n\nclass ClientReactiveApplicationTest extends AbstractClientApplicationTest {\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tsuper.setUp(WebApplicationType.REACTIVE);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/test/java/de/codecentric/boot/admin/client/ClientServletApplicationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.springframework.boot.WebApplicationType;\n\nclass ClientServletApplicationTest extends AbstractClientApplicationTest {\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tsuper.setUp(WebApplicationType.SERVLET);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/test/java/de/codecentric/boot/admin/client/config/ClientPropertiesTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.config;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.mock.env.MockEnvironment;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ClientPropertiesTest {\n\n\t@Test\n\tvoid should_default_autoDeregister_to_false() {\n\t\tMockEnvironment env = new MockEnvironment();\n\n\t\tClientProperties clientProperties = new ClientProperties();\n\t\tassertThat(clientProperties.isAutoDeregistration(env)).isFalse();\n\n\t\tclientProperties.setAutoDeregistration(false);\n\t\tassertThat(clientProperties.isAutoDeregistration(env)).isFalse();\n\n\t\tclientProperties.setAutoDeregistration(true);\n\t\tassertThat(clientProperties.isAutoDeregistration(env)).isTrue();\n\t}\n\n\t@Test\n\tvoid should_default_autoDeregister_to_true() {\n\t\tMockEnvironment env = new MockEnvironment();\n\t\tenv.setProperty(\"VCAP_APPLICATION\", \"\");\n\n\t\tClientProperties clientProperties = new ClientProperties();\n\t\tassertThat(clientProperties.isAutoDeregistration(env)).isTrue();\n\n\t\tclientProperties.setAutoDeregistration(false);\n\t\tassertThat(clientProperties.isAutoDeregistration(env)).isFalse();\n\n\t\tclientProperties.setAutoDeregistration(true);\n\t\tassertThat(clientProperties.isAutoDeregistration(env)).isTrue();\n\t}\n\n\t@Test\n\tvoid should_return_all_adminUrls() {\n\t\tClientProperties clientProperties = new ClientProperties();\n\t\tclientProperties.setApiPath(\"register\");\n\t\tclientProperties.setUrl(new String[] { \"http://first\", \"http://second\" });\n\n\t\tassertThat(clientProperties.getAdminUrl()).containsExactly(\"http://first/register\", \"http://second/register\");\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/test/java/de/codecentric/boot/admin/client/config/CloudFoundryApplicationPropertiesTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.config;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor;\nimport org.springframework.boot.context.properties.bind.Bindable;\nimport org.springframework.boot.context.properties.bind.Binder;\nimport org.springframework.boot.logging.DeferredLogs;\nimport org.springframework.mock.env.MockEnvironment;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass CloudFoundryApplicationPropertiesTest {\n\n\t@Test\n\tvoid bind() {\n\t\tString vcap = \"{\\\"application_users\\\":[],\" + \"\\\"application_id\\\":\\\"9958288f-9842-4ddc-93dd-1ea3c90634cd\\\",\"\n\t\t\t\t+ \"\\\"instance_id\\\":\\\"bb7935245adf3e650dfb7c58a06e9ece\\\",\"\n\t\t\t\t+ \"\\\"instance_index\\\":0,\\\"version\\\":\\\"3464e092-1c13-462e-a47c-807c30318a50\\\",\"\n\t\t\t\t+ \"\\\"name\\\":\\\"foo\\\",\\\"uris\\\":[\\\"foo.cfapps.io\\\"],\" + \"\\\"started_at\\\":\\\"2013-05-29 02:37:59 +0000\\\",\"\n\t\t\t\t+ \"\\\"started_at_timestamp\\\":1369795079,\" + \"\\\"host\\\":\\\"0.0.0.0\\\",\\\"port\\\":61034,\"\n\t\t\t\t+ \"\\\"limits\\\":{\\\"mem\\\":128,\\\"disk\\\":1024,\\\"fds\\\":16384},\"\n\t\t\t\t+ \"\\\"version\\\":\\\"3464e092-1c13-462e-a47c-807c30318a50\\\",\"\n\t\t\t\t+ \"\\\"name\\\":\\\"dsyerenv\\\",\\\"uris\\\":[\\\"dsyerenv.cfapps.io\\\"],\"\n\t\t\t\t+ \"\\\"users\\\":[],\\\"start\\\":\\\"2013-05-29 02:37:59 +0000\\\",\" + \"\\\"state_timestamp\\\":1369795079}\";\n\n\t\tMockEnvironment env = new MockEnvironment();\n\t\tenv.setProperty(\"VCAP_APPLICATION\", vcap);\n\t\tnew CloudFoundryVcapEnvironmentPostProcessor(new DeferredLogs()).postProcessEnvironment(env, null);\n\n\t\tCloudFoundryApplicationProperties cfProperties = Binder.get(env)\n\t\t\t.bind(\"vcap.application\", Bindable.of(CloudFoundryApplicationProperties.class))\n\t\t\t.get();\n\t\tassertThat(cfProperties.getApplicationId()).isEqualTo(\"9958288f-9842-4ddc-93dd-1ea3c90634cd\");\n\t\tassertThat(cfProperties.getInstanceIndex()).isEqualTo(\"0\");\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/test/java/de/codecentric/boot/admin/client/config/SpringBootAdminClientAutoConfigurationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.config;\n\nimport java.time.Duration;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;\nimport org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;\nimport org.springframework.boot.autoconfigure.AutoConfigurations;\nimport org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;\nimport org.springframework.boot.http.client.autoconfigure.HttpClientAutoConfiguration;\nimport org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration;\nimport org.springframework.boot.test.context.runner.ApplicationContextRunner;\nimport org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;\nimport org.springframework.boot.test.context.runner.WebApplicationContextRunner;\nimport org.springframework.boot.webflux.autoconfigure.WebFluxProperties;\nimport org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration;\nimport org.springframework.http.client.ClientHttpRequestFactory;\nimport org.springframework.test.util.ReflectionTestUtils;\nimport org.springframework.web.client.RestClient;\n\nimport de.codecentric.boot.admin.client.registration.ApplicationRegistrator;\nimport de.codecentric.boot.admin.client.registration.RegistrationClient;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass SpringBootAdminClientAutoConfigurationTest {\n\n\tprivate final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()\n\t\t.withConfiguration(AutoConfigurations.of(EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,\n\t\t\t\tDispatcherServletAutoConfiguration.class, HttpClientAutoConfiguration.class,\n\t\t\t\tRestClientAutoConfiguration.class, SpringBootAdminClientAutoConfiguration.class));\n\n\t@Test\n\tvoid not_active() {\n\t\tthis.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ApplicationRegistrator.class));\n\t}\n\n\t@Test\n\tvoid active() {\n\t\tthis.contextRunner.withPropertyValues(\"spring.boot.admin.client.url:http://localhost:8081\")\n\t\t\t.run((context) -> assertThat(context).hasSingleBean(ApplicationRegistrator.class));\n\t}\n\n\t@Test\n\tvoid disabled() {\n\t\tthis.contextRunner\n\t\t\t.withPropertyValues(\"spring.boot.admin.client.url:http://localhost:8081\",\n\t\t\t\t\t\"spring.boot.admin.client.enabled:false\")\n\t\t\t.run((context) -> assertThat(context).doesNotHaveBean(ApplicationRegistrator.class));\n\t}\n\n\t@Test\n\tvoid nonWebEnvironment() {\n\t\tApplicationContextRunner nonWebContextRunner = new ApplicationContextRunner()\n\t\t\t.withConfiguration(AutoConfigurations.of(SpringBootAdminClientAutoConfiguration.class));\n\n\t\tnonWebContextRunner.withPropertyValues(\"spring.boot.admin.client.url:http://localhost:8081\")\n\t\t\t.run((context) -> assertThat(context).doesNotHaveBean(ApplicationRegistrator.class));\n\t}\n\n\t@Test\n\tvoid reactiveEnvironment() {\n\t\tReactiveWebApplicationContextRunner reactiveContextRunner = new ReactiveWebApplicationContextRunner()\n\t\t\t.withConfiguration(AutoConfigurations.of(EndpointAutoConfiguration.class,\n\t\t\t\t\tWebEndpointAutoConfiguration.class, HttpClientAutoConfiguration.class,\n\t\t\t\t\tRestClientAutoConfiguration.class, SpringBootAdminClientAutoConfiguration.class))\n\t\t\t.withBean(WebFluxProperties.class);\n\t\treactiveContextRunner.withPropertyValues(\"spring.boot.admin.client.url:http://localhost:8081\")\n\t\t\t.run((context) -> assertThat(context).hasSingleBean(ApplicationRegistrator.class));\n\t}\n\n\t@Test\n\tvoid restClientRegistrationClientInBlockingEnvironment() {\n\t\tWebApplicationContextRunner webApplicationContextRunner = new WebApplicationContextRunner().withConfiguration(\n\t\t\t\tAutoConfigurations.of(EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,\n\t\t\t\t\t\tDispatcherServletAutoConfiguration.class, HttpClientAutoConfiguration.class,\n\t\t\t\t\t\tRestClientAutoConfiguration.class, SpringBootAdminClientAutoConfiguration.class));\n\n\t\twebApplicationContextRunner\n\t\t\t.withPropertyValues(\"spring.boot.admin.client.url:http://localhost:8081\",\n\t\t\t\t\t\"spring.boot.admin.client.connectTimeout=1337\", \"spring.boot.admin.client.readTimeout=42\")\n\t\t\t.withInitializer(new ConditionEvaluationReportLoggingListener())\n\t\t\t.run((context) -> {\n\t\t\t\tRegistrationClient registrationClient = context.getBean(RegistrationClient.class);\n\t\t\t\tRestClient restClient = (RestClient) ReflectionTestUtils.getField(registrationClient, \"restClient\");\n\t\t\t\tassertThat(restClient).isNotNull();\n\n\t\t\t\tClientHttpRequestFactory requestFactory = (ClientHttpRequestFactory) ReflectionTestUtils\n\t\t\t\t\t.getField(restClient, \"clientRequestFactory\");\n\n\t\t\t\tInteger connectTimeout = (Integer) ReflectionTestUtils.getField(requestFactory, \"connectTimeout\");\n\t\t\t\tassertThat(connectTimeout).isEqualTo(1337);\n\t\t\t\tDuration readTimeout = (Duration) ReflectionTestUtils.getField(requestFactory, \"readTimeout\");\n\t\t\t\tassertThat(readTimeout).isEqualTo(Duration.ofMillis(42));\n\t\t\t});\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/test/java/de/codecentric/boot/admin/client/config/SpringBootAdminClientCloudFoundryAutoConfigurationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.config;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;\nimport org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;\nimport org.springframework.boot.autoconfigure.AutoConfigurations;\nimport org.springframework.boot.http.client.autoconfigure.HttpClientAutoConfiguration;\nimport org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration;\nimport org.springframework.boot.test.context.runner.WebApplicationContextRunner;\nimport org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration;\nimport org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration;\n\nimport de.codecentric.boot.admin.client.registration.ApplicationFactory;\nimport de.codecentric.boot.admin.client.registration.CloudFoundryApplicationFactory;\nimport de.codecentric.boot.admin.client.registration.DefaultApplicationFactory;\nimport de.codecentric.boot.admin.client.registration.metadata.CloudFoundryMetadataContributor;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass SpringBootAdminClientCloudFoundryAutoConfigurationTest {\n\n\tprivate final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()\n\t\t.withConfiguration(AutoConfigurations.of(EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,\n\t\t\t\tWebMvcAutoConfiguration.class, DispatcherServletAutoConfiguration.class,\n\t\t\t\tHttpClientAutoConfiguration.class, RestClientAutoConfiguration.class,\n\t\t\t\tSpringBootAdminClientAutoConfiguration.class,\n\t\t\t\tSpringBootAdminClientCloudFoundryAutoConfiguration.class));\n\n\t@Test\n\tvoid non_cloud_platform() {\n\t\tthis.contextRunner.withPropertyValues(\"spring.boot.admin.client.url:http://localhost:8081\").run((context) -> {\n\t\t\tassertThat(context).doesNotHaveBean(CloudFoundryMetadataContributor.class);\n\t\t\tassertThat(context).getBean(ApplicationFactory.class).isInstanceOf(DefaultApplicationFactory.class);\n\t\t});\n\t}\n\n\t@Test\n\tvoid cloudfoundry() {\n\t\tthis.contextRunner.withPropertyValues(\"spring.boot.admin.client.url:http://localhost:8081\")\n\t\t\t.withPropertyValues(\"VCAP_APPLICATION:{}\")\n\t\t\t.run((context) -> {\n\t\t\t\tassertThat(context).hasSingleBean(CloudFoundryMetadataContributor.class);\n\t\t\t\tassertThat(context).getBean(ApplicationFactory.class)\n\t\t\t\t\t.isInstanceOf(CloudFoundryApplicationFactory.class);\n\t\t\t});\n\t}\n\n\t@Test\n\tvoid cloudfoundry_sba_disabled() {\n\t\tthis.contextRunner.withPropertyValues(\"VCAP_APPLICATION:{}\").run((context) -> {\n\t\t\tassertThat(context).doesNotHaveBean(CloudFoundryMetadataContributor.class);\n\t\t\tassertThat(context).doesNotHaveBean(ApplicationFactory.class);\n\t\t});\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/test/java/de/codecentric/boot/admin/client/config/SpringBootAdminClientEnabledConditionTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.config;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.BDDMockito;\nimport org.springframework.context.annotation.ConditionContext;\nimport org.springframework.core.type.AnnotatedTypeMetadata;\nimport org.springframework.mock.env.MockEnvironment;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\n\nclass SpringBootAdminClientEnabledConditionTest {\n\n\tprivate SpringBootAdminClientEnabledCondition condition;\n\n\tprivate AnnotatedTypeMetadata annotatedTypeMetadata;\n\n\tprivate ConditionContext conditionContext;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tcondition = new SpringBootAdminClientEnabledCondition();\n\t\tannotatedTypeMetadata = mock(AnnotatedTypeMetadata.class);\n\t\tconditionContext = mock(ConditionContext.class);\n\t}\n\n\t@Test\n\tvoid test_emptyUrl_enabled() {\n\t\tMockEnvironment environment = new MockEnvironment();\n\t\tBDDMockito.given(conditionContext.getEnvironment()).willReturn(environment);\n\t\tassertThat(condition.getMatchOutcome(conditionContext, annotatedTypeMetadata).isMatch()).isFalse();\n\t}\n\n\t@Test\n\tvoid test_emptyUrl_disabled() {\n\t\tMockEnvironment environment = new MockEnvironment();\n\t\tenvironment.setProperty(\"spring.boot.admin.client.enabled\", \"false\");\n\t\tBDDMockito.given(conditionContext.getEnvironment()).willReturn(environment);\n\t\tassertThat(condition.getMatchOutcome(conditionContext, annotatedTypeMetadata).isMatch()).isFalse();\n\t}\n\n\t@Test\n\tvoid test_nonEmptyUrl_disabled() {\n\t\tMockEnvironment environment = new MockEnvironment();\n\t\tenvironment.setProperty(\"spring.boot.admin.client.enabled\", \"false\");\n\t\tenvironment.setProperty(\"spring.boot.admin.client.url\", \"http://localhost:8080/management\");\n\t\tBDDMockito.given(conditionContext.getEnvironment()).willReturn(environment);\n\t\tassertThat(condition.getMatchOutcome(conditionContext, annotatedTypeMetadata).isMatch()).isFalse();\n\t}\n\n\t@Test\n\tvoid test_nonEmptyUrl_enabled() {\n\t\tMockEnvironment environment = new MockEnvironment();\n\t\tenvironment.setProperty(\"spring.boot.admin.client.url\", \"http://localhost:8080/management\");\n\t\tBDDMockito.given(conditionContext.getEnvironment()).willReturn(environment);\n\t\tassertThat(condition.getMatchOutcome(conditionContext, annotatedTypeMetadata).isMatch()).isTrue();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/test/java/de/codecentric/boot/admin/client/config/SpringBootAdminClientRegistrationClientAutoConfigurationTest.java",
    "content": "/*\n * Copyright 2014-2025 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.config;\n\nimport java.util.function.Function;\nimport java.util.stream.Stream;\n\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;\nimport org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;\nimport org.springframework.boot.autoconfigure.AutoConfigurations;\nimport org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;\nimport org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;\nimport org.springframework.boot.test.context.runner.WebApplicationContextRunner;\nimport org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration;\nimport org.springframework.web.client.RestClient;\n\nimport de.codecentric.boot.admin.client.registration.RegistrationClient;\nimport de.codecentric.boot.admin.client.registration.RestClientRegistrationClient;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class SpringBootAdminClientRegistrationClientAutoConfigurationTest {\n\n\t@ParameterizedTest(name = \"{0}\")\n\t@MethodSource(\"contextRunnerCustomizations\")\n\tvoid autoConfiguresRegistrationClient(String testCaseName,\n\t\t\tFunction<WebApplicationContextRunner, WebApplicationContextRunner> customizer,\n\t\t\tClass<? extends RegistrationClient> expectedRegistrationClient) {\n\t\tWebApplicationContextRunner webApplicationContextRunner = new WebApplicationContextRunner()\n\t\t\t.withConfiguration(\n\t\t\t\t\tAutoConfigurations.of(EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,\n\t\t\t\t\t\t\tDispatcherServletAutoConfiguration.class, SpringBootAdminClientAutoConfiguration.class))\n\t\t\t.with(customizer)\n\t\t\t.withInitializer(new ConditionEvaluationReportLoggingListener());\n\n\t\twebApplicationContextRunner.withPropertyValues(\"spring.boot.admin.client.url:http://localhost:8081\")\n\t\t\t.run((context) -> {\n\t\t\t\tRegistrationClient registrationClient = context.getBean(RegistrationClient.class);\n\n\t\t\t\tassertThat(registrationClient).isInstanceOf(expectedRegistrationClient);\n\t\t\t});\n\t}\n\n\tpublic static Stream<Arguments> contextRunnerCustomizations() {\n\t\treturn Stream.of(//\n\t\t\t\tArguments.of(//\n\t\t\t\t\t\t\"RestClientBuilder with ClientHttpRequestFactoryBuilder\", //\n\t\t\t\t\t\tcustomizer() //\n\t\t\t\t\t\t\t.withRestClientBuilder()\n\t\t\t\t\t\t\t.withClientHttpRequestFactoryBuilder()\n\t\t\t\t\t\t\t.build(), //\n\t\t\t\t\t\tRestClientRegistrationClient.class),\n\t\t\t\tArguments.of(//\n\t\t\t\t\t\t\"RestClientBuilder only\", //\n\t\t\t\t\t\tcustomizer() //\n\t\t\t\t\t\t\t.withRestClientBuilder()\n\t\t\t\t\t\t\t.build(), //\n\t\t\t\t\t\tRestClientRegistrationClient.class) //\n\t\t);\n\t}\n\n\tprivate static ContextRunnerCustomizerBuilder customizer() {\n\t\treturn new ContextRunnerCustomizerBuilder();\n\t}\n\n\tprivate static final class ContextRunnerCustomizerBuilder {\n\n\t\tprivate Function<WebApplicationContextRunner, WebApplicationContextRunner> customizer = (runner) -> runner;\n\n\t\tContextRunnerCustomizerBuilder withRestClientBuilder() {\n\t\t\tcustomizer = customizer.andThen((runner) -> runner.withBean(RestClient.Builder.class, RestClient::builder));\n\t\t\treturn this;\n\t\t}\n\n\t\tContextRunnerCustomizerBuilder withClientHttpRequestFactoryBuilder() {\n\t\t\tcustomizer = customizer.andThen((runner) -> runner.withBean(ClientHttpRequestFactoryBuilder.class,\n\t\t\t\t\tClientHttpRequestFactoryBuilder::detect));\n\t\t\treturn this;\n\t\t}\n\n\t\tFunction<WebApplicationContextRunner, WebApplicationContextRunner> build() {\n\t\t\treturn customizer;\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/test/java/de/codecentric/boot/admin/client/registration/AbstractRegistrationClientTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.registration;\n\nimport com.github.tomakehurst.wiremock.WireMockServer;\nimport com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;\nimport com.github.tomakehurst.wiremock.common.ConsoleNotifier;\nimport com.github.tomakehurst.wiremock.matching.RequestPatternBuilder;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.created;\nimport static com.github.tomakehurst.wiremock.client.WireMock.delete;\nimport static com.github.tomakehurst.wiremock.client.WireMock.deleteRequestedFor;\nimport static com.github.tomakehurst.wiremock.client.WireMock.equalTo;\nimport static com.github.tomakehurst.wiremock.client.WireMock.ok;\nimport static com.github.tomakehurst.wiremock.client.WireMock.post;\nimport static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;\nimport static com.github.tomakehurst.wiremock.client.WireMock.serverError;\nimport static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\npublic abstract class AbstractRegistrationClientTest {\n\n\tprivate final WireMockServer wireMock = new WireMockServer(\n\t\t\toptions().dynamicPort().notifier(new ConsoleNotifier(true)));\n\n\tprivate final Application application = Application.create(\"AppName\")\n\t\t.managementUrl(\"http://localhost:8080/mgmt\")\n\t\t.healthUrl(\"http://localhost:8080/health\")\n\t\t.serviceUrl(\"http://localhost:8080\")\n\t\t.build();\n\n\tprivate RegistrationClient registrationClient;\n\n\tpublic void setUp(RegistrationClient registrationClient) {\n\t\tthis.registrationClient = registrationClient;\n\t}\n\n\t@BeforeEach\n\tvoid setUpWiremock() {\n\t\twireMock.start();\n\t}\n\n\t@AfterEach\n\tvoid tearDown() {\n\t\twireMock.stop();\n\t}\n\n\t@Test\n\tpublic void register_should_return_id_when_successful() {\n\t\tResponseDefinitionBuilder response = created().withHeader(\"Content-Type\", \"application/json\")\n\t\t\t.withHeader(\"Location\", this.wireMock.url(\"/instances/abcdef\"))\n\t\t\t.withBody(\"{ \\\"id\\\" : \\\"-id-\\\" }\");\n\t\tthis.wireMock.stubFor(post(urlEqualTo(\"/instances\")).willReturn(response));\n\n\t\tassertThat(this.registrationClient.register(this.wireMock.url(\"/instances\"), this.application))\n\t\t\t.isEqualTo(\"-id-\");\n\n\t\tRequestPatternBuilder expectedRequest = postRequestedFor(urlEqualTo(\"/instances\"))\n\t\t\t.withHeader(\"Accept\", equalTo(\"application/json\"))\n\t\t\t.withHeader(\"Content-Type\", equalTo(\"application/json\"));\n\t\tthis.wireMock.verify(expectedRequest);\n\t}\n\n\t@Test\n\tpublic void register_should_throw() {\n\t\tthis.wireMock.stubFor(post(urlEqualTo(\"/instances\")).willReturn(serverError()));\n\n\t\tassertThatThrownBy(() -> this.registrationClient.register(this.wireMock.url(\"/instances\"), this.application))\n\t\t\t.isInstanceOf(Exception.class);\n\t}\n\n\t@Test\n\tpublic void deregister() {\n\t\tthis.wireMock.stubFor(delete(urlEqualTo(\"/instances/-id-\")).willReturn(ok()));\n\t\tthis.registrationClient.deregister(this.wireMock.url(\"/instances\"), \"-id-\");\n\t\tthis.wireMock.verify(deleteRequestedFor(urlEqualTo(\"/instances/-id-\")));\n\t}\n\n\t@Test\n\tpublic void deregister_should_trow() {\n\t\tthis.wireMock.stubFor(delete(urlEqualTo(\"/instances/-id-\")).willReturn(serverError()));\n\t\tassertThatThrownBy(() -> this.registrationClient.deregister(this.wireMock.url(\"/instances\"), \"-id-\"))\n\t\t\t.isInstanceOf(Exception.class);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/test/java/de/codecentric/boot/admin/client/registration/ApplicationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.registration;\n\nimport com.jayway.jsonpath.DocumentContext;\nimport com.jayway.jsonpath.JsonPath;\nimport org.junit.jupiter.api.Test;\nimport tools.jackson.databind.json.JsonMapper;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ApplicationTest {\n\n\t@Test\n\tvoid test_json_format() {\n\t\tJsonMapper jsonMapper = JsonMapper.builder().build();\n\n\t\tApplication app = Application.create(\"test\")\n\t\t\t.healthUrl(\"http://health\")\n\t\t\t.serviceUrl(\"http://service\")\n\t\t\t.managementUrl(\"http://management\")\n\t\t\t.build();\n\n\t\tDocumentContext json = JsonPath.parse(jsonMapper.writeValueAsString(app));\n\n\t\tassertThat((String) json.read(\"$.name\")).isEqualTo(\"test\");\n\t\tassertThat((String) json.read(\"$.serviceUrl\")).isEqualTo(\"http://service\");\n\t\tassertThat((String) json.read(\"$.managementUrl\")).isEqualTo(\"http://management\");\n\t\tassertThat((String) json.read(\"$.healthUrl\")).isEqualTo(\"http://health\");\n\t}\n\n\t@Test\n\tvoid test_equals_hashCode() {\n\t\tApplication a1 = Application.create(\"foo\")\n\t\t\t.healthUrl(\"healthUrl\")\n\t\t\t.managementUrl(\"mgmt\")\n\t\t\t.serviceUrl(\"svc\")\n\t\t\t.build();\n\t\tApplication a2 = Application.create(\"foo\")\n\t\t\t.healthUrl(\"healthUrl\")\n\t\t\t.managementUrl(\"mgmt\")\n\t\t\t.serviceUrl(\"svc\")\n\t\t\t.build();\n\n\t\tassertThat(a1).isEqualTo(a2).hasSameHashCodeAs(a2);\n\n\t\tApplication a3 = Application.create(\"foo\")\n\t\t\t.healthUrl(\"healthUrl2\")\n\t\t\t.managementUrl(\"mgmt\")\n\t\t\t.serviceUrl(\"svc\")\n\t\t\t.build();\n\n\t\tassertThat(a1).isNotEqualTo(a3);\n\t\tassertThat(a2).isNotEqualTo(a3);\n\t}\n\n\t@Test\n\tvoid should_not_return_sensitive_data_in_toString() {\n\t\tApplication application = Application.create(\"app\").healthUrl(\"HEALTH\").metadata(\"password\", \"geheim\").build();\n\t\tassertThat(application.toString()).doesNotContain(\"geheim\");\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/test/java/de/codecentric/boot/admin/client/registration/CloudFoundryApplicationFactoryTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.registration;\n\nimport org.assertj.core.api.SoftAssertions;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;\nimport org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;\nimport org.springframework.boot.actuate.endpoint.EndpointId;\nimport org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;\nimport org.springframework.boot.web.server.autoconfigure.ServerProperties;\n\nimport de.codecentric.boot.admin.client.config.CloudFoundryApplicationProperties;\nimport de.codecentric.boot.admin.client.config.InstanceProperties;\n\nimport static java.util.Collections.singletonList;\nimport static java.util.Collections.singletonMap;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass CloudFoundryApplicationFactoryTest {\n\n\tprivate final InstanceProperties instanceProperties = new InstanceProperties();\n\n\tprivate final ServerProperties server = new ServerProperties();\n\n\tprivate final ManagementServerProperties management = new ManagementServerProperties();\n\n\tprivate final PathMappedEndpoints pathMappedEndpoints = mock(PathMappedEndpoints.class);\n\n\tprivate final WebEndpointProperties webEndpoint = new WebEndpointProperties();\n\n\tprivate final CloudFoundryApplicationProperties cfApplicationProperties = new CloudFoundryApplicationProperties();\n\n\tprivate final CloudFoundryApplicationFactory factory = new CloudFoundryApplicationFactory(this.instanceProperties,\n\t\t\tthis.management, this.server, this.pathMappedEndpoints, this.webEndpoint,\n\t\t\t() -> singletonMap(\"contributor\", \"test\"), this.cfApplicationProperties);\n\n\t@BeforeEach\n\tvoid setup() {\n\t\tthis.instanceProperties.setName(\"test\");\n\t}\n\n\t@Test\n\tvoid should_use_application_uri() {\n\t\twhen(this.pathMappedEndpoints.getPath(EndpointId.of(\"health\"))).thenReturn(\"/actuator/health\");\n\t\tthis.cfApplicationProperties.setUris(singletonList(\"application/Uppercase\"));\n\n\t\tApplication app = this.factory.createApplication();\n\n\t\tSoftAssertions softly = new SoftAssertions();\n\t\tsoftly.assertThat(app.getManagementUrl()).isEqualTo(\"http://application/Uppercase/actuator\");\n\t\tsoftly.assertThat(app.getHealthUrl()).isEqualTo(\"http://application/Uppercase/actuator/health\");\n\t\tsoftly.assertThat(app.getServiceUrl()).isEqualTo(\"http://application/Uppercase/\");\n\t\tsoftly.assertAll();\n\t}\n\n\t@Test\n\tvoid should_use_service_base_uri() {\n\t\twhen(this.pathMappedEndpoints.getPath(EndpointId.of(\"health\"))).thenReturn(\"/actuator/health\");\n\t\tthis.cfApplicationProperties.setUris(singletonList(\"application/Uppercase\"));\n\t\tthis.instanceProperties.setServiceBaseUrl(\"https://serviceBaseUrl\");\n\n\t\tApplication app = this.factory.createApplication();\n\n\t\tSoftAssertions softly = new SoftAssertions();\n\t\tsoftly.assertThat(app.getManagementUrl()).isEqualTo(\"https://serviceBaseUrl/actuator\");\n\t\tsoftly.assertThat(app.getHealthUrl()).isEqualTo(\"https://serviceBaseUrl/actuator/health\");\n\t\tsoftly.assertThat(app.getServiceUrl()).isEqualTo(\"https://serviceBaseUrl/\");\n\t\tsoftly.assertAll();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/test/java/de/codecentric/boot/admin/client/registration/DefaultApplicationFactoryTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.registration;\n\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;\nimport org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;\nimport org.springframework.boot.actuate.endpoint.EndpointId;\nimport org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;\nimport org.springframework.boot.web.server.Ssl;\nimport org.springframework.boot.web.server.WebServer;\nimport org.springframework.boot.web.server.autoconfigure.ServerProperties;\nimport org.springframework.boot.web.server.context.WebServerApplicationContext;\nimport org.springframework.boot.web.server.context.WebServerInitializedEvent;\n\nimport de.codecentric.boot.admin.client.config.InstanceProperties;\n\nimport static java.util.Collections.singletonMap;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.assertj.core.api.Assertions.entry;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass DefaultApplicationFactoryTest {\n\n\tprivate final InstanceProperties instanceProperties = new InstanceProperties();\n\n\tprivate final ServerProperties server = new ServerProperties();\n\n\tprivate final ManagementServerProperties management = new ManagementServerProperties();\n\n\tprivate final PathMappedEndpoints pathMappedEndpoints = mock(PathMappedEndpoints.class);\n\n\tprivate final WebEndpointProperties webEndpoint = new WebEndpointProperties();\n\n\tprivate final DefaultApplicationFactory factory = new DefaultApplicationFactory(instanceProperties, management,\n\t\t\tserver, pathMappedEndpoints, webEndpoint, () -> singletonMap(\"contributor\", \"test\"));\n\n\t@BeforeEach\n\tvoid setup() {\n\t\tinstanceProperties.setName(\"test\");\n\t}\n\n\t@Test\n\tvoid test_mgmtPortPath() {\n\t\twebEndpoint.setBasePath(\"/admin\");\n\t\twhen(pathMappedEndpoints.getPath(EndpointId.of(\"health\"))).thenReturn(\"/admin/alive\");\n\t\tpublishApplicationReadyEvent(factory, 8080, 8081);\n\n\t\tApplication app = factory.createApplication();\n\t\tassertThat(app.getManagementUrl()).isEqualTo(\"http://\" + getHostname() + \":8081/admin\");\n\t\tassertThat(app.getHealthUrl()).isEqualTo(\"http://\" + getHostname() + \":8081/admin/alive\");\n\t\tassertThat(app.getServiceUrl()).isEqualTo(\"http://\" + getHostname() + \":8080/\");\n\t}\n\n\t@Test\n\tvoid test_default() {\n\t\tinstanceProperties.setMetadata(singletonMap(\"instance\", \"test\"));\n\t\twhen(pathMappedEndpoints.getPath(EndpointId.of(\"health\"))).thenReturn(\"/actuator/health\");\n\t\tpublishApplicationReadyEvent(factory, 8080, null);\n\n\t\tApplication app = factory.createApplication();\n\t\tassertThat(app.getManagementUrl()).isEqualTo(\"http://\" + getHostname() + \":8080/actuator\");\n\t\tassertThat(app.getHealthUrl()).isEqualTo(\"http://\" + getHostname() + \":8080/actuator/health\");\n\t\tassertThat(app.getServiceUrl()).isEqualTo(\"http://\" + getHostname() + \":8080/\");\n\n\t\tassertThat(app.getMetadata()).containsExactly(entry(\"contributor\", \"test\"), entry(\"instance\", \"test\"));\n\t}\n\n\t@Test\n\tvoid test_ssl() {\n\t\tserver.setSsl(new Ssl());\n\t\tserver.getSsl().setEnabled(true);\n\t\twhen(pathMappedEndpoints.getPath(EndpointId.of(\"health\"))).thenReturn(\"/actuator/health\");\n\t\tpublishApplicationReadyEvent(factory, 8080, null);\n\n\t\tApplication app = factory.createApplication();\n\t\tassertThat(app.getManagementUrl()).isEqualTo(\"https://\" + getHostname() + \":8080/actuator\");\n\t\tassertThat(app.getHealthUrl()).isEqualTo(\"https://\" + getHostname() + \":8080/actuator/health\");\n\t\tassertThat(app.getServiceUrl()).isEqualTo(\"https://\" + getHostname() + \":8080/\");\n\t}\n\n\t@Test\n\tvoid test_ssl_management() {\n\t\tmanagement.setSsl(new Ssl());\n\t\tmanagement.getSsl().setEnabled(true);\n\t\twhen(pathMappedEndpoints.getPath(EndpointId.of(\"health\"))).thenReturn(\"/actuator/alive\");\n\t\tpublishApplicationReadyEvent(factory, 8080, 9090);\n\n\t\tApplication app = factory.createApplication();\n\t\tassertThat(app.getManagementUrl()).isEqualTo(\"https://\" + getHostname() + \":9090/actuator\");\n\t\tassertThat(app.getHealthUrl()).isEqualTo(\"https://\" + getHostname() + \":9090/actuator/alive\");\n\t\tassertThat(app.getServiceUrl()).isEqualTo(\"http://\" + getHostname() + \":8080/\");\n\t}\n\n\t@Test\n\tvoid test_preferIpAddress_server_address_missing() {\n\t\tinstanceProperties.setPreferIp(true);\n\t\twhen(pathMappedEndpoints.getPath(EndpointId.of(\"health\"))).thenReturn(\"/application/alive\");\n\t\tpublishApplicationReadyEvent(factory, 8080, null);\n\n\t\tApplication app = factory.createApplication();\n\t\tassertThat(app.getServiceUrl()).matches(\"http://\\\\d{0,3}\\\\.\\\\d{0,3}\\\\.\\\\d{0,3}\\\\.\\\\d{0,3}:8080/\");\n\t}\n\n\t@Test\n\tvoid test_preferIpAddress_management_address_missing() {\n\t\tinstanceProperties.setPreferIp(true);\n\t\twhen(pathMappedEndpoints.getPath(EndpointId.of(\"health\"))).thenReturn(\"/application/alive\");\n\t\tpublishApplicationReadyEvent(factory, 8080, 8081);\n\n\t\tApplication app = factory.createApplication();\n\t\tassertThat(app.getManagementUrl()).matches(\"http://\\\\d{0,3}\\\\.\\\\d{0,3}\\\\.\\\\d{0,3}\\\\.\\\\d{0,3}:8081/actuator\");\n\t}\n\n\t@Test\n\tvoid test_preferIpAddress() throws UnknownHostException {\n\t\tinstanceProperties.setPreferIp(true);\n\t\tserver.setAddress(InetAddress.getByName(\"127.0.0.1\"));\n\t\tmanagement.setAddress(InetAddress.getByName(\"127.0.0.2\"));\n\t\twhen(pathMappedEndpoints.getPath(EndpointId.of(\"health\"))).thenReturn(\"/actuator/health\");\n\t\tpublishApplicationReadyEvent(factory, 8080, 8081);\n\n\t\tApplication app = factory.createApplication();\n\t\tassertThat(app.getManagementUrl()).isEqualTo(\"http://127.0.0.2:8081/actuator\");\n\t\tassertThat(app.getHealthUrl()).isEqualTo(\"http://127.0.0.2:8081/actuator/health\");\n\t\tassertThat(app.getServiceUrl()).isEqualTo(\"http://127.0.0.1:8080/\");\n\t}\n\n\t@Test\n\tvoid test_all_custom() {\n\t\tinstanceProperties.setHealthUrl(\"http://health\");\n\t\tinstanceProperties.setManagementUrl(\"http://management\");\n\t\tinstanceProperties.setServiceUrl(\"http://service\");\n\n\t\tApplication app = factory.createApplication();\n\t\tassertThat(app.getServiceUrl()).isEqualTo(\"http://service\");\n\t\tassertThat(app.getManagementUrl()).isEqualTo(\"http://management\");\n\t\tassertThat(app.getHealthUrl()).isEqualTo(\"http://health\");\n\t}\n\n\t@Test\n\tvoid test_all_baseUrls() {\n\t\tinstanceProperties.setManagementBaseUrl(\"http://management:8090\");\n\t\tinstanceProperties.setServiceBaseUrl(\"http://service:80\");\n\t\twebEndpoint.setBasePath(\"/admin\");\n\t\twhen(pathMappedEndpoints.getPath(EndpointId.of(\"health\"))).thenReturn(\"/admin/health\");\n\n\t\tApplication app = factory.createApplication();\n\t\tassertThat(app.getServiceUrl()).isEqualTo(\"http://service:80/\");\n\t\tassertThat(app.getManagementUrl()).isEqualTo(\"http://management:8090/admin\");\n\t\tassertThat(app.getHealthUrl()).isEqualTo(\"http://management:8090/admin/health\");\n\t}\n\n\t@Test\n\tvoid test_service_baseUrl() {\n\t\tinstanceProperties.setServiceBaseUrl(\"http://service:80\");\n\t\twebEndpoint.setBasePath(\"/admin\");\n\t\twhen(pathMappedEndpoints.getPath(EndpointId.of(\"health\"))).thenReturn(\"/admin/health\");\n\n\t\tApplication app = factory.createApplication();\n\t\tassertThat(app.getServiceUrl()).isEqualTo(\"http://service:80/\");\n\t\tassertThat(app.getManagementUrl()).isEqualTo(\"http://service:80/admin\");\n\t\tassertThat(app.getHealthUrl()).isEqualTo(\"http://service:80/admin/health\");\n\t}\n\n\t@Test\n\tvoid test_missing_ports() {\n\t\tassertThatThrownBy(factory::createApplication).isInstanceOf(IllegalStateException.class)\n\t\t\t.hasMessageContaining(\"service-base-url\");\n\t}\n\n\t@Test\n\tvoid test_service_path() {\n\t\tinstanceProperties.setServiceBaseUrl(\"http://service:80\");\n\t\tinstanceProperties.setServicePath(\"app\");\n\t\twebEndpoint.setBasePath(\"/admin\");\n\t\twhen(pathMappedEndpoints.getPath(EndpointId.of(\"health\"))).thenReturn(\"/admin/health\");\n\n\t\tApplication app = factory.createApplication();\n\t\tassertThat(app.getServiceUrl()).isEqualTo(\"http://service:80/app\");\n\t\tassertThat(app.getManagementUrl()).isEqualTo(\"http://service:80/app/admin\");\n\t\tassertThat(app.getHealthUrl()).isEqualTo(\"http://service:80/app/admin/health\");\n\t}\n\n\t@Test\n\tvoid test_service_path_default() {\n\t\tassertThat(factory.getServicePath()).isEqualTo(\"/\");\n\t}\n\n\tprivate String getHostname() {\n\t\ttry {\n\t\t\treturn InetAddress.getLocalHost().getCanonicalHostName();\n\t\t}\n\t\tcatch (UnknownHostException ex) {\n\t\t\tthrow new IllegalStateException(ex);\n\t\t}\n\t}\n\n\tprivate void publishApplicationReadyEvent(DefaultApplicationFactory factory, Integer serverport,\n\t\t\tInteger managementport) {\n\t\tfactory.onWebServerInitialized(new TestWebServerInitializedEvent(\"server\", serverport));\n\t\tfactory.onWebServerInitialized(new TestWebServerInitializedEvent(\"management\",\n\t\t\t\t(managementport != null) ? managementport : serverport));\n\t}\n\n\tprivate static final class TestWebServerInitializedEvent extends WebServerInitializedEvent {\n\n\t\tprivate final WebServer server = mock(WebServer.class);\n\n\t\tprivate final WebServerApplicationContext context = mock(WebServerApplicationContext.class);\n\n\t\tprivate TestWebServerInitializedEvent(String name, int port) {\n\t\t\tsuper(mock(WebServer.class));\n\t\t\twhen(server.getPort()).thenReturn(port);\n\t\t\twhen(context.getServerNamespace()).thenReturn(name);\n\t\t}\n\n\t\t@Override\n\t\tpublic WebServerApplicationContext getApplicationContext() {\n\t\t\treturn context;\n\t\t}\n\n\t\t@Override\n\t\tpublic WebServer getWebServer() {\n\t\t\treturn this.server;\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/test/java/de/codecentric/boot/admin/client/registration/DefaultApplicationRegistratorTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.registration;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.web.client.RestClientException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.doThrow;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass DefaultApplicationRegistratorTest {\n\n\tprivate final Application application = Application.create(\"AppName\")\n\t\t.managementUrl(\"http://localhost:8080/mgmt\")\n\t\t.healthUrl(\"http://localhost:8080/health\")\n\t\t.serviceUrl(\"http://localhost:8080\")\n\t\t.build();\n\n\tprivate final RegistrationClient registrationClient = mock(RegistrationClient.class);\n\n\t@Test\n\tvoid register_should_return_true_when_successful() {\n\t\tApplicationRegistrator registrator = new DefaultApplicationRegistrator(() -> this.application,\n\t\t\t\tthis.registrationClient, new String[] { \"http://sba:8080/instances\", \"http://sba2:8080/instances\" },\n\t\t\t\ttrue);\n\n\t\twhen(this.registrationClient.register(any(), eq(this.application))).thenReturn(\"-id-\");\n\t\tassertThat(registrator.register()).isTrue();\n\t\tassertThat(registrator.getRegisteredId()).isEqualTo(\"-id-\");\n\t}\n\n\t@Test\n\tvoid register_should_return_false_when_failed() {\n\t\tApplicationRegistrator registrator = new DefaultApplicationRegistrator(() -> this.application,\n\t\t\t\tthis.registrationClient, new String[] { \"http://sba:8080/instances\", \"http://sba2:8080/instances\" },\n\t\t\t\ttrue);\n\n\t\twhen(this.registrationClient.register(any(), eq(this.application))).thenThrow(new RestClientException(\"Error\"));\n\n\t\tassertThat(registrator.register()).isFalse();\n\t\tassertThat(registrator.register()).isFalse();\n\t\tassertThat(registrator.getRegisteredId()).isNull();\n\t}\n\n\t@Test\n\tvoid register_should_try_next_on_error() {\n\t\tApplicationRegistrator registrator = new DefaultApplicationRegistrator(() -> this.application,\n\t\t\t\tthis.registrationClient, new String[] { \"http://sba:8080/instances\", \"http://sba2:8080/instances\" },\n\t\t\t\ttrue);\n\n\t\twhen(this.registrationClient.register(\"http://sba:8080/instances\", this.application))\n\t\t\t.thenThrow(new RestClientException(\"Error\"));\n\t\twhen(this.registrationClient.register(\"http://sba2:8080/instances\", this.application)).thenReturn(\"-id-\");\n\n\t\tassertThat(registrator.register()).isTrue();\n\t\tassertThat(registrator.getRegisteredId()).isEqualTo(\"-id-\");\n\t}\n\n\t@Test\n\tvoid deregister_should_deregister_at_server() {\n\t\tApplicationRegistrator registrator = new DefaultApplicationRegistrator(() -> this.application,\n\t\t\t\tthis.registrationClient, new String[] { \"http://sba:8080/instances\", \"http://sba2:8080/instances\" },\n\t\t\t\ttrue);\n\n\t\twhen(this.registrationClient.register(any(), eq(this.application))).thenReturn(\"-id-\");\n\n\t\tregistrator.register();\n\t\tregistrator.deregister();\n\t\tassertThat(registrator.getRegisteredId()).isNull();\n\n\t\tverify(this.registrationClient).deregister(\"http://sba:8080/instances\", \"-id-\");\n\t}\n\n\t@Test\n\tvoid deregister_should_not_deregister_when_not_registered() {\n\t\tApplicationRegistrator registrator = new DefaultApplicationRegistrator(() -> this.application,\n\t\t\t\tthis.registrationClient, new String[] { \"http://sba:8080/instances\", \"http://sba2:8080/instances\" },\n\t\t\t\ttrue);\n\n\t\tregistrator.deregister();\n\n\t\tverify(this.registrationClient, never()).deregister(any(), any());\n\t}\n\n\t@Test\n\tvoid deregister_should_try_next_on_error() {\n\t\tApplicationRegistrator registrator = new DefaultApplicationRegistrator(() -> this.application,\n\t\t\t\tthis.registrationClient, new String[] { \"http://sba:8080/instances\", \"http://sba2:8080/instances\" },\n\t\t\t\ttrue);\n\n\t\twhen(this.registrationClient.register(any(), eq(this.application))).thenReturn(\"-id-\");\n\t\tdoThrow(new RestClientException(\"Error\")).when(this.registrationClient)\n\t\t\t.deregister(\"http://sba:8080/instances\", \"-id-\");\n\n\t\tregistrator.register();\n\t\tregistrator.deregister();\n\t\tassertThat(registrator.getRegisteredId()).isNull();\n\n\t\tverify(this.registrationClient).deregister(\"http://sba:8080/instances\", \"-id-\");\n\t\tverify(this.registrationClient).deregister(\"http://sba2:8080/instances\", \"-id-\");\n\t}\n\n\t@Test\n\tvoid register_should_register_on_multiple_servers() {\n\t\tApplicationRegistrator registrator = new DefaultApplicationRegistrator(() -> this.application,\n\t\t\t\tthis.registrationClient, new String[] { \"http://sba:8080/instances\", \"http://sba2:8080/instances\" },\n\t\t\t\tfalse);\n\n\t\twhen(this.registrationClient.register(any(), eq(this.application))).thenReturn(\"-id-\");\n\n\t\tassertThat(registrator.register()).isTrue();\n\t\tassertThat(registrator.getRegisteredId()).isEqualTo(\"-id-\");\n\n\t\tverify(this.registrationClient).register(\"http://sba:8080/instances\", this.application);\n\t\tverify(this.registrationClient).register(\"http://sba2:8080/instances\", this.application);\n\t}\n\n\t@Test\n\tvoid deregister_should_deregister_on_multiple_servers() {\n\t\tApplicationRegistrator registrator = new DefaultApplicationRegistrator(() -> this.application,\n\t\t\t\tthis.registrationClient, new String[] { \"http://sba:8080/instances\", \"http://sba2:8080/instances\" },\n\t\t\t\tfalse);\n\n\t\twhen(this.registrationClient.register(any(), eq(this.application))).thenReturn(\"-id-\");\n\n\t\tregistrator.register();\n\t\tregistrator.deregister();\n\t\tassertThat(registrator.getRegisteredId()).isNull();\n\n\t\tverify(this.registrationClient).deregister(\"http://sba:8080/instances\", \"-id-\");\n\t\tverify(this.registrationClient).deregister(\"http://sba2:8080/instances\", \"-id-\");\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/test/java/de/codecentric/boot/admin/client/registration/ReactiveApplicationFactoryTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.registration;\n\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;\nimport org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;\nimport org.springframework.boot.actuate.endpoint.EndpointId;\nimport org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;\nimport org.springframework.boot.web.server.WebServer;\nimport org.springframework.boot.web.server.autoconfigure.ServerProperties;\nimport org.springframework.boot.web.server.context.WebServerApplicationContext;\nimport org.springframework.boot.web.server.context.WebServerInitializedEvent;\nimport org.springframework.boot.webflux.autoconfigure.WebFluxProperties;\n\nimport de.codecentric.boot.admin.client.config.InstanceProperties;\n\nimport static java.util.Collections.singletonMap;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass ReactiveApplicationFactoryTest {\n\n\tprivate final InstanceProperties instanceProperties = new InstanceProperties();\n\n\tprivate final ServerProperties server = new ServerProperties();\n\n\tprivate final ManagementServerProperties management = new ManagementServerProperties();\n\n\tprivate final PathMappedEndpoints pathMappedEndpoints = mock(PathMappedEndpoints.class);\n\n\tprivate final WebEndpointProperties webEndpoint = new WebEndpointProperties();\n\n\tprivate final WebFluxProperties webflux = new WebFluxProperties();\n\n\tprivate final ReactiveApplicationFactory factory = new ReactiveApplicationFactory(instanceProperties, management,\n\t\t\tserver, pathMappedEndpoints, webEndpoint, () -> singletonMap(\"contributor\", \"test\"), webflux);\n\n\t@BeforeEach\n\tvoid setup() {\n\t\tinstanceProperties.setName(\"test\");\n\t}\n\n\t@Test\n\tvoid test_contextPath_mgmtPath() {\n\t\twebflux.setBasePath(\"/app\");\n\t\twebEndpoint.setBasePath(\"/admin\");\n\t\twhen(pathMappedEndpoints.getPath(EndpointId.of(\"health\"))).thenReturn(\"/admin/health\");\n\t\tpublishApplicationReadyEvent(factory, 8080, null);\n\n\t\tApplication app = factory.createApplication();\n\t\tassertThat(app.getManagementUrl()).isEqualTo(\"http://\" + getHostname() + \":8080/app/admin\");\n\t\tassertThat(app.getHealthUrl()).isEqualTo(\"http://\" + getHostname() + \":8080/app/admin/health\");\n\t\tassertThat(app.getServiceUrl()).isEqualTo(\"http://\" + getHostname() + \":8080/app\");\n\t}\n\n\t@Test\n\tvoid test_contextPath_mgmtPortPath() {\n\t\twebflux.setBasePath(\"/app\");\n\t\twebEndpoint.setBasePath(\"/admin\");\n\t\twhen(pathMappedEndpoints.getPath(EndpointId.of(\"health\"))).thenReturn(\"/admin/health\");\n\t\tpublishApplicationReadyEvent(factory, 8080, 8081);\n\n\t\tApplication app = factory.createApplication();\n\t\tassertThat(app.getManagementUrl()).isEqualTo(\"http://\" + getHostname() + \":8081/admin\");\n\t\tassertThat(app.getHealthUrl()).isEqualTo(\"http://\" + getHostname() + \":8081/admin/health\");\n\t\tassertThat(app.getServiceUrl()).isEqualTo(\"http://\" + getHostname() + \":8080/app\");\n\t}\n\n\t@Test\n\tvoid test_basePath() {\n\t\twebflux.setBasePath(\"/app\");\n\t\twhen(pathMappedEndpoints.getPath(EndpointId.of(\"health\"))).thenReturn(\"/actuator/health\");\n\t\tpublishApplicationReadyEvent(factory, 80, null);\n\n\t\tApplication app = factory.createApplication();\n\t\tassertThat(app.getManagementUrl()).isEqualTo(\"http://\" + getHostname() + \":80/app/actuator\");\n\t\tassertThat(app.getHealthUrl()).isEqualTo(\"http://\" + getHostname() + \":80/app/actuator/health\");\n\t\tassertThat(app.getServiceUrl()).isEqualTo(\"http://\" + getHostname() + \":80/app\");\n\t}\n\n\t@Test\n\tvoid test_noBasePath() {\n\t\twhen(pathMappedEndpoints.getPath(EndpointId.of(\"health\"))).thenReturn(\"/actuator/health\");\n\t\tpublishApplicationReadyEvent(factory, 80, null);\n\n\t\tApplication app = factory.createApplication();\n\t\tassertThat(app.getManagementUrl()).isEqualTo(\"http://\" + getHostname() + \":80/actuator\");\n\t\tassertThat(app.getHealthUrl()).isEqualTo(\"http://\" + getHostname() + \":80/actuator/health\");\n\t\tassertThat(app.getServiceUrl()).isEqualTo(\"http://\" + getHostname() + \":80/\");\n\t}\n\n\t@Test\n\tvoid test_mgmtBasePath_mgmtPortPath() {\n\t\twebflux.setBasePath(\"/app\");\n\t\tmanagement.setBasePath(\"/mgnt\");\n\t\twhen(pathMappedEndpoints.getPath(EndpointId.of(\"health\"))).thenReturn(\"/actuator/health\");\n\t\tpublishApplicationReadyEvent(factory, 8080, 8081);\n\n\t\tApplication app = factory.createApplication();\n\t\tassertThat(app.getManagementUrl()).isEqualTo(\"http://\" + getHostname() + \":8081/mgnt/actuator\");\n\t\tassertThat(app.getHealthUrl()).isEqualTo(\"http://\" + getHostname() + \":8081/mgnt/actuator/health\");\n\t\tassertThat(app.getServiceUrl()).isEqualTo(\"http://\" + getHostname() + \":8080/app\");\n\t}\n\n\tprivate String getHostname() {\n\t\ttry {\n\t\t\treturn InetAddress.getLocalHost().getCanonicalHostName();\n\t\t}\n\t\tcatch (UnknownHostException ex) {\n\t\t\tthrow new IllegalStateException(ex);\n\t\t}\n\t}\n\n\tprivate void publishApplicationReadyEvent(DefaultApplicationFactory factory, Integer serverport,\n\t\t\tInteger managementport) {\n\t\tfactory.onWebServerInitialized(new TestWebServerInitializedEvent(\"server\", serverport));\n\t\tfactory.onWebServerInitialized(new TestWebServerInitializedEvent(\"management\",\n\t\t\t\t(managementport != null) ? managementport : serverport));\n\t}\n\n\tprivate static final class TestWebServerInitializedEvent extends WebServerInitializedEvent {\n\n\t\tprivate final WebServer server = mock(WebServer.class);\n\n\t\tprivate final WebServerApplicationContext context = mock(WebServerApplicationContext.class);\n\n\t\tprivate TestWebServerInitializedEvent(String name, int port) {\n\t\t\tsuper(mock(WebServer.class));\n\t\t\twhen(server.getPort()).thenReturn(port);\n\t\t\twhen(context.getServerNamespace()).thenReturn(name);\n\t\t}\n\n\t\t@Override\n\t\tpublic WebServerApplicationContext getApplicationContext() {\n\t\t\treturn context;\n\t\t}\n\n\t\t@Override\n\t\tpublic WebServer getWebServer() {\n\t\t\treturn this.server;\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/test/java/de/codecentric/boot/admin/client/registration/RegistrationApplicationListenerTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.registration;\n\nimport java.time.Duration;\nimport java.util.concurrent.ScheduledFuture;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.context.event.ApplicationReadyEvent;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.context.event.ContextClosedEvent;\nimport org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;\nimport org.springframework.web.context.ConfigurableWebApplicationContext;\nimport org.springframework.web.context.WebApplicationContext;\n\nimport static java.time.Duration.ZERO;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.ArgumentMatchers.isA;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass RegistrationApplicationListenerTest {\n\n\t@Test\n\tvoid should_schedule_register_task() {\n\t\tApplicationRegistrator registrator = mock(ApplicationRegistrator.class);\n\t\tThreadPoolTaskScheduler scheduler = mock(ThreadPoolTaskScheduler.class);\n\t\tRegistrationApplicationListener listener = new RegistrationApplicationListener(registrator, scheduler);\n\n\t\tlistener.onApplicationReady(new ApplicationReadyEvent(mock(SpringApplication.class), null,\n\t\t\t\tmock(ConfigurableWebApplicationContext.class), ZERO));\n\n\t\tverify(scheduler).scheduleAtFixedRate(isA(Runnable.class), eq(Duration.ofSeconds(10)));\n\t}\n\n\t@Test\n\tvoid should_no_schedule_register_task_when_not_autoRegister() {\n\t\tApplicationRegistrator registrator = mock(ApplicationRegistrator.class);\n\t\tThreadPoolTaskScheduler scheduler = mock(ThreadPoolTaskScheduler.class);\n\t\tRegistrationApplicationListener listener = new RegistrationApplicationListener(registrator, scheduler);\n\t\tlistener.setAutoRegister(false);\n\n\t\tlistener.onApplicationReady(new ApplicationReadyEvent(mock(SpringApplication.class), null,\n\t\t\t\tmock(ConfigurableWebApplicationContext.class), ZERO));\n\n\t\tverify(scheduler, never()).scheduleAtFixedRate(isA(Runnable.class), eq(Duration.ofSeconds(10)));\n\t}\n\n\t@Test\n\tvoid should_cancel_register_task_on_context_close() {\n\t\tApplicationRegistrator registrator = mock(ApplicationRegistrator.class);\n\t\tThreadPoolTaskScheduler scheduler = mock(ThreadPoolTaskScheduler.class);\n\t\tRegistrationApplicationListener listener = new RegistrationApplicationListener(registrator, scheduler);\n\n\t\tScheduledFuture<?> task = mock(ScheduledFuture.class);\n\t\twhen(scheduler.scheduleAtFixedRate(isA(Runnable.class), eq(Duration.ofSeconds(10)))).then((invocation) -> task);\n\n\t\tlistener.onApplicationReady(new ApplicationReadyEvent(mock(SpringApplication.class), null,\n\t\t\t\tmock(ConfigurableWebApplicationContext.class), ZERO));\n\t\tverify(scheduler).scheduleAtFixedRate(isA(Runnable.class), eq(Duration.ofSeconds(10)));\n\n\t\tlistener.onClosedContext(new ContextClosedEvent(mock(WebApplicationContext.class)));\n\t\tverify(task).cancel(true);\n\t}\n\n\t@Test\n\tvoid should_start_and_cancel_task_on_request() {\n\t\tApplicationRegistrator registrator = mock(ApplicationRegistrator.class);\n\t\tThreadPoolTaskScheduler scheduler = mock(ThreadPoolTaskScheduler.class);\n\t\tRegistrationApplicationListener listener = new RegistrationApplicationListener(registrator, scheduler);\n\n\t\tScheduledFuture<?> task = mock(ScheduledFuture.class);\n\t\twhen(scheduler.scheduleAtFixedRate(isA(Runnable.class), eq(Duration.ofSeconds(10)))).then((invocation) -> task);\n\n\t\tlistener.startRegisterTask();\n\t\tverify(scheduler).scheduleAtFixedRate(isA(Runnable.class), eq(Duration.ofSeconds(10)));\n\n\t\tlistener.stopRegisterTask();\n\t\tverify(task).cancel(true);\n\t}\n\n\t@Test\n\tvoid should_not_deregister_when_not_autoDeregister() {\n\t\tApplicationRegistrator registrator = mock(ApplicationRegistrator.class);\n\t\tThreadPoolTaskScheduler scheduler = mock(ThreadPoolTaskScheduler.class);\n\t\tRegistrationApplicationListener listener = new RegistrationApplicationListener(registrator, scheduler);\n\n\t\tlistener.onClosedContext(new ContextClosedEvent(mock(WebApplicationContext.class)));\n\n\t\tverify(registrator, never()).deregister();\n\t}\n\n\t@Test\n\tvoid should_deregister_when_autoDeregister() {\n\t\tApplicationRegistrator registrator = mock(ApplicationRegistrator.class);\n\t\tThreadPoolTaskScheduler scheduler = mock(ThreadPoolTaskScheduler.class);\n\t\tRegistrationApplicationListener listener = new RegistrationApplicationListener(registrator, scheduler);\n\t\tlistener.setAutoDeregister(true);\n\n\t\tlistener.onClosedContext(new ContextClosedEvent(mock(ApplicationContext.class)));\n\n\t\tverify(registrator).deregister();\n\t}\n\n\t@Test\n\tvoid should_deregister_when_autoDeregister_and_parent_is_bootstrap_context() {\n\t\tApplicationRegistrator registrator = mock(ApplicationRegistrator.class);\n\t\tThreadPoolTaskScheduler scheduler = mock(ThreadPoolTaskScheduler.class);\n\t\tRegistrationApplicationListener listener = new RegistrationApplicationListener(registrator, scheduler);\n\t\tlistener.setAutoDeregister(true);\n\n\t\tApplicationContext parentContext = mock(ApplicationContext.class);\n\t\twhen(parentContext.getId()).thenReturn(\"bootstrap\");\n\t\tApplicationContext mockContext = mock(ApplicationContext.class);\n\t\twhen(mockContext.getParent()).thenReturn(parentContext);\n\t\tlistener.onClosedContext(new ContextClosedEvent(mockContext));\n\n\t\tverify(registrator).deregister();\n\t}\n\n\t@Test\n\tvoid should_not_deregister_when_autoDeregister_and_not_root() {\n\t\tApplicationRegistrator registrator = mock(ApplicationRegistrator.class);\n\t\tThreadPoolTaskScheduler scheduler = mock(ThreadPoolTaskScheduler.class);\n\t\tRegistrationApplicationListener listener = new RegistrationApplicationListener(registrator, scheduler);\n\t\tlistener.setAutoDeregister(true);\n\n\t\tApplicationContext mockContext = mock(ApplicationContext.class);\n\t\twhen(mockContext.getParent()).thenReturn(mock(ApplicationContext.class));\n\t\tlistener.onClosedContext(new ContextClosedEvent(mockContext));\n\n\t\tverify(registrator, never()).deregister();\n\t}\n\n\t@Test\n\tvoid should_init_and_shutdown_taskScheduler() {\n\t\tApplicationRegistrator registrator = mock(ApplicationRegistrator.class);\n\t\tThreadPoolTaskScheduler scheduler = mock(ThreadPoolTaskScheduler.class);\n\t\tRegistrationApplicationListener listener = new RegistrationApplicationListener(registrator, scheduler);\n\n\t\tlistener.afterPropertiesSet();\n\t\tverify(scheduler, times(1)).afterPropertiesSet();\n\n\t\tlistener.destroy();\n\t\tverify(scheduler, times(1)).destroy();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/test/java/de/codecentric/boot/admin/client/registration/RestClientRegistrationClientTest.java",
    "content": "/*\n * Copyright 2014-2024 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.registration;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.springframework.web.client.RestClient;\n\nclass RestClientRegistrationClientTest extends AbstractRegistrationClientTest {\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tsuper.setUp(new RestClientRegistrationClient(RestClient.create()));\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/test/java/de/codecentric/boot/admin/client/registration/ServletApplicationFactoryTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.registration;\n\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\nimport java.util.Collections;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;\nimport org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;\nimport org.springframework.boot.actuate.endpoint.EndpointId;\nimport org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;\nimport org.springframework.boot.web.server.WebServer;\nimport org.springframework.boot.web.server.autoconfigure.ServerProperties;\nimport org.springframework.boot.web.server.context.WebServerApplicationContext;\nimport org.springframework.boot.web.server.context.WebServerInitializedEvent;\nimport org.springframework.boot.webmvc.autoconfigure.DispatcherServletPath;\nimport org.springframework.mock.web.MockServletContext;\n\nimport de.codecentric.boot.admin.client.config.InstanceProperties;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass ServletApplicationFactoryTest {\n\n\tprivate final InstanceProperties instance = new InstanceProperties();\n\n\tprivate final ServerProperties server = new ServerProperties();\n\n\tprivate final ManagementServerProperties management = new ManagementServerProperties();\n\n\tprivate final MockServletContext servletContext = new MockServletContext();\n\n\tprivate final PathMappedEndpoints pathMappedEndpoints = mock(PathMappedEndpoints.class);\n\n\tprivate final WebEndpointProperties webEndpoint = new WebEndpointProperties();\n\n\tprivate final DispatcherServletPath dispatcherServletPath = mock(DispatcherServletPath.class);\n\n\tprivate final ServletApplicationFactory factory = new ServletApplicationFactory(instance, management, server,\n\t\t\tservletContext, pathMappedEndpoints, webEndpoint, Collections::emptyMap, dispatcherServletPath);\n\n\t@BeforeEach\n\tvoid setup() {\n\t\tinstance.setName(\"test\");\n\t\twhen(dispatcherServletPath.getPrefix()).thenReturn(\"\");\n\t}\n\n\t@Test\n\tvoid test_contextPath_mgmtPath() {\n\t\tservletContext.setContextPath(\"app\");\n\t\twebEndpoint.setBasePath(\"/admin\");\n\t\twhen(pathMappedEndpoints.getPath(EndpointId.of(\"health\"))).thenReturn(\"/admin/health\");\n\t\tpublishApplicationReadyEvent(factory, 8080, null);\n\n\t\tApplication app = factory.createApplication();\n\t\tassertThat(app.getManagementUrl()).isEqualTo(\"http://\" + getHostname() + \":8080/app/admin\");\n\t\tassertThat(app.getHealthUrl()).isEqualTo(\"http://\" + getHostname() + \":8080/app/admin/health\");\n\t\tassertThat(app.getServiceUrl()).isEqualTo(\"http://\" + getHostname() + \":8080/app\");\n\t}\n\n\t@Test\n\tvoid test_contextPath_mgmtPortPath() {\n\t\tservletContext.setContextPath(\"app\");\n\t\twebEndpoint.setBasePath(\"/admin\");\n\t\twhen(pathMappedEndpoints.getPath(EndpointId.of(\"health\"))).thenReturn(\"/admin/health\");\n\t\tpublishApplicationReadyEvent(factory, 8080, 8081);\n\n\t\tApplication app = factory.createApplication();\n\t\tassertThat(app.getManagementUrl()).isEqualTo(\"http://\" + getHostname() + \":8081/admin\");\n\t\tassertThat(app.getHealthUrl()).isEqualTo(\"http://\" + getHostname() + \":8081/admin/health\");\n\t\tassertThat(app.getServiceUrl()).isEqualTo(\"http://\" + getHostname() + \":8080/app\");\n\t}\n\n\t@Test\n\tvoid test_contextPath() {\n\t\tservletContext.setContextPath(\"app\");\n\t\twhen(pathMappedEndpoints.getPath(EndpointId.of(\"health\"))).thenReturn(\"/actuator/health\");\n\t\tpublishApplicationReadyEvent(factory, 80, null);\n\n\t\tApplication app = factory.createApplication();\n\t\tassertThat(app.getManagementUrl()).isEqualTo(\"http://\" + getHostname() + \":80/app/actuator\");\n\t\tassertThat(app.getHealthUrl()).isEqualTo(\"http://\" + getHostname() + \":80/app/actuator/health\");\n\t\tassertThat(app.getServiceUrl()).isEqualTo(\"http://\" + getHostname() + \":80/app\");\n\t}\n\n\t@Test\n\tvoid test_servletPath() {\n\t\twhen(dispatcherServletPath.getPrefix()).thenReturn(\"app\");\n\t\tservletContext.setContextPath(\"srv\");\n\t\twhen(pathMappedEndpoints.getPath(EndpointId.of(\"health\"))).thenReturn(\"/actuator/health\");\n\t\tpublishApplicationReadyEvent(factory, 80, null);\n\n\t\tApplication app = factory.createApplication();\n\t\tassertThat(app.getManagementUrl()).isEqualTo(\"http://\" + getHostname() + \":80/srv/app/actuator\");\n\t\tassertThat(app.getHealthUrl()).isEqualTo(\"http://\" + getHostname() + \":80/srv/app/actuator/health\");\n\t\tassertThat(app.getServiceUrl()).isEqualTo(\"http://\" + getHostname() + \":80/srv\");\n\t}\n\n\t@Test\n\tvoid test_servicePath() {\n\t\tservletContext.setContextPath(\"app\");\n\t\twhen(pathMappedEndpoints.getPath(EndpointId.of(\"health\"))).thenReturn(\"/actuator/health\");\n\t\tpublishApplicationReadyEvent(factory, 80, null);\n\t\tinstance.setServicePath(\"/servicePath/\");\n\n\t\tApplication app = factory.createApplication();\n\t\tassertThat(app.getManagementUrl()).isEqualTo(\"http://\" + getHostname() + \":80/servicePath/app/actuator\");\n\t\tassertThat(app.getHealthUrl()).isEqualTo(\"http://\" + getHostname() + \":80/servicePath/app/actuator/health\");\n\t\tassertThat(app.getServiceUrl()).isEqualTo(\"http://\" + getHostname() + \":80/servicePath/app\");\n\t}\n\n\tprivate String getHostname() {\n\t\ttry {\n\t\t\treturn InetAddress.getLocalHost().getCanonicalHostName();\n\t\t}\n\t\tcatch (UnknownHostException ex) {\n\t\t\tthrow new IllegalStateException(ex);\n\t\t}\n\t}\n\n\tprivate void publishApplicationReadyEvent(DefaultApplicationFactory factory, Integer serverport,\n\t\t\tInteger managementport) {\n\t\tfactory.onWebServerInitialized(new TestWebServerInitializedEvent(\"server\", serverport));\n\t\tfactory.onWebServerInitialized(new TestWebServerInitializedEvent(\"management\",\n\t\t\t\t(managementport != null) ? managementport : serverport));\n\t}\n\n\tprivate static final class TestWebServerInitializedEvent extends WebServerInitializedEvent {\n\n\t\tprivate final WebServer server = mock(WebServer.class);\n\n\t\tprivate final WebServerApplicationContext context = mock(WebServerApplicationContext.class);\n\n\t\tprivate TestWebServerInitializedEvent(String name, int port) {\n\t\t\tsuper(mock(WebServer.class));\n\t\t\twhen(server.getPort()).thenReturn(port);\n\t\t\twhen(context.getServerNamespace()).thenReturn(name);\n\t\t}\n\n\t\t@Override\n\t\tpublic WebServerApplicationContext getApplicationContext() {\n\t\t\treturn context;\n\t\t}\n\n\t\t@Override\n\t\tpublic WebServer getWebServer() {\n\t\t\treturn this.server;\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/test/java/de/codecentric/boot/admin/client/registration/metadata/CloudFoundryMetadataContributorTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.registration.metadata;\n\nimport org.junit.jupiter.api.Test;\n\nimport de.codecentric.boot.admin.client.config.CloudFoundryApplicationProperties;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass CloudFoundryMetadataContributorTest {\n\n\t@Test\n\tvoid should_return_empty_metadata() {\n\t\tCloudFoundryMetadataContributor contributor = new CloudFoundryMetadataContributor(\n\t\t\t\tnew CloudFoundryApplicationProperties());\n\t\tassertThat(contributor.getMetadata()).isEmpty();\n\t}\n\n\t@Test\n\tvoid should_return_metadata() {\n\t\tCloudFoundryApplicationProperties cfApplicationProperties = new CloudFoundryApplicationProperties();\n\t\tcfApplicationProperties.setApplicationId(\"appId\");\n\t\tcfApplicationProperties.setInstanceIndex(\"1\");\n\t\tCloudFoundryMetadataContributor contributor = new CloudFoundryMetadataContributor(cfApplicationProperties);\n\t\tassertThat(contributor.getMetadata()).containsEntry(\"applicationId\", \"appId\").containsEntry(\"instanceId\", \"1\");\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/test/java/de/codecentric/boot/admin/client/registration/metadata/CompositeMetadataContributorTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.registration.metadata;\n\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\n\nimport static java.util.Arrays.asList;\nimport static java.util.Collections.emptyList;\nimport static java.util.Collections.singletonMap;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.entry;\n\nclass CompositeMetadataContributorTest {\n\n\t@Test\n\tvoid should_merge_metadata() {\n\t\tCompositeMetadataContributor contributor = new CompositeMetadataContributor(\n\t\t\t\tasList(() -> singletonMap(\"a\", \"first\"), () -> singletonMap(\"b\", \"second\"),\n\t\t\t\t\t\t() -> singletonMap(\"b\", \"second-new\")));\n\n\t\tMap<String, String> metadata = contributor.getMetadata();\n\n\t\tassertThat(metadata).containsExactly(entry(\"a\", \"first\"), entry(\"b\", \"second-new\"));\n\t}\n\n\t@Test\n\tvoid should_return_empty_metadata() {\n\t\tCompositeMetadataContributor contributor = new CompositeMetadataContributor(emptyList());\n\n\t\tMap<String, String> metadata = contributor.getMetadata();\n\n\t\tassertThat(metadata).isEmpty();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/test/java/de/codecentric/boot/admin/client/registration/metadata/StartupDateMetadataContributorTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.client.registration.metadata;\n\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass StartupDateMetadataContributorTest {\n\n\t@Test\n\tvoid should_return_startupdate() {\n\t\tStartupDateMetadataContributor contributor = new StartupDateMetadataContributor();\n\n\t\tMap<String, String> metadata = contributor.getMetadata();\n\n\t\tassertThat(metadata).hasSize(1).hasEntrySatisfying(\"startup\", (value) -> assertThat(value).isNotEmpty());\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-client/src/test/resources/application.yml",
    "content": "server:\n  shutdown: immediate\n"
  },
  {
    "path": "spring-boot-admin-client/src/test/resources/junit-platform.properties",
    "content": "junit.jupiter.execution.timeout.test.method.default=1m\n"
  },
  {
    "path": "spring-boot-admin-client/src/test/resources/logback-test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright 2014-2018 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<configuration>\n    <include resource=\"org/springframework/boot/logging/logback/base.xml\"/>\n    <logger name=\"de.codecentric\" level=\"DEBUG\"/>\n</configuration>\n"
  },
  {
    "path": "spring-boot-admin-dependencies/pom.xml",
    "content": "<?xml version=\"1.0\"?>\n<!--\n  ~ Copyright 2014-2018 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>spring-boot-admin-dependencies</artifactId>\n\n    <packaging>pom</packaging>\n\n    <name>Spring Boot Admin Dependencies</name>\n    <description>Spring Boot Admin Dependencies</description>\n\n    <parent>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin</artifactId>\n        <version>${revision}</version>\n        <relativePath>../pom.xml</relativePath>\n    </parent>\n\n    <dependencyManagement>\n        <dependencies>\n            <dependency>\n                <groupId>de.codecentric</groupId>\n                <artifactId>spring-boot-admin-server</artifactId>\n                <version>${revision}</version>\n            </dependency>\n            <dependency>\n                <groupId>de.codecentric</groupId>\n                <artifactId>spring-boot-admin-server-ui</artifactId>\n                <version>${revision}</version>\n            </dependency>\n            <dependency>\n                <groupId>de.codecentric</groupId>\n                <artifactId>spring-boot-admin-client</artifactId>\n                <version>${revision}</version>\n            </dependency>\n            <dependency>\n                <groupId>de.codecentric</groupId>\n                <artifactId>spring-boot-admin-starter-client</artifactId>\n                <version>${revision}</version>\n            </dependency>\n            <dependency>\n                <groupId>de.codecentric</groupId>\n                <artifactId>spring-boot-admin-starter-server</artifactId>\n                <version>${revision}</version>\n            </dependency>\n        </dependencies>\n    </dependencyManagement>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.codehaus.mojo</groupId>\n                <artifactId>flatten-maven-plugin</artifactId>\n                <inherited>false</inherited>\n                <executions>\n                    <execution>\n                        <!-- Flatten and simplify our own POM for install/deploy -->\n                        <id>flatten</id>\n                        <phase>process-resources</phase>\n                        <goals>\n                            <goal>flatten</goal>\n                        </goals>\n                        <configuration>\n                            <updatePomFile>true</updatePomFile>\n                            <flattenMode>bom</flattenMode>\n                            <pomElements>\n                                <properties>remove</properties>\n                                <distributionManagement>remove</distributionManagement>\n                                <dependencyManagement>resolve</dependencyManagement>\n                            </pomElements>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n\n    <profiles>\n        <profile>\n            <id>include-cloud</id>\n            <activation>\n                <property>\n                    <name>!excludeSpringCloud</name>\n                </property>\n            </activation>\n            <dependencyManagement>\n                <dependencies>\n                    <dependency>\n                        <groupId>de.codecentric</groupId>\n                        <artifactId>spring-boot-admin-server-cloud</artifactId>\n                        <version>${revision}</version>\n                    </dependency>\n                </dependencies>\n            </dependencyManagement>\n        </profile>\n    </profiles>\n</project>\n"
  },
  {
    "path": "spring-boot-admin-docs/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright 2014-2020 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>spring-boot-admin-docs</artifactId>\n\n    <packaging>pom</packaging>\n\n    <name>Spring Boot Admin Docs</name>\n    <description>Spring Boot Admin Docs</description>\n\n    <parent>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-build</artifactId>\n        <version>${revision}</version>\n        <relativePath>../spring-boot-admin-build</relativePath>\n    </parent>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>com.github.eirslett</groupId>\n                <artifactId>frontend-maven-plugin</artifactId>\n                <configuration>\n                    <workingDirectory>src/site</workingDirectory>\n                    <environmentVariables>\n                        <NODE_ENV>production</NODE_ENV>\n                        <VERSION>${project.version}</VERSION>\n                    </environmentVariables>\n                </configuration>\n                <executions>\n                    <execution>\n                        <id>install-node-and-npm</id>\n                        <goals>\n                            <goal>install-node-and-npm</goal>\n                        </goals>\n                        <phase>pre-site</phase>\n                        <configuration>\n                            <nodeVersion>${node.version}</nodeVersion>\n                        </configuration>\n                    </execution>\n                    <execution>\n                        <id>npm-install</id>\n                        <phase>pre-site</phase>\n                        <goals>\n                            <goal>npm</goal>\n                        </goals>\n                        <configuration>\n                            <arguments>ci --prefer-offline --no-progress --no-audit --silent</arguments>\n                        </configuration>\n                    </execution>\n                    <execution>\n                        <id>npm-build</id>\n                        <phase>site</phase>\n                        <goals>\n                            <goal>npm</goal>\n                        </goals>\n                        <configuration>\n                            <arguments>run build:prod</arguments>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.rodnansol</groupId>\n                <artifactId>spring-configuration-property-documenter-maven-plugin</artifactId>\n                <executions>\n                    <execution>\n                        <id>generate-adoc</id>\n                        <goals>\n                            <goal>generate-and-aggregate-documents</goal>\n                        </goals>\n                        <phase>pre-site</phase>\n                        <configuration>\n                            <type>ADOC</type>\n                            <inputs>\n                                <input>\n                                    <name>spring-boot-admin-server</name>\n                                    <!-- TODO use ${project.rootDirectory}/spring-boot-admin-server as soon as migrated to maven 4 -->\n                                    <input>../spring-boot-admin-server</input>\n                                </input>\n                            </inputs>\n                            <outputFile>${project.build.directory}/aggregated-adoc.adoc</outputFile>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/.gitignore",
    "content": "# Dependencies\n/node_modules\n\n# Production\n/build\n\n# Generated files\n.docusaurus\n.cache-loader\n\n# Misc\n.DS_Store\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\nnpm-debug.log*\n\ncurrent/index.html\ncurrent/404.html\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/README.md",
    "content": "# Website\n\nThis website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.\n\n### Installation\n\n```\n$ npm install\n```\n\n### Local Development\n\n```\n$ npm run start\n```\n\nThis command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.\n\n### Build\n\n```\n$ npm run build\n```\n\nThis command generates static content into the `build` directory and can be served using any static contents hosting service.\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/babel.config.js",
    "content": "module.exports = {\n  presets: [require.resolve('@docusaurus/core/lib/babel/preset')],\n};\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/current/404.template.html",
    "content": "<!DOCTYPE html>\n<html lang='en'>\n<head>\n  <meta charset='utf-8'>\n  <title>Page not found · GitHub Pages</title>\n  <script type='text/javascript'>\n    function getRedirectUrl() {\n      const l = window.location;\n      if (l.pathname.includes(\"/current\")) {\n        return l.protocol + \"//\" + l.hostname + (l.port ? \":\" + l.port : \"\") +\n          [\n            \"\",\n            \"@@VERSION@@\",\n            ...l.pathname.split(\"/\").slice(2)\n          ].join(\"/\")\n          +\n          l.hash;\n      }\n    }\n\n    const redirectUrl = getRedirectUrl();\n    if (redirectUrl) {\n      window.location.replace(redirectUrl);\n    }\n  </script>\n\n  <style type=\"text/css\" media=\"screen\">\n      body {\n          background-color: #f1f1f1;\n          margin: 0;\n          font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n      }\n\n      .container {\n          margin: 50px auto 40px auto;\n          width: 600px;\n          text-align: center;\n      }\n\n      a {\n          color: #4183c4;\n          text-decoration: none;\n      }\n\n      a:hover {\n          text-decoration: underline;\n      }\n\n      h1 {\n          width: 800px;\n          position: relative;\n          left: -100px;\n          letter-spacing: -1px;\n          line-height: 60px;\n          font-size: 60px;\n          font-weight: 100;\n          margin: 0 0 50px 0;\n          text-shadow: 0 1px 0 #fff;\n      }\n\n      p {\n          color: rgba(0, 0, 0, 0.5);\n          margin: 20px 0;\n          line-height: 1.6;\n      }\n\n      ul {\n          list-style: none;\n          margin: 25px 0;\n          padding: 0;\n      }\n\n      li {\n          display: table-cell;\n          font-weight: bold;\n          width: 1%;\n      }\n\n      #suggestions {\n          margin-top: 35px;\n          color: #ccc;\n      }\n\n      #suggestions a {\n          color: #666666;\n          font-weight: 200;\n          font-size: 14px;\n          margin: 0 10px;\n      }\n  </style>\n</head>\n<body>\n<div class=\"container\">\n\n  <h1>404</h1>\n  <p><strong>There isn't a GitHub Pages site here.</strong></p>\n\n  <p>\n    Did you mean to visit <a href=\"//docs.spring-boot-admin.com/\">docs.spring-boot-admin.com</a>?\n    Please note that this site belongs to a GitHub user and is\n    <strong>not an official GitHub site</strong>.\n  </p>\n\n  <div id=\"suggestions\">\n    <a href=\"https://github.com/codecentric/spring-boot-admin\">@codecentric/spring-boot-admin</a> —\n    <a href=\"https://www.codecentric.de\">codecentric</a> —\n    <a href=\"https://github.com/codecentric/spring-boot-admin/issues\">Issues</a>\n  </div>\n</div>\n</body>\n</html>\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/current/index.template.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>Redirecting...</title>\n  <link rel='canonical' href='/@@VERSION@@' />\n  <meta charset='utf-8' />\n  <meta http-equiv='refresh' content='0; url=/@@VERSION@@' />\n</head>\n<body>\n<p>Redirecting...</p>\n</body>\n</html>\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/01-getting-started/10-server-setup.md",
    "content": "---\nsidebar_position: 10\nsidebar_custom_props:\n  icon: 'server'\n---\n\n# Server Setup\n\nSetting up a Spring Boot Admin Server is straightforward and requires only a few steps. The server acts as the central\nmonitoring hub for all your Spring Boot applications.\n\n## Creating the Admin Server\n\n### Step 1: Create a Spring Boot Project\n\nUse [Spring Initializr](https://start.spring.io) to create a new Spring Boot project, or add the dependencies to an\nexisting project.\n\n### Step 2: Add Maven Dependencies\n\nAdd the Spring Boot Admin Server starter and a web starter to your `pom.xml`:\n\n```xml title=\"pom.xml\"\n\n<dependencies>\n    <dependency>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-starter-server</artifactId>\n        <version>@VERSION@</version>\n    </dependency>\n    <dependency>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-webmvc</artifactId>\n    </dependency>\n</dependencies>\n```\n\nFor Gradle:\n\n```groovy title=\"build.gradle\"\ndependencies {\n    implementation 'de.codecentric:spring-boot-admin-starter-server:@VERSION@'\n    implementation 'org.springframework.boot:spring-boot-starter-webmvc'\n}\n```\n\n:::tip\nYou can choose either Servlet (WebMVC) or Reactive (WebFlux) as your web stack. For reactive applications, use\n`spring-boot-starter-webflux` instead.\n:::\n\n### Step 3: Enable Admin Server\n\nAnnotate your main application class with `@EnableAdminServer`:\n\n```java title=\"SpringBootAdminApplication.java\"\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\n\nimport de.codecentric.boot.admin.server.config.EnableAdminServer;\n\n@SpringBootApplication\n@EnableAdminServer\npublic class SpringBootAdminApplication {\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(SpringBootAdminApplication.class, args);\n\t}\n}\n```\n\nThe `@EnableAdminServer` annotation enables Spring Boot Admin Server by loading all required configuration through\nSpring's auto-discovery feature.\n\n### Step 4: Configure Application Properties\n\nCreate or update your `application.yml` or `application.properties`:\n\n```yaml title=\"application.yml\"\nspring:\n  application:\n    name: spring-boot-admin-server\n\nserver:\n  port: 8080\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n  endpoint:\n    health:\n      show-details: ALWAYS\n```\n\n### Step 5: Run the Server\n\nStart your application and navigate to `http://localhost:8080` to access the Spring Boot Admin UI.\n\n## Server Configuration Options\n\n### Custom Context Path\n\nIf you want to run the Admin Server under a different context path:\n\n```yaml title=\"application.yml\"\nspring:\n  boot:\n    admin:\n      context-path: /admin  # UI will be available at http://localhost:8080/admin\n```\n\n### Customizing the Server Port\n\n```yaml title=\"application.yml\"\nserver:\n  port: 9090  # Run on a different port\n```\n\n## Servlet vs. Reactive\n\nSpring Boot Admin Server can run on either a Servlet or Reactive stack:\n\n### Servlet (Default)\n\n```xml\n\n<dependency>\n    <groupId>org.springframework.boot</groupId>\n    <artifactId>spring-boot-starter-webmvc</artifactId>\n</dependency>\n```\n\nBest for traditional servlet-based applications and when you need features like Jolokia (JMX support).\n\n### Reactive (WebFlux)\n\n```xml\n\n<dependency>\n    <groupId>org.springframework.boot</groupId>\n    <artifactId>spring-boot-starter-webflux</artifactId>\n</dependency>\n```\n\nBest for fully reactive applications and high-concurrency scenarios.\n\n## Deployment Options\n\n### Standalone JAR\n\nBuild and run as a standalone application:\n\n```bash\nmvn clean package\njava -jar target/spring-boot-admin-server.jar\n```\n\n### WAR Deployment\n\nFor deployment to an external servlet container, see\nthe [spring-boot-admin-sample-war](https://github.com/codecentric/spring-boot-admin/tree/master/spring-boot-admin-samples/spring-boot-admin-sample-war/)\nexample.\n\n### Docker\n\nCreate a `Dockerfile`:\n\n```dockerfile\nFROM eclipse-temurin:17-jre\nCOPY target/spring-boot-admin-server.jar app.jar\nEXPOSE 8080\nENTRYPOINT [\"java\", \"-jar\", \"/app.jar\"]\n```\n\nBuild and run:\n\n```bash\ndocker build -t spring-boot-admin-server .\ndocker run -p 8080:8080 spring-boot-admin-server\n```\n\n## Next Steps\n\nNow that your server is running, you need to register your applications:\n\n- [Client Registration](./20-client-registration.md) - Learn how to register applications with the server\n- [Server Configuration](../02-server/01-server.mdx) - Explore advanced server configuration options\n- [Security](../02-server/02-security.md) - Secure your Admin Server\n\n## Example Projects\n\n- [spring-boot-admin-sample-servlet](https://github.com/codecentric/spring-boot-admin/tree/master/spring-boot-admin-samples/spring-boot-admin-sample-servlet/) -\n  Complete servlet-based example with security\n- [spring-boot-admin-sample-reactive](https://github.com/codecentric/spring-boot-admin/tree/master/spring-boot-admin-samples/spring-boot-admin-sample-reactive/) -\n  Reactive (WebFlux) example\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/01-getting-started/20-client-registration.md",
    "content": "---\nsidebar_position: 20\nsidebar_custom_props:\n  icon: 'link'\n---\n\n# Client Registration\n\nTo monitor your applications with Spring Boot Admin, they need to register with the Admin Server. There are three main\napproaches to achieve this:\n\n1. **Spring Boot Admin Client** - Direct registration\n2. **Spring Cloud Discovery** - Automatic registration via service discovery\n3. **Static Configuration** - Manual configuration on the server side\n\n## Using Spring Boot Admin Client\n\nThe Spring Boot Admin Client library enables applications to register themselves directly with the Admin Server.\n\n### Step 1: Add Dependencies\n\nAdd the Spring Boot Admin Client starter to your application:\n\n```xml title=\"pom.xml\"\n<dependency>\n    <groupId>de.codecentric</groupId>\n    <artifactId>spring-boot-admin-starter-client</artifactId>\n    <version>@VERSION@</version>\n</dependency>\n```\n\nFor Gradle:\n\n```groovy title=\"build.gradle\"\nimplementation 'de.codecentric:spring-boot-admin-starter-client:@VERSION@'\n```\n\n### Step 2: Configure the Admin Server URL\n\nAdd the Admin Server URL to your `application.properties` or `application.yml`:\n\n```yaml title=\"application.yml\"\nspring:\n  boot:\n    admin:\n      client:\n        url: http://localhost:8080  # URL of your Admin Server\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"  # Expose all actuator endpoints\n  endpoint:\n    health:\n      show-details: ALWAYS\n  info:\n    env:\n      enabled: true  # Enable the info endpoint\n```\n\n```properties title=\"application.properties\"\nspring.boot.admin.client.url=http://localhost:8080\nmanagement.endpoints.web.exposure.include=*\nmanagement.endpoint.health.show-details=ALWAYS\nmanagement.info.env.enabled=true\n```\n\n### Step 3: Start Your Application\n\nWhen your application starts, it will automatically register with the Admin Server. You'll see your application appear\nin the Admin Server's web interface.\n\n### Client Configuration Options\n\n#### Custom Instance Metadata\n\nAdd custom metadata to your application registration:\n\n```yaml title=\"application.yml\"\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          metadata:\n            tags:\n              environment: production\n              region: us-east-1\n            team: platform\n```\n\n#### Custom Service URL\n\nOverride the service URL that the Admin Server uses to connect:\n\n```yaml title=\"application.yml\"\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          service-url: https://my-app.example.com\n          service-host-type: IP  # or CANONICAL\n```\n\n#### Registration Interval\n\nConfigure how often the client registers with the server:\n\n```yaml title=\"application.yml\"\nspring:\n  boot:\n    admin:\n      client:\n        period: 10000  # milliseconds (default: 10000)\n        auto-registration: true  # Enable/disable auto-registration\n```\n\n## Using Spring Cloud Discovery\n\nIf you're using Spring Cloud Discovery (Eureka, Consul, Zookeeper), you don't need the Spring Boot Admin Client. The\nAdmin Server can discover applications automatically.\n\n### Eureka Example\n\n#### Step 1: Add Eureka Client Dependency\n\nAdd to your application:\n\n```xml title=\"pom.xml\"\n<dependency>\n    <groupId>org.springframework.cloud</groupId>\n    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>\n</dependency>\n```\n\n#### Step 2: Configure Eureka\n\n```yaml title=\"application.yml\"\neureka:\n  client:\n    serviceUrl:\n      defaultZone: http://localhost:8761/eureka/\n  instance:\n    leaseRenewalIntervalInSeconds: 10\n    health-check-url-path: /actuator/health\n    metadata-map:\n      startup: ${random.int}  # Trigger info update after restart\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n  endpoint:\n    health:\n      show-details: ALWAYS\n```\n\n#### Step 3: Enable Discovery on Admin Server\n\nAdd Eureka client to your Admin Server:\n\n```xml title=\"pom.xml\"\n<dependency>\n    <groupId>org.springframework.cloud</groupId>\n    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>\n</dependency>\n```\n\nEnable discovery in the Admin Server:\n\n```java title=\"SpringBootAdminApplication.java\"\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\n\n@EnableDiscoveryClient\n@EnableAdminServer\n@SpringBootApplication\npublic class SpringBootAdminApplication {\n    static void main(String[] args) {\n        SpringApplication.run(SpringBootAdminApplication.class, args);\n    }\n}\n```\n\n### Consul Example\n\n```yaml title=\"application.yml\"\nspring:\n  cloud:\n    consul:\n      discovery:\n        metadata:\n          user-name: ${spring.security.user.name}\n          user-password: ${spring.security.user.password}\n```\n\n:::warning\nConsul does not allow dots (\".\") in metadata keys. Use dashes instead (e.g., `user-name` instead of `user.name`).\n:::\n\n### Zookeeper Example\n\nFor Zookeeper integration, see\nthe [spring-boot-admin-sample-zookeeper](https://github.com/codecentric/spring-boot-admin/tree/master/spring-boot-admin-samples/spring-boot-admin-sample-zookeeper/)\nexample.\n\n## Static Configuration\n\nYou can configure applications statically on the Admin Server using Spring Cloud's `SimpleDiscoveryClient`.\n\n```yaml title=\"application.yml (Admin Server)\"\nspring:\n  cloud:\n    discovery:\n      client:\n        simple:\n          instances:\n            my-application:\n              - uri: http://localhost:8081\n                metadata:\n                  management.context-path: /actuator\n```\n\nThis approach is useful for:\n\n- Legacy applications that can't be modified\n- Applications running in environments without service discovery\n- Static infrastructure setups\n\n## Securing Client Registration\n\nWhen your Admin Server is secured, clients need credentials to register:\n\n```yaml title=\"application.yml (Client)\"\nspring:\n  boot:\n    admin:\n      client:\n        url: http://localhost:8080\n        username: admin\n        password: secret\n```\n\nFor more details, see [Security](../02-server/02-security.md).\n\n## Exposing Actuator Endpoints\n\nSpring Boot Admin requires access to actuator endpoints. Ensure they are properly exposed:\n\n```yaml title=\"application.yml\"\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"  # Expose all endpoints\n      # Or be more specific:\n      # include: health,info,metrics,env,loggers\n```\n\n:::warning\nIn production, carefully consider which endpoints to expose and implement proper security measures.\n:::\n\n## Verifying Registration\n\nAfter configuring your client:\n\n1. Start your Admin Server\n2. Start your client application\n3. Navigate to the Admin Server UI (`http://localhost:8080`)\n4. Your application should appear in the applications list\n\nCheck the logs for registration confirmation:\n\n```\nINFO: Application registered itself as <instance-id>\n```\n\n## Troubleshooting\n\n### Application Not Appearing\n\n- Verify the Admin Server URL is correct\n- Check network connectivity between client and server\n- Ensure actuator endpoints are exposed\n- Review client logs for registration errors\n- Verify security credentials if server is secured\n\n### Registration Keeps Failing\n\n- Check if the Admin Server is running\n- Verify firewall rules allow communication\n- Ensure the management port is accessible\n- Check for proxy or network configuration issues\n\n## Next Steps\n\n- [Client Features](../03-client/10-client-features.md) - Learn about version display, JMX, logs, and tags\n- [Client Configuration](../03-client/80-configuration.md) - Explore advanced client configuration\n- [Service Discovery](../03-client/40-service-discovery.md) - Deep dive into Spring Cloud integration\n\n## Example Projects\n\n- [spring-boot-admin-sample-servlet](https://github.com/codecentric/spring-boot-admin/tree/master/spring-boot-admin-samples/spring-boot-admin-sample-servlet/) -\n  Direct client registration with security\n- [spring-boot-admin-sample-eureka](https://github.com/codecentric/spring-boot-admin/tree/master/spring-boot-admin-samples/spring-boot-admin-sample-eureka/) -\n  Eureka discovery example\n- [spring-boot-admin-sample-consul](https://github.com/codecentric/spring-boot-admin/tree/master/spring-boot-admin-samples/spring-boot-admin-sample-consul/) -\n  Consul discovery example\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/01-getting-started/50-snapshots.md",
    "content": "---\nsidebar_custom_props:\n  icon: 'package'\n---\n\n# SNAPSHOT-Versions\n\nIf you want to use a snapshot version of Spring Boot Admin Server you most likely need to include the spring and\nsonatype snapshot repositories:\n\n```xml title=\"pom.xml\"\n<repositories>\n    <!-- Repo at GitHub to retrive snapshots of spring-boot-admin -->\n    <repository>\n        <id>spring-boot-admin-snapshot</id>\n        <name>Spring Boot Admin Snapshots</name>\n        <url>https://maven.pkg.github.com/codecentric/spring-boot-admin</url>\n        <snapshots>\n            <enabled>true</enabled>\n        </snapshots>\n        <releases>\n            <enabled>false</enabled>\n        </releases>\n    </repository>\n    \n    <!-- Repo for spring milestones, RCs, snapshots -->\n    <repository>\n        <id>spring-milestone</id>\n        <snapshots>\n            <enabled>false</enabled>\n        </snapshots>\n        <url>https://repo.spring.io/milestone</url>\n    </repository>\n    <repository>\n        <id>spring-snapshot</id>\n        <snapshots>\n            <enabled>true</enabled>\n        </snapshots>\n        <url>http:s//repo.spring.io/snapshot</url>\n    </repository>\n</repositories>\n```\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/01-getting-started/_category_.json",
    "content": "{\n  \"label\": \"Getting Started\",\n  \"position\": 1\n}\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/01-getting-started/index.md",
    "content": "---\nsidebar_position: 2\nsidebar_custom_props:\n  icon: 'rocket'\n---\n\n# Getting Started\n\nSpring Boot Admin follows a server-client architecture designed to provide centralized monitoring and management of\nSpring Boot applications. This guide will help you quickly set up both the server and client components.\n\n## Architecture Overview\n\nSpring Boot Admin consists of two main components:\n\n- **Server**: A centralized monitoring hub that provides a web-based UI and aggregates data from multiple applications\n- **Client**: Applications that register themselves with the server and expose management endpoints\n\nThe server continuously polls the clients' Actuator endpoints to collect health status, metrics, and other management\ninformation, making this data available through an intuitive dashboard.\n\n## Quick Start\n\nThe fastest way to get started with Spring Boot Admin:\n\n1. **Set up the Admin Server** - Create a Spring Boot application with `@EnableAdminServer`\n2. **Register your applications** - Add the Admin Client to your applications or use Spring Cloud Discovery\n3. **Access the dashboard** - Navigate to your server URL to view and manage your applications\n\n## Prerequisites\n\n- Java 17 or higher\n- Spring Boot 3.0 or higher\n- Maven or Gradle build tool\n\n:::note\nSpring Boot Admin 3.x requires Spring Boot 3.x. For Spring Boot 2.x applications, use Spring Boot Admin 2.x.\n:::\n\n## What's Next?\n\n- [Server Setup](./10-server-setup.md) - Learn how to configure the Admin Server\n- [Client Registration](./20-client-registration.md) - Discover different ways to register your applications\n\n## Motivation\n\nIn modern microservices architecture, monitoring and managing distributed systems is complex and challenging. Spring\nBoot Admin provides a powerful solution for visualizing, monitoring, and managing Spring Boot applications in real-time.\n\nBy offering a web interface that aggregates the health and metrics of all attached services, Spring Boot Admin\nsimplifies the process of ensuring system stability and performance. Whether you need insights into application health,\nmemory usage, or log output, Spring Boot Admin offers a centralized tool that streamlines operational management.\n\n:::info\nWhile Spring Boot Admin offers a user-friendly and centralized interface for monitoring Spring Boot applications, it is\nnot designed to replace sophisticated, full-scale monitoring and observability tools like Grafana, Datadog, or Instana.\nThese tools provide advanced capabilities such as real-time alerting, history data, complex metric analysis, distributed\ntracing, and customizable dashboards across diverse environments.\n\nSpring Boot Admin excels at providing a lightweight, application-centric view with essential health checks, metrics, and\nmanagement endpoints. For production-grade observability in larger, more complex systems, integrating Spring Boot Admin\nalongside these advanced platforms ensures comprehensive system monitoring and deep insights.\n:::\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/02-server/01-server.mdx",
    "content": "---\nsidebar_custom_props:\n  icon: 'server'\n---\nimport metadataServer from \"@sba/spring-boot-admin-server/target/classes/META-INF/spring-configuration-metadata.json\";\nimport metadataServerCloud from \"@sba/spring-boot-admin-server-cloud/target/classes/META-INF/spring-configuration-metadata.json\";\nimport { PropertyTable } from \"@sba/spring-boot-admin-docs/src/site/src/components/PropertyTable\";\n\n# Set up server\n\n## Running Behind a Front-end Proxy Server\n\nIn case the Spring Boot Admin server is running behind a reverse proxy, it may be requried to configure the public url where the server is reachable via (`spring.boot.admin.ui.public-url`). In addition, when the reverse proxy terminates the https connection, it may be necessary to configure `server.forward-headers-strategy=native` (also see [Spring Boot Reference Guide](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-use-tomcat-behind-a-proxy-server)).\n\n## Spring Cloud Discovery\n\nThe Spring Boot Admin Server can use Spring Clouds `DiscoveryClient` to discover applications. The advantage is that the clients don’t have to include the `spring-boot-admin-starter-client`. You just have to add a `DiscoveryClient` implementation to your admin server - everything else is done by AutoConfiguration.\n\n### Static Configuration using SimpleDiscoveryClient\n\nSpring Cloud provides a `SimpleDiscoveryClient`. It allows you to specify client applications via static configuration:\n\n```xml title=\"pom.xml\"\n<dependency>\n    <groupId>org.springframework.cloud</groupId>\n    <artifactId>spring-cloud-starter</artifactId>\n</dependency>\n```\n\n```yaml title=\"application.yml\"\nspring:\n  cloud:\n    discovery:\n      client:\n        simple:\n          instances:\n            test:\n              - uri: http://instance1.intern:8080\n                metadata:\n                  management.context-path: /actuator\n              - uri: http://instance2.intern:8080\n                metadata:\n                  management.context-path: /actuator\n```\n\n### Other DiscoveryClients\n\nSpring Boot Admin supports all other implementations of Spring Cloud's `DiscoveryClient` ([Eureka](https://docs.spring.io/spring-cloud-netflix/docs/current/reference/html/#service-discovery-eureka-clients/), [Zookeeper](https://docs.spring.io/spring-cloud-zookeeper/docs/current/reference/html/#spring-cloud-zookeeper-discovery), [Consul](https://docs.spring.io/spring-cloud-consul/docs/current/reference/html/#spring-cloud-consul-discovery), [Kubernetes](https://docs.spring.io/spring-cloud-kubernetes/docs/current/reference/html/#discoveryclient-for-kubernetes), …​). You need to add it to the Spring Boot Admin Server and configure it properly. See the [integration guides](../04-integration/) for detailed setup instructions.\n\n### Converting ServiceInstances\n\nThe information from the service registry are converted by the `ServiceInstanceConverter`. Spring Boot Admin ships with a default and Eureka converter implementation. The correct one is selected by AutoConfiguration.\n\n:::tip\nYou can modify how the information from the registry is used to register the application by using SBA Server configuration options and instance metadata. The values from the metadata takes precedence over the server config. If the plenty of options don’t fit your needs you can provide your own ServiceInstanceConverter.\n:::\n\n:::tip\nWhen using Eureka, the healthCheckUrl known to Eureka is used for health-checking, which can be set on your client using eureka.instance.healthCheckUrl.\n:::\n\n<PropertyTable\n  title=\"Discovery configuration options\"\n  properties={metadataServerCloud.properties}\n/>\n\n### CloudFoundry\n\nIf you are deploying your applications to CloudFoundry then `vcap.application.application_id` and `vcap.application.instance_index` **_must_** be added to the metadata for proper registration of applications with Spring Boot Admin Server. Here is a sample configuration for Eureka:\n\n```yml title=\"application.yml\"\neureka:\n  instance:\n    hostname: ${vcap.application.uris[0]}\n    nonSecurePort: 80\n    metadata-map:\n      applicationId: ${vcap.application.application_id}\n      instanceId: ${vcap.application.instance_index}\n```\n\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/02-server/02-security.md",
    "content": "---\nsidebar_custom_props:\n  icon: 'shield'\n---\n\n# Foster Security\n\nSince there are several approaches on solving authentication and authorization in distributed web applications Spring\nBoot Admin doesn't ship a default one. By default `spring-boot-admin-server-ui` provides a login page and a logout\nbutton.\n\nA Spring Security configuration for your server could look like this:\n\n```java title=\"SecuritySecureConfig.java\"\n\n@Configuration(proxyBeanMethods = false)\npublic class SecuritySecureConfig {\n\n\tprivate final AdminServerProperties adminServer;\n\n\tprivate final SecurityProperties security;\n\n\tpublic SecuritySecureConfig(AdminServerProperties adminServer, SecurityProperties security) {\n\t\tthis.adminServer = adminServer;\n\t\tthis.security = security;\n\t}\n\n\t@Bean\n\tprotected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n\t\tSavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();\n\t\tsuccessHandler.setTargetUrlParameter(\"redirectTo\");\n\t\tsuccessHandler.setDefaultTargetUrl(this.adminServer.path(\"/\"));\n\n\t\thttp.authorizeHttpRequests((authorizeRequests) -> authorizeRequests //\n\t\t\t\t\t\t.requestMatchers(new AntPathRequestMatcher(this.adminServer.path(\"/assets/**\")))\n\t\t\t\t\t\t.permitAll() // (1)\n\t\t\t\t\t\t.requestMatchers(new AntPathRequestMatcher(this.adminServer.path(\"/actuator/info\")))\n\t\t\t\t\t\t.permitAll()\n\t\t\t\t\t\t.requestMatchers(new AntPathRequestMatcher(adminServer.path(\"/actuator/health\")))\n\t\t\t\t\t\t.permitAll()\n\t\t\t\t\t\t.requestMatchers(new AntPathRequestMatcher(this.adminServer.path(\"/login\")))\n\t\t\t\t\t\t.permitAll()\n\t\t\t\t\t\t.dispatcherTypeMatchers(DispatcherType.ASYNC)\n\t\t\t\t\t\t.permitAll() // https://github.com/spring-projects/spring-security/issues/11027\n\t\t\t\t\t\t.anyRequest()\n\t\t\t\t\t\t.authenticated()) // (2)\n\t\t\t\t.formLogin(\n\t\t\t\t\t\t(formLogin) -> formLogin.loginPage(this.adminServer.path(\"/login\")).successHandler(successHandler)) // (3)\n\t\t\t\t.logout((logout) -> logout.logoutUrl(this.adminServer.path(\"/logout\")))\n\t\t\t\t.httpBasic(Customizer.withDefaults()); // (4)\n\n\t\thttp.addFilterAfter(new CustomCsrfFilter(), BasicAuthenticationFilter.class) // (5)\n\t\t\t\t.csrf((csrf) -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())\n\t\t\t\t\t\t.csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())\n\t\t\t\t\t\t.ignoringRequestMatchers(\n\t\t\t\t\t\t\t\tnew AntPathRequestMatcher(this.adminServer.path(\"/instances\"), POST.toString()), // (6)\n\t\t\t\t\t\t\t\tnew AntPathRequestMatcher(this.adminServer.path(\"/instances/*\"), DELETE.toString()), // (6)\n\t\t\t\t\t\t\t\tnew AntPathRequestMatcher(this.adminServer.path(\"/actuator/**\")) // (7)\n\t\t\t\t\t\t));\n\n\t\thttp.rememberMe((rememberMe) -> rememberMe.key(UUID.randomUUID().toString()).tokenValiditySeconds(1209600));\n\n\t\treturn http.build();\n\n\t}\n\n\t// Required to provide UserDetailsService for \"remember functionality\"\n\t@Bean\n\tpublic InMemoryUserDetailsManager userDetailsService(PasswordEncoder passwordEncoder) {\n\t\tUserDetails user = User.withUsername(\"user\").password(passwordEncoder.encode(\"password\")).roles(\"USER\").build();\n\t\treturn new InMemoryUserDetailsManager(user);\n\t}\n\n\t@Bean\n\tpublic PasswordEncoder passwordEncoder() {\n\t\treturn new BCryptPasswordEncoder();\n\t}\n\n}\n```\n\n1. Grants public access to all static assets and the login page.\n2. Every other request must be authenticated.\n3. Configures login and logout.\n4. Enables HTTP-Basic support. This is needed for the Spring Boot Admin Client to register.\n5. Enables CSRF-Protection using Cookies\n6. Disables CSRF-Protection for the endpoint the Spring Boot Admin Client uses to (de-)register.\n7. Disables CSRF-Protection for the actuator endpoints.\n\nIn case you use the Spring Boot Admin Client, it needs the credentials for accessing the server:\n\n```yaml title=\"application.yml\"\nspring.boot.admin.client:\n  username: sba-client\n  password: s3cret\n```\n\nFor a complete sample look\nat [spring-boot-admin-sample-servlet](https://github.com/codecentric/spring-boot-admin/tree/master/spring-boot-admin-samples/spring-boot-admin-sample-servlet/).\n\n:::note\nIf you protect the /instances endpoint don't forget to configure the username and password on your SBA-Client using\nspring.boot.admin.client.username and spring.boot.admin.client.password.\n:::\n\n## Securing Client Actuator Endpoints\n\nWhen the actuator endpoints are secured using HTTP Basic authentication the SBA Server needs credentials to access them.\nYou can submit the credentials in the metadata when registering the application. The `BasicAuthHttpHeaderProvider` then\nuses this metadata to add the `Authorization` header to access your application's actuator endpoints. You can provide\nyour own `HttpHeadersProvider` to alter the behaviour (e.g. add some decryption) or add extra headers.\n\n:::note\nThe SBA Server masks certain metadata in the HTTP interface to prevent leaking of sensitive information.\n:::\n\n:::warning\nYou should configure HTTPS for your SBA Server or (service registry) when transferring credentials via the metadata.\n:::\n\n:::warning\nWhen using Spring Cloud Discovery, you must be aware that anybody who can query your service registry can obtain the\ncredentials.\n:::\n\n:::tip\nWhen using this approach the SBA Server decides whether the user can access the registered applications. There are more\ncomplex solutions possible (using OAuth2) to let the clients decide if the user can access the endpoints. For that\nplease have a look at the samples\nin [joshiste/spring-boot-admin-samples](https://github.com/joshiste/spring-boot-admin-samples).\n:::\n\n### SBA Client\n\n```yaml title=\"application.yml\"\nspring.boot.admin.client:\n  url: http://localhost:8080\n  instance:\n    metadata:\n      user.name: ${spring.security.user.name}\n      user.password: ${spring.security.user.password}\n```\n\n### SBA Server\n\nYou can specify credentials via configuration properties in your admin server.\n\n:::tip\nYou can use this in conjunction\nwith [spring-cloud-kubernetes](https://cloud.spring.io/spring-cloud-kubernetes/1.1.x/reference/html/#secrets-propertysource)\nto pull credentials from [secrets](https://kubernetes.io/docs/concepts/configuration/secret/).\n:::\n\nTo enable pulling credentials from properties the `spring.boot.admin.instance-auth.enabled` property must be `true` (\ndefault).\n\n:::note\nIf your clients provide credentials via metadata (i.e., via service annotations), that metadata will be used instead of\nthe properties.\n:::\n\nYou can provide a default username and password by setting `spring.boot.admin.instance-auth.default-user-name` and\n`spring.boot.admin.instance-auth.default-user-password`. Optionally you can provide credentials for specific services (\nby name) using the `spring.boot.admin.instance-auth.service-map.*.user-name` pattern, replacing `*` with the service\nname.\n\n```yaml title=\"application.yml\"\nspring.boot.admin:\n  instance-auth:\n    enabled: true\n    default-user-name: \"${some.user.name.from.secret}\"\n    default-password: \"${some.user.password.from.secret}\"\n    service-map:\n      my-first-service-to-monitor:\n        user-name: \"${some.user.name.from.secret}\"\n        user-password: \"${some.user.password.from.secret}\"\n      my-second-service-to-monitor:\n        user-name: \"${some.user.name.from.secret}\"\n        user-password: \"${some.user.password.from.secret}\"\n```\n\n### Eureka\n\n```yaml title=\"application.yml\"\neureka:\n  instance:\n    metadata-map:\n      user.name: ${spring.security.user.name}\n      user.password: ${spring.security.user.password}\n```\n\n### Consul\n\n```yaml title=\"application.yml\"\nspring.cloud.consul:\n  discovery:\n    metadata:\n      user-name: ${spring.security.user.name}\n      user-password: ${spring.security.user.password}\n```\n\n:::warning\nConsul does not allow dots (\".\") in metadata keys, use dashes instead.\n:::\n\n## CSRF on Actuator Endpoints\n\nSome of the actuator endpoints (e.g. `/loggers`) support POST requests. When using Spring Security you need to ignore\nthe actuator endpoints for CSRF-Protection as the Spring Boot Admin Server currently lacks support.\n\n```java title=\"SecuritySecureConfig.java\"\n\n@Bean\nprivate SecurityFilterChain filterChain(HttpSecurity http) {\n\treturn http.csrf(c -> c.ignoringRequestMatchers(\"/actuator/**\")).build();\n}\n```\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/02-server/10-Events.mdx",
    "content": "---\nsidebar_custom_props:\n  icon: 'server'\n---\n# Server(-Sent) Events\n\nSpring Boot Admin uses event sourcing per default to track changes of registered applications.\nEvery change (see relevant events below) of an application is stored as an event in memory.\nThis allows to reconstruct the state of an application at any point in time, as far as the server is not restarted.\n\n:::info\nServer Events in Spring Boot Admin are not the same as Spring Boot's AuditEvent actuator events.\nServer Events track changes and lifecycle of registered instances for monitoring and UI purposes, while AuditEvents are used\nfor auditing application actions and security events.\n:::\n\n## Event Endpoints\n\nSpring Boot Admin utilizes several endpoints for accessing instance events via the `InstancesController`:\n\n### List All Events\n- **GET `/instances/events`**\n- Returns all instance events as a JSON array.\n- Use this to retrieve the complete event history for all registered instances on UI startup.\n\n### Stream All Events\n- **GET `/instances/events`** (with `Accept: text/event-stream`)\n- Returns a continuous stream of instance events using [Server-Sent Events (SSE)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events).\n- Useful for real-time monitoring and UI updates.\n\n### Stream Events for a Specific Instance\n- **GET `/instances/{id}`** (with `Accept: text/event-stream`)\n- Streams events for a single instance, identified by its ID.\n\n## Available Events\n\n### InstanceRegisteredEvent\nIndicates that a new instance has been registered with Spring Boot Admin.\nThis event is used to add the instance to SBA and starts monitoring its status and endpoints.\n\n### InstanceRegistrationUpdatedEvent\nSignals that the registration details of an instance have changed (e.g., new management URL or changed registration source).\nThis event is used to update how the instance is accessed and monitored.\n\n### InstanceEndpointsDetectedEvent\nSignals that the endpoints (such as actuator endpoints) of an instance have been detected or updated.\nThis event is used to refresh the available operations and monitoring features for the instance in the UI.\n\n### InstanceInfoChangedEvent\nOccurs when the metadata or information (e.g., version, build info, tags) of an instance changes.\nThis event is used to update the displayed details about the instance in the UI.\n\n### InstanceStatusChangedEvent\nOccurs when the status of an instance changes (e.g., from UP to DOWN, or vice versa).\nThis event is used to update the health indicator and status badge in the UI, and may trigger notifications or alerts.\n\n### InstanceDeregisteredEvent\nIndicates that an instance has been unregistered from Spring Boot Admin.\nThis event is used to remove the instance from the UI and stop monitoring its status and endpoints.\n\n## Viewing Events in the UI Journal\n\nAll instance events described above can be viewed in the Journal section of the Spring Boot Admin UI.\nThe Journal provides a chronological log of all relevant events for each registered instance, allowing users to track changes,\nstatus updates, and lifecycle actions directly from the web interface.\n\nThis feature helps administrators and operators to:\n- Audit the history of instance registrations, status changes, and endpoint updates\n- Troubleshoot issues by reviewing the sequence of events\n- Gain insights into the operational state of monitored applications\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/02-server/20-Clustering.mdx",
    "content": "---\nsidebar_custom_props:\n  icon: 'server'\n---\nimport metadataServer\n  from \"@sba/spring-boot-admin-server/target/classes/META-INF/spring-configuration-metadata.json\";\nimport { PropertyTable } from \"@sba/spring-boot-admin-docs/src/site/src/components/PropertyTable\";\n\n# Clustering\n\nSpring Boot Admin Server supports cluster replication via Hazelcast. It is automatically enabled when a `HazelcastConfig`\\- or `HazelcastInstance`\\-Bean is present.\nYou can also configure the Hazelcast instance to be persistent, to keep the status over restarts. Also have a look at the [Spring Boot support for Hazelcast](http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-hazelcast/).\n\nWhen using clustering, Spring Boot Admin Events and Notifications are replicated across the members in the cluster.\nThe applications are not replicated, each instance of Spring Boot Admin will have its own set of applications.\nThis means that each instance has to monitor all applications, which may lead to increased load on the monitored services.\nOtherwise, you would have to ensure that each application is only monitored by one instance of Spring Boot Admin.\n\n![Architecture](hazelcast-component-diagram.png)\n\n:::info\nIn the setup shown in the picture above, both Spring Boot Admin instances poll the monitored services for health checks.\nThis means each service receives health check requests from every SBA node in the cluster.\n:::\n\n## Example Configuration\n\nThe following example shows a simple Hazelcast configuration, which should work in most environments.\nIt uses multicast for discovery, which may not be available in all networks and does not require a dedicated Hazelcast server.\nAll instances (aka members) of the Spring Boot Admin Server when using this config, will form a cluster automatically.\n\nKeep in mind that Hazelcast has a lot of options to configure the network and discovery.\nPlease refer to the [Hazelcast Documentation](https://docs.hazelcast.com/) for more details,\nas we do not offer any kind of support for Hazelcast itself.\nAlso, this is just a basic example, you should adapt the configuration to your needs, especially regarding production readiness and network configuration.\n\n\n1. Add Hazelcast to your dependencies:\n```xml title=\"pom.xml\"\n<dependency>\n    <groupId>com.hazelcast</groupId>\n    <artifactId>hazelcast</artifactId>\n</dependency>\n```\n2. Instantiate a HazelcastConfig:\n```java title=\"HazelcastConfig.java\"\n@Bean\npublic Config hazelcastConfig() {\n    // This map is used to store the events.\n    // It should be configured to reliably hold all the data,\n    // Spring Boot Admin will compact the events, if there are too many\n    MapConfig eventStoreMap = new MapConfig(DEFAULT_NAME_EVENT_STORE_MAP).setInMemoryFormat(InMemoryFormat.OBJECT)\n            .setBackupCount(1)\n            .setMergePolicyConfig(new MergePolicyConfig(PutIfAbsentMergePolicy.class.getName(), 100));\n    // This map is used to deduplicate the notifications.\n    // If data in this map gets lost it should not be a big issue as it will atmost\n    // lead to\n    // the same notification to be sent by multiple instances\n    MapConfig sentNotificationsMap = new MapConfig(DEFAULT_NAME_SENT_NOTIFICATIONS_MAP)\n            .setInMemoryFormat(InMemoryFormat.OBJECT)\n            .setBackupCount(1)\n            .setEvictionConfig(\n                    new EvictionConfig().setEvictionPolicy(EvictionPolicy.LRU).setMaxSizePolicy(MaxSizePolicy.PER_NODE))\n            .setMergePolicyConfig(new MergePolicyConfig(PutIfAbsentMergePolicy.class.getName(), 100));\n\n    Config config = new Config();\n    config.addMapConfig(eventStoreMap);\n    config.addMapConfig(sentNotificationsMap);\n    config.setProperty(\"hazelcast.jmx\", \"true\");\n\n    // network and join configuration (simple defaults good for local/dev)\n    NetworkConfig network = config.getNetworkConfig();\n    network.setPort(5701).setPortAutoIncrement(true);\n\n    JoinConfig join = network.getJoin();\n    join.getMulticastConfig().setEnabled(true);\n\n    return config;\n}\n```\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/02-server/30-persistence.md",
    "content": "---\nsidebar_position: 30\nsidebar_custom_props:\n  icon: 'database'\n---\n\n# Persistence and Event Store\n\nSpring Boot Admin uses an event-sourced architecture to track the state of registered applications. All changes to\napplication instances are stored as events in an `InstanceEventStore`, allowing the server to rebuild application state\nand maintain a complete audit trail.\n\n## Event Store Architecture\n\nThe `InstanceEventStore` is responsible for storing all instance-related events. Spring Boot Admin provides two built-in\nimplementations:\n\n### InMemoryEventStore\n\nThe default implementation stores events in memory using a `ConcurrentHashMap`. This is suitable for single-instance\ndeployments and development environments.\n\n**Characteristics:**\n\n- Fast and lightweight\n- Non-persistent (data lost on restart)\n- Limited by available memory\n- Configurable maximum log size per instance\n\n**Configuration:**\n\n```java\n@Bean\npublic InstanceEventStore eventStore() {\n    return new InMemoryEventStore(100); // Max 100 events per instance\n}\n```\n\nThe default configuration creates an `InMemoryEventStore` with a maximum of 100 events per instance aggregate. Older\nevents are automatically removed when the limit is reached.\n\n### HazelcastEventStore\n\nFor clustered deployments, the `HazelcastEventStore` provides distributed persistence using Hazelcast's `IMap`.\n\n**Characteristics:**\n\n- Distributed across cluster nodes\n- Survives single-node failures\n- Automatic synchronization between nodes\n- Real-time event publishing across the cluster\n\n**Configuration:**\n\nFirst, add the Hazelcast dependency:\n\n```xml title=\"pom.xml\"\n<dependency>\n    <groupId>com.hazelcast</groupId>\n    <artifactId>hazelcast</artifactId>\n</dependency>\n```\n\nThen configure the Hazelcast-backed event store:\n\n```java\nimport com.hazelcast.config.Config;\nimport com.hazelcast.config.MapConfig;\nimport com.hazelcast.core.Hazelcast;\nimport com.hazelcast.core.HazelcastInstance;\nimport com.hazelcast.map.IMap;\nimport de.codecentric.boot.admin.server.eventstore.HazelcastEventStore;\n\n@Configuration\npublic class HazelcastConfig {\n\n    @Bean\n    public Config hazelcastConfig() {\n        MapConfig mapConfig = new MapConfig(\"spring-boot-admin-event-store\")\n            .setBackupCount(1)\n            .setMergePolicyConfig(new MergePolicyConfig(\n                PutIfAbsentMergePolicy.class.getName(), 100));\n\n        Config config = new Config();\n        config.addMapConfig(mapConfig);\n        return config;\n    }\n\n    @Bean\n    public HazelcastInstance hazelcastInstance(Config hazelcastConfig) {\n        return Hazelcast.newHazelcastInstance(hazelcastConfig);\n    }\n\n    @Bean\n    public InstanceEventStore eventStore(HazelcastInstance hazelcastInstance) {\n        IMap<InstanceId, List<InstanceEvent>> map =\n            hazelcastInstance.getMap(\"spring-boot-admin-event-store\");\n        return new HazelcastEventStore(100, map);\n    }\n}\n```\n\n**How it works:**\n\nThe `HazelcastEventStore` listens to map entry updates and publishes new events to all cluster nodes:\n\n```java\neventLog.addEntryListener(new EntryAdapter<InstanceId, List<InstanceEvent>>() {\n    @Override\n    public void entryUpdated(EntryEvent<InstanceId, List<InstanceEvent>> event) {\n        long lastKnownVersion = getLastVersion(event.getOldValue());\n        List<InstanceEvent> newEvents = event.getValue()\n            .stream()\n            .filter((e) -> e.getVersion() > lastKnownVersion)\n            .toList();\n        publish(newEvents);\n    }\n}, true);\n```\n\n## Event Types\n\nThe event store manages different types of instance events:\n\n- `InstanceRegisteredEvent` - Application registers with the server\n- `InstanceDeregisteredEvent` - Application unregisters or is removed\n- `InstanceStatusChangedEvent` - Health status changes\n- `InstanceEndpointsDetectedEvent` - Actuator endpoints discovered\n- `InstanceInfoChangedEvent` - Application info updated\n- `InstanceRegistrationUpdatedEvent` - Registration details changed\n\nEach event contains:\n\n- Instance ID\n- Timestamp\n- Version (for optimistic locking)\n- Event-specific data\n\n## InstanceEventStore Interface\n\n```java\npublic interface InstanceEventStore extends Publisher<InstanceEvent> {\n\n    Flux<InstanceEvent> findAll();\n\n    Flux<InstanceEvent> find(InstanceId id);\n\n    Mono<Void> append(List<InstanceEvent> events);\n}\n```\n\n### Methods\n\n- **`findAll()`** - Returns all events for all instances\n- **`find(InstanceId id)`** - Returns events for a specific instance\n- **`append(List<InstanceEvent> events)`** - Appends new events to the store\n\nThe store also implements `Publisher<InstanceEvent>`, allowing components to subscribe to new events in real-time.\n\n## Event Versioning and Optimistic Locking\n\nEvents are versioned to prevent concurrent modification issues. Each event includes a version number that increments\nwith each change:\n\n```java\npublic abstract class InstanceEvent implements Serializable {\n    private final InstanceId instance;\n    private final long version;\n    private final long timestamp;\n\n    // ...\n}\n```\n\nWhen appending events, the event store checks that the version matches the expected sequence, throwing an\n`OptimisticLockingException` if there's a conflict.\n\n## Event Publishing\n\nThe event store publishes events to subscribers, enabling reactive processing:\n\n```java\neventStore.subscribe(event -> {\n    if (event instanceof InstanceStatusChangedEvent statusEvent) {\n        // React to status changes\n        System.out.println(\"Instance \" + event.getInstance() +\n                          \" changed to \" + statusEvent.getStatusInfo().getStatus());\n    }\n});\n```\n\n## Configuring Event Store Size\n\nControl the maximum number of events stored per instance:\n\n```java\n@Bean\npublic InstanceEventStore eventStore() {\n    return new InMemoryEventStore(500); // Store up to 500 events per instance\n}\n```\n\nWhen the limit is reached, the oldest events are removed. This prevents unbounded memory growth while maintaining recent\nhistory.\n\n## Custom Event Store Implementation\n\nYou can implement your own event store for custom persistence requirements (e.g., database, external cache):\n\n```java\npublic class CustomEventStore implements InstanceEventStore {\n\n    @Override\n    public Flux<InstanceEvent> findAll() {\n        // Load all events from your storage\n    }\n\n    @Override\n    public Flux<InstanceEvent> find(InstanceId id) {\n        // Load events for specific instance\n    }\n\n    @Override\n    public Mono<Void> append(List<InstanceEvent> events) {\n        // Persist events and publish to subscribers\n    }\n\n    @Override\n    public void subscribe(Subscriber<? super InstanceEvent> subscriber) {\n        // Handle event subscriptions\n    }\n}\n```\n\nThen register your custom implementation as a bean:\n\n```java\n@Bean\npublic InstanceEventStore eventStore() {\n    return new CustomEventStore();\n}\n```\n\n## Best Practices\n\n1. **For Development**: Use `InMemoryEventStore` for simplicity\n2. **For Single Instance Deployments**: Use `InMemoryEventStore` if restart data loss is acceptable\n3. **For Clustered Deployments**: Use `HazelcastEventStore` for high availability\n4. **For Large Deployments**: Tune the max log size to balance memory usage and history retention\n5. **For Custom Requirements**: Implement your own event store with database or distributed cache backing\n\n## Monitoring Event Store\n\nMonitor event store health through actuator endpoints or by subscribing to events:\n\n```java\n@Component\npublic class EventStoreMonitor {\n\n    public EventStoreMonitor(InstanceEventStore eventStore) {\n        eventStore.subscribe(event -> {\n            // Log or metric collection\n            log.debug(\"Event: {} for instance {}\",\n                     event.getType(), event.getInstance());\n        });\n    }\n}\n```\n\n## See Also\n\n- [Clustering](./20-Clustering.mdx) - Learn about clustering with Hazelcast\n- [Events](./10-Events.mdx) - Understand the event system\n- [Instance Registry](./40-instance-registry.md) - How instances are managed\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/02-server/40-instance-registry.md",
    "content": "---\nsidebar_position: 40\nsidebar_custom_props:\n  icon: 'apps'\n---\n\n# Instance Registry\n\nThe Instance Registry is the core component responsible for managing registered applications in Spring Boot Admin. It\nuses an event-sourced architecture to track application state through the `InstanceRepository` interface.\n\n## InstanceRepository\n\nThe `InstanceRepository` is the primary interface for storing and retrieving application instances. It provides reactive\nmethods for managing instance lifecycle:\n\n```java\npublic interface InstanceRepository {\n\n    Mono<Instance> save(Instance app);\n\n    Flux<Instance> findAll();\n\n    Mono<Instance> find(InstanceId id);\n\n    Flux<Instance> findByName(String name);\n\n    Mono<Instance> compute(InstanceId id,\n        BiFunction<InstanceId, Instance, Mono<Instance>> remappingFunction);\n\n    Mono<Instance> computeIfPresent(InstanceId id,\n        BiFunction<InstanceId, Instance, Mono<Instance>> remappingFunction);\n}\n```\n\n## Event-Sourced Implementation\n\nSpring Boot Admin uses `EventsourcingInstanceRepository`, which rebuilds instance state from events stored in the\n`InstanceEventStore`.\n\n### How It Works\n\nInstead of directly storing instance state, the repository stores events that represent state changes:\n\n1. **Registration**: When an application registers, an `InstanceRegisteredEvent` is created\n2. **State Changes**: Each state change (health, info, endpoints) generates a new event\n3. **Reconstruction**: The current instance state is rebuilt by replaying all events\n\n```java\npublic class EventsourcingInstanceRepository implements InstanceRepository {\n\n    private final InstanceEventStore eventStore;\n\n    @Override\n    public Mono<Instance> save(Instance instance) {\n        return eventStore.append(instance.getUnsavedEvents())\n                        .then(Mono.just(instance.clearUnsavedEvents()));\n    }\n\n    @Override\n    public Mono<Instance> find(InstanceId id) {\n        return eventStore.find(id)\n                        .collectList()\n                        .filter(e -> !e.isEmpty())\n                        .map(events -> Instance.create(id).apply(events));\n    }\n\n    @Override\n    public Flux<Instance> findAll() {\n        return eventStore.findAll()\n                        .groupBy(InstanceEvent::getInstance)\n                        .flatMap(f -> f.reduce(Instance.create(f.key()),\n                                              Instance::apply));\n    }\n}\n```\n\n### Benefits of Event Sourcing\n\n- **Complete Audit Trail**: Every change is recorded as an event\n- **Temporal Queries**: Can reconstruct state at any point in time\n- **Event Replay**: Can rebuild state from events after crashes\n- **Debugging**: Full history of state changes for troubleshooting\n\n## Instance Lifecycle\n\n### 1. Registration\n\nWhen an application registers, a new instance is created:\n\n```java\nInstanceId id = idGenerator.generateId(registration);\nInstance newInstance = Instance.create(id).register(registration);\nrepository.save(newInstance);\n```\n\nThis generates an `InstanceRegisteredEvent`.\n\n### 2. Endpoint Detection\n\nAfter registration, the server detects available actuator endpoints:\n\n```java\ninstance = instance.withEndpoints(detectedEndpoints);\nrepository.save(instance);\n```\n\nThis generates an `InstanceEndpointsDetectedEvent`.\n\n### 3. Status Updates\n\nThe server periodically polls health endpoints:\n\n```java\ninstance = instance.withStatusInfo(statusInfo);\nrepository.save(instance);\n```\n\nThis generates an `InstanceStatusChangedEvent` when status changes.\n\n### 4. Info Updates\n\nApplication info is periodically refreshed:\n\n```java\ninstance = instance.withInfo(info);\nrepository.save(instance);\n```\n\nThis generates an `InstanceInfoChangedEvent` when info changes.\n\n### 5. Deregistration\n\nWhen an application shuts down or is removed:\n\n```java\ninstance = instance.deregister();\nrepository.save(instance);\n```\n\nThis generates an `InstanceDeregisteredEvent`.\n\n## Optimistic Locking\n\nThe repository uses optimistic locking to handle concurrent updates:\n\n```java\nprivate final Retry retryOptimisticLockException = Retry.max(10)\n    .doBeforeRetry(s -> log.debug(\"Retrying after OptimisticLockingException\",\n                                  s.failure()))\n    .filter(OptimisticLockingException.class::isInstance);\n\n@Override\npublic Mono<Instance> compute(InstanceId id,\n        BiFunction<InstanceId, Instance, Mono<Instance>> remappingFunction) {\n    return find(id)\n        .flatMap(app -> remappingFunction.apply(id, app))\n        .switchIfEmpty(Mono.defer(() -> remappingFunction.apply(id, null)))\n        .flatMap(this::save)\n        .retryWhen(retryOptimisticLockException);\n}\n```\n\nIf two updates conflict (based on event version numbers), the operation is automatically retried up to 10 times.\n\n## Querying Instances\n\n### Find All Instances\n\n```java\nFlux<Instance> instances = repository.findAll();\ninstances.subscribe(instance -> {\n    System.out.println(\"Instance: \" + instance.getRegistration().getName());\n});\n```\n\n### Find by Instance ID\n\n```java\nMono<Instance> instance = repository.find(instanceId);\ninstance.subscribe(inst -> {\n    System.out.println(\"Found: \" + inst.getRegistration().getName());\n});\n```\n\n### Find by Application Name\n\n```java\nFlux<Instance> instances = repository.findByName(\"my-application\");\ninstances.subscribe(instance -> {\n    System.out.println(\"Instance ID: \" + instance.getId());\n});\n```\n\n## Compute Operations\n\nThe `compute` methods provide atomic read-modify-write operations:\n\n### compute()\n\nUpdates an instance or creates it if it doesn't exist:\n\n```java\nrepository.compute(instanceId, (id, instance) -> {\n    if (instance == null) {\n        // Create new instance\n        return Mono.just(Instance.create(id).register(registration));\n    } else {\n        // Update existing instance\n        return Mono.just(instance.withStatusInfo(newStatus));\n    }\n}).subscribe();\n```\n\n### computeIfPresent()\n\nUpdates only if the instance exists:\n\n```java\nrepository.computeIfPresent(instanceId, (id, instance) -> {\n    return Mono.just(instance.withInfo(updatedInfo));\n}).subscribe();\n```\n\n## Instance State\n\nAn `Instance` object contains:\n\n```java\npublic class Instance {\n    private final InstanceId id;\n    private final long version;\n    private final Registration registration;\n    private final boolean registered;\n    private final StatusInfo statusInfo;\n    private final Info info;\n    private final Endpoints endpoints;\n    private final BuildVersion buildVersion;\n    private final Tags tags;\n    private final List<InstanceEvent> unsavedEvents;\n}\n```\n\n### Key Properties\n\n- **`id`**: Unique identifier for the instance\n- **`version`**: Event version for optimistic locking\n- **`registration`**: Registration details (name, URL, metadata)\n- **`registered`**: Whether the instance is currently registered\n- **`statusInfo`**: Current health status\n- **`info`**: Application info from `/actuator/info`\n- **`endpoints`**: Discovered actuator endpoints\n- **`buildVersion`**: Application version from build-info\n- **`tags`**: Custom tags for classification\n- **`unsavedEvents`**: Events pending persistence\n\n## Instance ID Generation\n\nInstance IDs are generated by `InstanceIdGenerator` implementations:\n\n### Default: HashingInstanceUrlIdGenerator\n\nGenerates stable IDs based on the service URL:\n\n```java\npublic class HashingInstanceUrlIdGenerator implements InstanceIdGenerator {\n    @Override\n    public InstanceId generateId(Registration registration) {\n        String serviceUrl = registration.getServiceUrl();\n        // Generate hash-based ID from URL\n        return InstanceId.of(hash(serviceUrl));\n    }\n}\n```\n\n### Cloud Foundry: CloudFoundryInstanceIdGenerator\n\nUses Cloud Foundry's application instance ID:\n\n```java\npublic class CloudFoundryInstanceIdGenerator implements InstanceIdGenerator {\n    @Override\n    public InstanceId generateId(Registration registration) {\n        String cfInstanceId = registration.getMetadata()\n                                         .get(\"applicationId\")\n            + \":\" + registration.getMetadata().get(\"instanceId\");\n        return InstanceId.of(cfInstanceId);\n    }\n}\n```\n\n### Custom ID Generator\n\nImplement your own ID generation strategy:\n\n```java\n@Component\npublic class CustomInstanceIdGenerator implements InstanceIdGenerator {\n\n    @Override\n    public InstanceId generateId(Registration registration) {\n        // Custom logic to generate instance ID\n        String customId = registration.getName()\n            + \"-\" + UUID.randomUUID().toString();\n        return InstanceId.of(customId);\n    }\n}\n```\n\n## Working with the Repository\n\n### Injecting the Repository\n\n```java\n@Component\npublic class InstanceManager {\n\n    private final InstanceRepository repository;\n\n    public InstanceManager(InstanceRepository repository) {\n        this.repository = repository;\n    }\n\n    public Flux<String> getApplicationNames() {\n        return repository.findAll()\n                        .filter(Instance::isRegistered)\n                        .map(i -> i.getRegistration().getName())\n                        .distinct();\n    }\n}\n```\n\n### Reacting to Changes\n\nSubscribe to the event store to react to instance changes:\n\n```java\n@Component\npublic class InstanceChangeListener {\n\n    public InstanceChangeListener(InstanceEventStore eventStore,\n                                  InstanceRepository repository) {\n        eventStore.subscribe(event -> {\n            if (event instanceof InstanceStatusChangedEvent statusEvent) {\n                repository.find(event.getInstance())\n                         .subscribe(instance -> {\n                             log.info(\"Instance {} status: {}\",\n                                     instance.getRegistration().getName(),\n                                     instance.getStatusInfo().getStatus());\n                         });\n            }\n        });\n    }\n}\n```\n\n## Best Practices\n\n1. **Use compute methods** for atomic updates to avoid race conditions\n2. **Don't modify Instance objects directly** - use the builder-style methods (withXxx)\n3. **Let the system retry** optimistic locking failures automatically\n4. **Subscribe to events** for reactive processing instead of polling\n5. **Use findByName** for multi-instance applications to find all instances of a service\n\n## See Also\n\n- [Persistence](./30-persistence.md) - Learn about event storage\n- [Events](./10-Events.mdx) - Understand the event system\n- [Clustering](./20-Clustering.mdx) - Distributed instance management\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/02-server/99-server-properties.mdx",
    "content": "---\nsidebar_custom_props:\n  icon: 'properties'\n---\n# Properties\n\nimport metadata from \"@sba/spring-boot-admin-server/target/classes/META-INF/spring-configuration-metadata.json\";\nimport { PropertyTable } from \"@sba/spring-boot-admin-docs/src/site/src/components/PropertyTable\";\n\n<PropertyTable\n  properties={metadata.properties}\n  filter={[\n    \"spring.boot.admin.notify\"\n  ]}\n  includeOnly={false}\n/>\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/02-server/_category_.json",
    "content": "{\n  \"position\": 2,\n  \"label\": \"Server\"\n}\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/02-server/index.md",
    "content": "---\nsidebar_custom_props:\n  icon: 'server'\n---\n\nimport DocCardList from '@theme/DocCardList';\n\n# Spring Boot Admin Server\n\nThe Spring Boot Admin Server acts as the core component for managing and monitoring multiple Spring Boot applications.\nIt collects health, metrics, and runtime information from registered applications and displays them on a user-friendly\nweb interface.\n\nTo set up the Spring Boot Admin Server, you'll need to create a Spring Boot application and add the Spring Boot Admin\nServer Starter dependency. The server can operate as a Servlet or Reactive (WebFlux) application, depending on your\nproject setup.\n\n**Key Features:**\n\n* **Application Registration**: Clients (Spring Boot applications) register with the Admin Server via HTTP or service\n  discovery mechanisms like Eureka or Consul.\n* **Health Monitoring**: Provides an overview of the health status of registered applications via Spring Boot Actuator\n  endpoints.\n* **Metrics and Logs**: Displays key performance metrics and logs in real-time.\n* **Management Actions**: Allows interaction with client applications for tasks like restarting, updating\n  configurations, or triggering garbage collection.\n\nThe Admin Server itself is stateless, meaning it relies on its registered applications to periodically poll their\nstatus. Once configured, the Admin Server dashboard provides a central view for managing all of your Spring Boot\nservices.\n\n <DocCardList />\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/02-server/notifications/90-custom-notifiers.md",
    "content": "---\nsidebar_position: 90\nsidebar_custom_props:\n  icon: 'bell'\n---\n\n# Creating Custom Notifiers\n\nSpring Boot Admin makes it easy to create custom notifiers to integrate with your preferred notification channels. You\ncan extend the built-in notifier base classes or implement the `Notifier` interface directly.\n\n## Overview\n\nNotifiers are Spring beans that implement the `Notifier` interface and react to instance events such as status changes,\nregistration, or deregistration.\n\n## Using AbstractEventNotifier\n\nThe recommended approach is to extend `AbstractEventNotifier`, which provides built-in support for:\n\n- Filtering events\n- Enabling/disabling notifications\n- Accessing instance details\n- Error handling\n\n### Basic Example\n\n```java\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.notify.AbstractEventNotifier;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport reactor.core.publisher.Mono;\n\npublic class CustomNotifier extends AbstractEventNotifier {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(CustomNotifier.class);\n\n\tpublic CustomNotifier(InstanceRepository repository) {\n\t\tsuper(repository);\n\t}\n\n\t@Override\n\tprotected Mono<Void> doNotify(InstanceEvent event, Instance instance) {\n\t\treturn Mono.fromRunnable(() -> {\n\t\t\tif (event instanceof InstanceStatusChangedEvent statusEvent) {\n\t\t\t\tlog.info(\"Instance {} ({}) is {}\",\n\t\t\t\t\t\tinstance.getRegistration().getName(),\n\t\t\t\t\t\tevent.getInstance(),\n\t\t\t\t\t\tstatusEvent.getStatusInfo().getStatus());\n\t\t\t} else {\n\t\t\t\tlog.info(\"Instance {} ({}) {}\",\n\t\t\t\t\t\tinstance.getRegistration().getName(),\n\t\t\t\t\t\tevent.getInstance(),\n\t\t\t\t\t\tevent.getType());\n\t\t\t}\n\t\t});\n\t}\n}\n```\n\n### Registering the Notifier\n\nRegister your custom notifier as a Spring bean:\n\n```java\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration\npublic class NotifierConfiguration {\n\n\t@Bean\n\tpublic CustomNotifier customNotifier(InstanceRepository repository) {\n\t\treturn new CustomNotifier(repository);\n\t}\n}\n```\n\n## Advanced Custom Notifier\n\nHere's a more advanced example that sends notifications to an external API:\n\n```java\nimport org.springframework.web.reactive.function.client.WebClient;\nimport reactor.core.publisher.Mono;\n\npublic class WebhookNotifier extends AbstractEventNotifier {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(WebhookNotifier.class);\n\n\tprivate final WebClient webClient;\n\tprivate final String webhookUrl;\n\n\tpublic WebhookNotifier(InstanceRepository repository,\n\t\t\t\t\t\t   WebClient.Builder webClientBuilder,\n\t\t\t\t\t\t   String webhookUrl) {\n\t\tsuper(repository);\n\t\tthis.webhookUrl = webhookUrl;\n\t\tthis.webClient = webClientBuilder.build();\n\t}\n\n\t@Override\n\tprotected Mono<Void> doNotify(InstanceEvent event, Instance instance) {\n\t\treturn Mono.fromSupplier(() -> createNotificationPayload(event, instance))\n\t\t\t\t.flatMap(this::sendWebhookNotification)\n\t\t\t\t.doOnError(ex -> log.error(\"Failed to send webhook notification\", ex))\n\t\t\t\t.then();\n\t}\n\n\tprivate NotificationPayload createNotificationPayload(InstanceEvent event,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  Instance instance) {\n\t\treturn NotificationPayload.builder()\n\t\t\t\t.instanceId(instance.getId().getValue())\n\t\t\t\t.instanceName(instance.getRegistration().getName())\n\t\t\t\t.eventType(event.getType())\n\t\t\t\t.status(instance.getStatusInfo().getStatus())\n\t\t\t\t.timestamp(event.getTimestamp())\n\t\t\t\t.serviceUrl(instance.getRegistration().getServiceUrl())\n\t\t\t\t.build();\n\t}\n\n\tprivate Mono<Void> sendWebhookNotification(NotificationPayload payload) {\n\t\treturn webClient.post()\n\t\t\t\t.uri(webhookUrl)\n\t\t\t\t.bodyValue(payload)\n\t\t\t\t.retrieve()\n\t\t\t\t.bodyToMono(Void.class)\n\t\t\t\t.doOnSuccess(v -> log.info(\"Webhook notification sent successfully\"))\n\t\t\t\t.onErrorResume(ex -> {\n\t\t\t\t\tlog.error(\"Webhook call failed: {}\", ex.getMessage());\n\t\t\t\t\treturn Mono.empty();\n\t\t\t\t});\n\t}\n\n\t@lombok.Data\n\t@lombok.Builder\n\tprivate static class NotificationPayload {\n\t\tprivate String instanceId;\n\t\tprivate String instanceName;\n\t\tprivate String eventType;\n\t\tprivate String status;\n\t\tprivate long timestamp;\n\t\tprivate String serviceUrl;\n\t}\n}\n```\n\n### Configuration\n\n```java\n\n@Configuration\npublic class WebhookNotifierConfiguration {\n\n\t@Bean\n\tpublic WebhookNotifier webhookNotifier(InstanceRepository repository,\n\t\t\t\t\t\t\t\t\t\t   WebClient.Builder webClientBuilder,\n\t\t\t\t\t\t\t\t\t\t   @Value(\"${webhook.url}\") String webhookUrl) {\n\t\treturn new WebhookNotifier(repository, webClientBuilder, webhookUrl);\n\t}\n}\n```\n\n```yaml title=\"application.yml\"\nwebhook:\n  url: https://your-webhook-endpoint.com/notifications\n```\n\n## Filtering Events\n\nYou can override `shouldNotify` to filter which events trigger notifications:\n\n```java\npublic class FilteredNotifier extends AbstractEventNotifier {\n\n\tpublic FilteredNotifier(InstanceRepository repository) {\n\t\tsuper(repository);\n\t}\n\n\t@Override\n\tprotected boolean shouldNotify(InstanceEvent event, Instance instance) {\n\t\t// Only notify for production instances\n\t\tString environment = instance.getRegistration()\n\t\t\t\t.getMetadata()\n\t\t\t\t.get(\"environment\");\n\t\treturn \"production\".equals(environment);\n\t}\n\n\t@Override\n\tprotected Mono<Void> doNotify(InstanceEvent event, Instance instance) {\n\t\t// Send notification\n\t\treturn Mono.fromRunnable(() -> {\n\t\t\tlog.info(\"Production instance event: {}\", event.getType());\n\t\t});\n\t}\n}\n```\n\n## Using AbstractStatusChangeNotifier\n\nIf you only care about status changes (UP/DOWN/OFFLINE), extend `AbstractStatusChangeNotifier`:\n\n```java\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\nimport de.codecentric.boot.admin.server.notify.AbstractStatusChangeNotifier;\n\nimport reactor.core.publisher.Mono;\n\npublic class StatusChangeNotifier extends AbstractStatusChangeNotifier {\n\n\tpublic StatusChangeNotifier(InstanceRepository repository) {\n\t\tsuper(repository);\n\t}\n\n\t@Override\n\tprotected Mono<Void> doNotify(InstanceEvent event, Instance instance) {\n\t\tStatusInfo statusInfo = instance.getStatusInfo();\n\t\tString status = statusInfo.getStatus();\n\n\t\treturn Mono.fromRunnable(() -> {\n\t\t\tif (\"DOWN\".equals(status)) {\n\t\t\t\t// Send critical alert\n\t\t\t\tlog.error(\"CRITICAL: Instance {} is DOWN!\",\n\t\t\t\t\t\tinstance.getRegistration().getName());\n\t\t\t} else if (\"UP\".equals(status)) {\n\t\t\t\t// Send recovery notification\n\t\t\t\tlog.info(\"Instance {} is back UP\",\n\t\t\t\t\t\tinstance.getRegistration().getName());\n\t\t\t}\n\t\t});\n\t}\n}\n```\n\n## Implementing Notifier Interface Directly\n\nFor full control, implement the `Notifier` interface:\n\n```java\nimport de.codecentric.boot.admin.server.notify.Notifier;\n\nimport reactor.core.publisher.Mono;\n\npublic class DirectNotifier implements Notifier {\n\n\tprivate final InstanceRepository repository;\n\tprivate boolean enabled = true;\n\n\tpublic DirectNotifier(InstanceRepository repository) {\n\t\tthis.repository = repository;\n\t}\n\n\t@Override\n\tpublic Mono<Void> notify(InstanceEvent event) {\n\t\tif (!enabled) {\n\t\t\treturn Mono.empty();\n\t\t}\n\n\t\treturn repository.find(event.getInstance())\n\t\t\t\t.flatMap(instance -> processNotification(event, instance))\n\t\t\t\t.then();\n\t}\n\n\tprivate Mono<Void> processNotification(InstanceEvent event, Instance instance) {\n\t\t// Custom notification logic\n\t\treturn Mono.fromRunnable(() -> {\n\t\t\t// Send notification\n\t\t});\n\t}\n\n\tpublic void setEnabled(boolean enabled) {\n\t\tthis.enabled = enabled;\n\t}\n}\n```\n\n## Configuration Properties\n\nMake your notifier configurable through application properties:\n\n```java\nimport org.springframework.boot.context.properties.ConfigurationProperties;\n\n@ConfigurationProperties(prefix = \"spring.boot.admin.notify.custom\")\npublic class CustomNotifierProperties {\n\tprivate final boolean enabled = true;\n\tprivate String apiUrl;\n\tprivate String apiKey;\n\tprivate final int timeout = 5000;\n\n\t// Getters and setters\n}\n```\n\n```java\n\n@Configuration\n@EnableConfigurationProperties(CustomNotifierProperties.class)\npublic class CustomNotifierConfiguration {\n\n\t@Bean\n\t@ConditionalOnProperty(prefix = \"spring.boot.admin.notify.custom\",\n\t\t\tname = \"enabled\",\n\t\t\thavingValue = \"true\",\n\t\t\tmatchIfMissing = true)\n\tpublic CustomNotifier customNotifier(InstanceRepository repository,\n\t\t\t\t\t\t\t\t\t\t CustomNotifierProperties properties) {\n\t\tCustomNotifier notifier = new CustomNotifier(repository);\n\t\tnotifier.setApiUrl(properties.getApiUrl());\n\t\tnotifier.setApiKey(properties.getApiKey());\n\t\tnotifier.setTimeout(properties.getTimeout());\n\t\treturn notifier;\n\t}\n}\n```\n\n```yaml title=\"application.yml\"\nspring:\n  boot:\n    admin:\n      notify:\n        custom:\n          enabled: true\n          api-url: https://api.example.com/notifications\n          api-key: ${NOTIFICATION_API_KEY}\n          timeout: 10000\n```\n\n## Combining with FilteringNotifier\n\nUse `FilteringNotifier` to allow runtime control:\n\n```java\n\n@Configuration\npublic class NotifierConfig {\n\n\t@Bean\n\tpublic FilteringNotifier filteringNotifier(InstanceRepository repository,\n\t\t\t\t\t\t\t\t\t\t\t   ObjectProvider<List<Notifier>> otherNotifiers) {\n\t\tCompositeNotifier delegate = new CompositeNotifier(\n\t\t\t\totherNotifiers.getIfAvailable(Collections::emptyList));\n\t\treturn new FilteringNotifier(delegate, repository);\n\t}\n\n\t@Primary\n\t@Bean(initMethod = \"start\", destroyMethod = \"stop\")\n\tpublic RemindingNotifier remindingNotifier(FilteringNotifier filteringNotifier,\n\t\t\t\t\t\t\t\t\t\t\t   InstanceRepository repository) {\n\t\tRemindingNotifier notifier = new RemindingNotifier(\n\t\t\t\tfilteringNotifier, repository);\n\t\tnotifier.setReminderPeriod(Duration.ofMinutes(10));\n\t\tnotifier.setCheckReminderInverval(Duration.ofSeconds(10));\n\t\treturn notifier;\n\t}\n\n\t@Bean\n\tpublic CustomNotifier customNotifier(InstanceRepository repository) {\n\t\treturn new CustomNotifier(repository);\n\t}\n}\n```\n\n## Testing Custom Notifiers\n\n```java\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mockito;\nimport reactor.test.StepVerifier;\n\npublic class CustomNotifierTest {\n\n\t@Test\n\tpublic void testNotification() {\n\t\tInstanceRepository repository = Mockito.mock(InstanceRepository.class);\n\t\tCustomNotifier notifier = new CustomNotifier(repository);\n\n\t\tInstance instance = Instance.create(InstanceId.of(\"test-instance\"))\n\t\t\t\t.register(Registration.create(\"test-app\", \"http://localhost:8080\")\n\t\t\t\t\t\t.build());\n\n\t\tInstanceEvent event = new InstanceStatusChangedEvent(\n\t\t\t\tinstance.getId(),\n\t\t\t\tinstance.getVersion(),\n\t\t\t\tStatusInfo.ofUp()\n\t\t);\n\n\t\tMockito.when(repository.find(instance.getId()))\n\t\t\t\t.thenReturn(Mono.just(instance));\n\n\t\tStepVerifier.create(notifier.notify(event))\n\t\t\t\t.verifyComplete();\n\t}\n}\n```\n\n## Best Practices\n\n1. **Extend AbstractEventNotifier** for most use cases - it provides essential features\n2. **Handle errors gracefully** - don't let notification failures affect the server\n3. **Use reactive programming** - return `Mono<Void>` for async operations\n4. **Make it configurable** - use `@ConfigurationProperties` for flexibility\n5. **Filter appropriately** - override `shouldNotify` to reduce noise\n6. **Log failures** - always log when notifications fail for debugging\n7. **Use WebClient** for HTTP calls - it's reactive and efficient\n8. **Consider rate limiting** - prevent notification storms\n9. **Test thoroughly** - ensure your notifier handles all event types\n10. **Document configuration** - provide clear examples for users\n\n## See Also\n\n- [Notifications Overview](./index.mdx) - Learn about the notification system\n- [Filtering Notifications](./index.mdx#filtering-notifications) - Control which notifications are sent\n- [Notification Reminders](./index.mdx#notification-reminder) - Set up reminder notifications\n- [Events](../10-Events.mdx) - Understand instance events\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/02-server/notifications/_category_.json",
    "content": "{\n  \"label\": \"Notifications\",\n  \"description\": \"Configure notifications to alert teams about instance status changes via email, Slack, Teams, and other channels.\"\n}\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/02-server/notifications/index.mdx",
    "content": "---\nsidebar_position: 80\nsidebar_custom_props:\n  icon: 'bell-ring'\n---\n\n# Notifications\n\nimport metadata from \"@sba/spring-boot-admin-server/target/classes/META-INF/spring-configuration-metadata.json\";\nimport { PropertyTable } from \"@sba/spring-boot-admin-docs/src/site/src/components/PropertyTable\";\nimport DocCardList from '@theme/DocCardList';\n\nYou can add your own Notifiers by adding Spring Beans which implement the `Notifier` interface, at best by extending`AbstractEventNotifier` or `AbstractStatusChangeNotifier`.\n\n```java title=\"CustomNotifier.java\"\npublic class CustomNotifier extends AbstractEventNotifier {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(CustomNotifier.class);\n\n    public CustomNotifier(InstanceRepository repository) {\n        super(repository);\n    }\n\n    @Override\n    protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {\n        return Mono.fromRunnable(() -> {\n            if (event instanceof InstanceStatusChangedEvent statusChangedEvent) {\n                LOGGER.info(\"Instance {} ({}) is {}\", instance.getRegistration().getName(), event.getInstance(),\n                        statusChangedEvent.getStatusInfo().getStatus());\n            }\n            else {\n                LOGGER.info(\"Instance {} ({}) {}\", instance.getRegistration().getName(), event.getInstance(),\n                        event.getType());\n            }\n        });\n    }\n}\n```\n\n## Notification Proxy Settings\n\nAll Notifiers which are using a `RestTemplate` can be configured to use a proxy.\n\n<PropertyTable\n  title=\"Notification Proxy configuration options\"\n  properties={metadata.properties}\n  filter={['notify.proxy']}\n  includeOnly={true}\n/>\n\n## Notification Reminder\n\nThe `RemindingNotifier` sends reminders for down/offline applications, it delegates the sending of notifications to another notifier.\n\nBy default, a reminder is triggered when a registered application changes to `DOWN` or `OFFLINE`. You can alter this behaviour via `setReminderStatuses()`. The reminder ends when either the status changes to a non-triggering status or the regarding application gets deregistered.\n\nBy default, the reminders are sent every 10 minutes, to change this use `setReminderPeriod()`. The `RemindingNotifier` itself doesn’t start the background thread to send the reminders, you need to take care of this as shown in the given example below;\n\nHow to configure reminders\n\n```java title=\"NotifierConfiguration.java\"\n@Configuration\npublic class NotifierConfiguration {\n    @Autowired\n    private Notifier notifier;\n\n    @Primary\n    @Bean(initMethod = \"start\", destroyMethod = \"stop\")\n    public RemindingNotifier remindingNotifier() {\n        RemindingNotifier notifier = new RemindingNotifier(notifier, repository);\n        notifier.setReminderPeriod(Duration.ofMinutes(10)); // (1)\n        notifier.setCheckReminderInverval(Duration.ofSeconds(10)); //(2)\n        return notifier;\n    }\n}\n```\n\n1. The reminders will be sent every 10 minutes.\n2. Schedules sending of due reminders every 10 seconds.\n\n## Filtering Notifications\n\nThe `FilteringNotifier` allows you to filter certain notification based on rules you can add/remove at runtime. It delegates the sending of notifications to another notifier.\n\nIf you add a `FilteringNotifier` to your `ApplicationContext` a RESTful interface on `notifications/filter` gets available. The restful interface provides the following methods for getting, adding, and deleting notification filters:\n\n* `GET notifications/filter`\n * Returns a list of all registered notification filters. Each containing the attributes `id`, `applicationName`, `expiry`, and `expired`.\n* `POST notifications/filters?instanceId=<yourInstanceId>&applicationName=<yourApplicationName>&ttl=<yourInstant>`\n * Posts a new notification filter for the application/instance of the given `instanceId` or `applicationName`. Either `instanceId` or `applicationName` must be set. The parameter `ttl` is optional and represents the expiration of the filter as an instant (the number of seconds from the epoch of `1970-01-01T00:00:00Z`).\n* `DELETE notifications/filters/{id}`\n * Deletes the notification filter with the requested id from the filters.\n\nYou may as well access all notification filter configurations via the main applications view inside SBA client, as seen in the screenshot below.\n\n![Sample notification filters](notification-filter.png)\n\nA `FilteringNotifier` might be useful, for instance, if you don’t want to receive notifications when deploying your applications. Before stopping the application, you can add an (expiring) filter via a `POST` request.\n\nHow to configure filtering\n\n```java title=\"NotifierConfig.java\"\n@Configuration(proxyBeanMethods = false)\npublic class NotifierConfig {\n\n    private final InstanceRepository repository;\n\n    private final ObjectProvider<List<Notifier>> otherNotifiers;\n\n    public NotifierConfig(InstanceRepository repository, ObjectProvider<List<Notifier>> otherNotifiers) {\n        this.repository = repository;\n        this.otherNotifiers = otherNotifiers;\n    }\n\n    @Bean\n    public FilteringNotifier filteringNotifier() { // (1)\n        CompositeNotifier delegate = new CompositeNotifier(this.otherNotifiers.getIfAvailable(Collections::emptyList));\n        return new FilteringNotifier(delegate, this.repository);\n    }\n\n    @Primary\n    @Bean(initMethod = \"start\", destroyMethod = \"stop\")\n    public RemindingNotifier remindingNotifier() { // (2)\n        RemindingNotifier notifier = new RemindingNotifier(filteringNotifier(), this.repository);\n        notifier.setReminderPeriod(Duration.ofMinutes(10));\n        notifier.setCheckReminderInverval(Duration.ofSeconds(10));\n        return notifier;\n    }\n\n}\n```\n\n1. Add the `FilteringNotifier` bean using a delegate (e.g. `MailNotifier` when configured)\n2. Add the `RemindingNotifier` as primary bean using the `FilteringNotifier` as delegate.\n\n:::tip\nThis example combines the reminding and filtering notifiers. This allows you to get notifications after the deployed application hasn’t restarted in a certain amount of time (until the filter expires).\n:::\n\n\n## Shipped Notifiers\n\n<DocCardList />\n\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/02-server/notifications/notifier-dingtalk.mdx",
    "content": "---\nsidebar_custom_props:\n  icon: 'notifications'\n---\nimport metadata from \"@sba/spring-boot-admin-server/target/classes/META-INF/spring-configuration-metadata.json\";\nimport { PropertyTable } from \"@sba/spring-boot-admin-docs/src/site/src/components/PropertyTable\";\n\n# DingTalk Notifications\n\nTo enable [DingTalk](https://www.dingtalk.com/) notifications you need to create and authorize a dingtalk bot and set the appropriate configuration properties for webhookUrl and secret.\n\n<PropertyTable\n  title=\"DingTalk notifications configuration options\"\n  properties={metadata.properties}\n  filter={['notify.dingtalk']}\n  includeOnly={true}\n/>\n\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/02-server/notifications/notifier-discord.mdx",
    "content": "---\nsidebar_custom_props:\n  icon: 'notifications'\n---\nimport metadata from \"@sba/spring-boot-admin-server/target/classes/META-INF/spring-configuration-metadata.json\";\nimport { PropertyTable } from \"@sba/spring-boot-admin-docs/src/site/src/components/PropertyTable\";\n\n# Discord Notifications\n\nTo enable Discord notifications you need to create a webhook and set the appropriate configuration property.\n\n<PropertyTable\n  title=\"Discord notifications configuration options\"\n  properties={metadata.properties}\n  filter={['notify.discord']}\n  includeOnly={true}\n/>\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/02-server/notifications/notifier-hipchat.mdx",
    "content": "---\nsidebar_custom_props:\n  icon: 'notifications'\n---\nimport metadata from \"@sba/spring-boot-admin-server/target/classes/META-INF/spring-configuration-metadata.json\";\nimport { PropertyTable } from \"@sba/spring-boot-admin-docs/src/site/src/components/PropertyTable\";\n\n# Hipchat Notifications\n\nTo enable [Hipchat](https://www.hipchat.com/) notifications you need to create an API token on your Hipchat account and set the appropriate configuration properties.\n\n<PropertyTable\n  title=\"Hipchat notifications configuration options\"\n  properties={metadata.properties}\n  filter={['notify.hipchat']}\n  includeOnly={true}\n/>\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/02-server/notifications/notifier-lets-chat.mdx",
    "content": "---\nsidebar_custom_props:\n  icon: 'notifications'\n---\nimport metadata from \"@sba/spring-boot-admin-server/target/classes/META-INF/spring-configuration-metadata.json\";\nimport { PropertyTable } from \"@sba/spring-boot-admin-docs/src/site/src/components/PropertyTable\";\n\n# Let’s Chat Notifications\n\nTo enable [Let’s Chat](https://sdelements.github.io/lets-chat/) notifications you need to add the host url and add the API token and username from Let’s Chat\n\n<PropertyTable\n  title=\"Let’s Chat notifications configuration options\"\n  properties={metadata.properties}\n  filter={['notify.letschat']}\n  includeOnly={true}\n/>\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/02-server/notifications/notifier-mail.mdx",
    "content": "---\nsidebar_custom_props:\n  icon: 'notifications'\n---\nimport metadata from \"@sba/spring-boot-admin-server/target/classes/META-INF/spring-configuration-metadata.json\";\nimport { PropertyTable } from \"@sba/spring-boot-admin-docs/src/site/src/components/PropertyTable\";\n\n# Mail Notifications\n\nMail notifications will be delivered as HTML emails rendered using https://www.thymeleaf.org/[Thymeleaf] templates.\nTo enable Mail notifications, configure a `JavaMailSender` using `spring-boot-starter-mail` and set a recipient.\n\n<figure>\n  ![mail-notification.png](mail-notification.png)\n  <figcaption>Sample Mail Notification with default template</figcaption>\n</figure>\n\n:::info\nTo prevent disclosure of sensitive information, the default mail template doesn’t show any metadata of the instance. If\nyou want to you show some of the metadata you can use a custom template.\n:::\n\n```xml title=\"Add spring-boot-starter-mail to your dependencies\"\n\n<dependency>\n  <groupId>org.springframework.boot</groupId>\n  <artifactId>spring-boot-starter-mail</artifactId>\n</dependency>\n```\n\n```properties title=\"application.properties\"\nspring.mail.host=smtp.example.com\nspring.boot.admin.notify.mail.to=admin@example.com\n```\n\n<PropertyTable\ntitle=\"Mail notifications configuration options\"\nproperties={metadata.properties}\nfilter={['notify.mail']}\nincludeOnly={true}\n/>\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/02-server/notifications/notifier-mattermost.mdx",
    "content": "---\nsidebar_custom_props:\n  icon: 'notifications'\n---\nimport metadata from \"@sba/spring-boot-admin-server/target/classes/META-INF/spring-configuration-metadata.json\";\nimport { PropertyTable } from \"@sba/spring-boot-admin-docs/src/site/src/components/PropertyTable\";\n\n# Mattermost Notifications\n\nTo enable [Mattermost](https://mattermost.com/) notifications you need to add a bot account under integrations on your Mattermost server and configure it appropriately.\n\n<PropertyTable\n  title=\"Mattermost notifications configuration options\"\n  properties={metadata.properties}\n  filter={['notify.mattermost']}\n  includeOnly={true}\n/>\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/02-server/notifications/notifier-msteams.mdx",
    "content": "---\nsidebar_custom_props:\n  icon: 'notifications'\n---\nimport metadata from \"@sba/spring-boot-admin-server/target/classes/META-INF/spring-configuration-metadata.json\";\nimport { PropertyTable } from \"@sba/spring-boot-admin-docs/src/site/src/components/PropertyTable\";\n\n# Microsoft Teams Notifications\n\nTo enable Microsoft Teams notifications you need to set up a connector webhook url and set the appropriate configuration property.\n\n<PropertyTable\n  title=\"Microsoft Teams notifications configuration options\"\n  properties={metadata.properties}\n  filter={['notify.ms-teams']}\n  includeOnly={true}\n/>\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/02-server/notifications/notifier-rocketchat.mdx",
    "content": "---\nsidebar_custom_props:\n  icon: 'notifications'\n---\nimport metadata from \"@sba/spring-boot-admin-server/target/classes/META-INF/spring-configuration-metadata.json\";\nimport { PropertyTable } from \"@sba/spring-boot-admin-docs/src/site/src/components/PropertyTable\";\n\n# RocketChat Notifications\n\nTo enable [Rocket.Chat](https://www.rocket.chat/) notifications you need a personal token access and create a room to send message with this token\n\n<PropertyTable\n  title=\"RocketChat notifications configuration options\"\n  properties={metadata.properties}\n  filter={['notify.rocketchat']}\n  includeOnly={true}\n/>\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/02-server/notifications/notifier-slack.mdx",
    "content": "---\nsidebar_custom_props:\n  icon: 'bell'\n---\nimport metadata from \"@sba/spring-boot-admin-server/target/classes/META-INF/spring-configuration-metadata.json\";\nimport { PropertyTable } from \"@sba/spring-boot-admin-docs/src/site/src/components/PropertyTable\";\n\n# Slack Notifications\n\nTo enable [Slack](https://slack.com/) notifications you need to add an incoming Webhook under custom integrations on your Slack account and configure it appropriately.\n\n<PropertyTable\n  title=\"Slack notifications configuration options\"\n  properties={metadata.properties}\n  filter={['notify.slack']}\n  includeOnly={true}\n/>\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/02-server/notifications/notifier-telegram.mdx",
    "content": "---\nsidebar_custom_props:\n  icon: 'notifications'\n---\nimport metadata from \"@sba/spring-boot-admin-server/target/classes/META-INF/spring-configuration-metadata.json\";\nimport { PropertyTable } from \"@sba/spring-boot-admin-docs/src/site/src/components/PropertyTable\";\n\n# Telegram Notifications\n\nTo enable [Telegram](https://telegram.org/) notifications you need to create and authorize a telegram bot and set the appropriate configuration properties for auth-token and chat-id.\n\n<PropertyTable\n  title=\"Telegram notifications configuration options\"\n  properties={metadata.properties}\n  filter={['notify.telegram']}\n  includeOnly={true}\n/>\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/02-server/notifications/notifier-webex.mdx",
    "content": "---\nsidebar_custom_props:\n  icon: 'notifications'\n---\nimport metadata from \"@sba/spring-boot-admin-server/target/classes/META-INF/spring-configuration-metadata.json\";\nimport { PropertyTable } from \"@sba/spring-boot-admin-docs/src/site/src/components/PropertyTable\";\n\n# Webex Notifications\n\nTo enable [Webex](https://www.webex.com/) notifications, you need to set the appropriate configuration properties for `auth-token` and `room-id`.\n\n<PropertyTable\n  title=\"Webex notifications configuration options\"\n  properties={metadata.properties}\n  filter={['notify.webex']}\n  includeOnly={true}\n/>\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/03-client/10-client-features.md",
    "content": "---\nsidebar_custom_props:\n  icon: 'features'\n---\n\n# Features\n\n## Show Version in Application List\n\nFor **Spring Boot** applications the easiest way to show the version, is to use the `build-info` goal from the\n`spring-boot-maven-plugin`, which generates the `META-INF/build-info.properties`. See also\nthe [Spring Boot Reference Guide](http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#howto-build-info).\n\nFor **non-Spring Boot** applications you can either add a `version` or `build.version` to the registration metadata and\nthe version will show up in the application list.\n\n```xml title=\"pom.xml\"\n<build>\n    <plugins>\n        <plugin>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-maven-plugin</artifactId>\n            <executions>\n                <execution>\n                    <goals>\n                        <goal>build-info</goal>\n                    </goals>\n                </execution>\n            </executions>\n        </plugin>\n    </plugins>\n</build>\n```\n\nTo generate the build-info in a gradle project, add the following snippet to your `build.gradle`:\n\n```groovy title=\"build.gradle\"\nspringBoot {\n    buildInfo()\n}\n```\n\n## JMX-Bean Management\n\nTo interact with JMX-beans in the admin UI you have to include [Jolokia](https://jolokia.org/) in your application and\nexpose it via the actuator endpoint. As Jolokia is servlet based there is no support for reactive applications.\n\nYou might want to set spring.jmx.enabled=true if you want to expose Spring beans via JMX.\n\n### Spring Boot 4 App\n\nSpring Boot 4 does not support Jolokia directly, you need a separate dependency for Spring Boot 4-based applications.\nSee https://jolokia.org/reference/html/manual/spring.html for more details.\n\n```xml title=\"pom.xml\"\n<dependency>\n    <groupId>org.jolokia</groupId>\n    <artifactId>jolokia-support-springboot</artifactId>\n    <version>2.5.0</version>\n</dependency>\n```\n\n### Spring Boot 3 App\n\nSpring Boot 3 does not support Jolokia directly, you need a separate dependency for Spring Boot 3-based applications.\nSee https://jolokia.org/reference/html/manual/spring.html for more details.\n\n```xml title=\"pom.xml\"\n<dependency>\n    <groupId>org.jolokia</groupId>\n    <artifactId>jolokia-support-springboot-3</artifactId>\n    <version>2.5.0</version>\n</dependency>\n```\n\n### Spring Boot 2 App\n\nYou can still monitor Spring Boot 2 applications with Jolokia endpoint using a Spring Boot Admin 3 server. Spring Boot 2\nprovided the actuator itself, so you only need the plain jolokia dependency.\n\n```xml title=\"pom.xml\"\n<dependency>\n    <groupId>org.jolokia</groupId>\n    <artifactId>jolokia-core</artifactId>\n</dependency>\n```\n\n## Logfile Viewer\n\nBy default, the logfile is not accessible via actuator endpoints and therefore not visible in Spring Boot Admin. In\norder to enable the logfile actuator endpoint you need to configure Spring Boot to write a logfile, either by setting\n`logging.file.path` or `logging.file.name`.\n\nSpring Boot Admin will detect everything that looks like an URL and render it as hyperlink.\n\nANSI color-escapes are also supported. You need to set a custom file log pattern as Spring Boot’s default one doesn’t\nuse colors.\n\nTo enforce the use of ANSI-colored output, set `spring.output.ansi.enabled=ALWAYS`. Otherwise Spring tries to detect if\nANSI-colored output is available and might disable it.\n\n```properties title=\"application.properties\"\nlogging.file.name=/var/log/sample-boot-application.log (1)\nlogging.pattern.file=%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID}){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx (2)\n```\n\n1. Destination the logfile is written to. Enables the logfile actuator endpoint.\n2. File log pattern using ANSI colors.\n\n## Show Tags per Instance\n\n`Tags` are a way to add visual markers per instance, they will appear in the application list as well as in the instance\nview. By default, no tags are added to instances, and it’s up to the client to specify the desired tags by adding the\ninformation to the metadata or info endpoint.\n\n```properties title=\"application.properties\"\n#using the metadata\nspring.boot.admin.client.instance.metadata.tags.environment=test\n\n#using the info endpoint\ninfo.tags.environment=test\n```\n\n## Spring Boot Admin Client\n\nThe Spring Boot Admin Client registers the application at the admin server. This is done by periodically doing an HTTP\npost request to the SBA Server providing information about the application.\n\n:::tip\nThere are plenty of properties to influence the way how the SBA Client registers your application. In case that doesn’t\nfit your needs, you can provide your own ApplicationFactory implementation.\n:::\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/03-client/20-registration.md",
    "content": "---\n\nsidebar_position: 20\nsidebar_custom_props:\n  icon: 'link'\n---\n\n# Application Registration\n\nThe Spring Boot Admin Client handles the registration of your application with the Admin Server through the\n`ApplicationRegistrator` and `ApplicationFactory` interfaces.\n\n## ApplicationRegistrator\n\nThe `ApplicationRegistrator` is responsible for managing the registration lifecycle:\n\n```java\npublic interface ApplicationRegistrator {\n\n    /**\n     * Registers the client application at spring-boot-admin-server.\n     * @return true if successful registration on at least one admin server\n     */\n    boolean register();\n\n    /**\n     * Tries to deregister currently registered application\n     */\n    void deregister();\n\n    /**\n     * @return the id of this client as given by the admin server.\n     * Returns null if not registered yet.\n     */\n    String getRegisteredId();\n}\n```\n\n### Default Implementation\n\nThe `DefaultApplicationRegistrator` automatically handles:\n\n- Initial registration on application startup\n- Periodic re-registration (heartbeat)\n- Automatic deregistration on shutdown\n- Retry logic for failed registrations\n\n### Configuration\n\n```yaml title=\"application.yml\"\nspring:\n  boot:\n    admin:\n      client:\n        url: http://localhost:8080  # Admin Server URL\n        period: 10000  # Registration interval in milliseconds\n        auto-registration: true  # Enable auto-registration\n        auto-deregistration: true  # Enable auto-deregistration on shutdown\n```\n\n### Registration Process\n\n1. **Application Startup**: The `RegistrationApplicationListener` triggers registration when `WebServerInitializedEvent`\n   is fired\n2. **Create Application**: `ApplicationFactory` creates the registration payload\n3. **HTTP POST**: Client sends POST request to `/instances` endpoint\n4. **Receive ID**: Server responds with an instance ID\n5. **Periodic Heartbeat**: Client re-registers at configured intervals\n6. **Shutdown Hook**: Application deregisters on graceful shutdown\n\n## ApplicationFactory\n\nThe `ApplicationFactory` is responsible for creating the `Application` object that contains all registration\ninformation.\n\n```java\npublic interface ApplicationFactory {\n    Application createApplication();\n}\n```\n\n### Default Implementation: DefaultApplicationFactory\n\nThe default factory gathers information from:\n\n- `InstanceProperties` - Client configuration\n- `ServerProperties` - Web server configuration\n- `ManagementServerProperties` - Actuator configuration\n- `PathMappedEndpoints` - Actuator endpoint mappings\n- `MetadataContributor` - Custom metadata\n\n```java\n@Override\npublic Application createApplication() {\n    return Application.create(getName())\n        .healthUrl(getHealthUrl())\n        .managementUrl(getManagementUrl())\n        .serviceUrl(getServiceUrl())\n        .metadata(getMetadata())\n        .build();\n}\n```\n\n### Application Properties\n\n#### Name\n\n```yaml\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          name: ${spring.application.name}  # Application name\n```\n\n#### Service URL\n\nThe URL where your application can be accessed:\n\n```yaml\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          service-url: https://my-app.example.com\n          # or let it auto-detect:\n          service-base-url: https://my-app.example.com\n          service-path: /\n```\n\nAuto-detection uses:\n\n1. Configured `service-url` (highest priority)\n2. `service-base-url` + `service-path`\n3. Auto-detected from server properties\n\n#### Management URL\n\nURL for actuator endpoints:\n\n```yaml\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          management-url: https://my-app.example.com/actuator\n          # or\n          management-base-url: https://my-app.example.com\nmanagement:\n  endpoints:\n    web:\n      base-path: /actuator\n```\n\n#### Health URL\n\nSpecific health endpoint URL:\n\n```yaml\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          health-url: https://my-app.example.com/actuator/health\n```\n\n### Host Type\n\nControl how the service host is determined:\n\n```yaml\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          service-host-type: IP  # or CANONICAL\n```\n\n- **`IP`**: Use the IP address\n- **`CANONICAL`**: Use the canonical hostname\n\n### Custom ApplicationFactory\n\nCreate a custom factory for specialized registration logic:\n\n```java\n@Component\npublic class CustomApplicationFactory implements ApplicationFactory {\n\n    private final InstanceProperties instance;\n    private final Environment environment;\n\n    public CustomApplicationFactory(InstanceProperties instance,\n                                   Environment environment) {\n        this.instance = instance;\n        this.environment = environment;\n    }\n\n    @Override\n    public Application createApplication() {\n        Map<String, String> metadata = new HashMap<>();\n        metadata.put(\"environment\", environment.getProperty(\"app.environment\"));\n        metadata.put(\"version\", environment.getProperty(\"app.version\"));\n        metadata.put(\"region\", environment.getProperty(\"cloud.region\"));\n\n        return Application.create(instance.getName())\n            .healthUrl(buildHealthUrl())\n            .managementUrl(buildManagementUrl())\n            .serviceUrl(buildServiceUrl())\n            .metadata(metadata)\n            .build();\n    }\n\n    private String buildHealthUrl() {\n        // Custom logic to build health URL\n        return \"https://my-app.com/health\";\n    }\n\n    private String buildManagementUrl() {\n        // Custom logic to build management URL\n        return \"https://my-app.com/management\";\n    }\n\n    private String buildServiceUrl() {\n        // Custom logic to build service URL\n        return \"https://my-app.com\";\n    }\n}\n```\n\n## Specialized ApplicationFactories\n\n### Servlet ApplicationFactory\n\nFor servlet-based applications:\n\n```java\npublic class ServletApplicationFactory extends DefaultApplicationFactory {\n    // Detects servlet port and context path automatically\n}\n```\n\n### Reactive ApplicationFactory\n\nFor WebFlux applications:\n\n```java\npublic class ReactiveApplicationFactory extends DefaultApplicationFactory {\n    // Detects Netty port and context automatically\n}\n```\n\n### Cloud Foundry ApplicationFactory\n\nFor Cloud Foundry deployments:\n\n```java\npublic class CloudFoundryApplicationFactory implements ApplicationFactory {\n    // Uses CF-specific environment variables:\n    // - vcap.application.application_id\n    // - vcap.application.instance_id\n    // - vcap.application.uris\n}\n```\n\nAutomatically activated when Cloud Foundry is detected.\n\n## Application Class\n\nThe `Application` class represents the registration payload:\n\n```java\npublic class Application {\n    private final String name;\n    private final String managementUrl;\n    private final String healthUrl;\n    private final String serviceUrl;\n    private final Map<String, String> metadata;\n\n    // Builder pattern\n    public static Builder create(String name) {\n        return new Builder(name);\n    }\n}\n```\n\n### Building an Application\n\n```java\nApplication app = Application.create(\"my-application\")\n    .healthUrl(\"http://localhost:8080/actuator/health\")\n    .managementUrl(\"http://localhost:8080/actuator\")\n    .serviceUrl(\"http://localhost:8080\")\n    .metadata(\"version\", \"1.0.0\")\n    .metadata(\"environment\", \"production\")\n    .build();\n```\n\n## Registration Lifecycle Events\n\nSpring Boot Admin Client fires application events during registration:\n\n```java\n@Component\npublic class RegistrationEventListener {\n\n    @EventListener\n    public void onRegistration(InstanceRegisteredEvent event) {\n        String instanceId = event.getRegistration().getInstanceId();\n        log.info(\"Registered with instance ID: {}\", instanceId);\n    }\n\n    @EventListener\n    public void onDeregistration(InstanceDeregisteredEvent event) {\n        log.info(\"Deregistered instance\");\n    }\n}\n```\n\n## Custom Registrator\n\nImplement custom registration logic:\n\n```java\n@Component\npublic class CustomApplicationRegistrator implements ApplicationRegistrator {\n\n    private final ApplicationFactory applicationFactory;\n    private final RestClient restClient;\n    private final String adminUrl;\n    private volatile String registeredId;\n\n    @Override\n    public boolean register() {\n        Application application = applicationFactory.createApplication();\n\n        try {\n            Map<String, Object> response = restClient.post()\n                .uri(adminUrl + \"/instances\")\n                .body(application)\n                .retrieve()\n                .body(new ParameterizedTypeReference<Map<String, Object>>() {});\n\n            this.registeredId = (String) response.get(\"id\");\n            log.info(\"Registered as: {}\", registeredId);\n            return true;\n        } catch (Exception e) {\n            log.error(\"Registration failed\", e);\n            return false;\n        }\n    }\n\n    @Override\n    public void deregister() {\n        if (registeredId != null) {\n            try {\n                restClient.delete()\n                    .uri(adminUrl + \"/instances/\" + registeredId)\n                    .retrieve()\n                    .toBodilessEntity();\n                log.info(\"Deregistered successfully\");\n            } catch (Exception e) {\n                log.error(\"Deregistration failed\", e);\n            }\n        }\n    }\n\n    @Override\n    public String getRegisteredId() {\n        return registeredId;\n    }\n}\n```\n\n## Troubleshooting\n\n### Registration Fails\n\nCheck:\n\n- Admin Server URL is correct and accessible\n- Network connectivity between client and server\n- Firewall rules allow outbound connections\n- Admin Server is running and healthy\n- Credentials are correct if security is enabled\n\n### Instance Not Appearing\n\nVerify:\n\n- Registration returned successfully (check logs)\n- Application name is configured\n- Health endpoint is accessible from Admin Server\n- Actuator endpoints are exposed\n\n### Repeated Re-registrations\n\nThis is normal behavior - the client re-registers periodically as a heartbeat. Adjust the period if needed:\n\n```yaml\nspring:\n  boot:\n    admin:\n      client:\n        period: 30000  # 30 seconds instead of default 10\n```\n\n## Best Practices\n\n1. **Use environment-specific URLs** for service URL in different environments\n2. **Configure appropriate metadata** to help identify instances\n3. **Set reasonable registration periods** - too frequent causes unnecessary load\n4. **Enable auto-deregistration** for clean shutdown\n5. **Use service discovery** for dynamic environments instead of direct client registration\n6. **Monitor registration logs** to ensure successful registration\n7. **Configure health check paths** correctly for proper monitoring\n\n## See Also\n\n- [Metadata](./30-metadata.md) - Learn about custom metadata\n- [Service Discovery](./40-service-discovery.md) - Alternative registration using Spring Cloud Discovery\n- [Client Configuration](./80-configuration.md) - Complete configuration reference\n- [Client Features](./10-client-features.md) - Additional client capabilities\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/03-client/30-metadata.md",
    "content": "---\nsidebar_position: 30\nsidebar_custom_props:\n  icon: 'link'\n---\n\n# Metadata and Tags\n\nMetadata allows you to attach custom information to your application registration, which can be used for filtering,\ngrouping, and providing additional context in the Spring Boot Admin UI.\n\n## MetadataContributor\n\nThe `MetadataContributor` interface enables you to programmatically add metadata to your application registration:\n\n```java\n@FunctionalInterface\npublic interface MetadataContributor {\n    Map<String, String> getMetadata();\n}\n```\n\n## Built-in Metadata Contributors\n\n### StartupDateMetadataContributor\n\nAutomatically adds the application startup timestamp:\n\n```java\npublic class StartupDateMetadataContributor implements MetadataContributor {\n\n    private final OffsetDateTime timestamp = OffsetDateTime.now();\n\n    @Override\n    public Map<String, String> getMetadata() {\n        return singletonMap(\"startup\",\n                          timestamp.format(DateTimeFormatter.ISO_DATE_TIME));\n    }\n}\n```\n\nThis metadata is automatically included and helps the Admin Server detect application restarts.\n\n### CloudFoundryMetadataContributor\n\nFor Cloud Foundry deployments, adds CF-specific metadata:\n\n```java\npublic class CloudFoundryMetadataContributor implements MetadataContributor {\n\n    @Override\n    public Map<String, String> getMetadata() {\n        Map<String, String> metadata = new HashMap<>();\n        metadata.put(\"applicationId\", vcapApplication.getApplicationId());\n        metadata.put(\"instanceId\", vcapApplication.getInstanceId());\n        // Additional CF metadata\n        return metadata;\n    }\n}\n```\n\nAutomatically activated when running on Cloud Foundry.\n\n### CompositeMetadataContributor\n\nCombines multiple metadata contributors:\n\n```java\npublic class CompositeMetadataContributor implements MetadataContributor {\n\n    private final List<MetadataContributor> delegates;\n\n    public CompositeMetadataContributor(List<MetadataContributor> delegates) {\n        this.delegates = delegates;\n    }\n\n    @Override\n    public Map<String, String> getMetadata() {\n        Map<String, String> metadata = new LinkedHashMap<>();\n        delegates.forEach(delegate -> metadata.putAll(delegate.getMetadata()));\n        return metadata;\n    }\n}\n```\n\nSpring Boot Admin automatically creates a composite contributor from all `MetadataContributor` beans.\n\n## Adding Metadata via Configuration\n\n### Static Metadata\n\nAdd static metadata through properties:\n\n```yaml title=\"application.yml\"\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          metadata:\n            team: platform-team\n            environment: production\n            region: us-east-1\n            version: 1.0.0\n            support-email: platform@example.com\n```\n\n### Dynamic Metadata from Environment\n\nUse property placeholders to inject environment-specific values:\n\n```yaml title=\"application.yml\"\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          metadata:\n            environment: ${APP_ENV:development}\n            version: ${APP_VERSION:unknown}\n            hostname: ${HOSTNAME:localhost}\n            pod-name: ${POD_NAME:}\n            namespace: ${NAMESPACE:default}\n```\n\n## Custom MetadataContributor\n\nCreate custom metadata contributors for dynamic or computed metadata:\n\n```java\nimport org.springframework.stereotype.Component;\nimport java.util.HashMap;\nimport java.util.Map;\n\n@Component\npublic class CustomMetadataContributor implements MetadataContributor {\n\n    private final Environment environment;\n    private final BuildProperties buildProperties;\n\n    public CustomMetadataContributor(Environment environment,\n                                    @Autowired(required = false) BuildProperties buildProperties) {\n        this.environment = environment;\n        this.buildProperties = buildProperties;\n    }\n\n    @Override\n    public Map<String, String> getMetadata() {\n        Map<String, String> metadata = new HashMap<>();\n\n        // Add build information\n        if (buildProperties != null) {\n            metadata.put(\"build.version\", buildProperties.getVersion());\n            metadata.put(\"build.time\", buildProperties.getTime().toString());\n            metadata.put(\"build.artifact\", buildProperties.getArtifact());\n        }\n\n        // Add environment information\n        metadata.put(\"spring.profiles\", String.join(\",\",\n                                                    environment.getActiveProfiles()));\n\n        // Add JVM information\n        metadata.put(\"java.version\", System.getProperty(\"java.version\"));\n        metadata.put(\"java.vendor\", System.getProperty(\"java.vendor\"));\n\n        // Add custom business metadata\n        metadata.put(\"feature-flags\",\n                    environment.getProperty(\"app.feature-flags\", \"\"));\n\n        return metadata;\n    }\n}\n```\n\n### Kubernetes Metadata\n\n```java\n@Component\n@ConditionalOnProperty(name = \"kubernetes.enabled\", havingValue = \"true\")\npublic class KubernetesMetadataContributor implements MetadataContributor {\n\n    @Override\n    public Map<String, String> getMetadata() {\n        Map<String, String> metadata = new HashMap<>();\n\n        // Read from environment variables set by Kubernetes\n        metadata.put(\"k8s.pod\", System.getenv(\"HOSTNAME\"));\n        metadata.put(\"k8s.namespace\", System.getenv(\"POD_NAMESPACE\"));\n        metadata.put(\"k8s.node\", System.getenv(\"NODE_NAME\"));\n        metadata.put(\"k8s.service-account\",\n                    System.getenv(\"SERVICE_ACCOUNT\"));\n\n        // Add labels as metadata\n        String labels = System.getenv(\"POD_LABELS\");\n        if (labels != null) {\n            metadata.put(\"k8s.labels\", labels);\n        }\n\n        return metadata;\n    }\n}\n```\n\n## Tags\n\nTags are a special type of metadata used for visual markers in the Admin UI. They appear as colored badges in the\napplication list and instance views.\n\n### Configuring Tags\n\n#### Via Metadata\n\n```yaml title=\"application.yml\"\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          metadata:\n            tags:\n              environment: production\n              region: us-west-2\n              tier: backend\n```\n\n#### Via Info Endpoint\n\n```yaml title=\"application.yml\"\ninfo:\n  tags:\n    environment: production\n    region: us-west-2\n    tier: backend\n```\n\n### Tag Display\n\nTags appear as colored badges:\n\n- In the applications list view\n- In the instance details header\n- Can be used for filtering and grouping\n\n### Dynamic Tags\n\nCreate tags dynamically based on runtime conditions:\n\n```java\n@Component\npublic class DynamicTagMetadataContributor implements MetadataContributor {\n\n    private final Environment environment;\n\n    public DynamicTagMetadataContributor(Environment environment) {\n        this.environment = environment;\n    }\n\n    @Override\n    public Map<String, String> getMetadata() {\n        Map<String, String> metadata = new HashMap<>();\n\n        // Environment tag\n        String env = environment.getProperty(\"spring.profiles.active\", \"default\");\n        metadata.put(\"tags.environment\", env);\n\n        // Deployment type\n        if (isKubernetes()) {\n            metadata.put(\"tags.platform\", \"kubernetes\");\n        } else if (isCloudFoundry()) {\n            metadata.put(\"tags.platform\", \"cloud-foundry\");\n        } else {\n            metadata.put(\"tags.platform\", \"standalone\");\n        }\n\n        // Health-based tag\n        metadata.put(\"tags.monitoring\", \"enabled\");\n\n        return metadata;\n    }\n\n    private boolean isKubernetes() {\n        return System.getenv(\"KUBERNETES_SERVICE_HOST\") != null;\n    }\n\n    private boolean isCloudFoundry() {\n        return System.getenv(\"VCAP_APPLICATION\") != null;\n    }\n}\n```\n\n## Metadata Use Cases\n\n### 1. Security Credentials\n\nPass credentials for actuator endpoint access:\n\n```yaml title=\"application.yml\"\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          metadata:\n            user.name: ${spring.security.user.name}\n            user.password: ${spring.security.user.password}\n```\n\n:::warning\nCredentials in metadata are masked in the Admin UI but transmitted over the network. Always use HTTPS when transmitting\nsensitive data.\n:::\n\n### 2. Service Discovery Integration\n\n#### Eureka\n\n```yaml title=\"application.yml\"\neureka:\n  instance:\n    metadata-map:\n      startup: ${random.int}  # Trigger update on restart\n      user.name: ${spring.security.user.name}\n      user.password: ${spring.security.user.password}\n      tags.environment: ${APP_ENV}\n```\n\n#### Consul\n\n```yaml title=\"application.yml\"\nspring:\n  cloud:\n    consul:\n      discovery:\n        metadata:\n          user-name: ${spring.security.user.name}  # Note: use dashes, not dots\n          user-password: ${spring.security.user.password}\n          environment: production\n```\n\n:::warning\nConsul does not allow dots (`.`) in metadata keys. Use dashes (`-`) instead.\n:::\n\n### 3. Grouping Applications\n\n```yaml title=\"application.yml\"\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          metadata:\n            group: Legacy Squad\n            squad: backend-team\n            cost-center: CC-1234\n```\n\nThe Admin UI can use the `group` metadata for visual grouping.\n\n### 4. Custom URLs and Paths\n\n```yaml title=\"application.yml\"\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          metadata:\n            management.context-path: /actuator\n            service-url: https://my-app.example.com\n            service-path: /api\n```\n\n### 5. Visibility Control\n\n```yaml title=\"application.yml\"\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          metadata:\n            hide-url: true  # Hide service URL in UI\n```\n\n## Accessing Metadata in Admin Server\n\nMetadata is available through the instance object:\n\n```java\n@Component\npublic class MetadataProcessor {\n\n    public void processInstance(Instance instance) {\n        Map<String, String> metadata = instance.getRegistration().getMetadata();\n\n        String environment = metadata.get(\"tags.environment\");\n        String team = metadata.get(\"team\");\n        String version = metadata.get(\"version\");\n\n        // Process metadata\n        log.info(\"Instance {} - Environment: {}, Team: {}, Version: {}\",\n                instance.getRegistration().getName(),\n                environment, team, version);\n    }\n}\n```\n\n## Best Practices\n\n1. **Use Meaningful Keys**: Use descriptive, hierarchical keys (e.g., `tags.environment`, `k8s.namespace`)\n2. **Avoid Sensitive Data**: Don't include secrets unless necessary; use secure transmission\n3. **Keep It Lightweight**: Don't overload metadata with large values\n4. **Use Tags for Visuals**: Leverage tags for important visual indicators\n5. **Document Metadata**: Maintain documentation of your metadata schema\n6. **Use Environment Variables**: Make metadata configurable per environment\n7. **Consistent Naming**: Use consistent naming conventions across services\n8. **Leverage Existing Info**: Use `/actuator/info` for build and git information\n\n## Metadata Security\n\n### Masked Metadata\n\nThe Admin Server masks certain metadata keys by default:\n\n- `password`\n- `secret`\n- `key`\n- `token`\n- `credentials`\n\nThese values are hidden in the UI but still transmitted to the server.\n\n### Secure Transmission\n\nAlways use HTTPS when transmitting sensitive metadata:\n\n```yaml\nspring:\n  boot:\n    admin:\n      client:\n        url: https://admin-server.example.com  # Use HTTPS\n```\n\n## See Also\n\n- [Client Features](./10-client-features.md) - Other client capabilities\n- [Registration](./20-registration.md) - Application registration process\n- [Service Discovery](./40-service-discovery.md) - Metadata in service discovery\n- [Security](../02-server/02-security.md) - Securing metadata transmission\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/03-client/40-service-discovery.md",
    "content": "---\nsidebar_position: 40\nsidebar_custom_props:\n  icon: 'cloud'\n---\n\n# Service Discovery Integration\n\nSpring Boot Admin integrates seamlessly with Spring Cloud Discovery services, allowing automatic registration without\nthe Spring Boot Admin Client library.\n\n## Overview\n\nWhen using service discovery, the Admin Server discovers applications automatically through the discovery client. This\neliminates the need for:\n\n- Spring Boot Admin Client dependency\n- Explicit Admin Server URL configuration\n- Manual registration code\n\n## Supported Discovery Services\n\n- **Eureka** (Netflix)\n- **Consul** (HashiCorp)\n- **Zookeeper** (Apache)\n- **Kubernetes** (via Spring Cloud Kubernetes)\n\n## Eureka Integration\n\n### Server Setup\n\nAdd Eureka Client to your Admin Server:\n\n```xml title=\"pom.xml\"\n<dependency>\n    <groupId>org.springframework.cloud</groupId>\n    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>\n</dependency>\n```\n\nEnable discovery in your Admin Server:\n\n```java title=\"SpringBootAdminApplication.java\"\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\nimport de.codecentric.boot.admin.server.config.EnableAdminServer;\n\n@EnableDiscoveryClient\n@EnableAdminServer\n@SpringBootApplication\npublic class SpringBootAdminApplication {\n    static void main(String[] args) {\n        SpringApplication.run(SpringBootAdminApplication.class, args);\n    }\n}\n```\n\nConfigure Eureka connection:\n\n```yaml title=\"application.yml\"\nspring:\n  application:\n    name: spring-boot-admin-server\n\neureka:\n  client:\n    serviceUrl:\n      defaultZone: http://localhost:8761/eureka/\n    registryFetchIntervalSeconds: 5\n  instance:\n    leaseRenewalIntervalInSeconds: 10\n    health-check-url-path: /actuator/health\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n  endpoint:\n    health:\n      show-details: ALWAYS\n```\n\n### Client Setup\n\nAdd Eureka Client to your application (no Admin Client needed):\n\n```xml title=\"pom.xml\"\n<dependency>\n    <groupId>org.springframework.cloud</groupId>\n    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>\n</dependency>\n```\n\nEnable discovery:\n\n```java\n@EnableDiscoveryClient\n@SpringBootApplication\npublic class MyApplication {\n    public static void main(String[] args) {\n        SpringApplication.run(MyApplication.class, args);\n    }\n}\n```\n\nConfigure Eureka and expose endpoints:\n\n```yaml title=\"application.yml\"\nspring:\n  application:\n    name: my-application\n\neureka:\n  client:\n    serviceUrl:\n      defaultZone: http://localhost:8761/eureka/\n  instance:\n    leaseRenewalIntervalInSeconds: 10\n    health-check-url-path: /actuator/health\n    metadata-map:\n      startup: ${random.int}  # Triggers info update on restart\n      user.name: ${spring.security.user.name}  # For secured actuators\n      user.password: ${spring.security.user.password}\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n  endpoint:\n    health:\n      show-details: ALWAYS\n```\n\n### Eureka Metadata\n\nAdd custom metadata through Eureka:\n\n```yaml title=\"application.yml\"\neureka:\n  instance:\n    metadata-map:\n      startup: ${random.int}\n      tags.environment: production\n      tags.region: us-east-1\n      team: platform\n      version: ${spring.application.version}\n```\n\n## Consul Integration\n\n### Server Setup\n\n```xml title=\"pom.xml\"\n<dependency>\n    <groupId>org.springframework.cloud</groupId>\n    <artifactId>spring-cloud-starter-consul-discovery</artifactId>\n</dependency>\n```\n\n```java\n@EnableDiscoveryClient\n@EnableAdminServer\n@SpringBootApplication\npublic class SpringBootAdminApplication {\n    static void main(String[] args) {\n        SpringApplication.run(SpringBootAdminApplication.class, args);\n    }\n}\n```\n\n```yaml title=\"application.yml\"\nspring:\n  application:\n    name: spring-boot-admin-server\n  cloud:\n    consul:\n      host: localhost\n      port: 8500\n      discovery:\n        prefer-ip-address: true\n        health-check-interval: 10s\n```\n\n### Client Setup\n\n```xml title=\"pom.xml\"\n<dependency>\n    <groupId>org.springframework.cloud</groupId>\n    <artifactId>spring-cloud-starter-consul-discovery</artifactId>\n</dependency>\n```\n\n```yaml title=\"application.yml\"\nspring:\n  application:\n    name: my-application\n  cloud:\n    consul:\n      host: localhost\n      port: 8500\n      discovery:\n        metadata:\n          user-name: ${spring.security.user.name}  # Note: dashes not dots!\n          user-password: ${spring.security.user.password}\n          environment: production\n          management-context-path: ${management.server.base-path:/actuator}\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n```\n\n:::warning\nConsul does not allow dots (`.`) in metadata keys. Use dashes (`-`) or underscores (`_`) instead:\n\n- ✅ `user-name` or `user_name`\n- ❌ `user.name`\n  :::\n\n## Zookeeper Integration\n\n### Server Setup\n\n```xml title=\"pom.xml\"\n<dependency>\n    <groupId>org.springframework.cloud</groupId>\n    <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>\n</dependency>\n```\n\n```yaml title=\"application.yml\"\nspring:\n  application:\n    name: spring-boot-admin-server\n  cloud:\n    zookeeper:\n      connect-string: localhost:2181\n      discovery:\n        enabled: true\n```\n\n### Client Setup\n\n```xml title=\"pom.xml\"\n<dependency>\n    <groupId>org.springframework.cloud</groupId>\n    <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>\n</dependency>\n```\n\n```yaml title=\"application.yml\"\nspring:\n  application:\n    name: my-application\n  cloud:\n    zookeeper:\n      connect-string: localhost:2181\n      discovery:\n        metadata:\n          user.name: ${spring.security.user.name}\n          user.password: ${spring.security.user.password}\n          management.context-path: /actuator\n```\n\n## Filtering Discovered Services\n\nBy default, the Admin Server monitors all discovered services. You can filter services using the `InstanceFilter`\ninterface.\n\n### Configuration-Based Filtering\n\n```yaml title=\"application.yml\"\nspring:\n  boot:\n    admin:\n      discovery:\n        ignored-services: consul,eureka,zookeeper  # Don't monitor discovery services\n```\n\n### Custom InstanceFilter\n\n```java\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.services.InstanceFilter;\nimport org.springframework.stereotype.Component;\n\n@Component\npublic class CustomInstanceFilter implements InstanceFilter {\n\n    @Override\n    public boolean test(Registration registration) {\n        String name = registration.getName();\n\n        // Ignore internal services\n        if (name.startsWith(\"internal-\")) {\n            return false;\n        }\n\n        // Only monitor services with specific metadata\n        String monitorable = registration.getMetadata().get(\"monitorable\");\n        if (!\"true\".equals(monitorable)) {\n            return false;\n        }\n\n        return true;\n    }\n}\n```\n\n## Instance Preference Strategy\n\nWhen multiple instances of the same service exist, configure which instance URL to use:\n\n```yaml title=\"application.yml\"\nspring:\n  boot:\n    admin:\n      discovery:\n        instance-prefer-ip: true  # Use IP instead of hostname\n```\n\n## Management Context Path\n\nIf your management endpoints are on a different port or path:\n\n```yaml title=\"application.yml (Client)\"\nmanagement:\n  server:\n    port: 9090  # Management on different port\n    base-path: /management\n\neureka:\n  instance:\n    metadata-map:\n      management.port: 9090\n      management.context-path: /management\n```\n\n## Health Check Configuration\n\nConfigure health check paths for discovery:\n\n```yaml title=\"application.yml\"\neureka:\n  instance:\n    health-check-url-path: /actuator/health\n    health-check-url: http://my-app.example.com/actuator/health\n    status-page-url-path: /actuator/info\n    home-page-url: /\n```\n\n## Service URL vs Management URL\n\nDiscovery services may return different URLs for the service and management endpoints:\n\n```yaml title=\"application.yml\"\neureka:\n  instance:\n    metadata-map:\n      management.context-path: /actuator  # Management endpoint path\n      service-url: https://my-app.example.com  # Public service URL\n      management-url: http://internal-app:8080/actuator  # Internal mgmt URL\n```\n\n## Securing Discovered Services\n\nPass credentials through metadata:\n\n```yaml title=\"application.yml\"\neureka:\n  instance:\n    metadata-map:\n      user.name: admin\n      user.password: ${ACTUATOR_PASSWORD}\n```\n\nOr configure on the Admin Server:\n\n```yaml title=\"application.yml (Admin Server)\"\nspring:\n  boot:\n    admin:\n      instance-auth:\n        enabled: true\n        default-user-name: admin\n        default-password: ${DEFAULT_PASSWORD}\n        service-map:\n          my-application:\n            user-name: app-admin\n            user-password: ${APP_PASSWORD}\n```\n\n## Advantages of Service Discovery\n\n1. **No Client Library Required**: Applications don't need Spring Boot Admin Client\n2. **Automatic Discovery**: New instances automatically appear\n3. **Centralized Configuration**: Manage discovery in one place\n4. **Load Balancing**: Discovery services handle load balancing\n5. **Health Checks**: Built-in health check integration\n6. **Service Metadata**: Rich metadata support\n\n## Disadvantages\n\n1. **Additional Infrastructure**: Requires running discovery service\n2. **Network Complexity**: Additional network hop\n3. **Discovery Lag**: Slight delay in detecting new instances\n4. **Metadata Limitations**: Some discovery services have metadata restrictions\n\n## Mixed Mode\n\nYou can use both service discovery and direct client registration simultaneously:\n\n```yaml title=\"application.yml\"\nspring:\n  boot:\n    admin:\n      client:\n        url: http://localhost:8080  # Direct registration\n        auto-registration: true\n\neureka:\n  client:\n    enabled: true  # Also register with Eureka\n```\n\nThis provides redundancy if one registration method fails.\n\n## Troubleshooting\n\n### Application Not Discovered\n\n1. **Check Discovery Registration**:\n   ```bash\n   # For Eureka\n   curl http://localhost:8761/eureka/apps\n   ```\n\n2. **Verify Admin Server Discovery Client**: Ensure `@EnableDiscoveryClient` is present\n\n3. **Check Network Connectivity**: Admin Server must reach discovery service\n\n4. **Review Metadata**: Ensure management URLs are correct\n\n### Incorrect Management URL\n\nSet explicit management metadata:\n\n```yaml\neureka:\n  instance:\n    metadata-map:\n      management.port: ${management.server.port}\n      management.context-path: ${management.server.base-path}\n```\n\n### Health Check Failures\n\nEnsure health endpoint is accessible:\n\n```yaml\nmanagement:\n  endpoint:\n    health:\n      show-details: ALWAYS\n  endpoints:\n    web:\n      exposure:\n        include: health,info\n```\n\n## Best Practices\n\n1. **Use Metadata for Configuration**: Leverage metadata for flexible configuration\n2. **Set Appropriate Intervals**: Balance between freshness and load\n3. **Implement Filters**: Don't monitor unnecessary services\n4. **Secure Metadata Transmission**: Use secure discovery service connections\n5. **Monitor Discovery Health**: Ensure discovery service is healthy\n6. **Document Metadata Schema**: Maintain clear metadata conventions\n7. **Test Failover**: Verify behavior when discovery service is down\n\n## See Also\n\n- [Registration](./20-registration.md) - Direct client registration\n- [Metadata](./30-metadata.md) - Working with metadata\n- [Eureka Sample](../09-samples/30-sample-eureka.md) - Complete Eureka example\n- [Consul Sample](../09-samples/40-sample-consul.md) - Complete Consul example\n- [Security](../02-server/02-security.md) - Securing discovered services\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/03-client/80-configuration.md",
    "content": "---\nsidebar_custom_props:\n  icon: 'configuration'\n---\n\n# Configuration\n\nIn addition to discovering and registering services, Spring Boot Admin makes use of metadata to control how individual\nclients are handled by the server.\nMetadata allows you to fine-tune the behavior of the admin server for each registered service and can influence how\nservices are displayed, monitored, or interacted with.\nA set of well-defined metadata attributes is recognized and evaluated directly on the server side.\nBecause the Spring Boot Admin client itself relies on the same Spring Cloud interfaces, these metadata properties can\nalso be applied consistently on the client, ensuring\na unified configuration approach across your entire system.\n\n__Instance metadata options__\n\n| Property name                                                                      | Description                                                                                                                                         |\n|------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|\n| `disable-url`                                                                      | Disables links of this instance in UI. Useful, when the URL does not point to a UI.                                                                 |\n| `group`                                                                            | Assign a group name. Used in UI to aggregate instances not by application but by assigned group.                                                    |\n| `hide-url`                                                                         | Hide URLs of the instance in UI. Useful, when running in a cluster, exposing a non routable URL.                                                    |\n| `service-url`                                                                      | Override the service url of the registered service. Allows to specify the actual URL to the UI. This does not affect management url.                |\n| `sidebar.links.N.label` <br/> `sidebar.links.N.url` <br/> `sidebar.links.N.iframe` | **label:** Shown in sidebar <br/>**url:** URL used as href in link or iframe. <br/> **iframe:** boolean value that allows to include URL as iframe. |\n| `tags.*`                                                                           | Tags as key-value-pairs to be associated with this instance.                                                                                        |\n| `user.name`<br/>`user.password`                                                    | Credentials being used to access the endpoints.                                                                                                     |\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/03-client/99-properties.mdx",
    "content": "---\nsidebar_custom_props:\n  icon: 'server'\n---\n\n# Properties\n\nimport metadata from \"@sba/spring-boot-admin-client/target/classes/META-INF/spring-configuration-metadata.json\";\nimport { PropertyTable } from \"@sba/spring-boot-admin-docs/src/site/src/components/PropertyTable\";\n\n<PropertyTable\n  properties={metadata.properties}\n  filter={['spring.boot.admin.client']}\n  includeOnly={true}\n/>\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/03-client/_category_.json",
    "content": "{\n  \"position\": 3,\n  \"label\": \"Clients\"\n}\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/03-client/index.md",
    "content": "---\nsidebar_custom_props:\n  icon: 'link'\n---\n\nimport DocCardList from '@theme/DocCardList';\n\n# Registering Clients\n\nSpring Boot Admin is built on top of the mechanisms provided by Spring Cloud. This means that it can integrate\nseamlessly with any Spring Cloud–compliant service discovery tool. Common options include Eureka, Kubernetes, Nacos, and\nmany others that implement the Spring Cloud Discovery interfaces.\n\nWhen it comes to connecting services, Spring Boot Admin offers flexible configuration options. You can choose to\nconfigure the services explicitly, so that the admin server is aware of them without relying on any discovery mechanism.\nThis approach is often useful in smaller environments or when the service landscape is relatively static.\n\nAlternatively, you can leverage Spring Cloud’s service discovery features. In this mode, Spring Boot Admin automatically\ndiscovers and registers services that are available within the configured discovery system. This reduces manual\nconfiguration overhead and is particularly well-suited for dynamic, cloud-native environments where services may scale\nup and down frequently.\n\nImportantly, Spring Boot Admin does not force you to choose one method exclusively. A hybrid setup is also possible,\nwhere some services are registered manually while others are discovered automatically through Spring Cloud. This allows\nyou to tailor the setup to your specific infrastructure and operational needs, combining the stability of manual\nconfiguration with the flexibility of automated discovery.\n\n**Key Features:**\n\n* **Automatic Registration:** The client can self-register with the Admin Server by sending regular status updates.\n* **Health and Metrics Exposure:** The client leverages Spring Boot Actuator to expose endpoints for monitoring health\n  status, system metrics, application logs, and other runtime data.\n* **Management Actions:** The Admin Server can interact with the client for actions such as restarting the application,\n  clearing caches, or triggering log file downloads.\n* **Secure Communication:** Spring Boot Admin supports configuring authentication and SSL to ensure secure communication\n  between the client and the server.\n\n <DocCardList />\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/04-integration/10-eureka.md",
    "content": "---\nsidebar_position: 10\nsidebar_custom_props:\n  icon: 'cloud'\n---\n\n# Eureka Integration\n\nNetflix Eureka is a service discovery and registration solution that integrates seamlessly with Spring Boot Admin. This\nguide shows how to set up automatic application discovery using Eureka.\n\n## Overview\n\nWith Eureka integration:\n\n- Applications register with Eureka server\n- Spring Boot Admin Server discovers applications automatically\n- No Spring Boot Admin Client library needed\n- Applications appear/disappear based on Eureka registration status\n\n## Architecture\n\n```mermaid\nflowchart LR\n    Apps[Applications]\n    Eureka[Eureka Server<br/>Service Registry]\n    Admin[Spring Boot Admin<br/>Server]\n\n    Apps -->|Register| Eureka\n    Admin -->|Discover| Eureka\n    Admin -.->|Monitor| Apps\n```\n\nApplications register with Eureka, and the Admin Server discovers them through Eureka's registry.\n\n## Setting Up Eureka Server\n\nFirst, you need a Eureka Server running. Here's a minimal setup:\n\n### Dependencies\n\n```xml title=\"pom.xml\"\n\n<dependency>\n    <groupId>org.springframework.cloud</groupId>\n    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>\n</dependency>\n```\n\n### Configuration\n\n```java title=\"EurekaServerApplication.java\"\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;\n\n@EnableEurekaServer\n@SpringBootApplication\npublic class EurekaServerApplication {\n\tstatic void main(String[] args) {\n\t\tSpringApplication.run(EurekaServerApplication.class, args);\n\t}\n}\n```\n\n```yaml title=\"application.yml\"\nserver:\n  port: 8761\n\neureka:\n  client:\n    registerWithEureka: false\n    fetchRegistry: false\n  server:\n    enableSelfPreservation: false\n```\n\n## Configuring Spring Boot Admin Server\n\n### Add Dependencies\n\nAdd Eureka client to your Admin Server:\n\n```xml title=\"pom.xml\"\n\n<dependencies>\n    <dependency>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-starter-server</artifactId>\n    </dependency>\n    <dependency>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-webflux</artifactId>\n    </dependency>\n    <dependency>\n        <groupId>org.springframework.cloud</groupId>\n        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>\n    </dependency>\n</dependencies>\n```\n\n### Enable Discovery\n\nEnable both Admin Server and Eureka Discovery:\n\n```java title=\"SpringBootAdminEurekaApplication.java\"\nimport de.codecentric.boot.admin.server.config.EnableAdminServer;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\n\n@EnableDiscoveryClient\n@EnableAdminServer\n@SpringBootApplication\npublic class SpringBootAdminEurekaApplication {\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(SpringBootAdminEurekaApplication.class, args);\n\t}\n}\n```\n\n### Configure Eureka Client\n\n```yaml title=\"application.yml\"\nspring:\n  application:\n    name: spring-boot-admin-server\n\neureka:\n  instance:\n    leaseRenewalIntervalInSeconds: 10\n    health-check-url-path: /actuator/health\n    metadata-map:\n      startup: ${random.int}  # Trigger info update on restart\n  client:\n    registryFetchIntervalSeconds: 5\n    serviceUrl:\n      defaultZone: http://localhost:8761/eureka/\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n  endpoint:\n    health:\n      show-details: ALWAYS\n```\n\n## Configuring Client Applications\n\nApplications only need Eureka Client - no Spring Boot Admin Client required!\n\n### Add Dependencies\n\n```xml title=\"pom.xml\"\n\n<dependency>\n    <groupId>org.springframework.cloud</groupId>\n    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>\n</dependency>\n```\n\n### Enable Discovery\n\n```java title=\"Application.java\"\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\n\n@EnableDiscoveryClient\n@SpringBootApplication\npublic class Application {\n\tstatic void main(String[] args) {\n\t\tSpringApplication.run(Application.class, args);\n\t}\n}\n```\n\n### Configure Application\n\n```yaml title=\"application.yml\"\nspring:\n  application:\n    name: my-application\n\neureka:\n  client:\n    serviceUrl:\n      defaultZone: http://localhost:8761/eureka/\n  instance:\n    leaseRenewalIntervalInSeconds: 10\n    health-check-url-path: /actuator/health\n    metadata-map:\n      startup: ${random.int}  # Triggers info/endpoint update on restart\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n  endpoint:\n    health:\n      show-details: ALWAYS\n```\n\n## Metadata Configuration\n\n### Adding Custom Metadata\n\nPass custom metadata through Eureka registration:\n\n```yaml title=\"application.yml\"\neureka:\n  instance:\n    metadata-map:\n      startup: ${random.int}\n      tags.environment: production\n      tags.region: us-east-1\n      team: platform\n      version: 1.0.0\n```\n\n### Security Credentials\n\nFor secured actuator endpoints, pass credentials via metadata:\n\n```yaml title=\"application.yml\"\neureka:\n  instance:\n    metadata-map:\n      user.name: ${spring.security.user.name}\n      user.password: ${spring.security.user.password}\n```\n\n:::warning\nCredentials in metadata are visible to anyone who can query Eureka. Use HTTPS and secure your Eureka server\nappropriately.\n:::\n\n### Management Port Configuration\n\nIf management endpoints are on a different port:\n\n```yaml title=\"application.yml\"\nserver:\n  port: 8080\n\nmanagement:\n  server:\n    port: 9090\n  endpoints:\n    web:\n      base-path: /actuator\n\neureka:\n  instance:\n    metadata-map:\n      management.port: 9090\n      management.context-path: /actuator\n```\n\n## Service URL Configuration\n\n### Custom Service URL\n\nOverride the service URL Spring Boot Admin uses:\n\n```yaml title=\"application.yml\"\neureka:\n  instance:\n    metadata-map:\n      service-url: https://my-app.example.com\n      management-url: http://internal-app:9090/actuator\n```\n\n### Prefer IP Address\n\nUse IP address instead of hostname:\n\n```yaml title=\"application.yml\"\neureka:\n  instance:\n    preferIpAddress: true\n```\n\nOn the Admin Server:\n\n```yaml title=\"application.yml\"\nspring:\n  boot:\n    admin:\n      discovery:\n        instancePreferIp: true\n```\n\n## Filtering Services\n\n### Ignore Specific Services\n\nDon't monitor certain services:\n\n```yaml title=\"application.yml (Admin Server)\"\nspring:\n  boot:\n    admin:\n      discovery:\n        ignored-services: eureka,config-server,gateway\n```\n\n### Custom Instance Filter\n\n```java\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.services.InstanceFilter;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration\npublic class InstanceFilterConfig {\n\n\t@Bean\n\tpublic InstanceFilter customInstanceFilter() {\n\t\treturn registration -> {\n\t\t\tString name = registration.getName();\n\n\t\t\t// Don't monitor infrastructure services\n\t\t\tif (name.startsWith(\"eureka\") ||\n\t\t\t\t\tname.startsWith(\"config\") ||\n\t\t\t\t\tname.startsWith(\"gateway\")) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// Only monitor services with specific metadata\n\t\t\tString monitorable = registration.getMetadata().get(\"monitor\");\n\t\t\treturn \"true\".equals(monitorable);\n\t\t};\n\t}\n}\n```\n\n## Health Check Configuration\n\n### Custom Health Check Path\n\n```yaml title=\"application.yml\"\neureka:\n  instance:\n    health-check-url-path: /actuator/health\n    health-check-url: http://my-app.example.com/actuator/health\n```\n\n### Status Page URL\n\n```yaml title=\"application.yml\"\neureka:\n  instance:\n    status-page-url-path: /actuator/info\n    status-page-url: http://my-app.example.com/actuator/info\n```\n\n## Securing Eureka Discovery\n\n### Basic Authentication\n\nSecure Eureka server with basic auth:\n\n```yaml title=\"application.yml (Admin Server)\"\neureka:\n  client:\n    serviceUrl:\n      defaultZone: http://user:password@localhost:8761/eureka/\n```\n\n### Mutual TLS\n\nConfigure SSL for Eureka communication:\n\n```yaml title=\"application.yml\"\neureka:\n  client:\n    serviceUrl:\n      defaultZone: https://localhost:8761/eureka/\n    tls:\n      enabled: true\n      key-store: classpath:keystore.p12\n      key-store-password: changeit\n      trust-store: classpath:truststore.jks\n      trust-store-password: changeit\n```\n\n## Docker Compose Example\n\n```yaml title=\"docker-compose.yml\"\nversion: '3'\n\nservices:\n  eureka:\n    image: springcloud/eureka\n    ports:\n      - \"8761:8761\"\n    environment:\n      - EUREKA_INSTANCE_HOSTNAME=eureka\n\n  spring-boot-admin:\n    build: ./admin-server\n    ports:\n      - \"8080:8080\"\n    environment:\n      - EUREKA_SERVICE_URL=http://eureka:8761\n    depends_on:\n      - eureka\n\n  my-application:\n    build: ./my-app\n    ports:\n      - \"8081:8081\"\n    environment:\n      - EUREKA_SERVICE_URL=http://eureka:8761\n    depends_on:\n      - eureka\n      - spring-boot-admin\n```\n\n## Troubleshooting\n\n### Application Not Appearing\n\n1. **Check Eureka Registration**:\n   ```bash\n   curl http://localhost:8761/eureka/apps\n   ```\n\n2. **Verify Admin Server sees Eureka apps**:\n   Check Admin Server logs for discovery messages\n\n3. **Confirm metadata is correct**:\n   ```bash\n   curl http://localhost:8761/eureka/apps/MY-APPLICATION | grep metadata\n   ```\n\n### Incorrect Management URL\n\nEnsure management metadata is set:\n\n```yaml\neureka:\n  instance:\n    metadata-map:\n      management.port: ${management.server.port}\n      management.context-path: ${management.server.base-path}\n```\n\n### Health Check Failures\n\nVerify health endpoint is accessible:\n\n```bash\ncurl http://localhost:8081/actuator/health\n```\n\nEnsure Eureka health check path matches:\n\n```yaml\neureka:\n  instance:\n    health-check-url-path: /actuator/health\n```\n\n### Stale Instances\n\nEureka may keep instances in registry after shutdown. Configure self-preservation:\n\n```yaml title=\"application.yml (Eureka Server)\"\neureka:\n  server:\n    enableSelfPreservation: false  # Disable for development\n    evictionIntervalTimerInMs: 5000\n```\n\n## Best Practices\n\n1. **Set Appropriate Intervals**: Balance between freshness and load\n   ```yaml\n   eureka:\n     instance:\n       leaseRenewalIntervalInSeconds: 10\n     client:\n       registryFetchIntervalSeconds: 5\n   ```\n\n2. **Use Startup Metadata**: Trigger updates on restart\n   ```yaml\n   eureka:\n     instance:\n       metadata-map:\n         startup: ${random.int}\n   ```\n\n3. **Expose Necessary Endpoints**: Only expose what's needed\n   ```yaml\n   management:\n     endpoints:\n       web:\n         exposure:\n           include: health,info,metrics\n   ```\n\n4. **Secure Metadata**: Use HTTPS for sensitive data\n   ```yaml\n   eureka:\n     client:\n       serviceUrl:\n         defaultZone: https://eureka:8761/eureka/\n   ```\n\n5. **Monitor Eureka Health**: Ensure Eureka is healthy\n   ```yaml\n   management:\n     health:\n       eureka:\n         enabled: true\n   ```\n\n6. **Use Instance Filters**: Don't monitor everything\n   ```java\n   @Bean\n   public InstanceFilter filter() {\n       return registration -> !registration.getName().startsWith(\"internal-\");\n   }\n   ```\n\n7. **Configure Timeouts**: Prevent hanging requests\n   ```yaml\n   eureka:\n     client:\n       eureka-server-connect-timeout-seconds: 5\n       eureka-server-read-timeout-seconds: 8\n   ```\n\n## Complete Example\n\nSee\nthe [spring-boot-admin-sample-eureka](https://github.com/codecentric/spring-boot-admin/tree/master/spring-boot-admin-samples/spring-boot-admin-sample-eureka/)\nproject for a complete working example.\n\n## See Also\n\n- [Service Discovery](../03-client/40-service-discovery.md) - Service discovery overview\n- [Eureka Sample](../09-samples/30-sample-eureka.md) - Detailed sample walkthrough\n- [Security](../02-server/02-security.md) - Securing discovered services\n- [Metadata](../03-client/30-metadata.md) - Working with metadata\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/04-integration/20-consul.md",
    "content": "---\nsidebar_position: 20\nsidebar_custom_props:\n  icon: 'cloud'\n---\n\n# Consul Integration\n\nHashiCorp Consul is a service mesh solution that provides service discovery, health checking, and key-value storage.\nThis guide shows how to integrate Spring Boot Admin with Consul.\n\n## Overview\n\nWith Consul integration:\n\n- Applications register with Consul\n- Spring Boot Admin Server discovers applications via Consul\n- Built-in health checks\n- No Spring Boot Admin Client library required\n\n## Architecture\n\n```mermaid\nflowchart LR\n    Apps[Applications]\n    Agent[Consul Agent]\n    Server[Consul Server<br/>Service Registry]\n    Admin[Spring Boot Admin<br/>Server]\n\n    Apps -->|Register| Agent\n    Agent -->|Sync| Server\n    Admin -->|Discover| Server\n    Admin -.->|Monitor| Apps\n```\n\n## Setting Up Consul\n\n### Install Consul\n\n```bash\n# macOS\nbrew install consul\n\n# Linux\nwget https://releases.hashicorp.com/consul/1.17.0/consul_1.17.0_linux_amd64.zip\nunzip consul_1.17.0_linux_amd64.zip\nsudo mv consul /usr/local/bin/\n\n# Docker\ndocker run -d --name=consul -p 8500:8500 consul:latest\n```\n\n### Start Consul\n\n```bash\n# Development mode\nconsul agent -dev\n\n# Production mode\nconsul agent -server -bootstrap-expect=1 -data-dir=/tmp/consul\n```\n\nAccess Consul UI at: `http://localhost:8500`\n\n## Configuring Spring Boot Admin Server\n\n### Add Dependencies\n\n```xml title=\"pom.xml\"\n<dependencies>\n    <dependency>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-starter-server</artifactId>\n    </dependency>\n    <dependency>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-webflux</artifactId>\n    </dependency>\n    <dependency>\n        <groupId>org.springframework.cloud</groupId>\n        <artifactId>spring-cloud-starter-consul-discovery</artifactId>\n    </dependency>\n</dependencies>\n```\n\n### Enable Discovery\n\n```java title=\"SpringBootAdminConsulApplication.java\"\nimport de.codecentric.boot.admin.server.config.EnableAdminServer;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\n\n@EnableDiscoveryClient\n@EnableAdminServer\n@SpringBootApplication\npublic class SpringBootAdminConsulApplication {\n    public static void main(String[] args) {\n        SpringApplication.run(SpringBootAdminConsulApplication.class, args);\n    }\n}\n```\n\n### Configure Consul Client\n\n```yaml title=\"application.yml\"\nspring:\n  application:\n    name: spring-boot-admin-server\n  cloud:\n    consul:\n      host: localhost\n      port: 8500\n      discovery:\n        preferIpAddress: true\n        health-check-interval: 10s\n        health-check-path: /actuator/health\n        instance-id: ${spring.application.name}:${random.value}\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n  endpoint:\n    health:\n      show-details: ALWAYS\n```\n\n### Ignore Consul Service\n\nDon't monitor Consul itself:\n\n```yaml title=\"application.yml\"\nspring:\n  boot:\n    admin:\n      discovery:\n        ignored-services: consul\n```\n\n## Configuring Client Applications\n\n### Add Dependencies\n\n```xml title=\"pom.xml\"\n<dependency>\n    <groupId>org.springframework.cloud</groupId>\n    <artifactId>spring-cloud-starter-consul-discovery</artifactId>\n</dependency>\n```\n\n### Enable Discovery\n\n```java\n@EnableDiscoveryClient\n@SpringBootApplication\npublic class Application {\n    public static void main(String[] args) {\n        SpringApplication.run(Application.class, args);\n    }\n}\n```\n\n### Configure Application\n\n```yaml title=\"application.yml\"\nspring:\n  application:\n    name: my-application\n  cloud:\n    consul:\n      host: localhost\n      port: 8500\n      discovery:\n        metadata:\n          management-context-path: ${management.server.base-path:/actuator}\n          health-path: ${management.endpoints.web.path-mapping.health:health}\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n      base-path: /actuator\n  endpoint:\n    health:\n      show-details: ALWAYS\n```\n\n## Metadata in Consul\n\n### Important: No Dots in Keys\n\n:::warning\nConsul **does not allow dots (`.`)** in metadata keys. Use dashes (`-`) or underscores (`_`) instead.\n:::\n\n**Wrong**:\n\n```yaml\nmetadata:\n  user.name: admin       # ❌ Won't work\n  user.password: secret  # ❌ Won't work\n```\n\n**Correct**:\n\n```yaml\nmetadata:\n  user-name: admin       # ✅ Works\n  user-password: secret  # ✅ Works\n```\n\n### Adding Custom Metadata\n\n```yaml title=\"application.yml\"\nspring:\n  cloud:\n    consul:\n      discovery:\n        metadata:\n          management-context-path: /actuator\n          health-path: /ping\n          user-name: ${spring.security.user.name}\n          user-password: ${spring.security.user.password}\n          tags-environment: production\n          tags-region: us-east-1\n          team: platform\n```\n\n### Tags\n\nConsul supports tags for grouping:\n\n```yaml title=\"application.yml\"\nspring:\n  cloud:\n    consul:\n      discovery:\n        tags:\n          - production\n          - us-east-1\n          - platform-team\n```\n\n## Custom Management Configuration\n\n### Different Management Port\n\n```yaml title=\"application.yml\"\nserver:\n  port: 8080\n\nmanagement:\n  server:\n    port: 9090\n  endpoints:\n    web:\n      base-path: /management\n\nspring:\n  cloud:\n    consul:\n      discovery:\n        metadata:\n          management-port: 9090\n          management-context-path: /management\n```\n\n### Custom Health Check Path\n\n```yaml title=\"application.yml\"\nmanagement:\n  endpoints:\n    web:\n      path-mapping:\n        health: /ping\n      base-path: /actuator\n\nspring:\n  cloud:\n    consul:\n      discovery:\n        health-check-path: /actuator/ping\n        metadata:\n          health-path: /ping\n```\n\n## Health Checks\n\n### Default Health Check\n\nConsul automatically creates HTTP health check:\n\n```yaml\nspring:\n  cloud:\n    consul:\n      discovery:\n        health-check-interval: 10s\n        health-check-timeout: 5s\n        health-check-path: /actuator/health\n```\n\n### Custom Health Check\n\n```yaml\nspring:\n  cloud:\n    consul:\n      discovery:\n        health-check-url: https://my-app.example.com/actuator/health\n        health-check-interval: 15s\n        health-check-critical-timeout: 30s\n```\n\n### TTL Health Check\n\nUse TTL-based health check instead of HTTP:\n\n```yaml\nspring:\n  cloud:\n    consul:\n      discovery:\n        health-check-interval: 10s\n        heartbeat:\n          enabled: true\n          ttl-value: 15\n          ttl-unit: s\n```\n\n## Service Filtering\n\n### By Service Name\n\n```yaml title=\"application.yml (Admin Server)\"\nspring:\n  boot:\n    admin:\n      discovery:\n        ignored-services: consul,config-server\n```\n\n### By Metadata\n\n```java\n@Bean\npublic InstanceFilter consulInstanceFilter() {\n    return registration -> {\n        // Only monitor services with 'monitor' tag\n        Map<String, String> metadata = registration.getMetadata();\n        return \"true\".equals(metadata.get(\"monitor\"));\n    };\n}\n```\n\n### By Tags\n\n```java\n@Bean\npublic InstanceFilter tagBasedFilter() {\n    return registration -> {\n        String tags = registration.getMetadata().get(\"tags\");\n        return tags != null && tags.contains(\"production\");\n    };\n}\n```\n\n## Securing Consul\n\n### ACL Token\n\n```yaml title=\"application.yml\"\nspring:\n  cloud:\n    consul:\n      host: localhost\n      port: 8500\n      discovery:\n        acl-token: ${CONSUL_ACL_TOKEN}\n```\n\n### TLS/SSL\n\n```yaml title=\"application.yml\"\nspring:\n  cloud:\n    consul:\n      host: localhost\n      port: 8501\n      scheme: https\n      tls:\n        enabled: true\n        cert-path: /path/to/cert.pem\n        key-path: /path/to/key.pem\n        ca-cert-path: /path/to/ca.pem\n```\n\n## Docker Compose Example\n\n```yaml title=\"docker-compose.yml\"\nversion: '3'\n\nservices:\n  consul:\n    image: consul:latest\n    ports:\n      - \"8500:8500\"\n      - \"8600:8600/udp\"\n    command: agent -server -ui -bootstrap-expect=1 -client=0.0.0.0\n\n  spring-boot-admin:\n    build: ./admin-server\n    ports:\n      - \"8080:8080\"\n    environment:\n      - SPRING_CLOUD_CONSUL_HOST=consul\n    depends_on:\n      - consul\n\n  my-application:\n    build: ./my-app\n    ports:\n      - \"8081:8081\"\n    environment:\n      - SPRING_CLOUD_CONSUL_HOST=consul\n    depends_on:\n      - consul\n```\n\n## Kubernetes Integration\n\nFor Kubernetes, use Consul Connect:\n\n```yaml title=\"application.yml\"\nspring:\n  cloud:\n    consul:\n      host: consul.service.consul\n      port: 8500\n      discovery:\n        preferIpAddress: false\n        hostname: ${HOSTNAME}.my-app.default.svc.cluster.local\n        metadata:\n          k8s-namespace: ${POD_NAMESPACE:default}\n          k8s-pod: ${HOSTNAME}\n```\n\n## Troubleshooting\n\n### Service Not Appearing\n\n1. **Check Consul registration**:\n   ```bash\n   curl http://localhost:8500/v1/catalog/services\n   curl http://localhost:8500/v1/health/service/my-application\n   ```\n\n2. **Verify health check**:\n   ```bash\n   consul catalog services\n   consul catalog nodes -service=my-application\n   ```\n\n3. **Check metadata**:\n   ```bash\n   curl http://localhost:8500/v1/catalog/service/my-application | jq\n   ```\n\n### Metadata Not Working\n\nEnsure no dots in keys:\n\n```yaml\n# Wrong\nmetadata:\n  user.name: admin\n\n# Correct\nmetadata:\n  user-name: admin\n```\n\n### Health Check Failing\n\nVerify endpoint is accessible:\n\n```bash\ncurl http://localhost:8081/actuator/health\n```\n\nCheck Consul health status:\n\n```bash\nconsul catalog nodes -service=my-application -detailed\n```\n\n### Deregistration Issues\n\nConfigure critical timeout:\n\n```yaml\nspring:\n  cloud:\n    consul:\n      discovery:\n        health-check-critical-timeout: 30s\n```\n\n## Best Practices\n\n1. **Use Instance IDs**: Ensure unique instance identifiers\n   ```yaml\n   spring:\n     cloud:\n       consul:\n         discovery:\n           instance-id: ${spring.application.name}:${random.value}\n   ```\n\n2. **Configure Health Checks**: Set appropriate intervals\n   ```yaml\n   spring:\n     cloud:\n       consul:\n         discovery:\n           health-check-interval: 10s\n           health-check-critical-timeout: 1m\n   ```\n\n3. **Use Metadata for Credentials**: Avoid hardcoding\n   ```yaml\n   spring:\n     cloud:\n       consul:\n         discovery:\n           metadata:\n             user-name: ${ACTUATOR_USER}\n             user-password: ${ACTUATOR_PASSWORD}\n   ```\n\n4. **Prefer IP Address**: For container environments\n   ```yaml\n   spring:\n     cloud:\n       consul:\n         discovery:\n           preferIpAddress: true\n   ```\n\n5. **Use Tags**: For service categorization\n   ```yaml\n   spring:\n     cloud:\n       consul:\n         discovery:\n           tags:\n             - production\n             - microservice\n   ```\n\n6. **Enable Deregistration**: Clean up on shutdown\n   ```yaml\n   spring:\n     cloud:\n       consul:\n         discovery:\n           deregister: true\n   ```\n\n7. **Monitor Consul Health**: Ensure Consul is operational\n   ```bash\n   consul members\n   consul info\n   ```\n\n## Complete Example\n\nSee\nthe [spring-boot-admin-sample-consul](https://github.com/codecentric/spring-boot-admin/tree/master/spring-boot-admin-samples/spring-boot-admin-sample-consul/)\nproject for a complete working example.\n\n## See Also\n\n- [Service Discovery](../03-client/40-service-discovery.md) - Service discovery overview\n- [Consul Sample](../09-samples/40-sample-consul.md) - Detailed sample walkthrough\n- [Metadata](../03-client/30-metadata.md) - Working with metadata\n- [Security](../02-server/02-security.md) - Securing discovered services\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/04-integration/30-zookeeper.md",
    "content": "---\nsidebar_position: 30\nsidebar_custom_props:\n  icon: 'cloud'\n---\n\n# Zookeeper Integration\n\nApache Zookeeper is a centralized coordination service that can be used for service discovery with Spring Cloud\nZookeeper. This guide shows how to integrate Spring Boot Admin with Zookeeper.\n\n## Overview\n\nWith Zookeeper integration:\n\n- Applications register with Zookeeper\n- Spring Boot Admin Server discovers applications via Zookeeper\n- Automatic ephemeral node management\n- No Spring Boot Admin Client library required\n\n## Setting Up Zookeeper\n\n### Install Zookeeper\n\n```bash\n# macOS\nbrew install zookeeper\n\n# Linux\nwget https://downloads.apache.org/zookeeper/zookeeper-3.8.3/apache-zookeeper-3.8.3-bin.tar.gz\ntar -xzf apache-zookeeper-3.8.3-bin.tar.gz\ncd apache-zookeeper-3.8.3-bin\n\n# Docker\ndocker run -d --name zookeeper -p 2181:2181 zookeeper:latest\n```\n\n### Start Zookeeper\n\n```bash\n# Direct\nzkServer start\n\n# Docker\ndocker start zookeeper\n```\n\nVerify Zookeeper is running:\n\n```bash\necho ruok | nc localhost 2181\n# Should respond with: imok\n```\n\n## Configuring Spring Boot Admin Server\n\n### Add Dependencies\n\n```xml title=\"pom.xml\"\n<dependencies>\n    <dependency>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-starter-server</artifactId>\n    </dependency>\n    <dependency>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-webflux</artifactId>\n    </dependency>\n    <dependency>\n        <groupId>org.springframework.cloud</groupId>\n        <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>\n    </dependency>\n</dependencies>\n```\n\n### Enable Discovery\n\n```java title=\"SpringBootAdminZookeeperApplication.java\"\nimport de.codecentric.boot.admin.server.config.EnableAdminServer;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\n\n@EnableDiscoveryClient\n@EnableAdminServer\n@SpringBootApplication\npublic class SpringBootAdminZookeeperApplication {\n    public static void main(String[] args) {\n        SpringApplication.run(SpringBootAdminZookeeperApplication.class, args);\n    }\n}\n```\n\n### Configure Zookeeper Client\n\n```yaml title=\"application.yml\"\nspring:\n  application:\n    name: spring-boot-admin-server\n  cloud:\n    zookeeper:\n      connect-string: localhost:2181\n      discovery:\n        enabled: true\n        register: true\n        root: /services\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n  endpoint:\n    health:\n      show-details: ALWAYS\n```\n\n## Configuring Client Applications\n\n### Add Dependencies\n\n```xml title=\"pom.xml\"\n<dependency>\n    <groupId>org.springframework.cloud</groupId>\n    <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>\n</dependency>\n```\n\n### Enable Discovery\n\n```java\n@EnableDiscoveryClient\n@SpringBootApplication\npublic class Application {\n    public static void main(String[] args) {\n        SpringApplication.run(Application.class, args);\n    }\n}\n```\n\n### Configure Application\n\n```yaml title=\"application.yml\"\nspring:\n  application:\n    name: my-application\n  cloud:\n    zookeeper:\n      connect-string: localhost:2181\n      discovery:\n        enabled: true\n        register: true\n        metadata:\n          management.context-path: /actuator\n          user.name: ${spring.security.user.name}\n          user.password: ${spring.security.user.password}\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n  endpoint:\n    health:\n      show-details: ALWAYS\n```\n\n## Metadata Configuration\n\n### Adding Custom Metadata\n\n```yaml title=\"application.yml\"\nspring:\n  cloud:\n    zookeeper:\n      discovery:\n        metadata:\n          management.context-path: /actuator\n          user.name: admin\n          user.password: secret\n          tags.environment: production\n          tags.region: us-east-1\n          team: platform\n          version: 1.0.0\n```\n\n### Management Port\n\n```yaml title=\"application.yml\"\nserver:\n  port: 8080\n\nmanagement:\n  server:\n    port: 9090\n  endpoints:\n    web:\n      base-path: /actuator\n\nspring:\n  cloud:\n    zookeeper:\n      discovery:\n        metadata:\n          management.port: 9090\n          management.context-path: /actuator\n```\n\n## Instance Configuration\n\n### Instance ID\n\n```yaml\nspring:\n  cloud:\n    zookeeper:\n      discovery:\n        instance-id: ${spring.application.name}:${random.value}\n```\n\n### Prefer IP Address\n\n```yaml\nspring:\n  cloud:\n    zookeeper:\n      discovery:\n        preferIpAddress: true\n```\n\n### Custom Service Name\n\n```yaml\nspring:\n  cloud:\n    zookeeper:\n      discovery:\n        serviceName: custom-service-name\n```\n\n## Connection Configuration\n\n### Connection Timeout\n\n```yaml\nspring:\n  cloud:\n    zookeeper:\n      connect-string: localhost:2181\n      max-retries: 10\n      max-sleep-ms: 500\n      connection-timeout: 15000\n      session-timeout: 60000\n```\n\n### Multiple Zookeeper Servers\n\n```yaml\nspring:\n  cloud:\n    zookeeper:\n      connect-string: zk1:2181,zk2:2181,zk3:2181\n```\n\n### Zookeeper Path\n\n```yaml\nspring:\n  cloud:\n    zookeeper:\n      discovery:\n        root: /services\n        uriSpec: '{scheme}://{address}:{port}'\n```\n\n## Docker Compose Example\n\n```yaml title=\"docker-compose.yml\"\nversion: '3'\n\nservices:\n  zookeeper:\n    image: zookeeper:3.8\n    ports:\n      - \"2181:2181\"\n    environment:\n      - ZOO_MY_ID=1\n\n  spring-boot-admin:\n    build: ./admin-server\n    ports:\n      - \"8080:8080\"\n    environment:\n      - SPRING_CLOUD_ZOOKEEPER_CONNECT_STRING=zookeeper:2181\n    depends_on:\n      - zookeeper\n\n  my-application:\n    build: ./my-app\n    ports:\n      - \"8081:8081\"\n    environment:\n      - SPRING_CLOUD_ZOOKEEPER_CONNECT_STRING=zookeeper:2181\n    depends_on:\n      - zookeeper\n```\n\n## Troubleshooting\n\n### Connection Failures\n\nCheck Zookeeper is running:\n\n```bash\necho ruok | nc localhost 2181\n```\n\nVerify connection:\n\n```bash\nzkCli.sh -server localhost:2181\nls /services\n```\n\n### Service Not Appearing\n\nList registered services:\n\n```bash\nzkCli.sh -server localhost:2181\nls /services\nget /services/my-application\n```\n\n### Session Timeout\n\nIncrease session timeout:\n\n```yaml\nspring:\n  cloud:\n    zookeeper:\n      session-timeout: 120000  # 2 minutes\n```\n\n## Best Practices\n\n1. **Configure Retries**:\n   ```yaml\n   spring:\n     cloud:\n       zookeeper:\n         max-retries: 10\n         max-sleep-ms: 500\n   ```\n\n2. **Use Ensemble**:\n   ```yaml\n   spring:\n     cloud:\n       zookeeper:\n         connect-string: zk1:2181,zk2:2181,zk3:2181\n   ```\n\n3. **Set Appropriate Timeouts**:\n   ```yaml\n   spring:\n     cloud:\n       zookeeper:\n         connection-timeout: 15000\n         session-timeout: 60000\n   ```\n\n4. **Use Instance IDs**:\n   ```yaml\n   spring:\n     cloud:\n       zookeeper:\n         discovery:\n           instance-id: ${spring.application.name}:${random.value}\n   ```\n\n5. **Monitor Zookeeper Health**:\n   ```bash\n   echo mntr | nc localhost 2181\n   ```\n\n## Complete Example\n\nSee\nthe [spring-boot-admin-sample-zookeeper](https://github.com/codecentric/spring-boot-admin/tree/master/spring-boot-admin-samples/spring-boot-admin-sample-zookeeper/)\nproject for a complete working example.\n\n## See Also\n\n- [Service Discovery](../03-client/40-service-discovery.md) - Service discovery overview\n- [Zookeeper Sample](../09-samples/50-sample-zookeeper.md) - Detailed sample walkthrough\n- [Metadata](../03-client/30-metadata.md) - Working with metadata\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/04-integration/40-hazelcast.md",
    "content": "---\nsidebar_position: 40\nsidebar_custom_props:\n  icon: 'server'\n---\n\n# Hazelcast Clustering\n\nHazelcast provides distributed data structures for clustering multiple Spring Boot Admin Server instances. This enables\nhigh availability and shared state across servers.\n\n## Overview\n\nWith Hazelcast clustering:\n\n- Multiple Admin Server instances share event store\n- No single point of failure\n- Automatic synchronization across nodes\n- Distributed notifications\n\n## Architecture\n\n```mermaid\nflowchart TB\n    Apps[Applications]\n\n    subgraph Cluster[\"Hazelcast Cluster\"]\n        HZ[(Hazelcast<br/>Distributed Data)]\n    end\n\n    subgraph Server1[\"Admin Server 1\"]\n        ES1[Event Store]\n    end\n\n    subgraph Server2[\"Admin Server 2\"]\n        ES2[Event Store]\n    end\n\n    Apps -->|Register| Server1\n    Apps -->|Register| Server2\n\n    Server1 <-->|Sync| HZ\n    Server2 <-->|Sync| HZ\n\n    ES1 -.->|Shared State| HZ\n    ES2 -.->|Shared State| HZ\n```\n\n## Why Hazelcast?\n\n- **High Availability**: No single point of failure\n- **Scalability**: Add more Admin Server instances\n- **Shared State**: All servers see the same application state\n- **Distributed Events**: Events propagated across cluster\n- **Simple Setup**: Minimal configuration required\n\n## Setting Up Hazelcast\n\n### Add Dependencies\n\n```xml title=\"pom.xml\"\n<dependencies>\n    <dependency>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-starter-server</artifactId>\n    </dependency>\n    <dependency>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-webflux</artifactId>\n    </dependency>\n    <dependency>\n        <groupId>com.hazelcast</groupId>\n        <artifactId>hazelcast</artifactId>\n    </dependency>\n</dependencies>\n```\n\n### Configure Hazelcast\n\n```java title=\"HazelcastConfig.java\"\nimport com.hazelcast.config.Config;\nimport com.hazelcast.config.MapConfig;\nimport com.hazelcast.config.MergePolicyConfig;\nimport com.hazelcast.core.Hazelcast;\nimport com.hazelcast.core.HazelcastInstance;\nimport com.hazelcast.map.IMap;\nimport com.hazelcast.spi.merge.PutIfAbsentMergePolicy;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.eventstore.HazelcastEventStore;\nimport de.codecentric.boot.admin.server.eventstore.InstanceEventStore;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport java.util.List;\n\n@Configuration\npublic class HazelcastConfig {\n\n    @Bean\n    public Config hazelcastConfig() {\n        MapConfig mapConfig = new MapConfig(\"spring-boot-admin-event-store\")\n            .setBackupCount(1)\n            .setMergePolicyConfig(new MergePolicyConfig(\n                PutIfAbsentMergePolicy.class.getName(), 100));\n\n        Config config = new Config();\n        config.addMapConfig(mapConfig);\n        config.setProperty(\"hazelcast.jmx\", \"true\");\n\n        // Network configuration\n        config.getNetworkConfig()\n            .setPort(5701)\n            .setPortAutoIncrement(true)\n            .getJoin()\n            .getMulticastConfig()\n            .setEnabled(true);\n\n        return config;\n    }\n\n    @Bean\n    public HazelcastInstance hazelcastInstance(Config hazelcastConfig) {\n        return Hazelcast.newHazelcastInstance(hazelcastConfig);\n    }\n\n    @Bean\n    public InstanceEventStore eventStore(HazelcastInstance hazelcastInstance) {\n        IMap<InstanceId, List<InstanceEvent>> map =\n            hazelcastInstance.getMap(\"spring-boot-admin-event-store\");\n        return new HazelcastEventStore(100, map);\n    }\n}\n```\n\n### Basic Configuration\n\n```yaml title=\"application.yml\"\nspring:\n  application:\n    name: spring-boot-admin-server\n\nhazelcast:\n  network:\n    port: 5701\n    port-auto-increment: true\n    join:\n      multicast:\n        enabled: true\n      tcp-ip:\n        enabled: false\n```\n\n## Network Configuration\n\n### Multicast (Development)\n\nAutomatic discovery using multicast:\n\n```java\nconfig.getNetworkConfig()\n    .getJoin()\n    .getMulticastConfig()\n    .setEnabled(true)\n    .setMulticastGroup(\"224.2.2.3\")\n    .setMulticastPort(54327);\n```\n\n```yaml\nhazelcast:\n  network:\n    join:\n      multicast:\n        enabled: true\n        multicast-group: 224.2.2.3\n        multicast-port: 54327\n```\n\n### TCP/IP (Production)\n\nExplicit member list for production:\n\n```java\nconfig.getNetworkConfig()\n    .getJoin()\n    .getMulticastConfig()\n    .setEnabled(false);\n\nconfig.getNetworkConfig()\n    .getJoin()\n    .getTcpIpConfig()\n    .setEnabled(true)\n    .addMember(\"192.168.1.100\")\n    .addMember(\"192.168.1.101\")\n    .addMember(\"192.168.1.102\");\n```\n\n```yaml\nhazelcast:\n  network:\n    join:\n      multicast:\n        enabled: false\n      tcp-ip:\n        enabled: true\n        members:\n          - 192.168.1.100\n          - 192.168.1.101\n          - 192.168.1.102\n```\n\n### Kubernetes\n\nFor Kubernetes deployments:\n\n```xml\n<dependency>\n    <groupId>com.hazelcast</groupId>\n    <artifactId>hazelcast-kubernetes</artifactId>\n</dependency>\n```\n\n```java\nconfig.getNetworkConfig()\n    .getJoin()\n    .getMulticastConfig()\n    .setEnabled(false);\n\nconfig.getNetworkConfig()\n    .getJoin()\n    .getKubernetesConfig()\n    .setEnabled(true)\n    .setProperty(\"namespace\", \"default\")\n    .setProperty(\"service-name\", \"spring-boot-admin\");\n```\n\n## Event Store Configuration\n\n### Map Configuration\n\n```java\nMapConfig mapConfig = new MapConfig(\"spring-boot-admin-event-store\")\n    .setBackupCount(1)  // Number of backup copies\n    .setAsyncBackupCount(0)  // Async backups\n    .setTimeToLiveSeconds(0)  // No expiration\n    .setMaxIdleSeconds(0)  // No idle timeout\n    .setMergePolicyConfig(new MergePolicyConfig(\n        PutIfAbsentMergePolicy.class.getName(), 100));\n```\n\n### Event Store Size\n\nLimit events per instance:\n\n```java\n@Bean\npublic InstanceEventStore eventStore(HazelcastInstance hazelcastInstance) {\n    IMap<InstanceId, List<InstanceEvent>> map =\n        hazelcastInstance.getMap(\"spring-boot-admin-event-store\");\n    return new HazelcastEventStore(500, map);  // Max 500 events per instance\n}\n```\n\n## High Availability Setup\n\n### Load Balancer Configuration\n\n```nginx\nupstream spring_boot_admin {\n    least_conn;\n    server admin1:8080;\n    server admin2:8080;\n    server admin3:8080;\n}\n\nserver {\n    listen 80;\n    server_name admin.example.com;\n\n    location / {\n        proxy_pass http://spring_boot_admin;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n}\n```\n\n### Session Persistence\n\nUse sticky sessions for the UI:\n\n```nginx\nupstream spring_boot_admin {\n    ip_hash;  # Sticky sessions\n    server admin1:8080;\n    server admin2:8080;\n}\n```\n\nOr use Spring Session:\n\n```xml\n<dependency>\n    <groupId>org.springframework.session</groupId>\n    <artifactId>spring-session-hazelcast</artifactId>\n</dependency>\n```\n\n```java\n@EnableHazelcastHttpSession\n@Configuration\npublic class SessionConfig {\n    // Hazelcast will be used for session storage\n}\n```\n\n## Docker Compose Example\n\n```yaml title=\"docker-compose.yml\"\nversion: '3'\n\nservices:\n  admin1:\n    build: ./admin-server\n    ports:\n      - \"8080:8080\"\n    environment:\n      - HAZELCAST_MEMBERS=admin1,admin2,admin3\n      - SERVER_PORT=8080\n\n  admin2:\n    build: ./admin-server\n    ports:\n      - \"8081:8080\"\n    environment:\n      - HAZELCAST_MEMBERS=admin1,admin2,admin3\n      - SERVER_PORT=8080\n\n  admin3:\n    build: ./admin-server\n    ports:\n      - \"8082:8080\"\n    environment:\n      - HAZELCAST_MEMBERS=admin1,admin2,admin3\n      - SERVER_PORT=8080\n\n  nginx:\n    image: nginx:alpine\n    ports:\n      - \"80:80\"\n    volumes:\n      - ./nginx.conf:/etc/nginx/nginx.conf\n    depends_on:\n      - admin1\n      - admin2\n      - admin3\n```\n\n## Monitoring Hazelcast\n\n### Management Center\n\nUse Hazelcast Management Center:\n\n```yaml\nhazelcast:\n  management-center:\n    enabled: true\n    url: http://localhost:8083/mancenter\n```\n\n### JMX Monitoring\n\nEnable JMX:\n\n```java\nconfig.setProperty(\"hazelcast.jmx\", \"true\");\n```\n\n### Health Checks\n\nCheck cluster health:\n\n```java\n@Component\npublic class HazelcastHealthCheck {\n\n    private final HazelcastInstance hazelcastInstance;\n\n    public HazelcastHealthCheck(HazelcastInstance hazelcastInstance) {\n        this.hazelcastInstance = hazelcastInstance;\n    }\n\n    public boolean isHealthy() {\n        return hazelcastInstance.getCluster().getMembers().size() > 0;\n    }\n\n    public int getClusterSize() {\n        return hazelcastInstance.getCluster().getMembers().size();\n    }\n}\n```\n\n## Troubleshooting\n\n### Split Brain\n\nConfigure merge policy:\n\n```java\nmapConfig.setMergePolicyConfig(new MergePolicyConfig(\n    PutIfAbsentMergePolicy.class.getName(), 100));\n```\n\n### Members Not Joining\n\n1. **Check network connectivity**:\n   ```bash\n   telnet admin1 5701\n   ```\n\n2. **Verify multicast**:\n   ```bash\n   # Check if multicast is enabled\n   ip maddr show\n   ```\n\n3. **Check logs**:\n   ```\n   Hazelcast logs will show connection attempts\n   ```\n\n### Performance Issues\n\n1. **Increase backup count**:\n   ```java\n   mapConfig.setBackupCount(2);\n   ```\n\n2. **Use async backups**:\n   ```java\n   mapConfig.setAsyncBackupCount(1);\n   ```\n\n3. **Monitor map size**:\n   ```java\n   IMap map = hazelcastInstance.getMap(\"spring-boot-admin-event-store\");\n   log.info(\"Map size: {}\", map.size());\n   ```\n\n## Best Practices\n\n1. **Use TCP/IP in Production**: Multicast may not work in cloud environments\n\n2. **Configure Appropriate Backups**:\n   ```java\n   mapConfig.setBackupCount(1);  // At least 1 backup\n   ```\n\n3. **Set Event Store Limits**:\n   ```java\n   new HazelcastEventStore(500, map);  // Reasonable limit\n   ```\n\n4. **Monitor Cluster Health**: Use Management Center or JMX\n\n5. **Use Load Balancer**: Distribute traffic across servers\n\n6. **Enable Session Persistence**: For seamless failover\n\n7. **Configure Network Properly**: Especially in Kubernetes/Docker\n\n## Complete Example\n\nSee\nthe [spring-boot-admin-sample-hazelcast](https://github.com/codecentric/spring-boot-admin/tree/master/spring-boot-admin-samples/spring-boot-admin-sample-hazelcast/)\nproject for a complete working example.\n\n## See Also\n\n- [Clustering](../02-server/20-Clustering.mdx) - Clustering overview\n- [Persistence](../02-server/30-persistence.md) - Event store details\n- [Hazelcast Sample](../09-samples/60-sample-hazelcast.md) - Detailed sample walkthrough\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/04-integration/_category_.json",
    "content": "{\n  \"position\": 4,\n  \"label\": \"Integration\",\n}\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/04-integration/index.md",
    "content": "---\nsidebar_position: 40\nsidebar_custom_props:\n  icon: 'puzzle'\n---\n\n# Integration\n\nSpring Boot Admin integrates seamlessly with various service discovery solutions and clustering technologies. This\nsection covers how to set up and configure these integrations.\n\n## Service Discovery\n\nInstead of using the Spring Boot Admin Client library, you can leverage Spring Cloud Discovery services to automatically\nregister applications:\n\n- **[Eureka](./10-eureka.md)** - Netflix Eureka service discovery\n- **[Consul](./20-consul.md)** - HashiCorp Consul service mesh\n- **[Zookeeper](./30-zookeeper.md)** - Apache Zookeeper coordination service\n\n### Benefits\n\n- No client library dependency required\n- Automatic discovery of new instances\n- Built-in health checking\n- Service metadata support\n- Load balancing integration\n\n## Clustering\n\nFor high-availability deployments, Spring Boot Admin supports clustering:\n\n- **[Hazelcast](./40-hazelcast.md)** - Distributed event store and coordination\n\n### Benefits\n\n- Shared event store across cluster nodes\n- No single point of failure\n- Automatic synchronization\n- Distributed notifications\n\n## Choosing an Integration\n\n### Use Service Discovery When:\n\n- You already have a service discovery infrastructure\n- Running in a microservices environment\n- Need automatic service registration\n- Want to leverage existing service mesh features\n\n### Use Direct Client Registration When:\n\n- Simple deployment with few applications\n- No service discovery infrastructure\n- Need full control over registration\n- Running in traditional environments\n\n### Use Clustering When:\n\n- Require high availability\n- Multiple Admin Server instances\n- Need shared state across servers\n- Running in production with SLAs\n\n## Integration Patterns\n\n### Pattern 1: Service Discovery Only\n\n```\nApplications → Service Discovery (Eureka/Consul) ← Admin Server\n```\n\nApplications register with service discovery, Admin Server discovers them automatically.\n\n### Pattern 2: Direct Registration with Clustering\n\n```\nApplications → Admin Server 1 ←→ Hazelcast ←→ Admin Server 2 ← Applications\n```\n\nApplications use client library, Admin Servers share state via Hazelcast.\n\n### Pattern 3: Service Discovery with Clustering\n\n```\nApplications → Service Discovery ← Admin Server 1 ←→ Hazelcast ←→ Admin Server 2\n```\n\nCombines automatic discovery with high availability.\n\n## Quick Comparison\n\n| Feature              | Eureka    | Consul         | Zookeeper    | Hazelcast  |\n|----------------------|-----------|----------------|--------------|------------|\n| Type                 | Discovery | Discovery + KV | Coordination | Clustering |\n| Setup Complexity     | Medium    | Medium         | High         | Low        |\n| Spring Cloud Support | Excellent | Excellent      | Good         | N/A        |\n| Health Checks        | Built-in  | Built-in       | Custom       | N/A        |\n| Metadata Support     | Yes       | Limited        | Yes          | N/A        |\n| HA                   | Yes       | Yes            | Yes          | Yes        |\n| Persistence          | In-memory | Persistent     | Persistent   | In-memory  |\n\n## Getting Started\n\n1. Choose your integration based on your infrastructure\n2. Follow the specific guide for setup instructions\n3. Configure your applications appropriately\n4. Test the integration in development\n5. Deploy to production with monitoring\n\n## See Also\n\n- [Service Discovery](../03-client/40-service-discovery.md) - Client-side discovery configuration\n- [Clustering](../02-server/20-Clustering.mdx) - Admin Server clustering details\n- [Samples](../09-samples/) - Working example projects\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/05-security/10-server-authentication.md",
    "content": "---\nsidebar_position: 10\nsidebar_custom_props:\n  icon: 'shield'\n---\n\n# Server Authentication\n\nSecure your Spring Boot Admin Server using Spring Security to protect the UI and API endpoints.\n\n## Overview\n\nA secured Admin Server requires:\n\n1. **Spring Security dependency**\n2. **SecurityFilterChain configuration**\n3. **User credentials** (in-memory, database, LDAP, OAuth2, etc.)\n4. **CSRF protection** with exemptions for client registration\n\n---\n\n## Quick Start\n\n### 1. Add Spring Security Dependency\n\n**Maven**:\n\n```xml\n<dependency>\n    <groupId>org.springframework.boot</groupId>\n    <artifactId>spring-boot-starter-security</artifactId>\n</dependency>\n```\n\n**Gradle**:\n\n```gradle\nimplementation 'org.springframework.boot:spring-boot-starter-security'\n```\n\n### 2. Basic Configuration\n\n**Minimal security** with default Spring Boot user:\n\n```yaml\nspring:\n  security:\n    user:\n      name: admin\n      password: ${ADMIN_PASSWORD}\n```\n\nThis provides:\n\n- Form login at `/login`\n- HTTP Basic authentication for API\n- Single user with username `admin`\n\n### 3. Access the UI\n\nNavigate to `http://localhost:8080`, and you'll be redirected to the login page.\n\n---\n\n## Complete Security Configuration\n\nFor more control, use a custom `SecurityFilterChain`:\n\n```java\npackage com.example.admin;\n\nimport java.util.UUID;\n\nimport org.springframework.boot.autoconfigure.security.SecurityProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.security.config.Customizer;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.core.userdetails.User;\nimport org.springframework.security.core.userdetails.UserDetails;\nimport org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;\nimport org.springframework.security.crypto.password.PasswordEncoder;\nimport org.springframework.security.provisioning.InMemoryUserDetailsManager;\nimport org.springframework.security.web.SecurityFilterChain;\nimport org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;\nimport org.springframework.security.web.authentication.www.BasicAuthenticationFilter;\nimport org.springframework.security.web.csrf.CookieCsrfTokenRepository;\nimport org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;\nimport org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;\n\nimport de.codecentric.boot.admin.server.config.AdminServerProperties;\n\nimport static org.springframework.http.HttpMethod.DELETE;\nimport static org.springframework.http.HttpMethod.POST;\n\n@Configuration\npublic class SecurityConfig {\n\n    private final AdminServerProperties adminServer;\n\n    public SecurityConfig(AdminServerProperties adminServer) {\n        this.adminServer = adminServer;\n    }\n\n    @Bean\n    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n        // Redirect to login after successful authentication\n        SavedRequestAwareAuthenticationSuccessHandler successHandler =\n            new SavedRequestAwareAuthenticationSuccessHandler();\n        successHandler.setTargetUrlParameter(\"redirectTo\");\n        successHandler.setDefaultTargetUrl(adminServer.path(\"/\"));\n\n        http\n            .authorizeHttpRequests(auth -> auth\n                // Permit access to static resources\n                .requestMatchers(PathPatternRequestMatcher.withDefaults()\n                    .matcher(adminServer.path(\"/assets/**\")))\n                .permitAll()\n                // Permit access to login page\n                .requestMatchers(PathPatternRequestMatcher.withDefaults()\n                    .matcher(adminServer.path(\"/login\")))\n                .permitAll()\n                // Permit Admin Server's own actuator endpoints\n                .requestMatchers(PathPatternRequestMatcher.withDefaults()\n                    .matcher(adminServer.path(\"/actuator/info\")))\n                .permitAll()\n                .requestMatchers(PathPatternRequestMatcher.withDefaults()\n                    .matcher(adminServer.path(\"/actuator/health\")))\n                .permitAll()\n                // Require authentication for all other requests\n                .anyRequest().authenticated()\n            )\n            // Form login for UI\n            .formLogin(formLogin -> formLogin\n                .loginPage(adminServer.path(\"/login\"))\n                .successHandler(successHandler)\n            )\n            // Logout configuration\n            .logout(logout -> logout\n                .logoutUrl(adminServer.path(\"/logout\"))\n            )\n            // HTTP Basic for API clients\n            .httpBasic(Customizer.withDefaults());\n\n        // CSRF configuration (see CSRF Protection section)\n        http.addFilterAfter(new CustomCsrfFilter(), BasicAuthenticationFilter.class)\n            .csrf(csrf -> csrf\n                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())\n                .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())\n                .ignoringRequestMatchers(\n                    // Exempt client registration endpoints\n                    PathPatternRequestMatcher.withDefaults()\n                        .matcher(POST, adminServer.path(\"/instances\")),\n                    PathPatternRequestMatcher.withDefaults()\n                        .matcher(DELETE, adminServer.path(\"/instances/*\")),\n                    // Exempt Admin Server's own actuator\n                    PathPatternRequestMatcher.withDefaults()\n                        .matcher(adminServer.path(\"/actuator/**\"))\n                )\n            );\n\n        // Remember-me functionality\n        http.rememberMe(rememberMe -> rememberMe\n            .key(UUID.randomUUID().toString())\n            .tokenValiditySeconds(1209600) // 2 weeks\n        );\n\n        return http.build();\n    }\n\n    @Bean\n    public InMemoryUserDetailsManager userDetailsService(PasswordEncoder passwordEncoder) {\n        UserDetails user = User.builder()\n            .username(\"admin\")\n            .password(passwordEncoder.encode(System.getenv(\"ADMIN_PASSWORD\")))\n            .roles(\"ADMIN\")\n            .build();\n\n        return new InMemoryUserDetailsManager(user);\n    }\n\n    @Bean\n    public PasswordEncoder passwordEncoder() {\n        return new BCryptPasswordEncoder();\n    }\n}\n```\n\n### Custom CSRF Filter\n\nRequired to make CSRF token available to JavaScript:\n\n```java\npackage com.example.admin;\n\nimport java.io.IOException;\n\nimport jakarta.servlet.FilterChain;\nimport jakarta.servlet.ServletException;\nimport jakarta.servlet.http.Cookie;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.springframework.security.web.csrf.CsrfToken;\nimport org.springframework.web.filter.OncePerRequestFilter;\nimport org.springframework.web.util.WebUtils;\n\npublic class CustomCsrfFilter extends OncePerRequestFilter {\n\n    public static final String CSRF_COOKIE_NAME = \"XSRF-TOKEN\";\n\n    @Override\n    protected void doFilterInternal(HttpServletRequest request,\n                                    HttpServletResponse response,\n                                    FilterChain filterChain)\n            throws ServletException, IOException {\n\n        CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());\n\n        if (csrf != null) {\n            Cookie cookie = WebUtils.getCookie(request, CSRF_COOKIE_NAME);\n            String token = csrf.getToken();\n\n            if (cookie == null || token != null && !token.equals(cookie.getValue())) {\n                cookie = new Cookie(CSRF_COOKIE_NAME, token);\n                cookie.setPath(\"/\");\n                response.addCookie(cookie);\n            }\n        }\n\n        filterChain.doFilter(request, response);\n    }\n}\n```\n\n---\n\n## Configuration Options\n\n### Context Path\n\nIf your Admin Server uses a custom context path:\n\n```yaml\nspring:\n  boot:\n    admin:\n      context-path: /admin\n```\n\nAdjust security matchers:\n\n```java\n.requestMatchers(PathPatternRequestMatcher.withDefaults()\n    .matcher(adminServer.path(\"/assets/**\")))\n.permitAll()\n```\n\nThe `adminServer.path()` method handles context path automatically.\n\n### Remember-Me\n\nEnable persistent sessions:\n\n```java\nhttp.rememberMe(rememberMe -> rememberMe\n    .key(UUID.randomUUID().toString())           // Unique key\n    .tokenValiditySeconds(1209600)               // 2 weeks\n    .rememberMeParameter(\"remember-me\")          // Form parameter name\n)\n```\n\n**Note**: Remember-me requires a `UserDetailsService` bean.\n\n### Session Management\n\nConfigure session behavior:\n\n```java\nhttp.sessionManagement(session -> session\n    .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)\n    .maximumSessions(1)                          // Max 1 session per user\n    .maxSessionsPreventsLogin(false)             // Invalidate old session\n)\n```\n\n---\n\n## User Management\n\n### In-Memory Users\n\nSimple for development or small deployments:\n\n```java\n@Bean\npublic InMemoryUserDetailsManager userDetailsService(PasswordEncoder encoder) {\n    UserDetails admin = User.builder()\n        .username(\"admin\")\n        .password(encoder.encode(System.getenv(\"ADMIN_PASSWORD\")))\n        .roles(\"ADMIN\")\n        .build();\n\n    UserDetails viewer = User.builder()\n        .username(\"viewer\")\n        .password(encoder.encode(System.getenv(\"VIEWER_PASSWORD\")))\n        .roles(\"USER\")\n        .build();\n\n    return new InMemoryUserDetailsManager(admin, viewer);\n}\n```\n\n### Database Users\n\nUse `JdbcUserDetailsManager` for database-backed users:\n\n```java\n@Bean\npublic JdbcUserDetailsManager userDetailsService(DataSource dataSource,\n                                                  PasswordEncoder encoder) {\n    JdbcUserDetailsManager manager = new JdbcUserDetailsManager(dataSource);\n\n    // Create default admin if not exists\n    if (!manager.userExists(\"admin\")) {\n        UserDetails admin = User.builder()\n            .username(\"admin\")\n            .password(encoder.encode(System.getenv(\"ADMIN_PASSWORD\")))\n            .roles(\"ADMIN\")\n            .build();\n        manager.createUser(admin);\n    }\n\n    return manager;\n}\n```\n\n**Database Schema**:\n\n```sql\nCREATE TABLE users (\n    username VARCHAR(50) NOT NULL PRIMARY KEY,\n    password VARCHAR(100) NOT NULL,\n    enabled BOOLEAN NOT NULL\n);\n\nCREATE TABLE authorities (\n    username VARCHAR(50) NOT NULL,\n    authority VARCHAR(50) NOT NULL,\n    FOREIGN KEY (username) REFERENCES users(username)\n);\n\nCREATE UNIQUE INDEX ix_auth_username ON authorities (username, authority);\n```\n\n### LDAP Authentication\n\nAuthenticate against an LDAP server:\n\n```java\n@Bean\npublic SecurityFilterChain filterChain(HttpSecurity http,\n                                       AdminServerProperties adminServer) throws Exception {\n    http\n        .authorizeHttpRequests(/* ... */)\n        .formLogin(/* ... */)\n        .logout(/* ... */)\n        .httpBasic(Customizer.withDefaults());\n\n    return http.build();\n}\n\n@Configuration\npublic static class LdapConfig {\n\n    @Bean\n    public EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() {\n        EmbeddedLdapServerContextSourceFactoryBean factory =\n            new EmbeddedLdapServerContextSourceFactoryBean();\n        factory.setPort(8389);\n        return factory;\n    }\n\n    @Bean\n    public AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {\n        LdapBindAuthenticationManagerFactory factory =\n            new LdapBindAuthenticationManagerFactory(contextSource);\n        factory.setUserDnPatterns(\"uid={0},ou=people\");\n        factory.setUserDetailsContextMapper(new PersonContextMapper());\n        return factory.createAuthenticationManager();\n    }\n}\n```\n\n**Configuration**:\n\n```yaml\nspring:\n  ldap:\n    urls: ldap://ldap.company.com:389\n    base: dc=company,dc=com\n    username: cn=admin,dc=company,dc=com\n    password: ${LDAP_PASSWORD}\n```\n\n### OAuth2 / OIDC\n\nUse OAuth2 for Single Sign-On (SSO):\n\n**Dependencies**:\n\n```xml\n<dependency>\n    <groupId>org.springframework.boot</groupId>\n    <artifactId>spring-boot-starter-oauth2-client</artifactId>\n</dependency>\n```\n\n**Configuration**:\n\n```yaml\nspring:\n  security:\n    oauth2:\n      client:\n        registration:\n          keycloak:\n            client-id: spring-boot-admin\n            client-secret: ${OAUTH2_CLIENT_SECRET}\n            scope: openid,profile,email\n            authorization-grant-type: authorization_code\n            redirect-uri: \"{baseUrl}/login/oauth2/code/{registrationId}\"\n        provider:\n          keycloak:\n            issuer-uri: https://keycloak.company.com/realms/main\n```\n\n**Security Configuration**:\n\n```java\n@Bean\npublic SecurityFilterChain filterChain(HttpSecurity http,\n                                       AdminServerProperties adminServer) throws Exception {\n    http\n        .authorizeHttpRequests(auth -> auth\n            .requestMatchers(PathPatternRequestMatcher.withDefaults()\n                .matcher(adminServer.path(\"/assets/**\")))\n            .permitAll()\n            .requestMatchers(PathPatternRequestMatcher.withDefaults()\n                .matcher(adminServer.path(\"/login\")))\n            .permitAll()\n            .anyRequest().authenticated()\n        )\n        .oauth2Login(oauth2 -> oauth2\n            .loginPage(adminServer.path(\"/login\"))\n        )\n        .logout(logout -> logout\n            .logoutUrl(adminServer.path(\"/logout\"))\n            .logoutSuccessUrl(adminServer.path(\"/\"))\n        );\n\n    // CSRF and other configurations...\n\n    return http.build();\n}\n```\n\n---\n\n## Role-Based Access Control\n\nRestrict access by roles:\n\n```java\n@Bean\npublic SecurityFilterChain filterChain(HttpSecurity http,\n                                       AdminServerProperties adminServer) throws Exception {\n    http.authorizeHttpRequests(auth -> auth\n            .requestMatchers(PathPatternRequestMatcher.withDefaults()\n                .matcher(adminServer.path(\"/assets/**\")))\n            .permitAll()\n            .requestMatchers(PathPatternRequestMatcher.withDefaults()\n                .matcher(adminServer.path(\"/login\")))\n            .permitAll()\n            // Only ADMIN can delete instances\n            .requestMatchers(PathPatternRequestMatcher.withDefaults()\n                .matcher(DELETE, adminServer.path(\"/instances/*\")))\n            .hasRole(\"ADMIN\")\n            // Only ADMIN can access logfile endpoint\n            .requestMatchers(PathPatternRequestMatcher.withDefaults()\n                .matcher(adminServer.path(\"/instances/*/actuator/logfile\")))\n            .hasRole(\"ADMIN\")\n            // USER and ADMIN can view everything else\n            .anyRequest().hasAnyRole(\"USER\", \"ADMIN\")\n        )\n        .formLogin(formLogin -> formLogin.loginPage(adminServer.path(\"/login\")))\n        .httpBasic(Customizer.withDefaults());\n\n    return http.build();\n}\n```\n\n**Create users with different roles**:\n\n```java\n@Bean\npublic InMemoryUserDetailsManager userDetailsService(PasswordEncoder encoder) {\n    UserDetails admin = User.builder()\n        .username(\"admin\")\n        .password(encoder.encode(System.getenv(\"ADMIN_PASSWORD\")))\n        .roles(\"ADMIN\")\n        .build();\n\n    UserDetails viewer = User.builder()\n        .username(\"viewer\")\n        .password(encoder.encode(System.getenv(\"VIEWER_PASSWORD\")))\n        .roles(\"USER\")\n        .build();\n\n    return new InMemoryUserDetailsManager(admin, viewer);\n}\n```\n\n---\n\n## HTTP vs HTTPS\n\n### Local Development (HTTP)\n\nFor local development, HTTP is acceptable:\n\n```yaml\nserver:\n  port: 8080\n```\n\n### HTTPS Configuration\n\nEnable HTTPS for secure communication:\n\n```yaml\nserver:\n  port: 8443\n  ssl:\n    enabled: true\n    key-store: classpath:keystore.p12\n    key-store-password: ${KEYSTORE_PASSWORD}\n    key-store-type: PKCS12\n    key-alias: spring-boot-admin\n```\n\n**Generate keystore**:\n\n```bash\nkeytool -genkeypair -alias spring-boot-admin \\\n  -keyalg RSA -keysize 2048 \\\n  -storetype PKCS12 \\\n  -keystore keystore.p12 \\\n  -validity 3650 \\\n  -storepass changeit\n```\n\n**Update Admin Client configuration**:\n\n```yaml\nspring:\n  boot:\n    admin:\n      client:\n        url: https://admin-server:8443\n```\n\n---\n\n## Reverse Proxy Setup\n\n### Behind Nginx\n\n**Nginx Configuration**:\n\n```nginx\nserver {\n    listen 80;\n    server_name admin.company.com;\n\n    location / {\n        proxy_pass http://localhost:8080;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n}\n```\n\n**Admin Server Configuration**:\n\n```yaml\nserver:\n  forward-headers-strategy: native\n\nspring:\n  boot:\n    admin:\n      ui:\n        public-url: https://admin.company.com\n```\n\n### Behind Apache\n\n**Apache Configuration**:\n\n```apache\n<VirtualHost *:80>\n    ServerName admin.company.com\n\n    ProxyPreserveHost On\n    ProxyPass / http://localhost:8080/\n    ProxyPassReverse / http://localhost:8080/\n\n    RequestHeader set X-Forwarded-Proto \"https\"\n    RequestHeader set X-Forwarded-Port \"443\"\n</VirtualHost>\n```\n\n---\n\n## Security Headers\n\nAdd security headers to protect against common attacks:\n\n```java\n@Bean\npublic SecurityFilterChain filterChain(HttpSecurity http,\n                                       AdminServerProperties adminServer) throws Exception {\n    http\n        .headers(headers -> headers\n            // Content Security Policy\n            .contentSecurityPolicy(csp -> csp\n                .policyDirectives(\"default-src 'self'; \" +\n                    \"script-src 'self' 'unsafe-inline'; \" +\n                    \"style-src 'self' 'unsafe-inline'; \" +\n                    \"img-src 'self' data:; \" +\n                    \"font-src 'self' data:\")\n            )\n            // Frame options\n            .frameOptions(frame -> frame.sameOrigin())\n            // XSS protection\n            .xssProtection(xss -> xss.headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK))\n            // HSTS\n            .httpStrictTransportSecurity(hsts -> hsts\n                .includeSubDomains(true)\n                .maxAgeInSeconds(31536000)\n            )\n        );\n\n    // Other configurations...\n\n    return http.build();\n}\n```\n\n---\n\n## Multiple Authentication Methods\n\nSupport both form login and HTTP Basic:\n\n```java\n@Bean\npublic SecurityFilterChain filterChain(HttpSecurity http,\n                                       AdminServerProperties adminServer) throws Exception {\n    http\n        .authorizeHttpRequests(/* ... */)\n        .formLogin(formLogin -> formLogin\n            .loginPage(adminServer.path(\"/login\"))\n        )\n        .httpBasic(Customizer.withDefaults())\n        .logout(logout -> logout\n            .logoutUrl(adminServer.path(\"/logout\"))\n        );\n\n    return http.build();\n}\n```\n\n- **Form login**: For browser-based UI access\n- **HTTP Basic**: For API clients, scripts, monitoring tools\n\n---\n\n## Troubleshooting\n\n### Issue: Login page not loading\n\n**Cause**: Assets blocked by security configuration.\n\n**Solution**: Permit `/assets/**`:\n\n```java\n.requestMatchers(PathPatternRequestMatcher.withDefaults()\n    .matcher(adminServer.path(\"/assets/**\")))\n.permitAll()\n```\n\n### Issue: Infinite redirect loop\n\n**Cause**: Login page requires authentication.\n\n**Solution**: Permit `/login`:\n\n```java\n.requestMatchers(PathPatternRequestMatcher.withDefaults()\n    .matcher(adminServer.path(\"/login\")))\n.permitAll()\n```\n\n### Issue: Clients cannot register\n\n**Cause**: CSRF protection blocking `/instances` endpoint.\n\n**Solution**: Exempt client registration endpoints:\n\n```java\n.csrf(csrf -> csrf\n    .ignoringRequestMatchers(\n        PathPatternRequestMatcher.withDefaults()\n            .matcher(POST, adminServer.path(\"/instances\")),\n        PathPatternRequestMatcher.withDefaults()\n            .matcher(DELETE, adminServer.path(\"/instances/*\"))\n    )\n)\n```\n\n### Issue: Remember-me not working\n\n**Cause**: No `UserDetailsService` configured.\n\n**Solution**: Add `UserDetailsService` bean:\n\n```java\n@Bean\npublic InMemoryUserDetailsManager userDetailsService(PasswordEncoder encoder) {\n    // ...\n}\n```\n\n### Issue: 401 on API requests\n\n**Cause**: API client not providing credentials.\n\n**Solution**: Use HTTP Basic authentication:\n\n```bash\ncurl -u admin:password http://localhost:8080/instances\n```\n\n---\n\n## Complete Example\n\n```java\npackage com.example.admin;\n\nimport java.util.UUID;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.security.config.Customizer;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.core.userdetails.User;\nimport org.springframework.security.core.userdetails.UserDetails;\nimport org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;\nimport org.springframework.security.crypto.password.PasswordEncoder;\nimport org.springframework.security.provisioning.InMemoryUserDetailsManager;\nimport org.springframework.security.web.SecurityFilterChain;\nimport org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;\nimport org.springframework.security.web.authentication.www.BasicAuthenticationFilter;\nimport org.springframework.security.web.csrf.CookieCsrfTokenRepository;\nimport org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;\nimport org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;\n\nimport de.codecentric.boot.admin.server.config.AdminServerProperties;\nimport de.codecentric.boot.admin.server.config.EnableAdminServer;\n\nimport static org.springframework.http.HttpMethod.DELETE;\nimport static org.springframework.http.HttpMethod.POST;\n\n@EnableAdminServer\n@Configuration\npublic class AdminServerConfig {\n\n    private final AdminServerProperties adminServer;\n\n    public AdminServerConfig(AdminServerProperties adminServer) {\n        this.adminServer = adminServer;\n    }\n\n    @Bean\n    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n        SavedRequestAwareAuthenticationSuccessHandler successHandler =\n            new SavedRequestAwareAuthenticationSuccessHandler();\n        successHandler.setTargetUrlParameter(\"redirectTo\");\n        successHandler.setDefaultTargetUrl(adminServer.path(\"/\"));\n\n        http\n            .authorizeHttpRequests(auth -> auth\n                .requestMatchers(PathPatternRequestMatcher.withDefaults()\n                    .matcher(adminServer.path(\"/assets/**\")))\n                .permitAll()\n                .requestMatchers(PathPatternRequestMatcher.withDefaults()\n                    .matcher(adminServer.path(\"/login\")))\n                .permitAll()\n                .requestMatchers(PathPatternRequestMatcher.withDefaults()\n                    .matcher(adminServer.path(\"/actuator/info\")))\n                .permitAll()\n                .requestMatchers(PathPatternRequestMatcher.withDefaults()\n                    .matcher(adminServer.path(\"/actuator/health\")))\n                .permitAll()\n                .anyRequest().authenticated()\n            )\n            .formLogin(formLogin -> formLogin\n                .loginPage(adminServer.path(\"/login\"))\n                .successHandler(successHandler)\n            )\n            .logout(logout -> logout\n                .logoutUrl(adminServer.path(\"/logout\"))\n            )\n            .httpBasic(Customizer.withDefaults());\n\n        http.addFilterAfter(new CustomCsrfFilter(), BasicAuthenticationFilter.class)\n            .csrf(csrf -> csrf\n                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())\n                .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())\n                .ignoringRequestMatchers(\n                    PathPatternRequestMatcher.withDefaults()\n                        .matcher(POST, adminServer.path(\"/instances\")),\n                    PathPatternRequestMatcher.withDefaults()\n                        .matcher(DELETE, adminServer.path(\"/instances/*\")),\n                    PathPatternRequestMatcher.withDefaults()\n                        .matcher(adminServer.path(\"/actuator/**\"))\n                )\n            );\n\n        http.rememberMe(rememberMe -> rememberMe\n            .key(UUID.randomUUID().toString())\n            .tokenValiditySeconds(1209600)\n        );\n\n        return http.build();\n    }\n\n    @Bean\n    public InMemoryUserDetailsManager userDetailsService(PasswordEncoder passwordEncoder) {\n        UserDetails admin = User.builder()\n            .username(\"admin\")\n            .password(passwordEncoder.encode(System.getenv(\"ADMIN_PASSWORD\")))\n            .roles(\"ADMIN\")\n            .build();\n\n        UserDetails viewer = User.builder()\n            .username(\"viewer\")\n            .password(passwordEncoder.encode(System.getenv(\"VIEWER_PASSWORD\")))\n            .roles(\"USER\")\n            .build();\n\n        return new InMemoryUserDetailsManager(admin, viewer);\n    }\n\n    @Bean\n    public PasswordEncoder passwordEncoder() {\n        return new BCryptPasswordEncoder();\n    }\n}\n```\n\n**application.yml**:\n\n```yaml\nspring:\n  application:\n    name: spring-boot-admin-server\n\n  boot:\n    admin:\n      context-path: /admin\n      ui:\n        title: \"Production Monitor\"\n\nserver:\n  port: 8443\n  ssl:\n    enabled: true\n    key-store: classpath:keystore.p12\n    key-store-password: ${KEYSTORE_PASSWORD}\n    key-store-type: PKCS12\n```\n\n---\n\n## See Also\n\n- [Actuator Security](./20-actuator-security.md) - Secure client actuator endpoints\n- [CSRF Protection](./30-csrf-protection.md) - Detailed CSRF configuration\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/05-security/20-actuator-security.md",
    "content": "---\nsidebar_position: 20\nsidebar_custom_props:\n  icon: 'shield'\n---\n\n# Actuator Security\n\nSecure your client application's actuator endpoints and configure Spring Boot Admin Server to access them.\n\n## Overview\n\nWhen client applications expose actuator endpoints, they should be secured. The Admin Server needs credentials to access\nthese secured endpoints.\n\n```mermaid\ngraph TD\n    A[\"Spring Boot Admin Server<br/>Needs credentials to access<br/>secured actuator endpoints\"] -->|HTTP Basic Auth<br/>user.name + user.password| B[\"Client Application<br/>Secured actuator endpoints:<br/>/actuator/health<br/>/actuator/metrics<br/>/actuator/env\"]\n```\n\n---\n\n## Quick Start\n\n### 1. Client: Secure Actuator Endpoints\n\nAdd Spring Security to your client application:\n\n**Maven**:\n\n```xml\n<dependency>\n    <groupId>org.springframework.boot</groupId>\n    <artifactId>spring-boot-starter-security</artifactId>\n</dependency>\n```\n\n**Gradle**:\n\n```gradle\nimplementation 'org.springframework.boot:spring-boot-starter-security'\n```\n\n**application.yml**:\n\n```yaml\nspring:\n  security:\n    user:\n      name: actuator\n      password: ${ACTUATOR_PASSWORD}\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n```\n\n### 2. Client: Share Credentials with Admin Server\n\nPass credentials via metadata:\n\n```yaml\nspring:\n  boot:\n    admin:\n      client:\n        url: http://admin-server:8080\n        instance:\n          metadata:\n            user.name: actuator\n            user.password: ${ACTUATOR_PASSWORD}\n```\n\n### 3. Server: Enable Instance Authentication\n\n**application.yml**:\n\n```yaml\nspring:\n  boot:\n    admin:\n      instance-auth:\n        enabled: true\n```\n\nThe Admin Server will automatically use credentials from instance metadata to access actuator endpoints.\n\n---\n\n## Client Configuration\n\n### Basic Actuator Security\n\nSimplest approach using default Spring Security user:\n\n```yaml\nspring:\n  application:\n    name: my-service\n\n  security:\n    user:\n      name: actuator\n      password: ${ACTUATOR_PASSWORD}\n\n  boot:\n    admin:\n      client:\n        url: http://admin-server:8080\n        instance:\n          metadata:\n            user.name: ${spring.security.user.name}\n            user.password: ${spring.security.user.password}\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: health,info,metrics,env,loggers\n```\n\n### Custom Security Configuration\n\nFor more control, create a custom `SecurityFilterChain`:\n\n```java\npackage com.example.myservice;\n\nimport org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.security.config.Customizer;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.core.userdetails.User;\nimport org.springframework.security.core.userdetails.UserDetails;\nimport org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;\nimport org.springframework.security.crypto.password.PasswordEncoder;\nimport org.springframework.security.provisioning.InMemoryUserDetailsManager;\nimport org.springframework.security.web.SecurityFilterChain;\n\n@Configuration\npublic class ActuatorSecurityConfig {\n\n    @Bean\n    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n        http\n            .authorizeHttpRequests(auth -> auth\n                // Permit health endpoint for load balancers\n                .requestMatchers(EndpointRequest.to(\"health\")).permitAll()\n                // Secure all other actuator endpoints\n                .requestMatchers(EndpointRequest.toAnyEndpoint()).authenticated()\n                // Allow application endpoints\n                .anyRequest().permitAll()\n            )\n            // Use HTTP Basic for actuator\n            .httpBasic(Customizer.withDefaults())\n            // Disable CSRF for stateless API\n            .csrf(csrf -> csrf.disable());\n\n        return http.build();\n    }\n\n    @Bean\n    public InMemoryUserDetailsManager userDetailsService(PasswordEncoder encoder) {\n        UserDetails actuator = User.builder()\n            .username(\"actuator\")\n            .password(encoder.encode(System.getenv(\"ACTUATOR_PASSWORD\")))\n            .roles(\"ACTUATOR\")\n            .build();\n\n        return new InMemoryUserDetailsManager(actuator);\n    }\n\n    @Bean\n    public PasswordEncoder passwordEncoder() {\n        return new BCryptPasswordEncoder();\n    }\n}\n```\n\n### Different Actuator and Application Security\n\nSeparate security for actuator and application:\n\n```java\n@Configuration\n@Order(1)  // Higher precedence\npublic class ActuatorSecurityConfig {\n\n    @Bean\n    @Order(1)\n    public SecurityFilterChain actuatorFilterChain(HttpSecurity http) throws Exception {\n        http\n            .securityMatcher(EndpointRequest.toAnyEndpoint())\n            .authorizeHttpRequests(auth -> auth\n                .requestMatchers(EndpointRequest.to(\"health\")).permitAll()\n                .anyRequest().hasRole(\"ACTUATOR\")\n            )\n            .httpBasic(Customizer.withDefaults())\n            .csrf(csrf -> csrf.disable());\n\n        return http.build();\n    }\n\n    @Bean\n    public InMemoryUserDetailsManager actuatorUserDetailsService(PasswordEncoder encoder) {\n        UserDetails actuator = User.builder()\n            .username(\"actuator\")\n            .password(encoder.encode(System.getenv(\"ACTUATOR_PASSWORD\")))\n            .roles(\"ACTUATOR\")\n            .build();\n\n        return new InMemoryUserDetailsManager(actuator);\n    }\n}\n\n@Configuration\n@Order(2)  // Lower precedence\npublic class ApplicationSecurityConfig {\n\n    @Bean\n    @Order(2)\n    public SecurityFilterChain applicationFilterChain(HttpSecurity http) throws Exception {\n        http\n            .authorizeHttpRequests(auth -> auth\n                .requestMatchers(\"/api/public/**\").permitAll()\n                .anyRequest().authenticated()\n            )\n            .oauth2Login(Customizer.withDefaults());\n\n        return http.build();\n    }\n}\n```\n\n### Actuator on Separate Port\n\nRun actuator on a different port for isolation:\n\n```yaml\nmanagement:\n  server:\n    port: 8081  # Separate management port\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n\nspring:\n  security:\n    user:\n      name: actuator\n      password: ${ACTUATOR_PASSWORD}\n\n  boot:\n    admin:\n      client:\n        instance:\n          # Admin Server will auto-detect management port\n          # Or specify explicitly:\n          management-base-url: http://localhost:8081\n          metadata:\n            user.name: actuator\n            user.password: ${ACTUATOR_PASSWORD}\n```\n\n**Security configuration**:\n\n```java\n@Configuration\npublic class SecurityConfig {\n\n    @Bean\n    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n        http\n            .authorizeHttpRequests(auth -> auth\n                // No actuator endpoints on main port\n                .anyRequest().permitAll()\n            )\n            .csrf(csrf -> csrf.disable());\n\n        return http.build();\n    }\n}\n```\n\nOn port 8081, actuator endpoints are secured with Spring Security's default security.\n\n---\n\n## Server Configuration\n\n### Enable Instance Authentication\n\n```yaml\nspring:\n  boot:\n    admin:\n      instance-auth:\n        enabled: true\n```\n\nAdmin Server will:\n\n1. Check instance metadata for `user.name` and `user.password`\n2. Use these credentials to access actuator endpoints via HTTP Basic\n\n### Default Credentials\n\nSet default credentials for all instances:\n\n```yaml\nspring:\n  boot:\n    admin:\n      instance-auth:\n        enabled: true\n        default-user-name: actuator\n        default-password: ${DEFAULT_ACTUATOR_PASSWORD}\n```\n\nInstances can override via metadata.\n\n### Per-Service Credentials\n\nConfigure different credentials for each service:\n\n```yaml\nspring:\n  boot:\n    admin:\n      instance-auth:\n        enabled: true\n        service-map:\n          # Service name from spring.application.name\n          my-service:\n            user-name: my-service-actuator\n            user-password: ${MY_SERVICE_PASSWORD}\n          another-service:\n            user-name: another-actuator\n            user-password: ${ANOTHER_SERVICE_PASSWORD}\n\n        # Fallback for services not in service-map\n        default-user-name: default-actuator\n        default-password: ${DEFAULT_PASSWORD}\n```\n\n**Client (my-service)**:\n\n```yaml\nspring:\n  application:\n    name: my-service\n\n  security:\n    user:\n      name: my-service-actuator\n      password: ${MY_SERVICE_PASSWORD}\n```\n\n---\n\n## Credential Strategies\n\n### Strategy 1: Metadata (Recommended)\n\n**Client passes credentials in metadata**:\n\n```yaml\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          metadata:\n            user.name: actuator\n            user.password: ${ACTUATOR_PASSWORD}\n```\n\n**Server uses metadata automatically**:\n\n```yaml\nspring:\n  boot:\n    admin:\n      instance-auth:\n        enabled: true\n```\n\n**Pros**:\n\n- Client controls its own credentials\n- Each instance can have unique credentials\n- Server automatically picks up credentials\n\n**Cons**:\n\n- Credentials visible in instance metadata (sanitized by default)\n- Requires client configuration\n\n### Strategy 2: Server-Side Configuration\n\n**Server has all credentials**:\n\n```yaml\nspring:\n  boot:\n    admin:\n      instance-auth:\n        enabled: true\n        service-map:\n          service-a:\n            user-name: service-a-user\n            user-password: ${SERVICE_A_PASSWORD}\n```\n\n**Client just secures actuator**:\n\n```yaml\nspring:\n  security:\n    user:\n      name: service-a-user\n      password: ${SERVICE_A_PASSWORD}\n```\n\n**Pros**:\n\n- Centralized credential management\n- Client configuration simpler\n\n**Cons**:\n\n- Server must know all client credentials\n- Harder to scale with many services\n\n### Strategy 3: Default Credentials\n\n**All clients use same credentials**:\n\n**Server**:\n\n```yaml\nspring:\n  boot:\n    admin:\n      instance-auth:\n        enabled: true\n        default-user-name: actuator\n        default-password: ${ACTUATOR_PASSWORD}\n```\n\n**All Clients**:\n\n```yaml\nspring:\n  security:\n    user:\n      name: actuator\n      password: ${ACTUATOR_PASSWORD}\n```\n\n**Pros**:\n\n- Simplest to configure\n- Uniform across all services\n\n**Cons**:\n\n- Single credentials compromise affects all services\n- Less secure\n\n---\n\n## Limiting Exposed Endpoints\n\nOnly expose necessary endpoints:\n\n```yaml\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: health,info,metrics,env,loggers\n```\n\nOr exclude specific endpoints:\n\n```yaml\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n        exclude: heapdump,threaddump\n```\n\n**Health endpoint details**:\n\n```yaml\nmanagement:\n  endpoint:\n    health:\n      show-details: when-authorized\n      roles: ACTUATOR\n```\n\n---\n\n## Metadata Sanitization\n\nBy default, credentials in metadata are sanitized:\n\n```yaml\nspring:\n  boot:\n    admin:\n      metadata-keys-to-sanitize:\n        - \".*password$\"\n        - \".*secret$\"\n        - \".*key$\"\n        - \".*token$\"\n        - \".*credentials.*\"\n```\n\nMetadata `user.password` will appear as `******` in responses, but the server still uses it internally.\n\n---\n\n## Service Discovery\n\nWhen using service discovery (Eureka, Consul, etc.), credentials can be set via metadata:\n\n**Eureka**:\n\n```yaml\neureka:\n  instance:\n    metadata-map:\n      user.name: actuator\n      user.password: ${ACTUATOR_PASSWORD}\n```\n\n**Consul**:\n\n```yaml\nspring:\n  cloud:\n    consul:\n      discovery:\n        metadata:\n          user.name: actuator\n          user.password: ${ACTUATOR_PASSWORD}\n```\n\n**Kubernetes ConfigMap**:\n\n```yaml\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: my-service-config\ndata:\n  application.yml: |\n    spring:\n      boot:\n        admin:\n          client:\n            instance:\n              metadata:\n                user.name: actuator\n                user.password: ${ACTUATOR_PASSWORD}\n```\n\n---\n\n## TLS/SSL for Actuator\n\nUse HTTPS for actuator endpoints:\n\n```yaml\nmanagement:\n  server:\n    port: 8443\n    ssl:\n      enabled: true\n      key-store: classpath:actuator-keystore.p12\n      key-store-password: ${KEYSTORE_PASSWORD}\n      key-store-type: PKCS12\n\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          management-base-url: https://localhost:8443\n          metadata:\n            user.name: actuator\n            user.password: ${ACTUATOR_PASSWORD}\n```\n\n**Generate keystore**:\n\n```bash\nkeytool -genkeypair -alias actuator \\\n  -keyalg RSA -keysize 2048 \\\n  -storetype PKCS12 \\\n  -keystore actuator-keystore.p12 \\\n  -validity 3650 \\\n  -storepass changeit\n```\n\n---\n\n## Examples\n\n### Example 1: Development (No Security)\n\n**Client**:\n\n```yaml\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n\nspring:\n  boot:\n    admin:\n      client:\n        url: http://localhost:8080\n```\n\nNo Spring Security dependency, all endpoints open.\n\n### Example 2: Full Security Setup\n\n**Client**:\n\n```yaml\nspring:\n  application:\n    name: payment-service\n\n  security:\n    user:\n      name: ${ACTUATOR_USER}\n      password: ${ACTUATOR_PASSWORD}\n\n  boot:\n    admin:\n      client:\n        url: https://admin.company.com\n        username: ${ADMIN_CLIENT_USER}\n        password: ${ADMIN_CLIENT_PASSWORD}\n        instance:\n          service-base-url: https://payment.company.com\n          management-base-url: https://payment.company.com:8443\n          metadata:\n            user.name: ${ACTUATOR_USER}\n            user.password: ${ACTUATOR_PASSWORD}\n            tags:\n              environment: production\n\nmanagement:\n  server:\n    port: 8443\n    ssl:\n      enabled: true\n      key-store: classpath:keystore.p12\n      key-store-password: ${KEYSTORE_PASSWORD}\n\n  endpoints:\n    web:\n      exposure:\n        include: health,info,metrics,env,loggers\n\n  endpoint:\n    health:\n      show-details: when-authorized\n```\n\n**Server**:\n\n```yaml\nspring:\n  boot:\n    admin:\n      instance-auth:\n        enabled: true\n        # Uses credentials from instance metadata\n```\n\n### Example 3: Kubernetes with Secrets\n\n**Secret**:\n\n```yaml\napiVersion: v1\nkind: Secret\nmetadata:\n  name: actuator-credentials\ntype: Opaque\nstringData:\n  username: actuator\n  password: secure-password-123\n```\n\n**Deployment**:\n\n```yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: my-service\nspec:\n  template:\n    spec:\n      containers:\n        - name: my-service\n          image: my-service:latest\n          env:\n            - name: ACTUATOR_USER\n              valueFrom:\n                secretKeyRef:\n                  name: actuator-credentials\n                  key: username\n            - name: ACTUATOR_PASSWORD\n              valueFrom:\n                secretKeyRef:\n                  name: actuator-credentials\n                  key: password\n          ports:\n            - containerPort: 8080\n            - containerPort: 8081  # Actuator\n```\n\n**application.yml** (in ConfigMap):\n\n```yaml\nspring:\n  security:\n    user:\n      name: ${ACTUATOR_USER}\n      password: ${ACTUATOR_PASSWORD}\n\n  boot:\n    admin:\n      client:\n        instance:\n          metadata:\n            user.name: ${ACTUATOR_USER}\n            user.password: ${ACTUATOR_PASSWORD}\n\nmanagement:\n  server:\n    port: 8081\n```\n\n### Example 4: Multiple Environments\n\n**application.yml** (common):\n\n```yaml\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          metadata:\n            user.name: ${ACTUATOR_USER:actuator}\n            user.password: ${ACTUATOR_PASSWORD}\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n```\n\n**application-dev.yml**:\n\n```yaml\nspring:\n  security:\n    user:\n      name: actuator\n      password: dev-password\n\n  boot:\n    admin:\n      client:\n        url: http://localhost:8080\n```\n\n**application-prod.yml**:\n\n```yaml\nspring:\n  security:\n    user:\n      name: ${ACTUATOR_USER}\n      password: ${ACTUATOR_PASSWORD}\n\n  boot:\n    admin:\n      client:\n        url: https://admin.company.com\n        username: ${ADMIN_CLIENT_USER}\n        password: ${ADMIN_CLIENT_PASSWORD}\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: health,info,metrics,env,loggers\n  endpoint:\n    health:\n      show-details: when-authorized\n```\n\n---\n\n## Troubleshooting\n\n### Issue: 401 Unauthorized on actuator endpoints\n\n**Cause**: Admin Server doesn't have valid credentials.\n\n**Check**:\n\n1. Instance metadata contains credentials:\n\n   ```bash\n   curl http://admin-server:8080/instances/{id} | jq '.metadata'\n   ```\n\n2. Credentials match actuator configuration:\n\n   ```bash\n   curl -u actuator:password http://client:8080/actuator/health\n   ```\n\n**Solution**: Add credentials to metadata:\n\n```yaml\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          metadata:\n            user.name: actuator\n            user.password: ${ACTUATOR_PASSWORD}\n```\n\n### Issue: Admin Server shows \"Unavailable\"\n\n**Cause**: Cannot access health endpoint.\n\n**Check**:\n\n```bash\ncurl -u actuator:password http://client:8080/actuator/health\n```\n\n**Solution**: Verify:\n\n1. Health endpoint is exposed\n2. Credentials are correct\n3. Health URL is accessible from Admin Server\n\n### Issue: Some endpoints work, others return 401\n\n**Cause**: Different security rules for different endpoints.\n\n**Solution**: Ensure all actuator endpoints have same security:\n\n```java\nhttp.authorizeHttpRequests(auth -> auth\n    .requestMatchers(EndpointRequest.toAnyEndpoint()).authenticated()\n)\n```\n\n### Issue: Metadata shows credentials in plain text\n\n**Expected**: Credentials should be sanitized as `******`.\n\n**Check sanitization patterns**:\n\n```yaml\nspring:\n  boot:\n    admin:\n      metadata-keys-to-sanitize:\n        - \".*password$\"\n```\n\nThis is working correctly if API responses show `******` for `user.password`, even though the server uses the real value\ninternally.\n\n---\n\n## Best Practices\n\n1. **Use Strong Passwords**: Generate secure random passwords\n2. **Environment Variables**: Never hardcode credentials\n3. **Limit Exposure**: Only expose necessary actuator endpoints\n4. **Use HTTPS**: Encrypt actuator traffic with TLS\n5. **Separate Port**: Consider separate management port for isolation\n6. **Role-Based Access**: Use roles for fine-grained control\n7. **Monitor Access**: Log actuator access attempts\n8. **Rotate Credentials**: Regularly update actuator passwords\n\n---\n\n## See Also\n\n- [Server Authentication](./10-server-authentication.md) - Secure Admin Server\n- [CSRF Protection](./30-csrf-protection.md) - Configure CSRF tokens\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/05-security/30-csrf-protection.md",
    "content": "---\nsidebar_position: 30\nsidebar_custom_props:\n  icon: 'shield'\n---\n\n# CSRF Protection\n\nConfigure Cross-Site Request Forgery (CSRF) protection for Spring Boot Admin while allowing client registration.\n\n## Overview\n\nSpring Boot Admin Server needs CSRF protection for the web UI, but must exempt certain endpoints:\n\n- `/instances` - Client registration endpoint (POST)\n- `/instances/*` - Client deregistration endpoint (DELETE)\n- `/actuator/**` - Admin Server's own actuator endpoints\n\n```mermaid\ngraph TB\n    A[\"**Browser Admin UI**<br/>• Sends CSRF token in requests<br/>• Token stored in XSRF-TOKEN cookie<br/>• Angular/React reads cookie and sends X-XSRF-TOKEN header\"] --> B[\"**Spring Boot Admin Server**<br/>• Validates CSRF token for UI requests<br/>• Exempts /instances endpoint client registration<br/>• Exempts /actuator/** health checks\"]\n    C[\"**Client Application**<br/>• Registers without CSRF token<br/>• Uses HTTP POST to /instances\"] --> B\n```\n\n---\n\n## Why CSRF Protection?\n\nCSRF attacks trick authenticated users into performing unwanted actions:\n\n1. User logs into Admin Server in their browser\n2. User visits malicious website\n3. Malicious website sends request to Admin Server using user's session\n4. Without CSRF protection, the request succeeds\n\n**CSRF tokens prevent this**:\n\n- Each request requires a unique token\n- Tokens are tied to the user's session\n- Malicious websites cannot obtain valid tokens\n\n---\n\n## CSRF Configuration\n\n### Complete Example\n\n```java\npackage com.example.admin;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.web.SecurityFilterChain;\nimport org.springframework.security.web.authentication.www.BasicAuthenticationFilter;\nimport org.springframework.security.web.csrf.CookieCsrfTokenRepository;\nimport org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;\nimport org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;\n\nimport de.codecentric.boot.admin.server.config.AdminServerProperties;\n\nimport static org.springframework.http.HttpMethod.DELETE;\nimport static org.springframework.http.HttpMethod.POST;\n\n@Configuration\npublic class SecurityConfig {\n\n    private final AdminServerProperties adminServer;\n\n    public SecurityConfig(AdminServerProperties adminServer) {\n        this.adminServer = adminServer;\n    }\n\n    @Bean\n    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n        http\n            .authorizeHttpRequests(auth -> auth\n                .requestMatchers(PathPatternRequestMatcher.withDefaults()\n                    .matcher(adminServer.path(\"/assets/**\")))\n                .permitAll()\n                .requestMatchers(PathPatternRequestMatcher.withDefaults()\n                    .matcher(adminServer.path(\"/login\")))\n                .permitAll()\n                .anyRequest().authenticated()\n            )\n            .formLogin(formLogin -> formLogin\n                .loginPage(adminServer.path(\"/login\"))\n            )\n            .httpBasic(Customizer.withDefaults());\n\n        // Custom CSRF filter to expose token to JavaScript\n        http.addFilterAfter(new CustomCsrfFilter(), BasicAuthenticationFilter.class);\n\n        // CSRF configuration\n        http.csrf(csrf -> csrf\n            // Use cookie-based token repository (accessible to JavaScript)\n            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())\n            // Use attribute-based token handler\n            .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())\n            // Exempt specific endpoints\n            .ignoringRequestMatchers(\n                // Client registration\n                PathPatternRequestMatcher.withDefaults()\n                    .matcher(POST, adminServer.path(\"/instances\")),\n                // Client deregistration\n                PathPatternRequestMatcher.withDefaults()\n                    .matcher(DELETE, adminServer.path(\"/instances/*\")),\n                // Notification endpoints\n                PathPatternRequestMatcher.withDefaults()\n                    .matcher(POST, adminServer.path(\"/notifications/**\")),\n                PathPatternRequestMatcher.withDefaults()\n                    .matcher(DELETE, adminServer.path(\"/notifications/**\")),\n                // Admin Server's own actuator\n                PathPatternRequestMatcher.withDefaults()\n                    .matcher(adminServer.path(\"/actuator/**\"))\n            )\n        );\n\n        return http.build();\n    }\n}\n```\n\n---\n\n## Custom CSRF Filter\n\nThe Admin UI (JavaScript) needs access to the CSRF token. Create a filter to expose it:\n\n```java\npackage com.example.admin;\n\nimport java.io.IOException;\n\nimport jakarta.servlet.FilterChain;\nimport jakarta.servlet.ServletException;\nimport jakarta.servlet.http.Cookie;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.springframework.security.web.csrf.CsrfToken;\nimport org.springframework.web.filter.OncePerRequestFilter;\nimport org.springframework.web.util.WebUtils;\n\npublic class CustomCsrfFilter extends OncePerRequestFilter {\n\n    public static final String CSRF_COOKIE_NAME = \"XSRF-TOKEN\";\n\n    @Override\n    protected void doFilterInternal(HttpServletRequest request,\n                                    HttpServletResponse response,\n                                    FilterChain filterChain)\n            throws ServletException, IOException {\n\n        // Get CSRF token from request attributes\n        CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());\n\n        if (csrf != null) {\n            Cookie cookie = WebUtils.getCookie(request, CSRF_COOKIE_NAME);\n            String token = csrf.getToken();\n\n            // Set cookie if not present or token changed\n            if (cookie == null || token != null && !token.equals(cookie.getValue())) {\n                cookie = new Cookie(CSRF_COOKIE_NAME, token);\n                cookie.setPath(\"/\");\n                response.addCookie(cookie);\n            }\n        }\n\n        filterChain.doFilter(request, response);\n    }\n}\n```\n\n**What this does**:\n\n1. Extracts CSRF token from Spring Security\n2. Stores it in a cookie named `XSRF-TOKEN`\n3. Cookie is **not** HTTP-only (JavaScript can read it)\n4. Admin UI reads cookie and includes token in requests\n\n---\n\n## How CSRF Works in Admin Server\n\n### 1. User Opens Admin UI\n\n```\nGET / HTTP/1.1\n```\n\n**Response**:\n\n```\nHTTP/1.1 200 OK\nSet-Cookie: XSRF-TOKEN=abc123; Path=/\nSet-Cookie: JSESSIONID=xyz789; Path=/; HttpOnly\n\n<!DOCTYPE html>\n<html>...</html>\n```\n\n### 2. JavaScript Makes Request\n\nAdmin UI JavaScript reads `XSRF-TOKEN` cookie and sends it in header:\n\n```javascript\nfetch('/instances/123/actuator/info', {\n  method: 'GET',\n  headers: {\n    'X-XSRF-TOKEN': 'abc123'  // From cookie\n  },\n  credentials: 'same-origin'\n})\n```\n\n**HTTP Request**:\n\n```\nGET /instances/123/actuator/info HTTP/1.1\nX-XSRF-TOKEN: abc123\nCookie: XSRF-TOKEN=abc123; JSESSIONID=xyz789\n```\n\n### 3. Spring Security Validates Token\n\nSpring Security compares:\n\n- Token from `X-XSRF-TOKEN` header\n- Token from `XSRF-TOKEN` cookie\n\nIf they match, request is allowed.\n\n### 4. Client Registration (Exempted)\n\nClient applications register **without** CSRF token:\n\n```\nPOST /instances HTTP/1.1\nContent-Type: application/json\n\n{\n  \"name\": \"my-service\",\n  \"healthUrl\": \"http://localhost:8081/actuator/health\"\n}\n```\n\nThis works because `/instances` is in `ignoringRequestMatchers`.\n\n---\n\n## Cookie vs Session Token Repository\n\n### Cookie-Based (Recommended for SPA)\n\n```java\n.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())\n```\n\n**Pros**:\n\n- JavaScript can read token from cookie\n- Works with Single Page Applications (SPA)\n- Stateless (no server-side session storage)\n\n**Cons**:\n\n- Cookie not HTTP-only (accessible to JavaScript)\n- Requires custom filter to set cookie\n\n### Session-Based (Default)\n\n```java\n.csrfTokenRepository(new HttpSessionCsrfTokenRepository())\n```\n\n**Pros**:\n\n- More secure (token not exposed to JavaScript)\n- Simpler configuration\n\n**Cons**:\n\n- Requires server-side session\n- Harder to use with SPA frameworks\n\n**Spring Boot Admin requires cookie-based** because the UI is a JavaScript SPA.\n\n---\n\n## Exempted Endpoints\n\n### Client Registration\n\n```java\n.ignoringRequestMatchers(\n    PathPatternRequestMatcher.withDefaults()\n        .matcher(POST, adminServer.path(\"/instances\")),\n    PathPatternRequestMatcher.withDefaults()\n        .matcher(DELETE, adminServer.path(\"/instances/*\"))\n)\n```\n\n**Why?**\n\n- Client applications don't have CSRF tokens\n- They register/deregister via simple HTTP POST/DELETE\n- Not vulnerable to CSRF (no browser session involved)\n\n### Actuator Endpoints\n\n```java\n.ignoringRequestMatchers(\n    PathPatternRequestMatcher.withDefaults()\n        .matcher(adminServer.path(\"/actuator/**\"))\n)\n```\n\n**Why?**\n\n- Admin Server's own health checks\n- Load balancers, monitoring tools access these\n- No CSRF risk (stateless, no session)\n\n### Notification Endpoints\n\n```java\n.ignoringRequestMatchers(\n    PathPatternRequestMatcher.withDefaults()\n        .matcher(POST, adminServer.path(\"/notifications/**\")),\n    PathPatternRequestMatcher.withDefaults()\n        .matcher(DELETE, adminServer.path(\"/notifications/**\"))\n)\n```\n\n**Why?**\n\n- Webhook endpoints from external services (Slack, Teams, etc.)\n- Cannot provide CSRF tokens\n- Authenticated via other means (webhook secrets)\n\n---\n\n## Context Path Support\n\nIf using a custom context path:\n\n```yaml\nspring:\n  boot:\n    admin:\n      context-path: /admin\n```\n\nUse `adminServer.path()` to include context path automatically:\n\n```java\nPathPatternRequestMatcher.withDefaults()\n    .matcher(POST, adminServer.path(\"/instances\"))\n```\n\nThis becomes `/admin/instances` automatically.\n\n---\n\n## Testing CSRF Protection\n\n### Test UI Request (Requires Token)\n\n**Without token**:\n\n```bash\ncurl -X POST http://localhost:8080/applications/my-app/restart \\\n  -H \"Cookie: JSESSIONID=abc123\"\n```\n\n**Response**: `403 Forbidden`\n\n**With token**:\n\n```bash\ncurl -X POST http://localhost:8080/applications/my-app/restart \\\n  -H \"Cookie: JSESSIONID=abc123; XSRF-TOKEN=def456\" \\\n  -H \"X-XSRF-TOKEN: def456\"\n```\n\n**Response**: `200 OK`\n\n### Test Client Registration (Exempted)\n\n```bash\ncurl -X POST http://localhost:8080/instances \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"name\": \"test-service\",\n    \"healthUrl\": \"http://localhost:8081/actuator/health\"\n  }'\n```\n\n**Response**: `201 Created` (no CSRF token needed)\n\n### Test Actuator (Exempted)\n\n```bash\ncurl http://localhost:8080/actuator/health\n```\n\n**Response**: `200 OK` (no CSRF token needed)\n\n---\n\n## Disable CSRF (Not Recommended)\n\nFor development/testing only:\n\n```java\n@Bean\n@Profile(\"dev\")\npublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n    http\n        .authorizeHttpRequests(/* ... */)\n        .csrf(csrf -> csrf.disable());  // Disable CSRF\n\n    return http.build();\n}\n```\n\n**Only disable CSRF for development and testing.**\n\n---\n\n## SameSite Cookie Attribute\n\nEnhance CSRF protection with SameSite cookies:\n\n```yaml\nserver:\n  servlet:\n    session:\n      cookie:\n        same-site: strict\n```\n\n**Options**:\n\n- `strict`: Cookie only sent for same-site requests (most secure)\n- `lax`: Cookie sent for top-level navigation (default)\n- `none`: Cookie sent for all requests (requires `secure=true`)\n\n**Recommendation**: Use `lax` for Admin Server (allows direct navigation).\n\n---\n\n## Troubleshooting\n\n### Issue: 403 Forbidden on all requests\n\n**Cause**: CSRF token missing or invalid.\n\n**Check**:\n\n1. Cookie is set:\n\n   ```bash\n   curl -i http://localhost:8080/\n   ```\n\n   Should see `Set-Cookie: XSRF-TOKEN=...`\n\n2. Custom CSRF filter is registered:\n\n   ```java\n   http.addFilterAfter(new CustomCsrfFilter(), BasicAuthenticationFilter.class)\n   ```\n\n3. Token repository is cookie-based:\n\n   ```java\n   .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())\n   ```\n\n### Issue: Client registration fails with 403\n\n**Cause**: `/instances` endpoint not exempted from CSRF.\n\n**Solution**: Add to `ignoringRequestMatchers`:\n\n```java\n.csrf(csrf -> csrf\n    .ignoringRequestMatchers(\n        PathPatternRequestMatcher.withDefaults()\n            .matcher(POST, adminServer.path(\"/instances\")),\n        PathPatternRequestMatcher.withDefaults()\n            .matcher(DELETE, adminServer.path(\"/instances/*\"))\n    )\n)\n```\n\n### Issue: Token cookie not accessible to JavaScript\n\n**Cause**: Cookie is HTTP-only.\n\n**Solution**: Use `withHttpOnlyFalse()`:\n\n```java\n.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())\n```\n\n### Issue: Token changes on every request\n\n**Expected behavior**. Spring Security generates new tokens regularly for security.\n\n**If problematic**: Use `CookieCsrfTokenRepository` with custom settings:\n\n```java\nCookieCsrfTokenRepository repository = CookieCsrfTokenRepository.withHttpOnlyFalse();\nrepository.setCookieName(\"XSRF-TOKEN\");\nrepository.setHeaderName(\"X-XSRF-TOKEN\");\n```\n\n### Issue: CSRF protection not working with context path\n\n**Cause**: Matchers don't include context path.\n\n**Solution**: Use `adminServer.path()`:\n\n```java\nPathPatternRequestMatcher.withDefaults()\n    .matcher(POST, adminServer.path(\"/instances\"))\n```\n\nNot:\n\n```java\nnew AntPathRequestMatcher(\"/instances\", POST.name())\n```\n\n---\n\n## Advanced Configuration\n\n### Custom Token Header/Cookie Names\n\n```java\nCookieCsrfTokenRepository repository = new CookieCsrfTokenRepository();\nrepository.setCookieName(\"MY-CSRF-TOKEN\");\nrepository.setHeaderName(\"X-MY-CSRF-TOKEN\");\nrepository.setParameterName(\"_csrf\");\nrepository.setCookieHttpOnly(false);\n\nhttp.csrf(csrf -> csrf\n    .csrfTokenRepository(repository)\n)\n```\n\nUpdate `CustomCsrfFilter` accordingly:\n\n```java\npublic static final String CSRF_COOKIE_NAME = \"MY-CSRF-TOKEN\";\n```\n\n### Conditional CSRF Protection\n\nEnable CSRF only for browser requests:\n\n```java\nhttp.csrf(csrf -> csrf\n    .requireCsrfProtectionMatcher(request -> {\n        // Require CSRF for browser requests (non-API)\n        String method = request.getMethod();\n        if (\"GET\".equals(method) || \"HEAD\".equals(method) ||\n            \"TRACE\".equals(method) || \"OPTIONS\".equals(method)) {\n            return false;  // Safe methods\n        }\n\n        String header = request.getHeader(\"X-Requested-With\");\n        if (\"XMLHttpRequest\".equals(header)) {\n            return true;  // AJAX requests\n        }\n\n        String accept = request.getHeader(\"Accept\");\n        if (accept != null && accept.contains(\"application/json\")) {\n            return false;  // API clients\n        }\n\n        return true;  // Browser requests\n    })\n)\n```\n\n### Multiple Security Filter Chains\n\nSeparate CSRF rules for UI and API:\n\n```java\n@Configuration\npublic class SecurityConfig {\n\n    @Bean\n    @Order(1)\n    public SecurityFilterChain apiFilterChain(HttpSecurity http,\n                                              AdminServerProperties adminServer) throws Exception {\n        http\n            .securityMatcher(adminServer.path(\"/api/**\"))\n            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())\n            .httpBasic(Customizer.withDefaults())\n            .csrf(csrf -> csrf.disable());  // No CSRF for API\n\n        return http.build();\n    }\n\n    @Bean\n    @Order(2)\n    public SecurityFilterChain uiFilterChain(HttpSecurity http,\n                                             AdminServerProperties adminServer) throws Exception {\n        http\n            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())\n            .formLogin(/* ... */)\n            .csrf(csrf -> csrf  // CSRF for UI\n                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())\n            );\n\n        return http.build();\n    }\n}\n```\n\n---\n\n## Best Practices\n\n1. **Always enable CSRF** when deploying\n2. **Use cookie-based repository** for JavaScript SPAs\n3. **Exempt only necessary endpoints** (client registration, actuator)\n4. **Use SameSite cookies** for additional protection\n5. **Test CSRF protection** before deploying\n6. **Use HTTPS** to prevent token theft\n7. **Rotate session IDs** after login\n8. **Monitor for CSRF attacks** in logs\n\n---\n\n## Complete Working Example\n\n**SecurityConfig.java**:\n\n```java\npackage com.example.admin;\n\nimport java.util.UUID;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.security.config.Customizer;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.core.userdetails.User;\nimport org.springframework.security.core.userdetails.UserDetails;\nimport org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;\nimport org.springframework.security.crypto.password.PasswordEncoder;\nimport org.springframework.security.provisioning.InMemoryUserDetailsManager;\nimport org.springframework.security.web.SecurityFilterChain;\nimport org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;\nimport org.springframework.security.web.authentication.www.BasicAuthenticationFilter;\nimport org.springframework.security.web.csrf.CookieCsrfTokenRepository;\nimport org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;\nimport org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;\n\nimport de.codecentric.boot.admin.server.config.AdminServerProperties;\n\nimport static org.springframework.http.HttpMethod.DELETE;\nimport static org.springframework.http.HttpMethod.POST;\n\n@Configuration\npublic class SecurityConfig {\n\n    private final AdminServerProperties adminServer;\n\n    public SecurityConfig(AdminServerProperties adminServer) {\n        this.adminServer = adminServer;\n    }\n\n    @Bean\n    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n        SavedRequestAwareAuthenticationSuccessHandler successHandler =\n            new SavedRequestAwareAuthenticationSuccessHandler();\n        successHandler.setTargetUrlParameter(\"redirectTo\");\n        successHandler.setDefaultTargetUrl(adminServer.path(\"/\"));\n\n        http\n            .authorizeHttpRequests(auth -> auth\n                .requestMatchers(PathPatternRequestMatcher.withDefaults()\n                    .matcher(adminServer.path(\"/assets/**\")))\n                .permitAll()\n                .requestMatchers(PathPatternRequestMatcher.withDefaults()\n                    .matcher(adminServer.path(\"/login\")))\n                .permitAll()\n                .requestMatchers(PathPatternRequestMatcher.withDefaults()\n                    .matcher(adminServer.path(\"/actuator/info\")))\n                .permitAll()\n                .requestMatchers(PathPatternRequestMatcher.withDefaults()\n                    .matcher(adminServer.path(\"/actuator/health\")))\n                .permitAll()\n                .anyRequest().authenticated()\n            )\n            .formLogin(formLogin -> formLogin\n                .loginPage(adminServer.path(\"/login\"))\n                .successHandler(successHandler)\n            )\n            .logout(logout -> logout\n                .logoutUrl(adminServer.path(\"/logout\"))\n            )\n            .httpBasic(Customizer.withDefaults());\n\n        http.addFilterAfter(new CustomCsrfFilter(), BasicAuthenticationFilter.class)\n            .csrf(csrf -> csrf\n                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())\n                .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())\n                .ignoringRequestMatchers(\n                    PathPatternRequestMatcher.withDefaults()\n                        .matcher(POST, adminServer.path(\"/instances\")),\n                    PathPatternRequestMatcher.withDefaults()\n                        .matcher(DELETE, adminServer.path(\"/instances/*\")),\n                    PathPatternRequestMatcher.withDefaults()\n                        .matcher(adminServer.path(\"/actuator/**\"))\n                )\n            );\n\n        http.rememberMe(rememberMe -> rememberMe\n            .key(UUID.randomUUID().toString())\n            .tokenValiditySeconds(1209600)\n        );\n\n        return http.build();\n    }\n\n    @Bean\n    public InMemoryUserDetailsManager userDetailsService(PasswordEncoder passwordEncoder) {\n        UserDetails user = User.builder()\n            .username(\"admin\")\n            .password(passwordEncoder.encode(System.getenv(\"ADMIN_PASSWORD\")))\n            .roles(\"ADMIN\")\n            .build();\n\n        return new InMemoryUserDetailsManager(user);\n    }\n\n    @Bean\n    public PasswordEncoder passwordEncoder() {\n        return new BCryptPasswordEncoder();\n    }\n}\n```\n\n**CustomCsrfFilter.java** (same as shown earlier).\n\n---\n\n## See Also\n\n- [Server Authentication](./10-server-authentication.md) - Configure Spring Security\n- [Actuator Security](./20-actuator-security.md) - Secure client endpoints\n- [Spring Security CSRF Documentation](https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html)\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/05-security/_category_.json",
    "content": "{\n  \"position\": 5,\n  \"label\": \"Security\"\n}\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/05-security/index.md",
    "content": "---\nsidebar_position: 1\nsidebar_custom_props:\n  icon: 'shield'\n---\n\n# Security\n\nSpring Boot Admin Server and Client can be secured using Spring Security. This section covers all aspects of securing\nyour Spring Boot Admin deployment.\n\n## Security Overview\n\nA complete Spring Boot Admin deployment has multiple security concerns:\n\n```mermaid\ngraph TD\n    A[User Browser] -->|1 Login to Admin UI| B[\"Spring Boot Admin Server<br/>• Form login + HTTP Basic authentication<br/>• CSRF protection for UI<br/>• Remember-me functionality\"]\n    B -->|2 Access actuator endpoints| C[\"Client Application Instance<br/>• Secured actuator endpoints<br/>• Client provides credentials via metadata<br/>• Server authenticates using instance-auth\"]\n```\n\n## Security Layers\n\n### 1. Admin Server Security\n\nProtect the Admin UI and API endpoints:\n\n- **Authentication**: Form login for UI, HTTP Basic for API clients\n- **Authorization**: Role-based access control\n- **CSRF Protection**: Protect against Cross-Site Request Forgery\n- **Session Management**: Remember-me functionality\n\n**See**: [Server Authentication](./10-server-authentication.md)\n\n### 2. Actuator Endpoint Security\n\nSecure the client application's actuator endpoints:\n\n- **Spring Security**: Protect actuator with authentication\n- **Credentials Sharing**: Pass credentials to Admin Server via metadata\n- **Per-Service Auth**: Different credentials per service\n\n**See**: [Actuator Security](./20-actuator-security.md)\n\n### 3. CSRF Protection\n\nConfigure CSRF tokens for Admin UI while allowing client registration:\n\n- **Cookie-based CSRF**: JavaScript-friendly token repository\n- **Exempted Endpoints**: Allow `/instances` registration without CSRF\n- **Custom CSRF Filter**: Make token available to JavaScript\n\n**See**: [CSRF Protection](./30-csrf-protection.md)\n\n### 4. Mutual TLS (Optional)\n\nEnhanced security with client certificates:\n\n- **mTLS Between Server and Clients**: Mutual authentication\n- **Certificate Validation**: Trust only specific clients\n- **SSL Configuration**: Keystore and truststore setup\n\n---\n\n## Quick Start Examples\n\n### Minimal Secured Server\n\n```yaml\nspring:\n  security:\n    user:\n      name: admin\n      password: ${ADMIN_PASSWORD}\n```\n\n```java\n@Configuration\npublic class SecurityConfig {\n\n    @Bean\n    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n        http.authorizeHttpRequests(auth -> auth\n                .requestMatchers(\"/assets/**\").permitAll()\n                .requestMatchers(\"/login\").permitAll()\n                .anyRequest().authenticated()\n            )\n            .formLogin(formLogin -> formLogin.loginPage(\"/login\"))\n            .httpBasic(Customizer.withDefaults());\n\n        return http.build();\n    }\n}\n```\n\n### Client with Secured Actuator\n\n**Client Configuration**:\n\n```yaml\nspring:\n  boot:\n    admin:\n      client:\n        url: http://admin-server:8080\n        instance:\n          metadata:\n            user.name: actuator\n            user.password: ${ACTUATOR_PASSWORD}\n\n  security:\n    user:\n      name: actuator\n      password: ${ACTUATOR_PASSWORD}\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n```\n\n**Server Configuration**:\n\n```yaml\nspring:\n  boot:\n    admin:\n      instance-auth:\n        enabled: true\n        # Credentials from instance metadata\n```\n\n---\n\n## Security Checklist\n\nUse this checklist to ensure your deployment is secure:\n\n### Admin Server\n\n- [ ] Enable Spring Security\n- [ ] Use strong passwords (externalize via environment variables)\n- [ ] Configure form login for UI access\n- [ ] Enable HTTP Basic for API/programmatic access\n- [ ] Configure CSRF protection with exemptions for `/instances`\n- [ ] Set up remember-me with secure random key\n- [ ] Use HTTPS for deployments\n- [ ] Restrict access by IP (if applicable)\n- [ ] Configure session timeout\n- [ ] Audit authentication attempts\n\n### Client Applications\n\n- [ ] Secure actuator endpoints with Spring Security\n- [ ] Pass actuator credentials via metadata (`user.name`, `user.password`)\n- [ ] Use strong actuator passwords\n- [ ] Limit exposed actuator endpoints to necessary ones\n- [ ] Use HTTPS for actuator if possible\n- [ ] Verify Admin Server certificate (if using HTTPS)\n- [ ] Consider mutual TLS for high-security environments\n\n### Network Security\n\n- [ ] Use HTTPS for all communication\n- [ ] Configure firewalls to restrict Admin Server access\n- [ ] Use VPN or private networks when possible\n- [ ] Enable mutual TLS if required\n- [ ] Monitor for suspicious access patterns\n\n---\n\n## Common Security Scenarios\n\n### Scenario 1: Development Environment\n\n**Goal**: Simple security for local development.\n\n```yaml\n# Admin Server\nspring:\n  security:\n    user:\n      name: user\n      password: password\n```\n\nNo actuator security needed in development.\n\n### Scenario 2: Production with Role-Based Access\n\n**Goal**: Different roles for read-only vs admin users.\n\n```java\n@Configuration\npublic class SecurityConfig {\n\n    @Bean\n    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n        http.authorizeHttpRequests(auth -> auth\n                .requestMatchers(\"/assets/**\", \"/login\").permitAll()\n                .requestMatchers(\"/instances/**\").hasRole(\"ADMIN\")\n                .anyRequest().hasAnyRole(\"ADMIN\", \"USER\")\n            )\n            .formLogin(formLogin -> formLogin.loginPage(\"/login\"))\n            .httpBasic(Customizer.withDefaults());\n\n        return http.build();\n    }\n\n    @Bean\n    public UserDetailsService userDetailsService(PasswordEncoder encoder) {\n        UserDetails admin = User.builder()\n            .username(\"admin\")\n            .password(encoder.encode(System.getenv(\"ADMIN_PASSWORD\")))\n            .roles(\"ADMIN\")\n            .build();\n\n        UserDetails user = User.builder()\n            .username(\"viewer\")\n            .password(encoder.encode(System.getenv(\"VIEWER_PASSWORD\")))\n            .roles(\"USER\")\n            .build();\n\n        return new InMemoryUserDetailsManager(admin, user);\n    }\n\n    @Bean\n    public PasswordEncoder passwordEncoder() {\n        return new BCryptPasswordEncoder();\n    }\n}\n```\n\n### Scenario 3: Kubernetes with Service Accounts\n\n**Goal**: Use Kubernetes service accounts for authentication.\n\n```yaml\n# Admin Server\nspring:\n  boot:\n    admin:\n      discovery:\n        enabled: true\n\n# Spring Security with OAuth2\nspring:\n  security:\n    oauth2:\n      client:\n        registration:\n          keycloak:\n            client-id: spring-boot-admin\n            client-secret: ${OAUTH2_CLIENT_SECRET}\n        provider:\n          keycloak:\n            issuer-uri: https://keycloak.company.com/realms/main\n```\n\n### Scenario 4: Different Credentials per Service\n\n**Goal**: Use unique credentials for each client service.\n\n**Admin Server**:\n\n```yaml\nspring:\n  boot:\n    admin:\n      instance-auth:\n        enabled: true\n        service-map:\n          service-a:\n            user-name: service-a-actuator\n            user-password: ${SERVICE_A_PASSWORD}\n          service-b:\n            user-name: service-b-actuator\n            user-password: ${SERVICE_B_PASSWORD}\n        default-user-name: default-actuator\n        default-password: ${DEFAULT_PASSWORD}\n```\n\n**Client (Service A)**:\n\n```yaml\nspring:\n  application:\n    name: service-a\n\n  security:\n    user:\n      name: service-a-actuator\n      password: ${SERVICE_A_PASSWORD}\n```\n\n---\n\n## Best Practices\n\n### 1. Externalize Secrets\n\nNever hardcode passwords. Use environment variables or secret management:\n\n```yaml\nspring:\n  security:\n    user:\n      name: ${ADMIN_USER:admin}\n      password: ${ADMIN_PASSWORD}\n```\n\n**Docker**:\n\n```bash\ndocker run -e ADMIN_PASSWORD=secret123 my-admin-server\n```\n\n**Kubernetes Secret**:\n\n```yaml\napiVersion: v1\nkind: Secret\nmetadata:\n  name: admin-credentials\ntype: Opaque\ndata:\n  password: c2VjcmV0MTIz  # base64 encoded\n```\n\n### 2. Use Strong Passwords\n\n- Minimum 16 characters\n- Mix of uppercase, lowercase, numbers, symbols\n- Use password generators\n- Rotate regularly\n\n### 3. Limit Actuator Exposure\n\nOnly expose necessary endpoints:\n\n```yaml\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: health,info,metrics,loggers\n```\n\n### 4. Enable HTTPS\n\nUse TLS for all communication:\n\n```yaml\nserver:\n  port: 8443\n  ssl:\n    enabled: true\n    key-store: classpath:keystore.p12\n    key-store-password: ${KEYSTORE_PASSWORD}\n    key-store-type: PKCS12\n```\n\n### 5. Monitor Security Events\n\nLog authentication attempts and failures:\n\n```yaml\nlogging:\n  level:\n    org.springframework.security: DEBUG\n    de.codecentric.boot.admin: DEBUG\n```\n\n---\n\n## Security Headers\n\nConfigure security headers for the Admin UI:\n\n```java\n@Configuration\npublic class SecurityConfig {\n\n    @Bean\n    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n        http\n            .headers(headers -> headers\n                .contentSecurityPolicy(csp -> csp\n                    .policyDirectives(\"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'\")\n                )\n                .frameOptions(frame -> frame.sameOrigin())\n                .xssProtection(xss -> xss.block(true))\n                .httpStrictTransportSecurity(hsts -> hsts\n                    .includeSubDomains(true)\n                    .maxAgeInSeconds(31536000)\n                )\n            );\n\n        return http.build();\n    }\n}\n```\n\n---\n\n## Troubleshooting\n\n### Issue: 401 Unauthorized when accessing instances\n\n**Cause**: Admin Server doesn't have credentials to access actuator endpoints.\n\n**Solution**: Add credentials to instance metadata:\n\n```yaml\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          metadata:\n            user.name: actuator\n            user.password: ${ACTUATOR_PASSWORD}\n```\n\n### Issue: CSRF token errors on client registration\n\n**Cause**: CSRF protection blocking `/instances` endpoint.\n\n**Solution**: Exempt registration endpoints from CSRF:\n\n```java\n.csrf(csrf -> csrf\n    .ignoringRequestMatchers(\n        new AntPathRequestMatcher(\"/instances\", POST.name()),\n        new AntPathRequestMatcher(\"/instances/*\", DELETE.name())\n    )\n)\n```\n\n### Issue: Login page not loading\n\n**Cause**: Login page assets blocked by security.\n\n**Solution**: Permit access to assets and login:\n\n```java\n.authorizeHttpRequests(auth -> auth\n    .requestMatchers(\"/assets/**\", \"/login\").permitAll()\n    .anyRequest().authenticated()\n)\n```\n\n### Issue: Remember-me not working\n\n**Cause**: No `UserDetailsService` configured.\n\n**Solution**: Add `UserDetailsService` bean:\n\n```java\n@Bean\npublic InMemoryUserDetailsManager userDetailsService(PasswordEncoder encoder) {\n    UserDetails user = User.builder()\n        .username(\"admin\")\n        .password(encoder.encode(\"password\"))\n        .roles(\"ADMIN\")\n        .build();\n    return new InMemoryUserDetailsManager(user);\n}\n```\n\n---\n\n## Next Steps\n\n- [Server Authentication](./10-server-authentication.md) - Secure Admin Server with Spring Security\n- [Actuator Security](./20-actuator-security.md) - Secure client actuator endpoints\n- [CSRF Protection](./30-csrf-protection.md) - Configure CSRF for UI and API\n\n---\n\n## See Also\n\n- [Server Configuration](../02-server/01-server.mdx)\n- [Client Configuration](../03-client/10-client-features.md)\n- [Spring Security Documentation](https://docs.spring.io/spring-security/reference/index.html)\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/06-customization/_category_.json",
    "content": "{\n  \"position\": 6,\n  \"label\": \"Customization\"\n}\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/06-customization/index.md",
    "content": "import DocCardList from '@theme/DocCardList';\n\n# Customization\n\n <DocCardList />\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/06-customization/monitoring/01-instance-filters.md",
    "content": "---\n\nsidebar_position: 1\nsidebar_custom_props:\n  icon: 'wrench'\n---\n\n# Instance Filters\n\nFilter which instances are visible and managed by Spring Boot Admin Server.\n\n## Overview\n\n`InstanceFilter` allows you to selectively include or exclude instances from being displayed and monitored by the Admin\nServer.\n\n**Use Cases**:\n\n- Hide test/development instances in production Admin Server\n- Filter instances by environment, region, or tags\n- Exclude specific services from monitoring\n- Show only instances matching certain criteria\n\n```mermaid\ngraph TD\n    A[\"Instances Registered<br/>• service-a (env=prod)<br/>• service-b (env=dev)<br/>• service-c (env=prod)<br/>• service-d (env=test)\"] --> B[\"InstanceFilter<br/>filter(instance) {<br/>  return 'prod'.equals(<br/>    instance.getMetadata().get('env')<br/>  );<br/>}\"]\n    B --> C[\"Visible Instances<br/>• service-a (env=prod)<br/>• service-c (env=prod)\"]\n```\n\n---\n\n## Default Behavior\n\nBy default, **all instances are visible**:\n\n```java\n@Bean\n@ConditionalOnMissingBean\npublic InstanceFilter instanceFilter() {\n    return instance -> true;  // Accept all instances\n}\n```\n\n---\n\n## InstanceFilter Interface\n\n```java\npackage de.codecentric.boot.admin.server.services;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\n\n@FunctionalInterface\npublic interface InstanceFilter {\n\n    /**\n     * Test if instance should be visible\n     * @param instance the instance to filter\n     * @return true if instance should be included, false to exclude\n     */\n    boolean filter(Instance instance);\n\n}\n```\n\n---\n\n## How It Works\n\n`InstanceFilter` is applied by `InstanceRegistry`:\n\n```java\npublic Flux<Instance> getInstances() {\n    return repository.findAll().filter(filter::filter);\n}\n\npublic Mono<Instance> getInstance(InstanceId id) {\n    return repository.find(id).filter(filter::filter);\n}\n```\n\n**Important**: Instances are **still stored** in the repository, but filtered from queries. This means:\n\n- Filtered instances continue to be monitored\n- Events are still generated for filtered instances\n- Filtering only affects visibility in the UI and API\n\n---\n\n## Filter by Environment\n\nShow only production instances:\n\n```java\npackage com.example.admin;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport de.codecentric.boot.admin.server.services.InstanceFilter;\n\n@Configuration\npublic class InstanceFilterConfig {\n\n    @Bean\n    public InstanceFilter instanceFilter() {\n        return instance -> {\n            String env = instance.getRegistration()\n                .getMetadata()\n                .get(\"environment\");\n\n            return \"production\".equals(env);\n        };\n    }\n}\n```\n\n**Client Configuration**:\n\n```yaml\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          metadata:\n            environment: production\n```\n\n---\n\n## Filter by Tags\n\nShow only instances with specific tags:\n\n```java\npackage com.example.admin;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport de.codecentric.boot.admin.server.services.InstanceFilter;\n\n@Configuration\npublic class InstanceFilterConfig {\n\n    @Bean\n    public InstanceFilter instanceFilter() {\n        return instance -> {\n            String tags = instance.getRegistration()\n                .getMetadata()\n                .get(\"tags\");\n\n            if (tags == null) {\n                return false;  // Exclude instances without tags\n            }\n\n            // Show instances with \"production\" or \"critical\" tag\n            return tags.contains(\"production\") || tags.contains(\"critical\");\n        };\n    }\n}\n```\n\n**Client Configuration**:\n\n```yaml\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          metadata:\n            tags: production,critical,payment\n```\n\n---\n\n## Filter by Service Name\n\nExclude specific services:\n\n```java\npackage com.example.admin;\n\nimport java.util.Set;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport de.codecentric.boot.admin.server.services.InstanceFilter;\n\n@Configuration\npublic class InstanceFilterConfig {\n\n    @Bean\n    public InstanceFilter instanceFilter() {\n        Set<String> excludedServices = Set.of(\n            \"test-service\",\n            \"dev-helper\",\n            \"mock-service\"\n        );\n\n        return instance -> {\n            String serviceName = instance.getRegistration().getName();\n            return !excludedServices.contains(serviceName);\n        };\n    }\n}\n```\n\n---\n\n## Filter by Status\n\nShow only healthy instances:\n\n```java\npackage com.example.admin;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\nimport de.codecentric.boot.admin.server.services.InstanceFilter;\n\n@Configuration\npublic class InstanceFilterConfig {\n\n    @Bean\n    public InstanceFilter instanceFilter() {\n        return instance -> {\n            StatusInfo status = instance.getStatusInfo();\n\n            // Show only UP or UNKNOWN instances\n            return status.isUp() || status.isUnknown();\n        };\n    }\n}\n```\n\n**Status values**:\n\n- `isUp()` - Instance is healthy\n- `isDown()` - Instance is unhealthy\n- `isOffline()` - Instance is unreachable\n- `isUnknown()` - Status not yet determined\n\n---\n\n## Filter by URL Pattern\n\nShow only instances from specific hosts:\n\n```java\npackage com.example.admin;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport de.codecentric.boot.admin.server.services.InstanceFilter;\n\n@Configuration\npublic class InstanceFilterConfig {\n\n    @Bean\n    public InstanceFilter instanceFilter() {\n        return instance -> {\n            String serviceUrl = instance.getRegistration().getServiceUrl();\n\n            // Show only instances on production domain\n            return serviceUrl != null &&\n                   serviceUrl.contains(\".prod.company.com\");\n        };\n    }\n}\n```\n\n---\n\n## Configurable Filter\n\nFilter based on application properties:\n\n**application.yml**:\n\n```yaml\nadmin:\n  filter:\n    enabled: true\n    environment: production\n    tags:\n      - critical\n      - production\n```\n\n**Configuration**:\n\n```java\npackage com.example.admin;\n\nimport java.util.List;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.stereotype.Component;\n\nimport de.codecentric.boot.admin.server.services.InstanceFilter;\n\n@Configuration\n@EnableConfigurationProperties(FilterProperties.class)\npublic class InstanceFilterConfig {\n\n    @Bean\n    public InstanceFilter instanceFilter(FilterProperties filterProperties) {\n        if (!filterProperties.isEnabled()) {\n            return instance -> true;  // No filtering\n        }\n\n        return instance -> {\n            String env = instance.getRegistration()\n                .getMetadata()\n                .get(\"environment\");\n\n            String tags = instance.getRegistration()\n                .getMetadata()\n                .get(\"tags\");\n\n            // Check environment\n            if (filterProperties.getEnvironment() != null) {\n                if (!filterProperties.getEnvironment().equals(env)) {\n                    return false;\n                }\n            }\n\n            // Check tags\n            if (filterProperties.getTags() != null && !filterProperties.getTags().isEmpty()) {\n                if (tags == null) {\n                    return false;\n                }\n\n                for (String requiredTag : filterProperties.getTags()) {\n                    if (tags.contains(requiredTag)) {\n                        return true;\n                    }\n                }\n                return false;\n            }\n\n            return true;\n        };\n    }\n}\n\n@Component\n@ConfigurationProperties(prefix = \"admin.filter\")\nclass FilterProperties {\n    private boolean enabled = false;\n    private String environment;\n    private List<String> tags;\n\n    // Getters and setters\n    public boolean isEnabled() {\n        return enabled;\n    }\n\n    public void setEnabled(boolean enabled) {\n        this.enabled = enabled;\n    }\n\n    public String getEnvironment() {\n        return environment;\n    }\n\n    public void setEnvironment(String environment) {\n        this.environment = environment;\n    }\n\n    public List<String> getTags() {\n        return tags;\n    }\n\n    public void setTags(List<String> tags) {\n        this.tags = tags;\n    }\n}\n```\n\n---\n\n## Multiple Conditions\n\nCombine multiple filter conditions:\n\n```java\npackage com.example.admin;\n\nimport java.util.Set;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport de.codecentric.boot.admin.server.services.InstanceFilter;\n\n@Configuration\npublic class InstanceFilterConfig {\n\n    @Bean\n    public InstanceFilter instanceFilter() {\n        Set<String> allowedEnvironments = Set.of(\"production\", \"staging\");\n        Set<String> excludedServices = Set.of(\"test-service\", \"dev-tool\");\n\n        return instance -> {\n            String env = instance.getRegistration()\n                .getMetadata()\n                .get(\"environment\");\n\n            String serviceName = instance.getRegistration().getName();\n\n            // Include if:\n            // 1. Environment is allowed AND\n            // 2. Service is not excluded\n            return allowedEnvironments.contains(env) &&\n                   !excludedServices.contains(serviceName);\n        };\n    }\n}\n```\n\n---\n\n## Advanced Filtering\n\n### Filter by Registration Time\n\nShow only recently registered instances:\n\n```java\npackage com.example.admin;\n\nimport java.time.Duration;\nimport java.time.Instant;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport de.codecentric.boot.admin.server.services.InstanceFilter;\n\n@Configuration\npublic class InstanceFilterConfig {\n\n    @Bean\n    public InstanceFilter instanceFilter() {\n        return instance -> {\n            Instant registrationTime = instance.getRegistration().getTimestamp();\n            if (registrationTime == null) {\n                return true;\n            }\n\n            // Show instances registered within last 7 days\n            Duration age = Duration.between(registrationTime, Instant.now());\n            return age.compareTo(Duration.ofDays(7)) < 0;\n        };\n    }\n}\n```\n\n### Filter by Build Info\n\nShow only instances with specific versions:\n\n```java\npackage com.example.admin;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport de.codecentric.boot.admin.server.services.InstanceFilter;\n\n@Configuration\npublic class InstanceFilterConfig {\n\n    @Bean\n    public InstanceFilter instanceFilter() {\n        return instance -> {\n            if (instance.getInfo() == null || instance.getInfo().getValues() == null) {\n                return true;  // No build info available\n            }\n\n            Object buildInfo = instance.getInfo().getValues().get(\"build\");\n            if (buildInfo instanceof Map) {\n                Map<?, ?> build = (Map<?, ?>) buildInfo;\n                String version = (String) build.get(\"version\");\n\n                // Only show version 2.x and above\n                return version != null && !version.startsWith(\"1.\");\n            }\n\n            return true;\n        };\n    }\n}\n```\n\n### Database-Driven Filter\n\nLoad filter rules from database:\n\n```java\npackage com.example.admin;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport de.codecentric.boot.admin.server.services.InstanceFilter;\n\n@Configuration\npublic class InstanceFilterConfig {\n\n    @Bean\n    public InstanceFilter instanceFilter(InstanceFilterRuleRepository repository) {\n        return instance -> {\n            String serviceName = instance.getRegistration().getName();\n            String env = instance.getRegistration()\n                .getMetadata()\n                .get(\"environment\");\n\n            // Query database for filter rules\n            return repository.shouldShowInstance(serviceName, env);\n        };\n    }\n}\n\ninterface InstanceFilterRuleRepository {\n    boolean shouldShowInstance(String serviceName, String environment);\n}\n```\n\n---\n\n## Composite Filters\n\nCombine multiple filters with AND/OR logic:\n\n```java\npackage com.example.admin;\n\nimport java.util.Arrays;\nimport java.util.List;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport de.codecentric.boot.admin.server.services.InstanceFilter;\n\n@Configuration\npublic class InstanceFilterConfig {\n\n    @Bean\n    public InstanceFilter instanceFilter() {\n        InstanceFilter envFilter = instance -> {\n            String env = instance.getRegistration()\n                .getMetadata()\n                .get(\"environment\");\n            return \"production\".equals(env);\n        };\n\n        InstanceFilter statusFilter = instance -> {\n            return instance.getStatusInfo().isUp();\n        };\n\n        InstanceFilter tagsFilter = instance -> {\n            String tags = instance.getRegistration()\n                .getMetadata()\n                .get(\"tags\");\n            return tags != null && tags.contains(\"monitored\");\n        };\n\n        // Combine with AND logic\n        return and(envFilter, statusFilter, tagsFilter);\n    }\n\n    private InstanceFilter and(InstanceFilter... filters) {\n        return instance -> {\n            for (InstanceFilter filter : filters) {\n                if (!filter.filter(instance)) {\n                    return false;\n                }\n            }\n            return true;\n        };\n    }\n\n    private InstanceFilter or(InstanceFilter... filters) {\n        return instance -> {\n            for (InstanceFilter filter : filters) {\n                if (filter.filter(instance)) {\n                    return true;\n                }\n            }\n            return false;\n        };\n    }\n}\n```\n\n---\n\n## Testing Filters\n\n### Unit Test\n\n```java\npackage com.example.admin;\n\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.services.InstanceFilter;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass InstanceFilterTest {\n\n    @Test\n    void shouldFilterByEnvironment() {\n        InstanceFilter filter = instance -> {\n            String env = instance.getRegistration()\n                .getMetadata()\n                .get(\"environment\");\n            return \"production\".equals(env);\n        };\n\n        Instance prodInstance = createInstance(\"prod-service\",\n            Map.of(\"environment\", \"production\"));\n        Instance devInstance = createInstance(\"dev-service\",\n            Map.of(\"environment\", \"development\"));\n\n        assertThat(filter.filter(prodInstance)).isTrue();\n        assertThat(filter.filter(devInstance)).isFalse();\n    }\n\n    private Instance createInstance(String name, Map<String, String> metadata) {\n        Registration registration = Registration.builder()\n            .name(name)\n            .healthUrl(\"http://localhost:8080/actuator/health\")\n            .metadata(metadata)\n            .build();\n\n        return Instance.create(InstanceId.of(\"test-id\"))\n            .register(registration);\n    }\n}\n```\n\n---\n\n## Troubleshooting\n\n### Issue: Instances not appearing in UI\n\n**Cause**: Filter is excluding them.\n\n**Debug**:\n\n```java\n@Bean\npublic InstanceFilter instanceFilter() {\n    return instance -> {\n        boolean result = /* your filter logic */;\n\n        // Log for debugging\n        if (!result) {\n            System.out.println(\"Filtered out: \" +\n                instance.getRegistration().getName());\n        }\n\n        return result;\n    };\n}\n```\n\n### Issue: Filter not applied\n\n**Cause**: Multiple `InstanceFilter` beans defined.\n\n**Solution**: Only define one `InstanceFilter` bean. Spring Boot Admin uses the first one it finds.\n\n### Issue: Metadata not available\n\n**Cause**: Client not sending metadata.\n\n**Solution**: Verify client configuration:\n\n```yaml\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          metadata:\n            environment: production\n```\n\n---\n\n## Best Practices\n\n1. **Keep filters simple**: Complex filters can impact performance\n2. **Document filter logic**: Make it clear why instances are excluded\n3. **Test thoroughly**: Ensure correct instances are visible\n4. **Use metadata**: Don't filter based on volatile data like status\n5. **Consider multiple Admin Servers**: Instead of complex filtering, run separate Admin Servers for different\n   environments\n\n---\n\n## Examples\n\n### Example 1: Multi-Tenant Filter\n\n```java\n@Bean\npublic InstanceFilter instanceFilter(@Value(\"${tenant.id}\") String tenantId) {\n    return instance -> {\n        String instanceTenant = instance.getRegistration()\n            .getMetadata()\n            .get(\"tenant\");\n        return tenantId.equals(instanceTenant);\n    };\n}\n```\n\n### Example 2: Region-Based Filter\n\n```java\n@Bean\npublic InstanceFilter instanceFilter(@Value(\"${aws.region}\") String currentRegion) {\n    return instance -> {\n        String instanceRegion = instance.getRegistration()\n            .getMetadata()\n            .get(\"region\");\n        return currentRegion.equals(instanceRegion);\n    };\n}\n```\n\n### Example 3: Whitelist/Blacklist Filter\n\n```java\n@Bean\npublic InstanceFilter instanceFilter(\n        @Value(\"${admin.whitelist:}\") List<String> whitelist,\n        @Value(\"${admin.blacklist:}\") List<String> blacklist) {\n\n    return instance -> {\n        String serviceName = instance.getRegistration().getName();\n\n        // Blacklist takes precedence\n        if (blacklist.contains(serviceName)) {\n            return false;\n        }\n\n        // If whitelist is empty, allow all (except blacklisted)\n        if (whitelist.isEmpty()) {\n            return true;\n        }\n\n        // Otherwise, only allow whitelisted\n        return whitelist.contains(serviceName);\n    };\n}\n```\n\n**Configuration**:\n\n```yaml\nadmin:\n  whitelist:\n    - payment-service\n    - user-service\n  blacklist:\n    - test-service\n```\n\n---\n\n## See Also\n\n- [Server Configuration](../../02-server/01-server.mdx)\n- [Custom Health Status](./02-custom-health-status.md)\n- [Endpoint Detection](../server/04-endpoint-detection.md)\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/06-customization/monitoring/02-custom-health-status.md",
    "content": "---\n\nsidebar_position: 2\nsidebar_custom_props:\n  icon: 'wrench'\n---\n\n# Custom Health Status\n\nCustomize how Spring Boot Admin Server retrieves and interprets health status from instances.\n\n## Overview\n\nThe Admin Server monitors instance health by querying the `/actuator/health` endpoint. You can customize:\n\n1. **StatusUpdater** - How health status is retrieved and parsed\n2. **InfoUpdater** - How instance info is retrieved\n3. **Status interpretation** - Custom status codes and logic\n\n```mermaid\ngraph TD\n    A[StatusUpdater every 10 seconds] --> B[GET /actuator/health]\n    B --> C[Parse response]\n    C --> D[Create StatusInfo]\n    D --> E[Update Instance.statusInfo]\n```\n\n---\n\n## Default Behavior\n\n### StatusUpdater\n\nBy default, `StatusUpdater` queries the health endpoint:\n\n**StatusUpdater.java** (simplified):\n\n```java\nprotected Mono<Instance> doUpdateStatus(Instance instance) {\n    return instanceWebClient.instance(instance)\n        .get()\n        .uri(Endpoint.HEALTH)\n        .exchangeToMono(this::convertStatusInfo)\n        .timeout(Duration.ofSeconds(10))\n        .onErrorResume(this::handleError)\n        .map(instance::withStatusInfo);\n}\n\nprotected StatusInfo getStatusInfoFromStatus(HttpStatusCode httpStatus, Map<String, ?> body) {\n    if (httpStatus.is2xxSuccessful()) {\n        return StatusInfo.ofUp();\n    }\n    // Return DOWN with error details\n    return StatusInfo.ofDown(details);\n}\n\nprivate Mono<StatusInfo> handleError(Throwable ex) {\n    Map<String, Object> details = new HashMap<>();\n    details.put(\"message\", ex.getMessage());\n    details.put(\"exception\", ex.getClass().getName());\n    return Mono.just(StatusInfo.ofOffline(details));\n}\n```\n\n**Status Mapping**:\n\n- HTTP 2xx → `UP`\n- HTTP 4xx/5xx → `DOWN`\n- Network error → `OFFLINE`\n\n---\n\n## StatusInfo\n\n### Built-in Status Codes\n\n```java\npublic static final String STATUS_UNKNOWN = \"UNKNOWN\";\npublic static final String STATUS_OUT_OF_SERVICE = \"OUT_OF_SERVICE\";\npublic static final String STATUS_UP = \"UP\";\npublic static final String STATUS_DOWN = \"DOWN\";\npublic static final String STATUS_OFFLINE = \"OFFLINE\";\npublic static final String STATUS_RESTRICTED = \"RESTRICTED\";\n```\n\n**Status Priority** (highest to lowest):\n\n1. `DOWN`\n2. `OUT_OF_SERVICE`\n3. `OFFLINE`\n4. `UNKNOWN`\n5. `RESTRICTED`\n6. `UP`\n\n### Creating StatusInfo\n\n```java\n// UP status\nStatusInfo.ofUp();\nStatusInfo.ofUp(Map.of(\"version\", \"1.0.0\"));\n\n// DOWN status\nStatusInfo.ofDown();\nStatusInfo.ofDown(Map.of(\"error\", \"Database unreachable\"));\n\n// OFFLINE status\nStatusInfo.ofOffline();\nStatusInfo.ofOffline(Map.of(\"message\", \"Connection timeout\"));\n\n// Custom status\nStatusInfo.valueOf(\"DEGRADED\", Map.of(\"reason\", \"High latency\"));\n```\n\n---\n\n## Custom StatusUpdater\n\n### Example: Custom Timeout\n\n```java\npackage com.example.admin;\n\nimport java.time.Duration;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.services.ApiMediaTypeHandler;\nimport de.codecentric.boot.admin.server.services.StatusUpdater;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\n\n@Configuration\npublic class StatusUpdaterConfig {\n\n    @Bean\n    public StatusUpdater statusUpdater(\n            InstanceRepository repository,\n            InstanceWebClient instanceWebClient,\n            ApiMediaTypeHandler apiMediaTypeHandler) {\n\n        return new StatusUpdater(repository, instanceWebClient, apiMediaTypeHandler)\n            .timeout(Duration.ofSeconds(30));  // Increase timeout\n    }\n}\n```\n\n### Example: Custom Status Interpretation\n\n```java\npackage com.example.admin;\n\nimport java.util.Map;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.http.HttpStatusCode;\n\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\nimport de.codecentric.boot.admin.server.services.ApiMediaTypeHandler;\nimport de.codecentric.boot.admin.server.services.StatusUpdater;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\n\n@Configuration\npublic class StatusUpdaterConfig {\n\n    @Bean\n    public StatusUpdater statusUpdater(\n            InstanceRepository repository,\n            InstanceWebClient instanceWebClient,\n            ApiMediaTypeHandler apiMediaTypeHandler) {\n\n        return new CustomStatusUpdater(repository, instanceWebClient, apiMediaTypeHandler);\n    }\n\n    static class CustomStatusUpdater extends StatusUpdater {\n\n        public CustomStatusUpdater(\n                InstanceRepository repository,\n                InstanceWebClient instanceWebClient,\n                ApiMediaTypeHandler apiMediaTypeHandler) {\n            super(repository, instanceWebClient, apiMediaTypeHandler);\n        }\n\n        @Override\n        protected StatusInfo getStatusInfoFromStatus(HttpStatusCode httpStatus, Map<String, ?> body) {\n            // Custom logic: 503 Service Unavailable = OUT_OF_SERVICE\n            if (httpStatus.value() == 503) {\n                return StatusInfo.valueOf(\"OUT_OF_SERVICE\", body);\n            }\n\n            // Custom logic: 429 Too Many Requests = RESTRICTED\n            if (httpStatus.value() == 429) {\n                return StatusInfo.valueOf(\"RESTRICTED\",\n                    Map.of(\"reason\", \"Rate limited\"));\n            }\n\n            // Delegate to default behavior\n            return super.getStatusInfoFromStatus(httpStatus, body);\n        }\n    }\n}\n```\n\n### Example: Custom Health Endpoint\n\nQuery a different health endpoint:\n\n```java\npackage com.example.admin;\n\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.services.ApiMediaTypeHandler;\nimport de.codecentric.boot.admin.server.services.StatusUpdater;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\n\npublic class CustomHealthEndpointStatusUpdater extends StatusUpdater {\n\n    public CustomHealthEndpointStatusUpdater(\n            InstanceRepository repository,\n            InstanceWebClient instanceWebClient,\n            ApiMediaTypeHandler apiMediaTypeHandler) {\n        super(repository, instanceWebClient, apiMediaTypeHandler);\n    }\n\n    @Override\n    protected Mono<Instance> doUpdateStatus(Instance instance) {\n        if (!instance.isRegistered()) {\n            return Mono.empty();\n        }\n\n        // Query custom health endpoint based on metadata\n        String customHealthPath = instance.getRegistration()\n            .getMetadata()\n            .getOrDefault(\"health-path\", \"/actuator/health\");\n\n        return instanceWebClient.instance(instance)\n            .get()\n            .uri(customHealthPath)\n            .exchangeToMono(this::convertStatusInfo)\n            .timeout(getTimeoutWithMargin())\n            .onErrorResume(this::handleError)\n            .map(instance::withStatusInfo);\n    }\n}\n```\n\n**Client Configuration**:\n\n```yaml\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          metadata:\n            health-path: /custom/health\n```\n\n### Example: Combine Multiple Health Checks\n\n```java\npackage com.example.admin;\n\nimport java.util.Map;\n\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\nimport de.codecentric.boot.admin.server.services.ApiMediaTypeHandler;\nimport de.codecentric.boot.admin.server.services.StatusUpdater;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\n\npublic class AggregatedStatusUpdater extends StatusUpdater {\n\n    public AggregatedStatusUpdater(\n            InstanceRepository repository,\n            InstanceWebClient instanceWebClient,\n            ApiMediaTypeHandler apiMediaTypeHandler) {\n        super(repository, instanceWebClient, apiMediaTypeHandler);\n    }\n\n    @Override\n    protected Mono<Instance> doUpdateStatus(Instance instance) {\n        if (!instance.isRegistered()) {\n            return Mono.empty();\n        }\n\n        // Check both health and readiness\n        Mono<StatusInfo> health = instanceWebClient.instance(instance)\n            .get()\n            .uri(\"/actuator/health\")\n            .exchangeToMono(this::convertStatusInfo)\n            .onErrorResume(ex -> Mono.just(StatusInfo.ofOffline()));\n\n        Mono<StatusInfo> readiness = instanceWebClient.instance(instance)\n            .get()\n            .uri(\"/actuator/health/readiness\")\n            .exchangeToMono(this::convertStatusInfo)\n            .onErrorResume(ex -> Mono.just(StatusInfo.ofUp()));\n\n        return Mono.zip(health, readiness, this::combineStatus)\n            .map(instance::withStatusInfo);\n    }\n\n    private StatusInfo combineStatus(StatusInfo health, StatusInfo readiness) {\n        // If either is DOWN, overall is DOWN\n        if (health.isDown() || readiness.isDown()) {\n            return StatusInfo.ofDown(Map.of(\n                \"health\", health.getStatus(),\n                \"readiness\", readiness.getStatus()\n            ));\n        }\n\n        // If either is OFFLINE, overall is OFFLINE\n        if (health.isOffline() || readiness.isOffline()) {\n            return StatusInfo.ofOffline(Map.of(\n                \"health\", health.getStatus(),\n                \"readiness\", readiness.getStatus()\n            ));\n        }\n\n        // Otherwise UP\n        return StatusInfo.ofUp(Map.of(\n            \"health\", health.getStatus(),\n            \"readiness\", readiness.getStatus()\n        ));\n    }\n}\n```\n\n---\n\n## Custom InfoUpdater\n\n### Default Behavior\n\n`InfoUpdater` queries `/actuator/info`:\n\n```java\nprotected Mono<Instance> doUpdateInfo(Instance instance) {\n    if (instance.getStatusInfo().isOffline() || instance.getStatusInfo().isUnknown()) {\n        return Mono.empty();  // Skip if offline\n    }\n\n    if (!instance.getEndpoints().isPresent(Endpoint.INFO)) {\n        return Mono.empty();  // Skip if no info endpoint\n    }\n\n    return instanceWebClient.instance(instance)\n        .get()\n        .uri(Endpoint.INFO)\n        .exchangeToMono(response -> convertInfo(instance, response))\n        .onErrorResume(ex -> Mono.just(convertInfo(instance, ex)))\n        .map(instance::withInfo);\n}\n```\n\n### Example: Custom Info Endpoint\n\n```java\npackage com.example.admin;\n\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.values.Info;\nimport de.codecentric.boot.admin.server.services.ApiMediaTypeHandler;\nimport de.codecentric.boot.admin.server.services.InfoUpdater;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\n\npublic class CustomInfoUpdater extends InfoUpdater {\n\n    public CustomInfoUpdater(\n            InstanceRepository repository,\n            InstanceWebClient instanceWebClient,\n            ApiMediaTypeHandler apiMediaTypeHandler) {\n        super(repository, instanceWebClient, apiMediaTypeHandler);\n    }\n\n    @Override\n    protected Mono<Instance> doUpdateInfo(Instance instance) {\n        if (instance.getStatusInfo().isOffline() || instance.getStatusInfo().isUnknown()) {\n            return Mono.empty();\n        }\n\n        // Query custom info endpoint\n        String infoPath = instance.getRegistration()\n            .getMetadata()\n            .getOrDefault(\"info-path\", \"/actuator/info\");\n\n        return instanceWebClient.instance(instance)\n            .get()\n            .uri(infoPath)\n            .exchangeToMono(response -> convertInfo(instance, response))\n            .onErrorResume(ex -> Mono.just(Info.empty()))\n            .map(instance::withInfo);\n    }\n}\n```\n\n### Example: Enrich Info with Metadata\n\n```java\npackage com.example.admin;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport reactor.core.publisher.Mono;\nimport org.springframework.web.reactive.function.client.ClientResponse;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.values.Info;\nimport de.codecentric.boot.admin.server.services.ApiMediaTypeHandler;\nimport de.codecentric.boot.admin.server.services.InfoUpdater;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\n\npublic class EnrichedInfoUpdater extends InfoUpdater {\n\n    public EnrichedInfoUpdater(\n            InstanceRepository repository,\n            InstanceWebClient instanceWebClient,\n            ApiMediaTypeHandler apiMediaTypeHandler) {\n        super(repository, instanceWebClient, apiMediaTypeHandler);\n    }\n\n    @Override\n    protected Mono<Info> convertInfo(Instance instance, ClientResponse response) {\n        return super.convertInfo(instance, response)\n            .map(info -> enrichInfo(instance, info));\n    }\n\n    private Info enrichInfo(Instance instance, Info originalInfo) {\n        Map<String, Object> enriched = new HashMap<>(originalInfo.getValues());\n\n        // Add metadata to info\n        enriched.put(\"metadata\", instance.getRegistration().getMetadata());\n\n        // Add custom fields\n        enriched.put(\"registrationTime\",\n            instance.getRegistration().getTimestamp().toString());\n\n        enriched.put(\"instanceId\", instance.getId().getValue());\n\n        return Info.from(enriched);\n    }\n}\n```\n\n---\n\n## Custom Status Codes\n\nDefine custom status codes for specific scenarios:\n\n```java\npackage com.example.admin;\n\nimport java.util.Map;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.http.HttpStatusCode;\n\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\nimport de.codecentric.boot.admin.server.services.ApiMediaTypeHandler;\nimport de.codecentric.boot.admin.server.services.StatusUpdater;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\n\n@Configuration\npublic class CustomStatusConfig {\n\n    @Bean\n    public StatusUpdater statusUpdater(\n            InstanceRepository repository,\n            InstanceWebClient instanceWebClient,\n            ApiMediaTypeHandler apiMediaTypeHandler) {\n\n        return new CustomStatusCodeUpdater(repository, instanceWebClient, apiMediaTypeHandler);\n    }\n\n    static class CustomStatusCodeUpdater extends StatusUpdater {\n\n        public CustomStatusCodeUpdater(\n                InstanceRepository repository,\n                InstanceWebClient instanceWebClient,\n                ApiMediaTypeHandler apiMediaTypeHandler) {\n            super(repository, instanceWebClient, apiMediaTypeHandler);\n        }\n\n        @Override\n        protected StatusInfo getStatusInfoFromStatus(HttpStatusCode httpStatus, Map<String, ?> body) {\n            // Custom status codes\n            if (body.containsKey(\"status\")) {\n                String status = body.get(\"status\").toString();\n\n                return switch (status) {\n                    case \"DEGRADED\" -> StatusInfo.valueOf(\"DEGRADED\",\n                        Map.of(\"details\", \"Service running with reduced capacity\"));\n                    case \"MAINTENANCE\" -> StatusInfo.valueOf(\"OUT_OF_SERVICE\",\n                        Map.of(\"reason\", \"Under maintenance\"));\n                    case \"WARMING_UP\" -> StatusInfo.valueOf(\"RESTRICTED\",\n                        Map.of(\"reason\", \"Service is warming up\"));\n                    default -> super.getStatusInfoFromStatus(httpStatus, body);\n                };\n            }\n\n            return super.getStatusInfoFromStatus(httpStatus, body);\n        }\n    }\n}\n```\n\n**Client Health Indicator**:\n\n```java\npackage com.example.client;\n\nimport org.springframework.boot.actuate.health.Health;\nimport org.springframework.boot.actuate.health.HealthIndicator;\nimport org.springframework.stereotype.Component;\n\n@Component\npublic class CustomHealthIndicator implements HealthIndicator {\n\n    private boolean warming = true;\n\n    @Override\n    public Health health() {\n        if (warming) {\n            return Health.status(\"WARMING_UP\")\n                .withDetail(\"progress\", \"50%\")\n                .build();\n        }\n\n        return Health.up().build();\n    }\n}\n```\n\n---\n\n## Advanced Scenarios\n\n### Scenario 1: External Health Check\n\nQuery an external monitoring service:\n\n```java\npackage com.example.admin;\n\nimport org.springframework.web.reactive.function.client.WebClient;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\nimport de.codecentric.boot.admin.server.services.ApiMediaTypeHandler;\nimport de.codecentric.boot.admin.server.services.StatusUpdater;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\n\npublic class ExternalHealthCheckStatusUpdater extends StatusUpdater {\n\n    private final WebClient externalMonitor;\n\n    public ExternalHealthCheckStatusUpdater(\n            InstanceRepository repository,\n            InstanceWebClient instanceWebClient,\n            ApiMediaTypeHandler apiMediaTypeHandler,\n            WebClient.Builder webClientBuilder) {\n        super(repository, instanceWebClient, apiMediaTypeHandler);\n        this.externalMonitor = webClientBuilder\n            .baseUrl(\"https://monitoring-service.company.com\")\n            .build();\n    }\n\n    @Override\n    protected Mono<Instance> doUpdateStatus(Instance instance) {\n        String serviceName = instance.getRegistration().getName();\n\n        // Query external monitoring service\n        Mono<StatusInfo> externalStatus = externalMonitor.get()\n            .uri(\"/health/{service}\", serviceName)\n            .retrieve()\n            .bodyToMono(ExternalHealthResponse.class)\n            .map(this::convertExternalHealth)\n            .onErrorResume(ex -> super.doUpdateStatus(instance)\n                .map(Instance::getStatusInfo));\n\n        return externalStatus.map(instance::withStatusInfo);\n    }\n\n    private StatusInfo convertExternalHealth(ExternalHealthResponse response) {\n        return StatusInfo.valueOf(response.getStatus(), response.getDetails());\n    }\n\n    record ExternalHealthResponse(String status, Map<String, Object> details) {\n        public String getStatus() { return status; }\n        public Map<String, Object> getDetails() { return details; }\n    }\n}\n```\n\n### Scenario 2: Synthetic Monitoring\n\nPerform synthetic transactions:\n\n```java\npackage com.example.admin;\n\nimport java.util.Map;\n\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\nimport de.codecentric.boot.admin.server.services.ApiMediaTypeHandler;\nimport de.codecentric.boot.admin.server.services.StatusUpdater;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\n\npublic class SyntheticMonitoringStatusUpdater extends StatusUpdater {\n\n    public SyntheticMonitoringStatusUpdater(\n            InstanceRepository repository,\n            InstanceWebClient instanceWebClient,\n            ApiMediaTypeHandler apiMediaTypeHandler) {\n        super(repository, instanceWebClient, apiMediaTypeHandler);\n    }\n\n    @Override\n    protected Mono<Instance> doUpdateStatus(Instance instance) {\n        // 1. Check health endpoint\n        Mono<StatusInfo> healthCheck = super.doUpdateStatus(instance)\n            .map(Instance::getStatusInfo);\n\n        // 2. Perform synthetic transaction\n        Mono<Boolean> syntheticCheck = performSyntheticTransaction(instance);\n\n        return Mono.zip(healthCheck, syntheticCheck,\n            (health, synthetic) -> {\n                if (health.isDown()) {\n                    return health;  // Already down\n                }\n\n                if (!synthetic) {\n                    return StatusInfo.valueOf(\"DEGRADED\",\n                        Map.of(\"reason\", \"Synthetic transaction failed\"));\n                }\n\n                return health;\n            })\n            .map(instance::withStatusInfo);\n    }\n\n    private Mono<Boolean> performSyntheticTransaction(Instance instance) {\n        // Example: Try to fetch a known endpoint\n        return instanceWebClient.instance(instance)\n            .get()\n            .uri(\"/api/health-check\")\n            .retrieve()\n            .toBodilessEntity()\n            .map(response -> response.getStatusCode().is2xxSuccessful())\n            .onErrorReturn(false);\n    }\n}\n```\n\n### Scenario 3: Database-Backed Status\n\nStore and retrieve status from database:\n\n```java\npackage com.example.admin;\n\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\nimport de.codecentric.boot.admin.server.services.ApiMediaTypeHandler;\nimport de.codecentric.boot.admin.server.services.StatusUpdater;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\n\npublic class DatabaseBackedStatusUpdater extends StatusUpdater {\n\n    private final HealthStatusRepository healthStatusRepository;\n\n    public DatabaseBackedStatusUpdater(\n            InstanceRepository repository,\n            InstanceWebClient instanceWebClient,\n            ApiMediaTypeHandler apiMediaTypeHandler,\n            HealthStatusRepository healthStatusRepository) {\n        super(repository, instanceWebClient, apiMediaTypeHandler);\n        this.healthStatusRepository = healthStatusRepository;\n    }\n\n    @Override\n    protected Mono<Instance> doUpdateStatus(Instance instance) {\n        return super.doUpdateStatus(instance)\n            .flatMap(updatedInstance -> {\n                // Save status to database\n                HealthStatus status = new HealthStatus(\n                    instance.getId().getValue(),\n                    updatedInstance.getStatusInfo().getStatus(),\n                    updatedInstance.getStatusInfo().getDetails()\n                );\n\n                return healthStatusRepository.save(status)\n                    .thenReturn(updatedInstance);\n            });\n    }\n}\n\ninterface HealthStatusRepository {\n    Mono<HealthStatus> save(HealthStatus status);\n}\n\nrecord HealthStatus(String instanceId, String status, Map<String, Object> details) {}\n```\n\n---\n\n## Debugging\n\n### Enable Debug Logging\n\n```yaml\nlogging:\n  level:\n    de.codecentric.boot.admin.server.services.StatusUpdater: DEBUG\n    de.codecentric.boot.admin.server.services.InfoUpdater: DEBUG\n```\n\n**Log Output**:\n\n```\nDEBUG StatusUpdater - Update status for Instance{id=abc123, name=my-service}\nDEBUG StatusUpdater - Status updated: UP\n```\n\n### Monitor Status Updates\n\nListen to `InstanceStatusChangedEvent`:\n\n```java\npackage com.example.admin;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.context.event.EventListener;\nimport org.springframework.stereotype.Component;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\n\n@Component\npublic class StatusChangeLogger {\n\n    private static final Logger log = LoggerFactory.getLogger(StatusChangeLogger.class);\n\n    @EventListener\n    public void onStatusChanged(InstanceStatusChangedEvent event) {\n        log.info(\"Status changed for instance {}: {} -> {}\",\n            event.getInstance(),\n            event.getStatusInfo().getStatus(),\n            event.getInstance().getStatusInfo().getStatus());\n    }\n}\n```\n\n---\n\n## Complete Example\n\n```java\npackage com.example.admin;\n\nimport java.time.Duration;\nimport java.util.Map;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.http.HttpStatusCode;\n\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\nimport de.codecentric.boot.admin.server.services.ApiMediaTypeHandler;\nimport de.codecentric.boot.admin.server.services.StatusUpdater;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\n\n@Configuration\npublic class CustomMonitoringConfig {\n\n    @Bean\n    public StatusUpdater statusUpdater(\n            InstanceRepository repository,\n            InstanceWebClient instanceWebClient,\n            ApiMediaTypeHandler apiMediaTypeHandler) {\n\n        return new EnhancedStatusUpdater(repository, instanceWebClient, apiMediaTypeHandler)\n            .timeout(Duration.ofSeconds(15));\n    }\n\n    static class EnhancedStatusUpdater extends StatusUpdater {\n\n        public EnhancedStatusUpdater(\n                InstanceRepository repository,\n                InstanceWebClient instanceWebClient,\n                ApiMediaTypeHandler apiMediaTypeHandler) {\n            super(repository, instanceWebClient, apiMediaTypeHandler);\n        }\n\n        @Override\n        protected StatusInfo getStatusInfoFromStatus(HttpStatusCode httpStatus, Map<String, ?> body) {\n            // Support custom status codes\n            if (body.containsKey(\"status\")) {\n                String status = body.get(\"status\").toString().toUpperCase();\n\n                return switch (status) {\n                    case \"DEGRADED\" -> StatusInfo.valueOf(\"DEGRADED\", body);\n                    case \"MAINTENANCE\" -> StatusInfo.valueOf(\"OUT_OF_SERVICE\", body);\n                    case \"STARTING\" -> StatusInfo.valueOf(\"RESTRICTED\",\n                        Map.of(\"reason\", \"Service starting\"));\n                    default -> StatusInfo.valueOf(status, body);\n                };\n            }\n\n            // HTTP 503 = OUT_OF_SERVICE\n            if (httpStatus.value() == 503) {\n                return StatusInfo.valueOf(\"OUT_OF_SERVICE\", body);\n            }\n\n            // HTTP 429 = RESTRICTED\n            if (httpStatus.value() == 429) {\n                return StatusInfo.valueOf(\"RESTRICTED\",\n                    Map.of(\"reason\", \"Rate limited\"));\n            }\n\n            return super.getStatusInfoFromStatus(httpStatus, body);\n        }\n    }\n}\n```\n\n---\n\n## See Also\n\n- [Server Configuration](../../02-server/01-server.mdx)\n- [Instance Filters](./01-instance-filters.md)\n- [Spring Boot Actuator Health](https://docs.spring.io/spring-boot/reference/actuator/endpoints.html#actuator.endpoints.health)\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/06-customization/monitoring/_category_.json",
    "content": "{\n  \"label\": \"Monitoring\",\n}\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/06-customization/server/04-endpoint-detection.md",
    "content": "---\n\nsidebar_position: 4\nsidebar_custom_props:\n  icon: 'wrench'\n---\n\n# Custom Endpoint Detection\n\nCustomize how Spring Boot Admin Server discovers actuator endpoints from registered instances.\n\n## Overview\n\nWhen a client registers, the Admin Server detects available actuator endpoints. This detection uses strategies that can\nbe customized:\n\n1. **QueryIndexEndpointStrategy** (default) - Queries `/actuator` index for links\n2. **ProbeEndpointsStrategy** - Probes individual endpoints with OPTIONS requests\n3. **ChainingStrategy** - Combines multiple strategies with fallback\n\n```mermaid\ngraph TD\n    A[\"Client Registers<br/>POST /instances<br/>{managementUrl: 'http://client:8080/actuator'}\"] --> B[EndpointDetector]\n    B --> C[\"1. QueryIndexEndpointStrategy<br/>GET /actuator → Read _links\"]\n    B --> D[\"2. Fallback ProbeEndpointsStrategy<br/>OPTIONS /actuator/health → 200 OK<br/>OPTIONS /actuator/metrics → 200 OK<br/>OPTIONS /actuator/info → 200 OK\"]\n    C --> E[\"Instance.endpoints updated<br/>{health, info, metrics, env, loggers, ...}\"]\n    D --> E\n```\n\n---\n\n## Default Behavior\n\nBy default, Admin Server uses a **ChainingStrategy** that:\n\n1. First tries **QueryIndexEndpointStrategy** (Spring Boot 2.x+ with `/actuator` index)\n2. Falls back to **ProbeEndpointsStrategy** (Spring Boot 1.x or if index query fails)\n\n**AdminServerAutoConfiguration.java**:\n\n```java\n@Bean\n@ConditionalOnMissingBean\npublic EndpointDetectionStrategy endpointDetectionStrategy(\n        InstanceWebClient instanceWebClient,\n        AdminServerProperties adminServerProperties,\n        ApiMediaTypeHandler apiMediaTypeHandler) {\n\n    return new ChainingStrategy(\n        new QueryIndexEndpointStrategy(instanceWebClient, apiMediaTypeHandler),\n        new ProbeEndpointsStrategy(instanceWebClient,\n            adminServerProperties.getProbedEndpoints())\n    );\n}\n```\n\n---\n\n## QueryIndexEndpointStrategy\n\nQueries the actuator index at `/actuator` to discover endpoints.\n\n### How It Works\n\n**Request**:\n\n```http\nGET /actuator HTTP/1.1\nAccept: application/vnd.spring-boot.actuator.v3+json\n```\n\n**Response**:\n\n```json\n{\n  \"_links\": {\n    \"self\": {\n      \"href\": \"http://localhost:8080/actuator\",\n      \"templated\": false\n    },\n    \"health\": {\n      \"href\": \"http://localhost:8080/actuator/health\",\n      \"templated\": false\n    },\n    \"info\": {\n      \"href\": \"http://localhost:8080/actuator/info\",\n      \"templated\": false\n    },\n    \"metrics\": {\n      \"href\": \"http://localhost:8080/actuator/metrics/{requiredMetricName}\",\n      \"templated\": true\n    }\n  }\n}\n```\n\n**Extracted Endpoints**:\n\n- `health` → `http://localhost:8080/actuator/health`\n- `info` → `http://localhost:8080/actuator/info`\n- `metrics` is **excluded** (templated)\n\n### Use Only QueryIndexEndpointStrategy\n\n```java\npackage com.example.admin;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport de.codecentric.boot.admin.server.services.ApiMediaTypeHandler;\nimport de.codecentric.boot.admin.server.services.endpoints.EndpointDetectionStrategy;\nimport de.codecentric.boot.admin.server.services.endpoints.QueryIndexEndpointStrategy;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\n\n@Configuration\npublic class EndpointDetectionConfig {\n\n    @Bean\n    public EndpointDetectionStrategy endpointDetectionStrategy(\n            InstanceWebClient instanceWebClient,\n            ApiMediaTypeHandler apiMediaTypeHandler) {\n\n        return new QueryIndexEndpointStrategy(instanceWebClient, apiMediaTypeHandler);\n    }\n}\n```\n\n**When to use**:\n\n- All clients are Spring Boot 2.x or newer\n- `/actuator` index is available on all clients\n- Want fastest detection\n\n---\n\n## ProbeEndpointsStrategy\n\nProbes individual endpoints using HTTP OPTIONS requests.\n\n### How It Works\n\nFor each endpoint in the probed list:\n\n```http\nOPTIONS /actuator/health HTTP/1.1\n```\n\nIf response is `2xx`, the endpoint is considered available.\n\n### Configuration\n\n**application.yml**:\n\n```yaml\nspring:\n  boot:\n    admin:\n      probed-endpoints:\n        - health\n        - info\n        - metrics\n        - env\n        - loggers\n        - logfile\n        - threaddump\n        - heapdump\n```\n\n### Custom Endpoint Paths\n\nIf endpoint ID differs from path, use `id:path` syntax:\n\n```yaml\nspring:\n  boot:\n    admin:\n      probed-endpoints:\n        - health:ping        # Endpoint ID \"health\" at path \"/actuator/ping\"\n        - metrics:stats      # Endpoint ID \"metrics\" at path \"/actuator/stats\"\n        - custom:my-custom   # Endpoint ID \"custom\" at path \"/actuator/my-custom\"\n```\n\n### Use Only ProbeEndpointsStrategy\n\n```java\npackage com.example.admin;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport de.codecentric.boot.admin.server.config.AdminServerProperties;\nimport de.codecentric.boot.admin.server.services.endpoints.EndpointDetectionStrategy;\nimport de.codecentric.boot.admin.server.services.endpoints.ProbeEndpointsStrategy;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\n\n@Configuration\npublic class EndpointDetectionConfig {\n\n    @Bean\n    public EndpointDetectionStrategy endpointDetectionStrategy(\n            InstanceWebClient instanceWebClient,\n            AdminServerProperties properties) {\n\n        return new ProbeEndpointsStrategy(\n            instanceWebClient,\n            properties.getProbedEndpoints()\n        );\n    }\n}\n```\n\n**When to use**:\n\n- Supporting Spring Boot 1.x applications\n- Actuator index is disabled/unavailable\n- Need to detect specific custom endpoints\n\n---\n\n## ChainingStrategy\n\nCombines multiple strategies with fallback.\n\n### How It Works\n\nTries strategies in order until one succeeds:\n\n```java\nChainingStrategy(\n    new QueryIndexEndpointStrategy(...),  // Try first\n    new ProbeEndpointsStrategy(...)       // Fallback\n)\n```\n\nIf first strategy returns empty, tries next strategy.\n\n### Custom Chaining\n\n```java\npackage com.example.admin;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport de.codecentric.boot.admin.server.config.AdminServerProperties;\nimport de.codecentric.boot.admin.server.services.ApiMediaTypeHandler;\nimport de.codecentric.boot.admin.server.services.endpoints.ChainingStrategy;\nimport de.codecentric.boot.admin.server.services.endpoints.EndpointDetectionStrategy;\nimport de.codecentric.boot.admin.server.services.endpoints.ProbeEndpointsStrategy;\nimport de.codecentric.boot.admin.server.services.endpoints.QueryIndexEndpointStrategy;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\n\n@Configuration\npublic class EndpointDetectionConfig {\n\n    @Bean\n    public EndpointDetectionStrategy endpointDetectionStrategy(\n            InstanceWebClient instanceWebClient,\n            AdminServerProperties properties,\n            ApiMediaTypeHandler apiMediaTypeHandler) {\n\n        return new ChainingStrategy(\n            new QueryIndexEndpointStrategy(instanceWebClient, apiMediaTypeHandler),\n            new ProbeEndpointsStrategy(instanceWebClient, properties.getProbedEndpoints()),\n            new CustomEndpointStrategy()  // Your custom strategy as last resort\n        );\n    }\n}\n```\n\n---\n\n## Custom EndpointDetectionStrategy\n\nImplement `EndpointDetectionStrategy` interface for custom detection logic.\n\n### Interface\n\n```java\npackage de.codecentric.boot.admin.server.services.endpoints;\n\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.values.Endpoints;\n\npublic interface EndpointDetectionStrategy {\n\n    Mono<Endpoints> detectEndpoints(Instance instance);\n\n}\n```\n\n### Example: Static Endpoint Strategy\n\nDefine endpoints based on metadata:\n\n```java\npackage com.example.admin;\n\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.values.Endpoint;\nimport de.codecentric.boot.admin.server.domain.values.Endpoints;\nimport de.codecentric.boot.admin.server.services.endpoints.EndpointDetectionStrategy;\n\npublic class MetadataEndpointStrategy implements EndpointDetectionStrategy {\n\n    @Override\n    public Mono<Endpoints> detectEndpoints(Instance instance) {\n        String managementUrl = instance.getRegistration().getManagementUrl();\n        if (managementUrl == null) {\n            return Mono.empty();\n        }\n\n        // Read endpoints from metadata\n        String endpointList = instance.getRegistration()\n            .getMetadata()\n            .get(\"endpoints\");\n\n        if (endpointList == null || endpointList.isBlank()) {\n            return Mono.empty();\n        }\n\n        // Parse comma-separated endpoint IDs\n        List<Endpoint> endpoints = Arrays.stream(endpointList.split(\",\"))\n            .map(String::trim)\n            .map(id -> Endpoint.of(id, managementUrl + \"/\" + id))\n            .toList();\n\n        return Mono.just(Endpoints.of(endpoints));\n    }\n}\n```\n\n**Client Configuration**:\n\n```yaml\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          metadata:\n            endpoints: health,info,metrics,env\n```\n\n**Server Configuration**:\n\n```java\n@Bean\npublic EndpointDetectionStrategy endpointDetectionStrategy() {\n    return new ChainingStrategy(\n        new MetadataEndpointStrategy(),\n        new QueryIndexEndpointStrategy(...)\n    );\n}\n```\n\n### Example: Service-Specific Endpoints\n\nDifferent endpoint detection based on service name:\n\n```java\npackage com.example.admin;\n\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.values.Endpoint;\nimport de.codecentric.boot.admin.server.domain.values.Endpoints;\nimport de.codecentric.boot.admin.server.services.endpoints.EndpointDetectionStrategy;\n\npublic class ServiceSpecificEndpointStrategy implements EndpointDetectionStrategy {\n\n    private final Map<String, List<String>> serviceEndpoints;\n\n    public ServiceSpecificEndpointStrategy() {\n        this.serviceEndpoints = Map.of(\n            \"payment-service\", List.of(\"health\", \"info\", \"metrics\", \"payments\"),\n            \"user-service\", List.of(\"health\", \"info\", \"metrics\", \"users\"),\n            \"legacy-service\", List.of(\"health\", \"info\")  // Limited endpoints\n        );\n    }\n\n    @Override\n    public Mono<Endpoints> detectEndpoints(Instance instance) {\n        String serviceName = instance.getRegistration().getName();\n        String managementUrl = instance.getRegistration().getManagementUrl();\n\n        if (managementUrl == null) {\n            return Mono.empty();\n        }\n\n        List<String> endpointIds = serviceEndpoints.get(serviceName);\n        if (endpointIds == null) {\n            return Mono.empty();  // Fall back to next strategy\n        }\n\n        List<Endpoint> endpoints = endpointIds.stream()\n            .map(id -> Endpoint.of(id, managementUrl + \"/\" + id))\n            .toList();\n\n        return Mono.just(Endpoints.of(endpoints));\n    }\n}\n```\n\n### Example: Database-Driven Endpoints\n\nLoad endpoint configuration from database:\n\n```java\npackage com.example.admin;\n\nimport java.util.List;\n\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.values.Endpoint;\nimport de.codecentric.boot.admin.server.domain.values.Endpoints;\nimport de.codecentric.boot.admin.server.services.endpoints.EndpointDetectionStrategy;\n\npublic class DatabaseEndpointStrategy implements EndpointDetectionStrategy {\n\n    private final EndpointConfigRepository endpointConfigRepository;\n\n    public DatabaseEndpointStrategy(EndpointConfigRepository repository) {\n        this.endpointConfigRepository = repository;\n    }\n\n    @Override\n    public Mono<Endpoints> detectEndpoints(Instance instance) {\n        String serviceName = instance.getRegistration().getName();\n        String managementUrl = instance.getRegistration().getManagementUrl();\n\n        if (managementUrl == null) {\n            return Mono.empty();\n        }\n\n        return endpointConfigRepository.findByServiceName(serviceName)\n            .map(config -> {\n                List<Endpoint> endpoints = config.getEndpointIds().stream()\n                    .map(id -> Endpoint.of(id, managementUrl + \"/\" + id))\n                    .toList();\n                return Endpoints.of(endpoints);\n            })\n            .switchIfEmpty(Mono.empty());\n    }\n}\n\n@Repository\ninterface EndpointConfigRepository extends ReactiveMongoRepository<EndpointConfig, String> {\n    Mono<EndpointConfig> findByServiceName(String serviceName);\n}\n\n@Document\nclass EndpointConfig {\n    private String serviceName;\n    private List<String> endpointIds;\n    // getters/setters\n}\n```\n\n### Example: HTTP-Based Discovery\n\nFetch endpoints from custom discovery endpoint:\n\n```java\npackage com.example.admin;\n\nimport reactor.core.publisher.Mono;\nimport org.springframework.web.reactive.function.client.WebClient;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.values.Endpoint;\nimport de.codecentric.boot.admin.server.domain.values.Endpoints;\nimport de.codecentric.boot.admin.server.services.endpoints.EndpointDetectionStrategy;\n\npublic class DiscoveryServiceEndpointStrategy implements EndpointDetectionStrategy {\n\n    private final WebClient webClient;\n\n    public DiscoveryServiceEndpointStrategy(WebClient.Builder webClientBuilder) {\n        this.webClient = webClientBuilder.baseUrl(\"http://discovery-service\").build();\n    }\n\n    @Override\n    public Mono<Endpoints> detectEndpoints(Instance instance) {\n        String serviceName = instance.getRegistration().getName();\n        String managementUrl = instance.getRegistration().getManagementUrl();\n\n        if (managementUrl == null) {\n            return Mono.empty();\n        }\n\n        return webClient.get()\n            .uri(\"/services/{name}/endpoints\", serviceName)\n            .retrieve()\n            .bodyToFlux(String.class)\n            .collectList()\n            .map(endpointIds -> {\n                List<Endpoint> endpoints = endpointIds.stream()\n                    .map(id -> Endpoint.of(id, managementUrl + \"/\" + id))\n                    .toList();\n                return Endpoints.of(endpoints);\n            })\n            .onErrorResume(e -> Mono.empty());\n    }\n}\n```\n\n---\n\n## Endpoint Detection Lifecycle\n\n### When Detection Occurs\n\nEndpoints are detected:\n\n1. **After instance registration** (InstanceRegisteredEvent)\n2. **When endpoints are first accessed** (if not yet detected)\n3. **Periodically** (can be triggered manually)\n\n### Trigger Manual Detection\n\n```java\n@Autowired\nprivate EndpointDetector endpointDetector;\n\npublic void refreshEndpoints(InstanceId instanceId) {\n    endpointDetector.detectEndpoints(instanceId).subscribe();\n}\n```\n\n---\n\n## Debugging Endpoint Detection\n\n### Enable Debug Logging\n\n```yaml\nlogging:\n  level:\n    de.codecentric.boot.admin.server.services.EndpointDetector: DEBUG\n    de.codecentric.boot.admin.server.services.endpoints: DEBUG\n```\n\n**Log Output**:\n\n```\nDEBUG EndpointDetector - Detect endpoints for Instance{id=abc123}\nDEBUG QueryIndexEndpointStrategy - Querying actuator-index for instance abc123 on 'http://client:8080/actuator' successful.\nDEBUG EndpointDetector - Detected endpoints: [health, info, metrics, env]\n```\n\n### Check Detected Endpoints\n\n**API**:\n\n```bash\ncurl http://admin-server:8080/instances/{id} | jq '.endpoints'\n```\n\n**Response**:\n\n```json\n[\n  {\n    \"id\": \"health\",\n    \"url\": \"http://localhost:8080/actuator/health\"\n  },\n  {\n    \"id\": \"info\",\n    \"url\": \"http://localhost:8080/actuator/info\"\n  },\n  {\n    \"id\": \"metrics\",\n    \"url\": \"http://localhost:8080/actuator/metrics\"\n  }\n]\n```\n\n---\n\n## Advanced Scenarios\n\n### Scenario 1: Legacy Spring Boot 1.x Support\n\n**Configuration**:\n\n```yaml\nspring:\n  boot:\n    admin:\n      probed-endpoints:\n        # Spring Boot 1.x endpoints\n        - health\n        - info\n        - metrics\n        - env\n        - trace:httptrace\n        - dump:threaddump\n```\n\n**Strategy**:\n\n```java\n@Bean\npublic EndpointDetectionStrategy endpointDetectionStrategy(\n        InstanceWebClient instanceWebClient,\n        AdminServerProperties properties) {\n\n    // Only use probing for legacy apps\n    return new ProbeEndpointsStrategy(\n        instanceWebClient,\n        properties.getProbedEndpoints()\n    );\n}\n```\n\n### Scenario 2: Mixed Spring Boot Versions\n\n**Strategy**:\n\n```java\n@Bean\npublic EndpointDetectionStrategy endpointDetectionStrategy(\n        InstanceWebClient instanceWebClient,\n        AdminServerProperties properties,\n        ApiMediaTypeHandler apiMediaTypeHandler) {\n\n    return new ChainingStrategy(\n        // Try modern Spring Boot 2.x+ first\n        new QueryIndexEndpointStrategy(instanceWebClient, apiMediaTypeHandler),\n        // Fall back to probing for legacy apps\n        new ProbeEndpointsStrategy(instanceWebClient, properties.getProbedEndpoints())\n    );\n}\n```\n\n### Scenario 3: Custom Actuator Path\n\n**Client** (custom actuator base path):\n\n```yaml\nmanagement:\n  endpoints:\n    web:\n      base-path: /management  # Not /actuator\n```\n\n**Server Strategy**:\n\n```java\npublic class CustomPathEndpointStrategy implements EndpointDetectionStrategy {\n\n    @Override\n    public Mono<Endpoints> detectEndpoints(Instance instance) {\n        String managementUrl = instance.getRegistration().getManagementUrl();\n\n        // Adjust for custom base path\n        if (managementUrl != null && !managementUrl.endsWith(\"/actuator\")) {\n            // Query custom index endpoint\n            return queryIndex(instance, managementUrl);\n        }\n\n        return Mono.empty();\n    }\n}\n```\n\n### Scenario 4: Conditional Detection Based on Metadata\n\n```java\npublic class ConditionalEndpointStrategy implements EndpointDetectionStrategy {\n\n    private final QueryIndexEndpointStrategy queryStrategy;\n    private final ProbeEndpointsStrategy probeStrategy;\n\n    @Override\n    public Mono<Endpoints> detectEndpoints(Instance instance) {\n        String version = instance.getRegistration()\n            .getMetadata()\n            .get(\"spring-boot-version\");\n\n        if (version != null && version.startsWith(\"1.\")) {\n            // Use probing for Spring Boot 1.x\n            return probeStrategy.detectEndpoints(instance);\n        } else {\n            // Use index query for Spring Boot 2.x+\n            return queryStrategy.detectEndpoints(instance);\n        }\n    }\n}\n```\n\n---\n\n## Performance Considerations\n\n### QueryIndexEndpointStrategy\n\n**Pros**:\n\n- Single HTTP request\n- Fast detection\n- Accurate (no false positives)\n\n**Cons**:\n\n- Requires Spring Boot 2.x+\n- Requires `/actuator` index enabled\n\n### ProbeEndpointsStrategy\n\n**Pros**:\n\n- Works with any Spring Boot version\n- Detects custom endpoints\n\n**Cons**:\n\n- Multiple HTTP requests (one per endpoint)\n- Slower detection\n- Potential false positives if OPTIONS not supported\n\n### Optimization\n\nLimit probed endpoints to essentials:\n\n```yaml\nspring:\n  boot:\n    admin:\n      probed-endpoints:\n        - health\n        - info\n        - metrics\n        # Remove rarely-used endpoints\n```\n\n---\n\n## Troubleshooting\n\n### Issue: No endpoints detected\n\n**Cause**: Detection strategy failing.\n\n**Debug**:\n\n```yaml\nlogging:\n  level:\n    de.codecentric.boot.admin.server.services.endpoints: DEBUG\n```\n\n**Check**:\n\n1. `managementUrl` is set\n2. Instance is reachable\n3. Actuator endpoints are exposed\n\n### Issue: Wrong endpoints detected\n\n**Cause**: Probing detecting endpoints that don't exist.\n\n**Solution**: Use `QueryIndexEndpointStrategy` only:\n\n```java\n@Bean\npublic EndpointDetectionStrategy endpointDetectionStrategy(\n        InstanceWebClient instanceWebClient,\n        ApiMediaTypeHandler apiMediaTypeHandler) {\n    return new QueryIndexEndpointStrategy(instanceWebClient, apiMediaTypeHandler);\n}\n```\n\n### Issue: Custom endpoints not detected\n\n**Cause**: Custom endpoints not in probed list or not in actuator index.\n\n**Solution**: Add to probed endpoints:\n\n```yaml\nspring:\n  boot:\n    admin:\n      probed-endpoints:\n        - health\n        - info\n        - my-custom-endpoint\n```\n\nOr create custom strategy.\n\n---\n\n## See Also\n\n- [Server Configuration](../../02-server/01-server.mdx)\n- [Spring Boot Actuator Endpoints](https://docs.spring.io/spring-boot/reference/actuator/endpoints.html)\n\n---\n\n## Complete Example\n\n```java\npackage com.example.admin;\n\nimport java.util.Arrays;\nimport java.util.List;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.config.AdminServerProperties;\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.values.Endpoint;\nimport de.codecentric.boot.admin.server.domain.values.Endpoints;\nimport de.codecentric.boot.admin.server.services.ApiMediaTypeHandler;\nimport de.codecentric.boot.admin.server.services.endpoints.ChainingStrategy;\nimport de.codecentric.boot.admin.server.services.endpoints.EndpointDetectionStrategy;\nimport de.codecentric.boot.admin.server.services.endpoints.ProbeEndpointsStrategy;\nimport de.codecentric.boot.admin.server.services.endpoints.QueryIndexEndpointStrategy;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\n\n@Configuration\npublic class EndpointDetectionConfig {\n\n    @Bean\n    public EndpointDetectionStrategy endpointDetectionStrategy(\n            InstanceWebClient instanceWebClient,\n            AdminServerProperties properties,\n            ApiMediaTypeHandler apiMediaTypeHandler) {\n\n        return new ChainingStrategy(\n            // 1. Try metadata-based detection\n            new MetadataEndpointStrategy(),\n            // 2. Try standard actuator index query\n            new QueryIndexEndpointStrategy(instanceWebClient, apiMediaTypeHandler),\n            // 3. Fall back to probing\n            new ProbeEndpointsStrategy(instanceWebClient, properties.getProbedEndpoints())\n        );\n    }\n\n    /**\n     * Detect endpoints from instance metadata\n     */\n    static class MetadataEndpointStrategy implements EndpointDetectionStrategy {\n\n        @Override\n        public Mono<Endpoints> detectEndpoints(Instance instance) {\n            String managementUrl = instance.getRegistration().getManagementUrl();\n            if (managementUrl == null) {\n                return Mono.empty();\n            }\n\n            String endpointList = instance.getRegistration()\n                .getMetadata()\n                .get(\"endpoints\");\n\n            if (endpointList == null || endpointList.isBlank()) {\n                return Mono.empty();\n            }\n\n            List<Endpoint> endpoints = Arrays.stream(endpointList.split(\",\"))\n                .map(String::trim)\n                .map(id -> Endpoint.of(id, managementUrl + \"/\" + id))\n                .toList();\n\n            return Mono.just(Endpoints.of(endpoints));\n        }\n    }\n}\n```\n\n**Client Configuration** (optional metadata):\n\n```yaml\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          metadata:\n            endpoints: health,info,metrics,custom\n```\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/06-customization/server/_category_.json",
    "content": "{\n  \"label\": \"Server\"\n}\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/08-third-party/_category_.json",
    "content": "{\n  \"position\": 8,\n  \"label\": \"Third Party Integrations\"\n}\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/08-third-party/index.md",
    "content": "---\nsidebar_custom_props:\n  icon: 'puzzle'\n---\n\nimport DocCardList from '@theme/DocCardList';\n\n# Third Party Integrations\n\n <DocCardList />\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/08-third-party/pyctuator.md",
    "content": "---\nsidebar_custom_props:\n  icon: 'python'\n---\n\n# Pyctuator\n\nYou can easily integrate Spring Boot Admin with [Flask](https://flask.palletsprojects.com)\nor [FastAPI](https://fastapi.tiangolo.com/) Python applications using\nthe [Pyctuator](https://github.com/SolarEdgeTech/pyctuator) project.\n\nThe following steps uses Flask, but other web frameworks are supported as well. See Pyctuator’s documentation for an\nupdated list of supported frameworks and features.\n\n1. Install the pyctuator package:\n\n```bash\npip install pyctuator\n```\n\n2. Enable pyctuator by pointing it to your Flask app and letting it know where Spring Boot Admin is running:\n\n```python title=\"app.py\"\nimport os\nfrom flask import Flask\nfrom pyctuator.pyctuator import Pyctuator\napp_name = \"Flask App with Pyctuator\"\napp = Flask(app_name)\n@app.route(\"/\")\ndef hello():\n    return \"Hello World!\"\nPyctuator(\n    app,\n    app_name,\n    app_url=\"http://example-app.com\",\n    pyctuator_endpoint_url=\"http://example-app.com/pyctuator\",\n    registration_url=os.getenv(\"SPRING_BOOT_ADMIN_URL\")\n)\napp.run()\n```\n\nFor further details and examples, see\nPyctuator’s [documentation](https://github.com/SolarEdgeTech/pyctuator/blob/master/README.md)\nand [examples](https://github.com/SolarEdgeTech/pyctuator/tree/master/examples).\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/09-samples/10-sample-servlet.md",
    "content": "---\n\nsidebar_position: 10\nsidebar_custom_props:\n  icon: 'file-code'\n---\n\n# Servlet Sample\n\nThe Servlet sample demonstrates a complete Spring Boot Admin Server deployment using traditional servlet-based Spring\nMVC. This is the most feature-rich sample, showcasing security, custom UI extensions, notifications, and\nself-monitoring.\n\n## Overview\n\n**Location**: `spring-boot-admin-samples/spring-boot-admin-sample-servlet/`\n\n**Features**:\n\n- Traditional servlet-based deployment (Spring MVC)\n- Spring Security integration with form login\n- Self-monitoring using Admin Client\n- Mail notifications configured\n- Custom UI extensions included\n- Custom notifier implementation\n- HTTP exchange tracking\n- Audit event logging\n- Session persistence with JDBC\n- Custom actuator endpoint\n- JMX support via Jolokia\n\n## Prerequisites\n\n- Java 17 or higher\n- Maven 3.6+\n- Optional: Mail server for notifications\n\n## Running the Sample\n\n### Quick Start\n\n```bash\ncd spring-boot-admin-samples/spring-boot-admin-sample-servlet\nmvn spring-boot:run\n```\n\nAccess the application at: `http://localhost:8080`\n\n### With Security Enabled\n\n```bash\nmvn spring-boot:run -Dspring-boot.run.profiles=secure\n```\n\n**Login Credentials**:\n\n- Username: `user`\n- Password: `password`\n\n### Change Port\n\n```bash\nSERVER_PORT=9090 mvn spring-boot:run\n```\n\n## Project Structure\n\n### Dependencies\n\nKey dependencies from `pom.xml`:\n\n```xml\n<dependencies>\n    <!-- Admin Server -->\n    <dependency>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-starter-server</artifactId>\n    </dependency>\n\n    <!-- Admin Client (for self-monitoring) -->\n    <dependency>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-starter-client</artifactId>\n    </dependency>\n\n    <!-- Security -->\n    <dependency>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-security</artifactId>\n    </dependency>\n\n    <!-- Web (Servlet) -->\n    <dependency>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-webmvc</artifactId>\n    </dependency>\n\n    <!-- Notifications -->\n    <dependency>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-mail</artifactId>\n    </dependency>\n\n    <!-- Session Management -->\n    <dependency>\n        <groupId>org.springframework.session</groupId>\n        <artifactId>spring-session-jdbc</artifactId>\n    </dependency>\n\n    <!-- Custom UI Extensions -->\n    <dependency>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-sample-custom-ui</artifactId>\n    </dependency>\n</dependencies>\n```\n\n### Main Application Class\n\n```java title=\"SpringBootAdminServletApplication.java\"\n@SpringBootApplication\n@EnableAdminServer\n@EnableCaching\npublic class SpringBootAdminServletApplication {\n\n    static void main(String[] args) {\n        SpringApplication app = new SpringApplication(\n            SpringBootAdminServletApplication.class\n        );\n        app.setApplicationStartup(new BufferingApplicationStartup(1500));\n        app.run(args);\n    }\n\n    @Bean\n    public CustomNotifier customNotifier(InstanceRepository repository) {\n        return new CustomNotifier(repository);\n    }\n\n    @Bean\n    public HttpHeadersProvider customHttpHeadersProvider() {\n        return (instance) -> {\n            HttpHeaders httpHeaders = new HttpHeaders();\n            httpHeaders.add(\"X-CUSTOM\", \"My Custom Value\");\n            return httpHeaders;\n        };\n    }\n\n    @Bean\n    public InstanceExchangeFilterFunction auditLog() {\n        return (instance, request, next) -> next.exchange(request)\n            .doOnSubscribe((s) -> {\n                if (HttpMethod.DELETE.equals(request.method())\n                    || HttpMethod.POST.equals(request.method())) {\n                    log.info(\"{} for {} on {}\",\n                        request.method(), instance.getId(), request.url());\n                }\n            });\n    }\n}\n```\n\n**Key Points**:\n\n- `@EnableAdminServer` activates Admin Server functionality\n- Custom HTTP headers added to all instance requests\n- Audit logging for DELETE/POST operations\n- Application startup tracking enabled\n\n## Configuration\n\n### Application Configuration\n\n```yaml title=\"application.yml\"\nspring:\n  application:\n    name: spring-boot-admin-sample-servlet\n\n  boot:\n    admin:\n      client:\n        url: http://localhost:8080  # Self-registration\n        instance:\n          service-host-type: IP\n          metadata:\n            tags:\n              environment: test\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n  endpoint:\n    health:\n      show-details: ALWAYS\n    shutdown:\n      enabled: true\n    restart:\n      enabled: true\n\nlogging:\n  file:\n    name: \"target/boot-admin-sample-servlet.log\"\n  level:\n    de.codecentric: info\n```\n\n### Static Instance Configuration\n\nThe sample includes multiple static instances with creative names:\n\n```yaml\nspring:\n  cloud:\n    discovery:\n      client:\n        simple:\n          instances:\n            \"Captain Debugbeard\":\n              - uri: http://localhost:8080\n                metadata:\n                  management.context-path: /actuator\n                  group: Pirates of the Caribbean Bean\n\n            \"Stack Overflow Sorcerer\":\n              - uri: http://localhost:8080\n                metadata:\n                  management.context-path: /actuator\n                  group: Wizarding World\n```\n\n## Security Configuration\n\n### Spring Security Setup\n\n```java title=\"SecuritySecureConfig.java\"\n@Profile(\"secure\")\n@Configuration\npublic class SecuritySecureConfig {\n\n    private final AdminServerProperties adminServer;\n\n    @Bean\n    protected SecurityFilterChain filterChain(HttpSecurity http)\n            throws Exception {\n        SavedRequestAwareAuthenticationSuccessHandler successHandler =\n            new SavedRequestAwareAuthenticationSuccessHandler();\n        successHandler.setTargetUrlParameter(\"redirectTo\");\n        successHandler.setDefaultTargetUrl(adminServer.path(\"/\"));\n\n        http.authorizeHttpRequests((authorizeRequests) -> authorizeRequests\n            .requestMatchers(adminServer.path(\"/assets/**\"))\n                .permitAll()  // Allow static resources\n            .requestMatchers(adminServer.path(\"/actuator/info\"))\n                .permitAll()\n            .requestMatchers(adminServer.path(\"/actuator/health\"))\n                .permitAll()\n            .requestMatchers(adminServer.path(\"/login\"))\n                .permitAll()\n            .anyRequest()\n                .authenticated())\n            .formLogin((formLogin) -> formLogin\n                .loginPage(adminServer.path(\"/login\"))\n                .successHandler(successHandler))\n            .logout((logout) -> logout\n                .logoutUrl(adminServer.path(\"/logout\")))\n            .httpBasic(Customizer.withDefaults());\n\n        // CSRF Configuration\n        http.csrf((csrf) -> csrf\n            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())\n            .ignoringRequestMatchers(\n                adminServer.path(\"/instances\"),      // Instance registration\n                adminServer.path(\"/instances/*\"),    // Instance deregistration\n                adminServer.path(\"/actuator/**\")     // Actuator endpoints\n            ));\n\n        http.rememberMe((rememberMe) -> rememberMe\n            .key(UUID.randomUUID().toString())\n            .tokenValiditySeconds(1209600));  // 14 days\n\n        return http.build();\n    }\n\n    @Bean\n    public InMemoryUserDetailsManager userDetailsService(\n            PasswordEncoder passwordEncoder) {\n        UserDetails user = User.withUsername(\"user\")\n            .password(passwordEncoder.encode(\"password\"))\n            .roles(\"USER\")\n            .build();\n        return new InMemoryUserDetailsManager(user);\n    }\n\n    @Bean\n    public PasswordEncoder passwordEncoder() {\n        return new BCryptPasswordEncoder();\n    }\n}\n```\n\n**Security Features**:\n\n1. Form-based login with custom page\n2. HTTP Basic authentication support\n3. CSRF protection with token repository\n4. Remember-me functionality (14 days)\n5. Static resources publicly accessible\n6. Health/info endpoints publicly accessible\n\n## Custom Notifier\n\nThe sample includes a custom notifier implementation:\n\n```java title=\"CustomNotifier.java\"\npublic class CustomNotifier extends AbstractEventNotifier {\n\n    private static final Logger LOGGER =\n        LoggerFactory.getLogger(CustomNotifier.class);\n\n    public CustomNotifier(InstanceRepository repository) {\n        super(repository);\n    }\n\n    @Override\n    protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {\n        return Mono.fromRunnable(() -> {\n            if (event instanceof InstanceStatusChangedEvent statusChangedEvent) {\n                LOGGER.info(\"Instance {} ({}) is {}\",\n                    instance.getRegistration().getName(),\n                    event.getInstance(),\n                    statusChangedEvent.getStatusInfo().getStatus());\n            } else {\n                LOGGER.info(\"Instance {} ({}) {}\",\n                    instance.getRegistration().getName(),\n                    event.getInstance(),\n                    event.getType());\n            }\n        });\n    }\n}\n```\n\n### Notifier Configuration\n\n```java title=\"NotifierConfig.java\"\n@Configuration\npublic class NotifierConfig {\n\n    @Bean\n    public FilteringNotifier filteringNotifier() {\n        CompositeNotifier delegate = new CompositeNotifier(\n            otherNotifiers.getIfAvailable(Collections::emptyList)\n        );\n        return new FilteringNotifier(delegate, repository);\n    }\n\n    @Primary\n    @Bean(initMethod = \"start\", destroyMethod = \"stop\")\n    public RemindingNotifier remindingNotifier() {\n        RemindingNotifier notifier = new RemindingNotifier(\n            filteringNotifier(), repository\n        );\n        notifier.setReminderPeriod(Duration.ofMinutes(10));\n        notifier.setCheckReminderInverval(Duration.ofSeconds(10));\n        return notifier;\n    }\n}\n```\n\n**Notification Features**:\n\n- Custom event logging\n- Filtering notifier for selective notifications\n- Reminding notifier (sends reminders every 10 minutes)\n- Composable notifier chain\n\n## UI Customizations\n\n### External Views\n\nThe sample demonstrates various external view configurations:\n\n```yaml\nspring:\n  boot:\n    admin:\n      ui:\n        external-views:\n          # Simple link\n          - label: \"🚀\"\n            url: \"https://codecentric.de\"\n            order: 2000\n\n          # Dropdown with links\n          - label: Resources\n            children:\n              - label: \"📖 Docs\"\n                url: https://codecentric.github.io/spring-boot-admin/\n              - label: \"📦 Maven\"\n                url: https://search.maven.org/...\n              - label: \"🐙 GitHub\"\n                url: https://github.com/codecentric/spring-boot-admin\n\n          # Iframe view\n          - label: \"🎅 Is it christmas\"\n            url: https://isitchristmas.com\n            iframe: true\n```\n\n### View Settings\n\n```yaml\nspring:\n  boot:\n    admin:\n      ui:\n        view-settings:\n          - name: \"journal\"\n            enabled: false\n```\n\n## Session Management\n\nThe sample uses JDBC-based session persistence:\n\n```java\n@Bean\npublic EmbeddedDatabase dataSource() {\n    return new EmbeddedDatabaseBuilder()\n        .setType(EmbeddedDatabaseType.HSQL)\n        .addScript(\"org/springframework/session/jdbc/schema-hsqldb.sql\")\n        .build();\n}\n```\n\n**Benefits**:\n\n- Session persistence across restarts\n- Support for clustered deployments\n- Built-in session cleanup\n\n## Testing the Sample\n\n### Access the UI\n\n1. Start the application\n2. Navigate to `http://localhost:8080`\n3. Login (if secure profile is active)\n4. View the registered instances\n\n### Test Self-Monitoring\n\nThe application monitors itself via the Admin Client. You should see:\n\n- Application name: `spring-boot-admin-sample-servlet`\n- Status: UP\n- All actuator endpoints available\n- Custom metadata and tags\n\n### Test Notifications\n\nMonitor the logs for custom notification events:\n\n```\nINFO - Instance spring-boot-admin-sample-servlet (...) is UP\nINFO - Instance spring-boot-admin-sample-servlet (...) ENDPOINTS_DETECTED\n```\n\n### Test Custom Headers\n\nAll requests to instances include the custom header `X-CUSTOM: My Custom Value`.\n\n### Test External Views\n\nClick the external view links in the navigation:\n\n- Rocket emoji (🚀) - Opens codecentric.de\n- Resources dropdown - Multiple documentation links\n- \"Is it christmas\" - Iframe view\n\n## Build and Deploy\n\n### Build JAR\n\n```bash\nmvn clean package\n```\n\nProduces: `target/spring-boot-admin-sample-servlet.jar`\n\n### Run JAR\n\n```bash\njava -jar target/spring-boot-admin-sample-servlet.jar\n```\n\n### With Profiles\n\n```bash\njava -jar target/spring-boot-admin-sample-servlet.jar \\\n  --spring.profiles.active=secure\n```\n\n### Deployment Considerations\n\nWhen deploying, consider:\n\n1. **External Database**: Replace HSQLDB with PostgreSQL/MySQL\n2. **Mail Server**: Configure SMTP for real notifications\n3. **Security**: Use external user store (LDAP/OAuth2)\n4. **HTTPS**: Enable TLS/SSL\n5. **Session Store**: Use Redis or external database\n\nExample deployment configuration:\n\n```yaml\nspring:\n  datasource:\n    url: jdbc:postgresql://localhost:5432/admin\n    username: admin\n    password: ${DB_PASSWORD}\n\n  mail:\n    host: smtp.company.com\n    port: 587\n    username: ${SMTP_USER}\n    password: ${SMTP_PASSWORD}\n\nserver:\n  port: 8443\n  ssl:\n    enabled: true\n    key-store: classpath:keystore.p12\n    key-store-password: ${KEYSTORE_PASSWORD}\n```\n\n## Customization Ideas\n\n### Add Custom Actuator Endpoint\n\nCreate a custom endpoint (example included):\n\n```java\n@Component\n@Endpoint(id = \"custom\")\npublic class CustomEndpoint {\n\n    @ReadOperation\n    public Map<String, Object> customEndpoint() {\n        return Map.of(\n            \"message\", \"Hello from custom endpoint\",\n            \"timestamp\", Instant.now()\n        );\n    }\n}\n```\n\n### Add Database Notifier\n\nReplace log-based notifier with database persistence:\n\n```java\npublic class DatabaseNotifier extends AbstractEventNotifier {\n\n    private final EventRepository eventRepository;\n\n    @Override\n    protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {\n        return Mono.fromRunnable(() -> {\n            eventRepository.save(new EventEntity(event, instance));\n        });\n    }\n}\n```\n\n### Add Custom Metadata\n\nEnhance self-registration with custom metadata:\n\n```yaml\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          metadata:\n            version: ${project.version}\n            region: us-east-1\n            team: platform\n            tags:\n              environment: production\n              cost-center: engineering\n```\n\n## Troubleshooting\n\n### Port Already in Use\n\n```bash\n# Change port\nSERVER_PORT=9090 mvn spring-boot:run\n```\n\n### Security Issues\n\nIf you cannot access the UI:\n\n1. Check if `secure` profile is active\n2. Verify credentials: `user` / `password`\n3. Check CSRF token in browser console\n4. Clear browser cookies\n\n### Self-Registration Fails\n\n1. Verify client URL matches server URL\n2. Check actuator endpoints are exposed\n3. Review logs for connection errors\n4. Ensure security permits instance registration\n\n### Mail Notifications Not Working\n\n1. Configure valid SMTP server\n2. Check firewall/network access\n3. Verify credentials\n4. Enable debug logging:\n\n```yaml\nlogging:\n  level:\n    org.springframework.mail: DEBUG\n```\n\n## Key Takeaways\n\nThis sample demonstrates:\n\n✅ **Complete Deployment Setup**\n\n- Security, session management, notifications\n\n✅ **Self-Monitoring Pattern**\n\n- Admin Server monitoring itself via Admin Client\n\n✅ **Extensibility**\n\n- Custom notifiers, endpoints, headers, UI views\n\n✅ **Best Practices**\n\n- Profile-based configuration\n- CSRF protection\n- Proper security configuration\n\n## Next Steps\n\n- Explore [Reactive Sample](./20-sample-reactive.md) for WebFlux alternative\n- Review [Security Documentation](../05-security/) for deployment hardening\n- Check [Customization Guide](../06-customization/) for more extensions\n- See [Notification Configuration](../02-server/notifications/) for notifier options\n\n## See Also\n\n- [Server Configuration](../02-server/01-server.mdx)\n- [Client Registration](../03-client/20-registration.md)\n- [UI Customization](../06-customization/ui/)\n- [Spring Security Integration](../05-security/10-server-authentication.md)\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/09-samples/20-sample-reactive.md",
    "content": "---\n\nsidebar_position: 20\nsidebar_custom_props:\n  icon: 'file-code'\n---\n\n# Reactive Sample\n\nThe Reactive sample demonstrates a Spring Boot Admin Server deployment using Spring WebFlux, the reactive, non-blocking\nweb framework. This sample showcases how to run Admin Server in a fully reactive environment with minimal dependencies.\n\n## Overview\n\n**Location**: `spring-boot-admin-samples/spring-boot-admin-sample-reactive/`\n\n**Features**:\n\n- Reactive stack using Spring WebFlux\n- Non-blocking I/O operations\n- Spring Security for WebFlux\n- Self-monitoring using Admin Client\n- Profile-based security configuration\n- Minimal dependencies\n- DevTools support for development\n\n## Prerequisites\n\n- Java 17 or higher\n- Maven 3.6+\n\n## Running the Sample\n\n### Quick Start (Insecure Mode)\n\n```bash\ncd spring-boot-admin-samples/spring-boot-admin-sample-reactive\nmvn spring-boot:run\n```\n\nAccess the application at: `http://localhost:8080`\n\nThe application runs with the `insecure` profile by default, allowing access without authentication.\n\n### With Security Enabled\n\n```bash\nmvn spring-boot:run -Dspring-boot.run.profiles=secure\n```\n\n**Login Credentials**: Configure in `application.yml` or use default Spring Security credentials\n\n### Change Port\n\n```bash\nSERVER_PORT=9090 mvn spring-boot:run\n```\n\n## Key Differences from Servlet Sample\n\n### Dependencies\n\nThe reactive sample uses minimal dependencies:\n\n```xml\n<dependencies>\n    <!-- Admin Server -->\n    <dependency>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-starter-server</artifactId>\n    </dependency>\n\n    <!-- Admin Client (for self-monitoring) -->\n    <dependency>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-starter-client</artifactId>\n    </dependency>\n\n    <!-- Security for WebFlux -->\n    <dependency>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-security</artifactId>\n    </dependency>\n\n    <!-- DevTools -->\n    <dependency>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-devtools</artifactId>\n        <optional>true</optional>\n    </dependency>\n</dependencies>\n```\n\n**Notice**: No explicit WebFlux dependency needed - it's pulled in transitively by `spring-boot-admin-starter-server`\nwhen no servlet container is present.\n\n### Reactive Architecture\n\nThe reactive sample leverages:\n\n- **Non-blocking I/O**: All HTTP requests are handled reactively\n- **Backpressure**: Built-in flow control for data streams\n- **Event Loop**: Efficient thread utilization with Netty\n- **Reactive Types**: `Mono` and `Flux` for asynchronous operations\n\n## Application Structure\n\n### Main Application Class\n\n```java title=\"SpringBootAdminReactiveApplication.java\"\n@SpringBootApplication\n@EnableAdminServer\npublic class SpringBootAdminReactiveApplication {\n\n    private final AdminServerProperties adminServer;\n\n    public SpringBootAdminReactiveApplication(AdminServerProperties adminServer) {\n        this.adminServer = adminServer;\n    }\n\n    static void main(String[] args) {\n        SpringApplication.run(SpringBootAdminReactiveApplication.class, args);\n    }\n\n    @Bean\n    public Notifier notifier() {\n        return (e) -> Mono.empty();  // No-op notifier\n    }\n}\n```\n\n**Key Points**:\n\n- `@EnableAdminServer` enables Admin Server functionality\n- AdminServerProperties injected for security configuration\n- Simple no-op notifier returns `Mono.empty()`\n\n## Security Configuration\n\n### Insecure Profile (Default)\n\n```java\n@Bean\n@Profile(\"insecure\")\npublic SecurityWebFilterChain securityWebFilterChainPermitAll(\n        ServerHttpSecurity http) {\n    return http\n        .authorizeExchange((authorizeExchange) ->\n            authorizeExchange.anyExchange().permitAll())\n        .csrf(ServerHttpSecurity.CsrfSpec::disable)\n        .build();\n}\n```\n\n**Characteristics**:\n\n- All endpoints accessible without authentication\n- CSRF protection disabled\n- Useful for development and testing\n\n:::warning Development Only\nThe insecure profile should only be used for local development and testing. Always enable security when deploying.\n:::\n\n### Secure Profile\n\n```java\n@Bean\n@Profile(\"secure\")\npublic SecurityWebFilterChain securityWebFilterChainSecure(\n        ServerHttpSecurity http) {\n    return http\n        .authorizeExchange((authorizeExchange) ->\n            authorizeExchange\n                .pathMatchers(adminServer.path(\"/assets/**\"))\n                    .permitAll()  // Static resources\n                .pathMatchers(\"/actuator/health/**\")\n                    .permitAll()  // Health endpoint\n                .pathMatchers(adminServer.path(\"/login\"))\n                    .permitAll()  // Login page\n                .anyExchange()\n                    .authenticated())  // Everything else requires auth\n        .formLogin((formLogin) -> formLogin\n            .loginPage(adminServer.path(\"/login\"))\n            .authenticationSuccessHandler(\n                loginSuccessHandler(adminServer.path(\"/\"))))\n        .logout((logout) -> logout\n            .logoutUrl(adminServer.path(\"/logout\"))\n            .logoutSuccessHandler(\n                logoutSuccessHandler(adminServer.path(\"/login?logout\"))))\n        .httpBasic(Customizer.withDefaults())\n        .csrf(ServerHttpSecurity.CsrfSpec::disable)  // Simplified for demo\n        .build();\n}\n\nprivate ServerAuthenticationSuccessHandler loginSuccessHandler(String uri) {\n    RedirectServerAuthenticationSuccessHandler successHandler =\n        new RedirectServerAuthenticationSuccessHandler();\n    successHandler.setLocation(URI.create(uri));\n    return successHandler;\n}\n\nprivate ServerLogoutSuccessHandler logoutSuccessHandler(String uri) {\n    RedirectServerLogoutSuccessHandler successHandler =\n        new RedirectServerLogoutSuccessHandler();\n    successHandler.setLogoutSuccessUrl(URI.create(uri));\n    return successHandler;\n}\n```\n\n**Security Features**:\n\n1. **Form Login**: Custom login page at Admin Server path\n2. **HTTP Basic**: Support for basic authentication\n3. **Public Endpoints**: Static resources, health, and login page\n4. **Custom Redirects**: Success handlers for login/logout\n5. **Path-based Authorization**: Uses `ServerHttpSecurity` for reactive security\n\n:::info Reactive Security\nNotice the use of `SecurityWebFilterChain` and `ServerHttpSecurity` instead of servlet-based `SecurityFilterChain` and\n`HttpSecurity`.\n:::\n\n## Configuration\n\n### Application Configuration\n\n```yaml title=\"application.yml\"\nspring:\n  application:\n    name: spring-boot-admin-sample-reactive\n\n  boot:\n    admin:\n      client:\n        url: http://localhost:8080  # Self-registration\n\n  profiles:\n    active:\n      - insecure  # Default profile\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n  endpoint:\n    health:\n      show-details: ALWAYS\n\nlogging:\n  file:\n    name: \"target/boot-admin-sample-reactive.log\"\n```\n\n**Configuration Highlights**:\n\n- Self-monitoring via Admin Client\n- All actuator endpoints exposed\n- Health details always shown\n- Insecure profile active by default\n\n## Reactive Stack Benefits\n\n### 1. Non-Blocking I/O\n\nAll operations are non-blocking:\n\n```java\n// Instance queries are reactive\nFlux<Instance> instances = instanceRepository.findAll();\n\n// Event streams are reactive\nFlux<InstanceEvent> events = eventStore.findAll();\n\n// HTTP calls are reactive\nMono<ClientResponse> response = webClient\n    .get()\n    .uri(\"/actuator/health\")\n    .exchange();\n```\n\n### 2. Efficient Resource Usage\n\n- **Thread Pool**: Small fixed thread pool (typically 2x CPU cores)\n- **Memory**: Lower memory footprint\n- **Scalability**: Handles more concurrent connections with fewer threads\n\n### 3. Backpressure Support\n\nThe reactive stack automatically handles backpressure:\n\n```java\n// Slow consumers won't overwhelm fast producers\neventStore.findAll()\n    .limitRate(100)  // Process 100 events at a time\n    .subscribe(event -> processEvent(event));\n```\n\n### 4. Better for Microservices\n\n- **Resilience**: Non-blocking calls prevent thread exhaustion\n- **Latency**: Better tail latency under load\n- **Throughput**: Higher throughput for I/O-bound operations\n\n## Testing the Sample\n\n### Access the UI\n\n1. Start the application\n2. Navigate to `http://localhost:8080`\n3. No login required (insecure mode)\n\n### Test Self-Monitoring\n\nThe application monitors itself:\n\n- Application name: `spring-boot-admin-sample-reactive`\n- Status: UP\n- All actuator endpoints available\n- Check logs: `target/boot-admin-sample-reactive.log`\n\n### Test Reactive Behavior\n\nMonitor thread usage:\n\n```bash\n# Check thread count (should be low)\njcmd <pid> Thread.print | grep \"nioEventLoopGroup\" | wc -l\n```\n\nExpected: ~4-8 threads vs. hundreds in servlet mode under load\n\n### Performance Testing\n\nCompare reactive vs. servlet performance:\n\n```bash\n# Reactive sample\nab -n 10000 -c 100 http://localhost:8080/actuator/health\n\n# Servlet sample\nab -n 10000 -c 100 http://localhost:8081/actuator/health\n```\n\nReactive should handle higher concurrency with fewer resources.\n\n## Build and Deploy\n\n### Build JAR\n\n```bash\nmvn clean package\n```\n\nProduces: `target/spring-boot-admin-sample-reactive.jar`\n\n### Run JAR\n\n```bash\njava -jar target/spring-boot-admin-sample-reactive.jar\n```\n\n### With Security Profile\n\n```bash\njava -jar target/spring-boot-admin-sample-reactive.jar \\\n  --spring.profiles.active=secure\n```\n\n### Production Configuration\n\nExample production configuration:\n\n```yaml\nspring:\n  profiles:\n    active:\n      - secure  # Enable security\n\n  security:\n    user:\n      name: admin\n      password: ${ADMIN_PASSWORD}\n\nserver:\n  port: 8443\n  ssl:\n    enabled: true\n    key-store: classpath:keystore.p12\n    key-store-password: ${KEYSTORE_PASSWORD}\n\nmanagement:\n  server:\n    port: 8081  # Separate management port\n```\n\n## Comparison: Reactive vs. Servlet\n\n| Aspect           | Reactive Sample             | Servlet Sample                    |\n|------------------|-----------------------------|-----------------------------------|\n| **Web Stack**    | WebFlux (Netty)             | Spring MVC (Tomcat)               |\n| **Thread Model** | Event loop (4-8 threads)    | Thread per request (200+ threads) |\n| **I/O Model**    | Non-blocking                | Blocking                          |\n| **Memory**       | Lower footprint             | Higher footprint                  |\n| **Scalability**  | High (10K+ connections)     | Medium (100s of connections)      |\n| **Complexity**   | Higher learning curve       | Traditional, simpler              |\n| **Dependencies** | Minimal                     | More dependencies                 |\n| **Use Case**     | High concurrency, I/O-bound | CPU-bound, traditional apps       |\n\n## When to Use Reactive Sample\n\n✅ **Use Reactive When**:\n\n- Monitoring many instances (100+)\n- High concurrency requirements\n- Microservices architecture\n- Cloud-native deployments\n- Limited resources (memory/CPU)\n- I/O-bound workloads\n\n❌ **Use Servlet When**:\n\n- Traditional monolithic applications\n- Team unfamiliar with reactive programming\n- Heavy CPU-bound processing\n- Existing servlet-based infrastructure\n- Simpler debugging requirements\n\n## Common Issues\n\n### ClassNotFoundException\n\nIf you see WebFlux-related errors:\n\n```\njava.lang.ClassNotFoundException: reactor.netty.http.server.HttpServer\n```\n\n**Solution**: Ensure no servlet dependencies are present:\n\n```xml\n<!-- Remove if present -->\n<dependency>\n    <groupId>org.springframework.boot</groupId>\n    <artifactId>spring-boot-starter-webmvc</artifactId>\n</dependency>\n```\n\n### Port Conflict\n\nIf port 8080 is in use:\n\n```bash\nSERVER_PORT=9090 mvn spring-boot:run\n```\n\n### Security Configuration Not Applied\n\nIf security profile doesn't work:\n\n```bash\n# Verify active profiles\ncurl http://localhost:8080/actuator/env | jq '.activeProfiles'\n```\n\nEnsure profile is set correctly in `application.yml` or via command line.\n\n## Customization Ideas\n\n### Add Custom Reactive Notifier\n\n```java\n@Bean\npublic Notifier customReactiveNotifier() {\n    return (event) -> {\n        return webClient\n            .post()\n            .uri(\"https://webhook.site/...\")\n            .bodyValue(event)\n            .retrieve()\n            .bodyToMono(Void.class)\n            .onErrorResume(e -> {\n                log.error(\"Notification failed\", e);\n                return Mono.empty();\n            });\n    };\n}\n```\n\n### Add WebClient Customization\n\n```java\n@Bean\npublic InstanceWebClientCustomizer customTimeout() {\n    return (builder) -> builder\n        .clientConnector(new ReactorClientHttpConnector(\n            HttpClient.create()\n                .responseTimeout(Duration.ofSeconds(10))\n        ));\n}\n```\n\n### Add Reactive Health Indicator\n\n```java\n@Component\npublic class CustomHealthIndicator implements ReactiveHealthIndicator {\n\n    @Override\n    public Mono<Health> health() {\n        return Mono.just(Health.up()\n            .withDetail(\"custom\", \"Reactive health check\")\n            .build());\n    }\n}\n```\n\n## Key Takeaways\n\nThis sample demonstrates:\n\n✅ **Reactive Architecture**\n\n- Non-blocking I/O with WebFlux\n- Efficient resource utilization\n\n✅ **Security Options**\n\n- Profile-based security configuration\n- Reactive security filters\n\n✅ **Minimal Dependencies**\n\n- Lightweight deployment\n- Faster startup time\n\n✅ **Fully Configured**\n\n- Self-monitoring capability\n- Scalable architecture\n\n## Next Steps\n\n- Explore [Servlet Sample](./10-sample-servlet.md) for traditional deployment\n- Review [Eureka Sample](./30-sample-eureka.md) for service discovery\n- Check [Hazelcast Sample](./60-sample-hazelcast.md) for clustering\n- Read [Customization Guide](../06-customization/) for extensions\n\n## See Also\n\n- [Server Configuration](../02-server/01-server.mdx)\n- [Client Registration](../03-client/20-registration.md)\n- [Spring WebFlux Documentation](https://docs.spring.io/spring-framework/reference/web/webflux.html)\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/09-samples/30-sample-eureka.md",
    "content": "---\n\nsidebar_position: 30\nsidebar_custom_props:\n  icon: 'file-code'\n---\n\n# Eureka Sample\n\nThe Eureka sample demonstrates Spring Boot Admin Server integration with Netflix Eureka service discovery. This sample\nshows how to automatically discover and monitor Spring Boot applications registered with Eureka without using the Admin\nClient.\n\n## Overview\n\n**Location**: `spring-boot-admin-samples/spring-boot-admin-sample-eureka/`\n\n**Features**:\n\n- Automatic service discovery via Eureka\n- No Admin Client required on monitored applications\n- Dynamic instance registration and deregistration\n- Metadata-based configuration\n- Docker Compose setup with multiple services\n- Spring Security integration\n- Health check integration with Eureka\n\n## Prerequisites\n\n- Java 17 or higher\n- Maven 3.6+\n- Docker and Docker Compose (for full stack)\n- Eureka Server running (or use Docker Compose)\n\n## Architecture\n\n```mermaid\ngraph TD\n    ES[Eureka Server<br/>Port 8761] -->|Service Registry| AS[Admin Server<br/>Port 8080]\n    ES -->|Service Registry| MA[Monitored Apps]\n```\n\n**Key Points**:\n\n1. Applications register with Eureka\n2. Admin Server discovers applications from Eureka\n3. No direct registration needed\n\n## Running the Sample\n\n### Option 1: With External Eureka Server\n\n#### Start Eureka Server\n\n```bash\n# Using Docker\ndocker run -d -p 8761:8761 springcloud/eureka\n\n# Or using Spring Cloud Eureka server JAR\njava -jar eureka-server.jar\n```\n\nVerify Eureka is running: `http://localhost:8761`\n\n#### Start Admin Server\n\n```bash\ncd spring-boot-admin-samples/spring-boot-admin-sample-eureka\nmvn spring-boot:run\n```\n\nAccess Admin UI at: `http://localhost:8080`\n\n### Option 2: Using Docker Compose (Recommended)\n\n```bash\ncd spring-boot-admin-samples/spring-boot-admin-sample-eureka\ndocker-compose up\n```\n\nThis starts:\n\n- Eureka Server (port 8761)\n- Admin Server (port 8080)\n- Spring Cloud Config Server (port 8888)\n- Sample microservices (customers, stores)\n- Supporting infrastructure (MongoDB, RabbitMQ)\n\n**Access Points**:\n\n- Admin UI: `http://localhost:8080`\n- Eureka UI: `http://localhost:8761`\n- Config Server: `http://localhost:8888`\n- Sample App UI: `http://localhost:80`\n\n### Change Eureka URL\n\n```bash\nmvn spring-boot:run -Dspring-boot.run.arguments=\\\n  --eureka.client.serviceUrl.defaultZone=http://other-eureka:8761/eureka/\n```\n\nOr set environment variable:\n\n```bash\nexport EUREKA_SERVICE_URL=http://other-eureka:8761\nmvn spring-boot:run\n```\n\n## Project Structure\n\n### Dependencies\n\n```xml\n<dependencies>\n    <!-- Admin Server -->\n    <dependency>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-starter-server</artifactId>\n    </dependency>\n\n    <!-- Eureka Discovery Client -->\n    <dependency>\n        <groupId>org.springframework.cloud</groupId>\n        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>\n    </dependency>\n\n    <!-- Security -->\n    <dependency>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-security</artifactId>\n    </dependency>\n\n    <!-- Web (Servlet excluded, uses WebFlux) -->\n    <dependency>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-webmvc</artifactId>\n        <exclusions>\n            <exclusion>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-starter-tomcat</artifactId>\n            </exclusion>\n        </exclusions>\n    </dependency>\n</dependencies>\n```\n\n**Note**: Tomcat is excluded, so this sample runs on Netty (reactive stack).\n\n### Main Application Class\n\n```java title=\"SpringBootAdminEurekaApplication.java\"\n@Configuration\n@EnableAutoConfiguration\n@EnableDiscoveryClient  // Enable Eureka discovery\n@EnableAdminServer      // Enable Admin Server\npublic class SpringBootAdminEurekaApplication {\n\n    private final AdminServerProperties adminServer;\n\n    public SpringBootAdminEurekaApplication(AdminServerProperties adminServer) {\n        this.adminServer = adminServer;\n    }\n\n    public static void main(String[] args) {\n        SpringApplication.run(SpringBootAdminEurekaApplication.class, args);\n    }\n}\n```\n\n**Key Annotations**:\n\n- `@EnableDiscoveryClient`: Enables Eureka client functionality\n- `@EnableAdminServer`: Enables Admin Server\n- Both work together to discover and monitor services\n\n## Configuration\n\n### Admin Server Configuration\n\n```yaml title=\"application.yml\"\nspring:\n  application:\n    name: spring-boot-admin-sample-eureka\n  profiles:\n    active:\n      - secure\n\neureka:\n  instance:\n    leaseRenewalIntervalInSeconds: 10  # Heartbeat interval\n    health-check-url-path: /actuator/health\n    metadata-map:\n      startup: ${random.int}  # Trigger refresh on restart\n  client:\n    registryFetchIntervalSeconds: 5  # Fetch registry every 5s\n    serviceUrl:\n      defaultZone: ${EUREKA_SERVICE_URL:http://localhost:8761}/eureka/\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"  # Expose all actuator endpoints\n  endpoint:\n    health:\n      show-details: ALWAYS\n```\n\n**Configuration Details**:\n\n1. **Lease Renewal**: 10 seconds (faster detection of down instances)\n2. **Registry Fetch**: 5 seconds (quick discovery of new services)\n3. **Health Check**: Registered with Eureka\n4. **Startup Metadata**: Random value triggers endpoint update after restart\n\n### Client Application Configuration\n\nFor applications to be monitored, they only need:\n\n```yaml\nspring:\n  application:\n    name: my-service  # Service name in Eureka\n\neureka:\n  client:\n    serviceUrl:\n      defaultZone: http://localhost:8761/eureka/\n  instance:\n    metadata-map:\n      management.context-path: /actuator  # Tell Admin where actuator is\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"  # Expose endpoints\n```\n\n**No Admin Client dependency needed!**\n\n## Security Configuration\n\n### Insecure Profile\n\n```java\n@Bean\n@Profile(\"insecure\")\npublic SecurityWebFilterChain securityWebFilterChainPermitAll(\n        ServerHttpSecurity http) {\n    return http\n        .authorizeExchange((authorizeExchange) ->\n            authorizeExchange.anyExchange().permitAll())\n        .csrf(ServerHttpSecurity.CsrfSpec::disable)\n        .build();\n}\n```\n\n### Secure Profile (Default)\n\n```java\n@Bean\n@Profile(\"secure\")\npublic SecurityWebFilterChain securityWebFilterChainSecure(\n        ServerHttpSecurity http) {\n    return http\n        .authorizeExchange((authorizeExchange) ->\n            authorizeExchange\n                .pathMatchers(adminServer.path(\"/assets/**\"))\n                    .permitAll()\n                .pathMatchers(\"/actuator/health/**\")\n                    .permitAll()\n                .pathMatchers(adminServer.path(\"/login\"))\n                    .permitAll()\n                .anyExchange()\n                    .authenticated())\n        .formLogin((formLogin) -> formLogin\n            .loginPage(adminServer.path(\"/login\"))\n            .authenticationSuccessHandler(loginSuccessHandler(...)))\n        .logout((logout) -> logout\n            .logoutUrl(adminServer.path(\"/logout\"))\n            .logoutSuccessHandler(logoutSuccessHandler(...)))\n        .httpBasic(Customizer.withDefaults())\n        .csrf(ServerHttpSecurity.CsrfSpec::disable)\n        .build();\n}\n```\n\n## Docker Compose Setup\n\n### Complete Stack\n\nThe `docker-compose.yml` provides a complete microservices environment:\n\n```yaml title=\"docker-compose.yml\"\nversion: '2'\n\nservices:\n  # Eureka Server\n  eureka:\n    image: springcloud/eureka\n    container_name: eureka\n    ports:\n      - \"8761:8761\"\n    environment:\n      - EUREKA_INSTANCE_PREFERIPADDRESS=true\n\n  # Admin Server\n  admin:\n    build:\n      context: .\n      dockerfile: ./src/main/docker/Dockerfile\n    depends_on:\n      - eureka\n    ports:\n     - \"8080:8080\"\n    environment:\n      - EUREKA_SERVICE_URL=http://eureka:8761\n      - EUREKA_INSTANCE_PREFER_IP_ADDRESS=true\n\n  # Spring Cloud Config Server\n  config:\n    image: springcloud/configserver\n    depends_on:\n      - eureka\n    ports:\n      - \"8888:8888\"\n    environment:\n      - EUREKA_SERVICE_URL=http://eureka:8761\n\n  # Sample Microservices\n  customers:\n    image: springcloud/customers\n    depends_on:\n      - config\n      - rabbit\n    environment:\n      - CONFIG_SERVER_URI=http://config:8888\n      - RABBITMQ_HOST=rabbit\n\n  stores:\n    image: springcloud/stores\n    depends_on:\n      - config\n      - rabbit\n      - mongodb\n    environment:\n      - CONFIG_SERVER_URI=http://config:8888\n      - RABBITMQ_HOST=rabbit\n      - MONGODB_HOST=mongodb\n\n  # Infrastructure\n  mongodb:\n    image: tutum/mongodb\n    ports:\n      - \"27017:27017\"\n    environment:\n      - AUTH=no\n\n  rabbit:\n    image: \"rabbitmq:4\"\n    ports:\n     - \"5672:5672\"\n```\n\n### Start Stack\n\n```bash\ndocker-compose up\n```\n\n### Stop Stack\n\n```bash\ndocker-compose down\n```\n\n### View Logs\n\n```bash\n# All services\ndocker-compose logs -f\n\n# Specific service\ndocker-compose logs -f admin\n```\n\n## How It Works\n\n### Service Discovery Flow\n\n1. **Application Startup**:\n    - Application starts and registers with Eureka\n    - Sends metadata including actuator path\n    - Eureka assigns instance ID\n\n2. **Admin Server Discovery**:\n    - Admin Server fetches registry from Eureka every 5s\n    - Discovers new services\n    - Reads metadata to find actuator endpoints\n\n3. **Health Monitoring**:\n    - Admin Server polls actuator endpoints\n    - Updates instance status\n    - Triggers notifications on status changes\n\n4. **Application Shutdown**:\n    - Application deregisters from Eureka\n    - Admin Server removes instance from monitoring\n\n### Metadata Mapping\n\nAdmin Server reads specific metadata keys:\n\n```yaml\neureka:\n  instance:\n    metadata-map:\n      # Required for proper endpoint detection\n      management.context-path: /actuator\n      management.port: 8081  # If different from service port\n\n      # Optional - for authenticated actuators\n      user.name: admin\n      user.password: ${admin.password}\n\n      # Optional - custom metadata\n      startup: ${random.int}  # Triggers refresh\n      environment: production\n      version: ${project.version}\n```\n\n**Important Keys**:\n\n- `management.context-path`: Where actuator endpoints are located\n- `management.port`: If management port differs from application port\n- `user.name` / `user.password`: Credentials for secured actuators\n- `startup`: Random value forces Admin to refresh endpoints after restart\n\n## Testing the Sample\n\n### Verify Eureka Registration\n\n1. Access Eureka UI: `http://localhost:8761`\n2. Check \"Instances currently registered with Eureka\"\n3. Should see:\n    - `SPRING-BOOT-ADMIN-SAMPLE-EUREKA`\n    - Other registered services\n\n### Verify Admin Server Discovery\n\n1. Access Admin UI: `http://localhost:8080`\n2. Should see all Eureka-registered services\n3. Click on each service to view:\n    - Health status\n    - Metrics\n    - Environment\n    - Logs\n    - JVM details\n\n### Test Dynamic Discovery\n\n#### Register New Service\n\n```bash\n# Start another instance\nSERVER_PORT=8081 mvn spring-boot:run\n```\n\nWithin 5 seconds, it should appear in Admin UI.\n\n#### Deregister Service\n\nStop the application (Ctrl+C). Within ~40 seconds (lease timeout), it should disappear from Admin UI.\n\n### Test Health Status Changes\n\nStop a monitored service and watch status change from UP → DOWN in Admin UI.\n\n## Advanced Configuration\n\n### Custom Service Filtering\n\nFilter which services to monitor:\n\n```yaml\nspring:\n  boot:\n    admin:\n      discovery:\n        ignored-services:\n          - eureka-server  # Don't monitor Eureka itself\n          - config-server  # Don't monitor Config Server\n```\n\n### Service Grouping\n\nGroup services using metadata:\n\n```yaml\n# Client application\neureka:\n  instance:\n    metadata-map:\n      group: backend-services\n      team: platform\n```\n\n### Secure Actuator Endpoints\n\nIf client actuators are secured:\n\n```yaml\n# Client application\neureka:\n  instance:\n    metadata-map:\n      user.name: actuator-admin\n      user.password: ${actuator.password}\n```\n\nAdmin Server automatically uses these credentials.\n\n### Custom Health Check URL\n\n```yaml\neureka:\n  instance:\n    health-check-url-path: /custom/health\n    metadata-map:\n      management.context-path: /custom\n```\n\n## Comparison: Eureka vs. Direct Registration\n\n| Aspect                | Eureka Discovery          | Direct Registration          |\n|-----------------------|---------------------------|------------------------------|\n| **Setup**             | Eureka Server required    | No additional infrastructure |\n| **Client Dependency** | Only Eureka client        | Admin Client required        |\n| **Discovery**         | Automatic                 | Manual configuration         |\n| **Scalability**       | Excellent (100+ services) | Limited (static config)      |\n| **Dynamic Updates**   | Automatic                 | Manual restart               |\n| **Use Case**          | Microservices             | Monoliths, small deployments |\n| **Complexity**        | Higher                    | Lower                        |\n\n## Troubleshooting\n\n### Admin Server Not Discovering Services\n\n**Check Eureka connectivity**:\n\n```bash\n# Test Eureka API\ncurl http://localhost:8761/eureka/apps\n\n# Check Admin logs\ndocker-compose logs admin | grep -i eureka\n```\n\n**Common Issues**:\n\n1. Eureka URL incorrect\n2. Network connectivity issues\n3. Services not exposing actuator endpoints\n\n**Solution**:\n\n```yaml\n# Verify configuration\neureka:\n  client:\n    serviceUrl:\n      defaultZone: http://localhost:8761/eureka/  # Trailing slash!\n```\n\n### Services Show as DOWN\n\n**Check health endpoint**:\n\n```bash\ncurl http://localhost:8080/actuator/health\n```\n\n**Verify metadata**:\n\n```yaml\neureka:\n  instance:\n    metadata-map:\n      management.context-path: /actuator  # Must match actual path\n```\n\n### Slow Discovery\n\nServices take too long to appear:\n\n```yaml\neureka:\n  client:\n    registryFetchIntervalSeconds: 5  # Reduce from default 30s\n  instance:\n    leaseRenewalIntervalInSeconds: 10  # Reduce from default 30s\n```\n\n### Docker Compose Issues\n\n**Port conflicts**:\n\n```bash\n# Change ports in docker-compose.yml\nports:\n  - \"9090:8080\"  # Map to different host port\n```\n\n**Container connectivity**:\n\n```bash\n# Check network\ndocker network inspect spring-boot-admin-sample-eureka_discovery\n\n# Check container logs\ndocker-compose logs eureka\n```\n\n## Production Considerations\n\n### High Availability\n\nRun multiple Eureka servers:\n\n```yaml\neureka:\n  client:\n    serviceUrl:\n      defaultZone: http://eureka1:8761/eureka/,http://eureka2:8761/eureka/\n```\n\n### Security\n\nSecure Eureka communication:\n\n```yaml\neureka:\n  client:\n    serviceUrl:\n      defaultZone: https://${eureka.user}:${eureka.password}@eureka:8761/eureka/\n```\n\n### Performance Tuning\n\nOptimize for large deployments:\n\n```yaml\nspring:\n  boot:\n    admin:\n      monitor:\n        period: 20000  # Increase polling interval (ms)\n        connect-timeout: 5000\n        read-timeout: 10000\n\neureka:\n  client:\n    registryFetchIntervalSeconds: 10  # Balance freshness vs. load\n```\n\n### Monitoring Eureka Itself\n\nRegister Admin Server with itself:\n\n```yaml\nspring:\n  boot:\n    admin:\n      discovery:\n        ignored-services: []  # Don't ignore any services\n```\n\n## Key Takeaways\n\nThis sample demonstrates:\n\n✅ **Service Discovery Integration**\n\n- Automatic discovery via Eureka\n- No Admin Client dependency needed\n\n✅ **Dynamic Monitoring**\n\n- Services auto-register/deregister\n- Real-time discovery updates\n\n✅ **Complete Setup**\n\n- Complete microservices stack\n- Docker Compose orchestration\n\n✅ **Scalable Architecture**\n\n- Handles many services efficiently\n- Centralized monitoring\n\n## Next Steps\n\n- Explore [Consul Sample](./40-sample-consul.md) for alternative service discovery\n- Review [Zookeeper Sample](./50-sample-zookeeper.md) for Apache Zookeeper\n- Check [Integration Guide](../04-integration/10-eureka.md) for detailed Eureka setup\n- See [Hazelcast Sample](./60-sample-hazelcast.md) for clustering\n\n## See Also\n\n- [Eureka Integration Guide](../04-integration/10-eureka.md)\n- [Service Discovery](../03-client/40-service-discovery.md)\n- [Server Configuration](../02-server/01-server.mdx)\n- [Netflix Eureka Documentation](https://github.com/Netflix/eureka/wiki)\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/09-samples/40-sample-consul.md",
    "content": "---\n\nsidebar_position: 40\nsidebar_custom_props:\n  icon: 'file-code'\n---\n\n# Consul Sample\n\nThe Consul sample demonstrates Spring Boot Admin Server integration with HashiCorp Consul for service discovery. This\nsample shows how to leverage Consul's powerful service registry and health checking capabilities to automatically\ndiscover and monitor Spring Boot applications.\n\n## Overview\n\n**Location**: `spring-boot-admin-samples/spring-boot-admin-sample-consul/`\n\n**Features**:\n\n- Automatic service discovery via Consul\n- No Admin Client required on monitored applications\n- Consul health check integration\n- Metadata-based configuration\n- Custom actuator endpoint paths\n- Spring Security integration\n- Servlet-based deployment\n\n## Prerequisites\n\n- Java 17 or higher\n- Maven 3.6+\n- Consul installed and running\n\n## Installing Consul\n\n### macOS\n\n```bash\nbrew install consul\n```\n\n### Linux\n\n```bash\nwget https://releases.hashicorp.com/consul/1.17.0/consul_1.17.0_linux_amd64.zip\nunzip consul_1.17.0_linux_amd64.zip\nsudo mv consul /usr/local/bin/\n```\n\n### Windows\n\nDownload from: https://www.consul.io/downloads\n\n### Docker\n\n```bash\ndocker run -d -p 8500:8500 -p 8600:8600/udp --name=consul consul agent -server -ui -bootstrap-expect=1 -client=0.0.0.0\n```\n\n### Verify Installation\n\n```bash\nconsul version\n```\n\n## Running the Sample\n\n### Start Consul\n\n```bash\n# Development mode (single node)\nconsul agent -dev\n```\n\nVerify Consul is running: `http://localhost:8500/ui`\n\n### Start Admin Server\n\n```bash\ncd spring-boot-admin-samples/spring-boot-admin-sample-consul\nmvn spring-boot:run\n```\n\nAccess Admin UI at: `http://localhost:8080`\n\n### With Different Consul Host\n\n```bash\nmvn spring-boot:run -Dspring-boot.run.arguments=\\\n  --spring.cloud.consul.host=consul-server\n```\n\n### Insecure Mode\n\n```bash\nmvn spring-boot:run -Dspring-boot.run.profiles=insecure\n```\n\n## Project Structure\n\n### Dependencies\n\n```xml\n<dependencies>\n    <!-- Admin Server -->\n    <dependency>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-starter-server</artifactId>\n    </dependency>\n\n    <!-- Consul Discovery -->\n    <dependency>\n        <groupId>org.springframework.cloud</groupId>\n        <artifactId>spring-cloud-starter-consul-discovery</artifactId>\n    </dependency>\n\n    <!-- Web (Servlet) -->\n    <dependency>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-webmvc</artifactId>\n    </dependency>\n\n    <!-- Security -->\n    <dependency>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-security</artifactId>\n    </dependency>\n</dependencies>\n```\n\n### Main Application Class\n\n```java title=\"SpringBootAdminConsulApplication.java\"\n@SpringBootApplication\n@EnableDiscoveryClient  // Enable Consul discovery\n@EnableAdminServer      // Enable Admin Server\npublic class SpringBootAdminConsulApplication {\n\n    static void main(String[] args) {\n        SpringApplication.run(SpringBootAdminConsulApplication.class, args);\n    }\n}\n```\n\n## Configuration\n\n### Admin Server Configuration\n\n```yaml title=\"application.yml\"\nspring:\n  application:\n    name: consul-example\n\n  cloud:\n    config:\n      enabled: false  # Disable config client\n    consul:\n      host: localhost\n      port: 8500\n      discovery:\n        metadata:\n          # IMPORTANT: Use dashes, not dots in metadata keys!\n          management-context-path: /foo\n          health-path: /ping\n          user-name: user\n          user-password: password\n\n  profiles:\n    active:\n      - secure\n\n  boot:\n    admin:\n      discovery:\n        ignored-services: consul  # Don't monitor Consul itself\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n      base-path: /foo  # Custom actuator base path\n      path-mapping:\n        health: /ping  # Custom health endpoint path\n  endpoint:\n    health:\n      show-details: ALWAYS\n```\n\n:::warning Metadata Key Restriction\n**CRITICAL**: Consul metadata keys **cannot contain dots**. Use dashes instead:\n\n- ✅ `management-context-path`\n- ❌ `management.context-path`\n\nThis is a Consul limitation, not a Spring Boot Admin limitation.\n:::\n\n### Client Application Configuration\n\nFor applications to be monitored:\n\n```yaml\nspring:\n  application:\n    name: my-service\n\n  cloud:\n    consul:\n      host: localhost\n      port: 8500\n      discovery:\n        metadata:\n          management-context-path: /actuator  # Use dashes!\n          health-path: /actuator/health\n          # For secured actuators\n          user-name: ${actuator.username}\n          user-password: ${actuator.password}\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n```\n\n## Security Configuration\n\n### Insecure Profile\n\n```java\n@Profile(\"insecure\")\n@Configuration\npublic static class SecurityPermitAllConfig {\n\n    @Bean\n    protected SecurityFilterChain filterChain(HttpSecurity http)\n            throws Exception {\n        http.authorizeHttpRequests((authorizeRequests) ->\n                authorizeRequests.anyRequest().permitAll())\n            .csrf((csrf) -> csrf\n                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())\n                .ignoringRequestMatchers(\n                    adminContextPath + \"/instances\",\n                    adminContextPath + \"/instances/*\",\n                    adminContextPath + \"/actuator/**\"\n                ));\n        return http.build();\n    }\n}\n```\n\n### Secure Profile (Default)\n\n```java\n@Profile(\"secure\")\n@Configuration\npublic static class SecuritySecureConfig {\n\n    @Bean\n    protected SecurityFilterChain filterChain(HttpSecurity http)\n            throws Exception {\n        SavedRequestAwareAuthenticationSuccessHandler successHandler =\n            new SavedRequestAwareAuthenticationSuccessHandler();\n        successHandler.setTargetUrlParameter(\"redirectTo\");\n        successHandler.setDefaultTargetUrl(adminContextPath + \"/\");\n\n        http.authorizeHttpRequests((authorizeRequests) ->\n                authorizeRequests\n                    .requestMatchers(adminContextPath + \"/assets/**\")\n                        .permitAll()\n                    .requestMatchers(adminContextPath + \"/login\")\n                        .permitAll()\n                    .anyRequest()\n                        .authenticated())\n            .formLogin((formLogin) -> formLogin\n                .loginPage(adminContextPath + \"/login\")\n                .successHandler(successHandler))\n            .logout((logout) -> logout\n                .logoutUrl(adminContextPath + \"/logout\"))\n            .httpBasic(Customizer.withDefaults())\n            .csrf((csrf) -> csrf\n                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())\n                .ignoringRequestMatchers(\n                    adminContextPath + \"/instances\",\n                    adminContextPath + \"/instances/*\",\n                    adminContextPath + \"/actuator/**\"\n                ));\n\n        return http.build();\n    }\n}\n```\n\n## How It Works\n\n### Service Discovery Flow\n\n1. **Application Registration**:\n    - Application registers with Consul on startup\n    - Sends service metadata including actuator paths\n    - Consul assigns service ID and health checks\n\n2. **Health Checking**:\n    - Consul performs HTTP health checks\n    - Marks services as passing/failing\n    - Admin Server queries only healthy services\n\n3. **Admin Discovery**:\n    - Admin Server queries Consul for registered services\n    - Reads metadata to locate actuator endpoints\n    - Begins monitoring discovered services\n\n4. **Deregistration**:\n    - Application deregisters on shutdown\n    - Consul removes from registry\n    - Admin Server stops monitoring\n\n### Metadata Mapping\n\nAdmin Server reads specific metadata keys from Consul:\n\n```yaml\nspring:\n  cloud:\n    consul:\n      discovery:\n        metadata:\n          # Required for endpoint detection\n          management-context-path: /actuator  # Dashes only!\n          management-port: 8081  # If different\n\n          # Optional - for secured actuators\n          user-name: admin\n          user-password: ${ACTUATOR_PASSWORD}\n\n          # Custom metadata\n          environment: production\n          version: 1.0.0\n          team: platform\n```\n\n**Key Mappings**:\n\n- `management-context-path` → Where to find actuator endpoints\n- `management-port` → Management port if different from service port\n- `health-path` → Custom health endpoint path\n- `user-name` / `user-password` → Actuator credentials\n\n## Custom Actuator Paths\n\nThis sample demonstrates custom actuator paths:\n\n```yaml\nmanagement:\n  endpoints:\n    web:\n      base-path: /foo          # Actuator at /foo instead of /actuator\n      path-mapping:\n        health: /ping          # Health at /foo/ping instead of /foo/health\n```\n\nAdmin Server discovers these via metadata:\n\n```yaml\nspring:\n  cloud:\n    consul:\n      discovery:\n        metadata:\n          management-context-path: /foo\n          health-path: /ping\n```\n\n## Testing the Sample\n\n### Verify Consul Registration\n\n1. Access Consul UI: `http://localhost:8500/ui`\n2. Navigate to \"Services\"\n3. Should see:\n    - `consul-example` (Admin Server)\n    - Other registered services\n\n### Check Service Health\n\nIn Consul UI, services should show:\n\n- Status: Passing (green)\n- Health check URL displayed\n- Metadata visible\n\n### Verify Admin Discovery\n\n1. Access Admin UI: `http://localhost:8080`\n2. Should see services registered in Consul\n3. Click service to view:\n    - Health status\n    - Metrics\n    - Environment\n    - Custom /ping endpoint\n\n### Test Dynamic Discovery\n\nRegister a new service:\n\n```bash\n# Register via Consul API\ncurl -X PUT -d '{\n  \"Name\": \"test-service\",\n  \"Address\": \"127.0.0.1\",\n  \"Port\": 8081,\n  \"Meta\": {\n    \"management-context-path\": \"/actuator\"\n  },\n  \"Check\": {\n    \"HTTP\": \"http://127.0.0.1:8081/actuator/health\",\n    \"Interval\": \"10s\"\n  }\n}' http://localhost:8500/v1/agent/service/register\n```\n\nService appears in Admin UI within seconds.\n\n## Consul Features\n\n### Health Checks\n\nConsul supports multiple health check types:\n\n#### HTTP Health Check\n\n```yaml\nspring:\n  cloud:\n    consul:\n      discovery:\n        health-check-path: /actuator/health\n        health-check-interval: 10s\n        health-check-timeout: 5s\n```\n\n#### TTL Health Check\n\n```yaml\nspring:\n  cloud:\n    consul:\n      discovery:\n        health-check-ttl: 30s\n```\n\nApplication must send heartbeat to Consul every 30 seconds.\n\n### Service Tags\n\nAdd tags for filtering:\n\n```yaml\nspring:\n  cloud:\n    consul:\n      discovery:\n        tags:\n          - production\n          - backend\n          - v1.0.0\n```\n\n### Service Filtering\n\nFilter services monitored by Admin:\n\n```yaml\nspring:\n  boot:\n    admin:\n      discovery:\n        ignored-services:\n          - consul        # Don't monitor Consul\n          - config-server # Don't monitor Config Server\n        services:         # Only monitor these (if specified)\n          - my-service\n          - another-service\n```\n\n## Advanced Configuration\n\n### Consul ACL\n\nSecure Consul with ACL tokens:\n\n```yaml\nspring:\n  cloud:\n    consul:\n      token: ${CONSUL_TOKEN}\n      discovery:\n        acl-token: ${CONSUL_ACL_TOKEN}\n```\n\n### Consul TLS\n\nConnect to Consul over TLS:\n\n```yaml\nspring:\n  cloud:\n    consul:\n      scheme: https\n      tls:\n        enabled: true\n        key-store-path: classpath:consul-keystore.p12\n        key-store-password: ${KEYSTORE_PASSWORD}\n```\n\n### Multiple Datacenters\n\nRegister in specific datacenter:\n\n```yaml\nspring:\n  cloud:\n    consul:\n      discovery:\n        datacenter: dc1\n```\n\n### Prefer IP Address\n\nUse IP instead of hostname:\n\n```yaml\nspring:\n  cloud:\n    consul:\n      discovery:\n        prefer-ip-address: true\n        ip-address: 192.168.1.100\n```\n\n## Comparison: Consul vs. Eureka\n\n| Feature             | Consul                            | Eureka                        |\n|---------------------|-----------------------------------|-------------------------------|\n| **Health Checks**   | Built-in (HTTP, TCP, TTL, Script) | Via Spring Boot actuator only |\n| **Key-Value Store** | Yes                               | No                            |\n| **ACL**             | Yes                               | Basic                         |\n| **Multi-DC**        | Native support                    | Requires setup                |\n| **DNS Interface**   | Yes                               | No                            |\n| **Metadata Keys**   | No dots allowed                   | Dots allowed                  |\n| **Complexity**      | Higher                            | Lower                         |\n| **Ecosystem**       | HashiCorp ecosystem               | Netflix stack                 |\n\n## Troubleshooting\n\n### Metadata Key Errors\n\n**Symptom**: Admin Server can't find actuator endpoints\n\n**Cause**: Used dots in metadata keys\n\n**Solution**: Use dashes instead:\n\n```yaml\n# Wrong\nmetadata:\n  management.context-path: /actuator\n\n# Correct\nmetadata:\n  management-context-path: /actuator\n```\n\n### Services Not Discovered\n\n**Check Consul connectivity**:\n\n```bash\n# Test Consul API\ncurl http://localhost:8500/v1/catalog/services\n\n# Check health\ncurl http://localhost:8500/v1/health/state/passing\n```\n\n**Verify Admin logs**:\n\n```bash\ntail -f logs/spring-boot-admin.log | grep -i consul\n```\n\n### Health Check Failures\n\nServices show as \"failing\" in Consul:\n\n1. Verify health endpoint is accessible:\n   ```bash\n   curl http://localhost:8080/actuator/health\n   ```\n\n2. Check health check interval:\n   ```yaml\n   spring:\n     cloud:\n       consul:\n         discovery:\n           health-check-interval: 30s  # Increase if needed\n   ```\n\n3. Review Consul logs:\n   ```bash\n   consul monitor\n   ```\n\n### Connection Timeouts\n\nIncrease timeout values:\n\n```yaml\nspring:\n  cloud:\n    consul:\n      discovery:\n        health-check-timeout: 10s  # Increase from default\n```\n\n## Production Considerations\n\n### Consul Cluster\n\nRun Consul in cluster mode (3 or 5 nodes):\n\n```bash\n# Server node 1\nconsul agent -server -bootstrap-expect=3 -data-dir=/consul/data \\\n  -bind=192.168.1.10\n\n# Server node 2\nconsul agent -server -data-dir=/consul/data \\\n  -bind=192.168.1.11 -join=192.168.1.10\n\n# Server node 3\nconsul agent -server -data-dir=/consul/data \\\n  -bind=192.168.1.12 -join=192.168.1.10\n```\n\n### Enable ACL\n\n```yaml\nspring:\n  cloud:\n    consul:\n      token: ${CONSUL_MANAGEMENT_TOKEN}\n      discovery:\n        acl-token: ${CONSUL_SERVICE_TOKEN}\n```\n\n### Monitor Consul Health\n\nRegister Admin Server to monitor itself:\n\n```yaml\nspring:\n  boot:\n    admin:\n      discovery:\n        ignored-services: []  # Don't ignore any services\n```\n\n## Key Takeaways\n\nThis sample demonstrates:\n\n✅ **Consul Integration**\n\n- Service discovery via Consul\n- Health check integration\n\n✅ **Metadata Handling**\n\n- Proper metadata key formatting (dashes not dots)\n- Custom actuator paths\n\n✅ **Production Features**\n\n- ACL support\n- TLS encryption\n- Multi-datacenter awareness\n\n✅ **Flexibility**\n\n- Custom endpoint paths\n- Secure and insecure modes\n\n## Next Steps\n\n- Explore [Eureka Sample](./30-sample-eureka.md) for Netflix Eureka\n- Review [Zookeeper Sample](./50-sample-zookeeper.md) for Apache Zookeeper\n- Check [Consul Integration Guide](../04-integration/20-consul.md) for detailed setup\n- See [Hazelcast Sample](./60-sample-hazelcast.md) for clustering\n\n## See Also\n\n- [Consul Integration Guide](../04-integration/20-consul.md)\n- [Service Discovery](../03-client/40-service-discovery.md)\n- [Server Configuration](../02-server/01-server.mdx)\n- [HashiCorp Consul Documentation](https://www.consul.io/docs)\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/09-samples/50-sample-zookeeper.md",
    "content": "---\n\nsidebar_position: 50\nsidebar_custom_props:\n  icon: 'file-code'\n---\n\n# Zookeeper Sample\n\nSpring Boot Admin Server integration with Apache Zookeeper for service discovery. This sample shows how to use Zookeeper\nas a service registry to automatically discover and monitor Spring Boot applications.\n\n## Overview\n\n**Location**: `spring-boot-admin-samples/spring-boot-admin-sample-zookeeper/`\n\n**Features**:\n\n- Service discovery via Apache Zookeeper\n- No Admin Client required on monitored apps\n- Metadata-based configuration\n- Custom actuator paths (/foo, /ping)\n- Profile-based security\n\n## Prerequisites\n\n- Java 17+, Maven 3.6+\n- Apache Zookeeper installed\n\n## Running\n\n### Start Zookeeper\n\n```bash\n# Docker\ndocker run -d -p 2181:2181 zookeeper:3.8\n\n# Or download from https://zookeeper.apache.org/\n```\n\n### Start Admin Server\n\n```bash\ncd spring-boot-admin-samples/spring-boot-admin-sample-zookeeper\nmvn spring-boot:run\n```\n\nAccess at: `http://localhost:8080`\n\n## Configuration\n\n```yaml\nspring:\n  application:\n    name: zookeeper-example\n  cloud:\n    zookeeper:\n      connect-string: localhost:2181\n      discovery:\n        metadata:\n          management.context-path: /foo  # Dots allowed (unlike Consul)\n          health.path: /ping\n          user.name: user\n          user.password: password\n\nmanagement:\n  endpoints:\n    web:\n      base-path: /foo\n      path-mapping:\n        health: /ping\n```\n\n## Key Differences\n\n### vs. Consul\n\n- **Metadata keys**: Dots allowed in Zookeeper\n- **Simplicity**: Fewer features, simpler setup\n- **Use case**: Hadoop/Big Data ecosystems\n\n### vs. Eureka\n\n- **Maturity**: Zookeeper is older, more established\n- **Ecosystem**: Hadoop/Kafka integration\n- **Complexity**: More configuration required\n\n## See Also\n\n- [Zookeeper Integration](../04-integration/30-zookeeper.md)\n- [Eureka Sample](./30-sample-eureka.md)\n- [Consul Sample](./40-sample-consul.md)\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/09-samples/60-sample-hazelcast.md",
    "content": "---\n\nsidebar_position: 60\nsidebar_custom_props:\n  icon: 'file-code'\n---\n\n# Hazelcast Sample\n\nClustered Spring Boot Admin Server deployment using Hazelcast for distributed event storage. This sample demonstrates\nhigh-availability setup where multiple Admin Server instances share state via Hazelcast.\n\n## Overview\n\n**Location**: `spring-boot-admin-samples/spring-boot-admin-sample-hazelcast/`\n\n**Features**:\n\n- Distributed event store with Hazelcast\n- Multi-instance clustering\n- Shared notification state\n- High availability\n- TCP/IP cluster discovery\n- JMX monitoring enabled\n\n## Prerequisites\n\n- Java 17+, Maven 3.6+\n\n## Running Multiple Instances\n\n### Terminal 1 (Port 8080)\n\n```bash\ncd spring-boot-admin-samples/spring-boot-admin-sample-hazelcast\nmvn spring-boot:run\n```\n\n### Terminal 2 (Port 8081)\n\n```bash\nSERVER_PORT=8081 mvn spring-boot:run\n```\n\n### Terminal 3 (Port 8082)\n\n```bash\nSERVER_PORT=8082 mvn spring-boot:run\n```\n\nAll instances automatically form a cluster and share:\n\n- Instance events\n- Notification state\n- Application registry\n\n## Dependencies\n\n```xml\n<dependency>\n    <groupId>de.codecentric</groupId>\n    <artifactId>spring-boot-admin-starter-server</artifactId>\n</dependency>\n<dependency>\n    <groupId>de.codecentric</groupId>\n    <artifactId>spring-boot-admin-starter-client</artifactId>\n</dependency>\n<dependency>\n    <groupId>com.hazelcast</groupId>\n    <artifactId>hazelcast</artifactId>\n</dependency>\n```\n\n## Hazelcast Configuration\n\n```java\n@Bean\npublic Config hazelcastConfig() {\n    // Event store map - holds all instance events\n    MapConfig eventStoreMap = new MapConfig(DEFAULT_NAME_EVENT_STORE_MAP)\n        .setInMemoryFormat(InMemoryFormat.OBJECT)\n        .setBackupCount(1)  // 1 backup copy\n        .setMergePolicyConfig(\n            new MergePolicyConfig(PutIfAbsentMergePolicy.class.getName(), 100)\n        );\n\n    // Notification map - deduplicates notifications\n    MapConfig sentNotificationsMap = new MapConfig(DEFAULT_NAME_SENT_NOTIFICATIONS_MAP)\n        .setInMemoryFormat(InMemoryFormat.OBJECT)\n        .setBackupCount(1)\n        .setEvictionConfig(\n            new EvictionConfig()\n                .setEvictionPolicy(EvictionPolicy.LRU)\n                .setMaxSizePolicy(MaxSizePolicy.PER_NODE)\n        );\n\n    Config config = new Config();\n    config.addMapConfig(eventStoreMap);\n    config.addMapConfig(sentNotificationsMap);\n    config.setProperty(\"hazelcast.jmx\", \"true\");\n\n    // TCP/IP cluster discovery (local)\n    config.getNetworkConfig().getJoin().getMulticastConfig().setEnabled(false);\n    TcpIpConfig tcpIpConfig = config.getNetworkConfig()\n        .getJoin()\n        .getTcpIpConfig();\n    tcpIpConfig.setEnabled(true);\n    tcpIpConfig.setMembers(singletonList(\"127.0.0.1\"));\n\n    return config;\n}\n```\n\n**Configuration Details**:\n\n- **Event Store**: Reliable storage with 1 backup\n- **Sent Notifications**: LRU eviction to prevent memory growth\n- **Cluster**: TCP/IP discovery on localhost\n- **JMX**: Enabled for monitoring\n\n## How Clustering Works\n\n### Event Synchronization\n\n1. Instance A receives status change event\n2. Event stored in Hazelcast distributed map\n3. Instances B and C immediately see the event\n4. All instances update their local state\n\n### Notification Deduplication\n\n1. Instance A sends notification\n2. Records in Hazelcast sent-notifications map\n3. Instance B sees event, checks sent-notifications\n4. B skips sending (already sent by A)\n5. No duplicate notifications to users\n\n### Load Balancing\n\n```mermaid\ngraph TD\n    LB[Load Balancer] --> A[Admin 8080]\n    LB --> B[Admin 8081]\n    LB --> C[Admin 8082]\n    A -.-> HC[Hazelcast Cluster]\n    B -.-> HC\n    C -.-> HC\n```\n\nUsers can connect to any instance - they all show the same data.\n\n## Testing Clustering\n\n### Verify Cluster Formation\n\nCheck logs for:\n\n```\nMembers {size:3, ver:3} [\n    Member [127.0.0.1]:5701 - e1f2g3h4\n    Member [127.0.0.1]:5702 - a5b6c7d8\n    Member [127.0.0.1]:5703 - i9j0k1l2\n]\n```\n\n### Test Event Sharing\n\n1. Register application on instance 8080\n2. Check instance 8081 - application appears\n3. Stop instance 8080\n4. Application still visible on 8081\n\n### Test High Availability\n\n1. Start 3 instances\n2. Stop instance 8080\n3. Instances 8081 and 8082 continue operating\n4. All data preserved (1 backup)\n\n## Production Configuration\n\n### Multicast Discovery\n\n```java\nconfig.getNetworkConfig()\n    .getJoin()\n    .getMulticastConfig()\n    .setEnabled(true)\n    .setMulticastGroup(\"224.2.2.3\")\n    .setMulticastPort(54327);\n```\n\n### TCP/IP with Multiple Hosts\n\n```java\ntcpIpConfig.setMembers(Arrays.asList(\n    \"admin-1.company.com\",\n    \"admin-2.company.com\",\n    \"admin-3.company.com\"\n));\n```\n\n### Kubernetes Discovery\n\n```java\nconfig.getNetworkConfig()\n    .getJoin()\n    .getKubernetesConfig()\n    .setEnabled(true)\n    .setProperty(\"namespace\", \"default\")\n    .setProperty(\"service-name\", \"spring-boot-admin\");\n```\n\n## Monitoring Hazelcast\n\n### JMX\n\nEnable JMX and connect with JConsole:\n\n```bash\njconsole\n```\n\nLook for `com.hazelcast` MBeans.\n\n### Hazelcast Management Center\n\n```bash\ndocker run -p 8080:8080 hazelcast/management-center\n```\n\nConnect to: `http://localhost:8080`\n\n## Key Takeaways\n\n✅ **High Availability**: No single point of failure  \n✅ **Horizontal Scaling**: Add instances dynamically  \n✅ **Shared State**: All instances synchronized  \n✅ **Enterprise Features**: Backup, eviction, merge policies  \n\n## Next Steps\n\n- [Hazelcast Integration Guide](../04-integration/40-hazelcast.md)\n- [Servlet Sample](./10-sample-servlet.md)\n- [Custom UI Sample](./70-sample-custom-ui.md)\n\n## See Also\n\n- [Hazelcast Documentation](https://docs.hazelcast.com/)\n- [Clustering](../04-integration/40-hazelcast.md)\n- [Server Configuration](../02-server/01-server.mdx)\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/09-samples/70-sample-custom-ui.md",
    "content": "---\n\nsidebar_position: 70\nsidebar_custom_props:\n  icon: 'file-code'\n---\n\n# Custom UI Sample\n\nDemonstrates how to create custom UI extensions for Spring Boot Admin using Vue.js components. This sample shows how to\nadd custom views, menu items, and instance-specific endpoints to the Admin UI.\n\n## Overview\n\n**Location**: `spring-boot-admin-samples/spring-boot-admin-sample-custom-ui/`\n\n**Features**:\n\n- Custom top-level navigation views\n- Custom instance endpoint views\n- Submenu integration\n- Internationalization (i18n)\n- Custom icons and handles\n- Vue 3 components\n- Access to SBA global components\n- ApplicationStore integration\n\n## Prerequisites\n\n- Java 17+, Maven 3.6+\n- Node.js and npm (for building UI)\n\n## Project Structure\n\nThis is a **library module** that gets included by other samples (like servlet sample):\n\n```xml\n<!-- In servlet sample -->\n<dependency>\n    <groupId>de.codecentric</groupId>\n    <artifactId>spring-boot-admin-sample-custom-ui</artifactId>\n</dependency>\n```\n\n## Building\n\n```bash\ncd spring-boot-admin-samples/spring-boot-admin-sample-custom-ui\nmvn clean package\n```\n\n**Build Process**:\n\n1. Frontend Maven Plugin installs Node.js\n2. Runs `npm ci` to install dependencies\n3. Runs `npm run build` to compile Vue components\n4. Copies dist files to `META-INF/spring-boot-admin-server-ui/extensions/custom/`\n5. Admin Server auto-loads extensions from this location\n\n## Custom View Examples\n\n### 1. Top-Level View\n\n```javascript title=\"src/index.js\"\nSBA.use({\n  install({ viewRegistry, i18n }) {\n    viewRegistry.addView({\n      name: \"custom\",              // Unique view name\n      path: \"/custom\",             // URL path\n      component: custom,           // Vue component\n      group: \"custom\",             // Group for styling\n      handle,                      // Custom navigation handle\n      order: 1000,                 // Menu order\n    });\n\n    // Add translations\n    i18n.mergeLocaleMessage(\"en\", {\n      custom: {\n        label: \"My Extensions\",\n      },\n    });\n    i18n.mergeLocaleMessage(\"de\", {\n      custom: {\n        label: \"Meine Erweiterung\",\n      },\n    });\n  },\n});\n```\n\n### 2. Submenu Item\n\n```javascript\nSBA.viewRegistry.addView({\n  name: \"customSub\",\n  parent: \"custom\",          // Parent view name\n  path: \"/customSub\",\n  component: customSubitem,\n  label: \"Custom Sub\",\n  order: 1000,\n});\n```\n\n### 3. Instance Endpoint View\n\n```javascript\nSBA.viewRegistry.addView({\n  name: \"instances/custom\",\n  parent: \"instances\",       // Under instance views\n  path: \"custom\",\n  component: customEndpoint,\n  label: \"Custom\",\n  group: \"custom\",\n  order: 1000,\n  isEnabled: ({ instance }) => {\n    return instance.hasEndpoint(\"custom\");  // Conditional rendering\n  },\n});\n```\n\n### 4. Custom Group Icon\n\n```javascript\nSBA.viewRegistry.setGroupIcon(\n  \"custom\",\n  `<svg xmlns='http://www.w3.org/2000/svg' class='h-5 mr-3' viewBox='0 0 576 512'>\n    <path d='M512 80c8.8 0 16 7.2 16 16V416c0...'/>\n  </svg>`\n);\n```\n\n## Vue Component Example\n\n```vue title=\"src/custom.vue\"\n<template>\n  <div class=\"m-4\">\n    <template v-for=\"application in applications\" :key=\"application.name\">\n      <sba-panel :title=\"application.name\">\n        This application has the following instances:\n\n        <ul>\n          <template v-for=\"instance in application.instances\">\n            <li>\n              <span class=\"mx-1\" v-text=\"instance.registration.name\"></span>\n\n              <!-- SBA components are registered globally -->\n              <sba-status :status=\"instance.statusInfo.status\" class=\"mx-1\" />\n              <sba-tag :value=\"instance.id\" class=\"mx-1\" label=\"id\" />\n            </li>\n          </template>\n        </ul>\n      </sba-panel>\n    </template>\n  </div>\n</template>\n\n<script>\nexport default {\n  setup() {\n    const { applications } = SBA.useApplicationStore();  // Access store\n    return {\n      applications,\n    };\n  },\n};\n</script>\n```\n\n**Key Features**:\n\n- Access to `SBA.useApplicationStore()` for application data\n- Global SBA components (`sba-panel`, `sba-status`, `sba-tag`)\n- Reactive data from Vuex store\n- Tailwind CSS classes for styling\n\n## Available SBA Components\n\nGlobal components you can use without importing:\n\n- `<sba-panel>` - Card/panel container\n- `<sba-status>` - Status indicator (UP/DOWN/etc.)\n- `<sba-tag>` - Tag display\n- `<sba-icon>` - Icon component\n- `<sba-button>` - Button component\n- `<sba-input>` - Input field\n- `<sba-toggle>` - Toggle switch\n- `<sba-instance-selector>` - Instance dropdown\n- Many more in `spring-boot-admin-server-ui` module\n\n## Available Stores and APIs\n\n### ApplicationStore\n\n```javascript\nconst { applications } = SBA.useApplicationStore();\n\n// applications is reactive\n// Contains: { name, instances[], buildVersion, status, ... }\n```\n\n### Instance API\n\n```javascript\nconst instance = await SBA.getInstanceById(instanceId);\nconst health = await instance.fetchHealth();\nconst metrics = await instance.fetchMetrics();\nconst info = await instance.fetchInfo();\n```\n\n### Event Bus\n\n```javascript\nSBA.eventBus.on('event-name', (data) => {\n  // Handle event\n});\n\nSBA.eventBus.emit('custom-event', { foo: 'bar' });\n```\n\n## File Structure\n\n```\nspring-boot-admin-sample-custom-ui/\n├── src/\n│   ├── index.js              # Main entry point\n│   ├── custom.vue            # Top-level view component\n│   ├── custom-subitem.vue    # Submenu component\n│   ├── custom-endpoint.vue   # Instance endpoint component\n│   ├── handle.vue            # Navigation handle component\n│   └── custom.css            # Custom styles\n├── package.json\n├── vite.config.js\n└── pom.xml\n```\n\n## Build Configuration\n\n### package.json\n\n```json\n{\n  \"scripts\": {\n    \"build\": \"vite build\"\n  },\n  \"dependencies\": {\n    \"vue\": \"^3.x\"\n  }\n}\n```\n\n### vite.config.js\n\n```javascript\nexport default {\n  build: {\n    lib: {\n      entry: 'src/index.js',\n      formats: ['es'],\n      fileName: 'index'\n    },\n    rollupOptions: {\n      external: ['vue'],  // Vue provided by SBA\n      output: {\n        globals: {\n          vue: 'Vue'\n        }\n      }\n    }\n  }\n}\n```\n\n### pom.xml (Maven Integration)\n\n```xml\n<plugin>\n    <groupId>com.github.eirslett</groupId>\n    <artifactId>frontend-maven-plugin</artifactId>\n    <executions>\n        <execution>\n            <id>install-node-and-npm</id>\n            <goals>\n                <goal>install-node-and-npm</goal>\n            </goals>\n        </execution>\n        <execution>\n            <id>npm-build</id>\n            <goals>\n                <goal>npm</goal>\n            </goals>\n            <configuration>\n                <arguments>run build</arguments>\n            </configuration>\n        </execution>\n    </executions>\n</plugin>\n\n<plugin>\n    <groupId>org.apache.maven.plugins</groupId>\n    <artifactId>maven-resources-plugin</artifactId>\n    <executions>\n        <execution>\n            <id>copy-resources</id>\n            <phase>process-resources</phase>\n            <goals>\n                <goal>copy-resources</goal>\n            </goals>\n            <configuration>\n                <outputDirectory>\n                    ${project.build.outputDirectory}/META-INF/spring-boot-admin-server-ui/extensions/custom\n                </outputDirectory>\n                <resources>\n                    <resource>\n                        <directory>${project.build.directory}/dist</directory>\n                    </resource>\n                </resources>\n            </configuration>\n        </execution>\n    </executions>\n</plugin>\n```\n\n## Development Workflow\n\n### 1. Make Changes\n\nEdit Vue components in `src/`:\n\n```vue\n<template>\n  <div>My custom view</div>\n</template>\n```\n\n### 2. Build\n\n```bash\nmvn clean package\n```\n\n### 3. Use in Application\n\n```xml\n<dependency>\n    <groupId>de.codecentric</groupId>\n    <artifactId>spring-boot-admin-sample-custom-ui</artifactId>\n</dependency>\n```\n\n### 4. Run & Test\n\n```bash\nmvn spring-boot:run\n```\n\nNavigate to your custom view in the UI.\n\n## Common Use Cases\n\n### Custom Dashboard\n\n```javascript\nviewRegistry.addView({\n  name: \"dashboard\",\n  path: \"/dashboard\",\n  component: CustomDashboard,\n  label: \"Dashboard\",\n  order: 1,  // First in menu\n});\n```\n\n### Instance Action Button\n\n```vue\n<template>\n  <sba-button @click=\"restartInstance\">\n    Restart\n  </sba-button>\n</template>\n\n<script>\nexport default {\n  props: ['instance'],\n  methods: {\n    async restartInstance() {\n      await this.instance.restart();\n    }\n  }\n}\n</script>\n```\n\n### Custom Metrics View\n\n```vue\n<template>\n  <div>\n    <h2>CPU Usage: {{ cpuUsage }}%</h2>\n  </div>\n</template>\n\n<script>\nexport default {\n  props: ['instance'],\n  data() {\n    return { cpuUsage: 0 };\n  },\n  async mounted() {\n    const metrics = await this.instance.fetchMetrics();\n    this.cpuUsage = metrics['process.cpu.usage'] * 100;\n  }\n}\n</script>\n```\n\n## Key Takeaways\n\n✅ **Full Customization**: Add any Vue component to UI\n✅ **SBA Integration**: Access stores, components, APIs\n✅ **Maven Integration**: Build with Maven\n✅ **Reusable**: Package as library for multiple projects\n\n## Next Steps\n\n- [UI Customization Guide](../06-customization/ui/)\n- [Servlet Sample](./10-sample-servlet.md) (uses this extension)\n\n## See Also\n\n- [Vue.js Documentation](https://vuejs.org/)\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/09-samples/_category_.json",
    "content": "{\n  \"position\": 9,\n  \"label\": \"Samples\"\n}\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/09-samples/index.md",
    "content": "---\n\nsidebar_position: 70\nsidebar_custom_props:\n  icon: 'file-code'\n---\n\n# Sample Projects\n\nSpring Boot Admin includes several sample projects demonstrating different deployment scenarios and integration\npatterns. These samples provide working examples you can use as starting points for your own implementations.\n\n## Available Samples\n\n### Basic Deployments\n\n- **[Servlet Sample](./10-sample-servlet.md)** - Traditional servlet-based deployment with security\n- **[Reactive Sample](./20-sample-reactive.md)** - WebFlux reactive deployment\n\n### Service Discovery\n\n- **[Eureka Sample](./30-sample-eureka.md)** - Netflix Eureka integration\n- **[Consul Sample](./40-sample-consul.md)** - HashiCorp Consul integration\n- **[Zookeeper Sample](./50-sample-zookeeper.md)** - Apache Zookeeper integration\n\n### Advanced\n\n- **[Hazelcast Sample](./60-sample-hazelcast.md)** - Clustered deployment with Hazelcast\n- **[Custom UI Sample](./70-sample-custom-ui.md)** - Custom UI extensions and branding\n\n## Repository Location\n\nAll samples are available in\nthe [Spring Boot Admin GitHub repository](https://github.com/codecentric/spring-boot-admin/tree/master/spring-boot-admin-samples):\n\n```\nspring-boot-admin-samples/\n├── spring-boot-admin-sample-servlet/\n├── spring-boot-admin-sample-reactive/\n├── spring-boot-admin-sample-war/\n├── spring-boot-admin-sample-eureka/\n├── spring-boot-admin-sample-consul/\n├── spring-boot-admin-sample-zookeeper/\n├── spring-boot-admin-sample-hazelcast/\n└── spring-boot-admin-sample-custom-ui/\n```\n\n## Running the Samples\n\n### Prerequisites\n\n- Java 17 or higher\n- Maven 3.6 or higher\n- Docker (optional, for some samples)\n\n### Build All Samples\n\n```bash\ngit clone https://github.com/codecentric/spring-boot-admin.git\ncd spring-boot-admin\nmvn clean install -DskipTests\n```\n\n### Run Individual Sample\n\n```bash\ncd spring-boot-admin-samples/spring-boot-admin-sample-servlet\nmvn spring-boot:run\n```\n\nAccess the Admin UI at: `http://localhost:8080`\n\n### Default Credentials\n\nMost secured samples use:\n\n- **Username**: `user`\n- **Password**: Check console output or `application.yml`\n\n## Sample Features Comparison\n\n| Feature           | Servlet | Reactive | Eureka  | Consul  | Zookeeper | Hazelcast | Custom UI | WAR     |\n|-------------------|---------|----------|---------|---------|-----------|-----------|-----------|---------|\n| Web Stack         | Servlet | WebFlux  | Servlet | Servlet | Servlet   | Servlet   | Servlet   | Servlet |\n| Security          | ✅       | ✅        | ✅       | ✅       | -         | -         | -         | -       |\n| Service Discovery | Static  | Static   | Eureka  | Consul  | Zookeeper | Static    | Static    | Static  |\n| Clustering        | -       | -        | -       | -       | -         | ✅         | -         | -       |\n| Custom UI         | -       | -        | -       | -       | -         | -         | ✅         | -       |\n| JMX Support       | ✅       | -        | -       | -       | -         | -         | -         | ✅       |\n| Notifications     | ✅       | -        | -       | -       | -         | -         | -         | -       |\n\n## Common Configuration\n\nAll samples share common patterns:\n\n### Actuator Configuration\n\n```yaml\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n  endpoint:\n    health:\n      show-details: ALWAYS\n```\n\n### Logging Configuration\n\n```yaml\nlogging:\n  file:\n    name: \"target/boot-admin-sample.log\"\n  pattern:\n    file: \"%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID}){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx\"\n```\n\n### Build Info\n\nAll samples generate build information:\n\n```xml\n<plugin>\n    <groupId>org.springframework.boot</groupId>\n    <artifactId>spring-boot-maven-plugin</artifactId>\n    <executions>\n        <execution>\n            <goals>\n                <goal>build-info</goal>\n            </goals>\n        </execution>\n    </executions>\n</plugin>\n```\n\n## Quick Start Guide\n\n### 1. Servlet Sample (Recommended for Beginners)\n\n```bash\ncd spring-boot-admin-samples/spring-boot-admin-sample-servlet\nmvn spring-boot:run\n```\n\nFeatures:\n\n- Security enabled\n- Self-monitoring\n- Mail notifications\n- Custom UI extensions\n\n### 2. Eureka Sample (Recommended for Microservices)\n\n```bash\n# Start Eureka Server\ndocker run -d -p 8761:8761 springcloud/eureka\n\n# Start Admin Server\ncd spring-boot-admin-samples/spring-boot-admin-sample-eureka\nmvn spring-boot:run\n```\n\nFeatures:\n\n- Automatic service discovery\n- Dynamic registration\n- No client library needed\n\n### 3. Hazelcast Sample (Recommended for Production)\n\n```bash\n# Start multiple instances\ncd spring-boot-admin-samples/spring-boot-admin-sample-hazelcast\n\n# Terminal 1\nSERVER_PORT=8080 mvn spring-boot:run\n\n# Terminal 2\nSERVER_PORT=8081 mvn spring-boot:run\n```\n\nFeatures:\n\n- High availability\n- Shared event store\n- Load balancing ready\n\n## Docker Support\n\nSome samples include Docker Compose configurations:\n\n```bash\ncd spring-boot-admin-samples/spring-boot-admin-sample-eureka\ndocker-compose up\n```\n\n## Customizing Samples\n\nUse the samples as templates:\n\n1. **Copy sample directory**:\n   ```bash\n   cp -r spring-boot-admin-sample-servlet my-admin-server\n   ```\n\n2. **Update `pom.xml`**:\n   ```xml\n   <artifactId>my-admin-server</artifactId>\n   <name>My Admin Server</name>\n   ```\n\n3. **Customize configuration**:\n    - Update `application.yml`\n    - Add security configuration\n    - Configure notifications\n\n4. **Build and run**:\n   ```bash\n   mvn clean package\n   java -jar target/my-admin-server.jar\n   ```\n\n## Testing Samples\n\nEach sample includes tests:\n\n```bash\ncd spring-boot-admin-samples/spring-boot-admin-sample-servlet\nmvn test\n```\n\n## Troubleshooting Samples\n\n### Port Already in Use\n\nChange the port:\n\n```bash\nSERVER_PORT=9090 mvn spring-boot:run\n```\n\nOr in `application.yml`:\n\n```yaml\nserver:\n  port: 9090\n```\n\n### Build Failures\n\nClean and rebuild:\n\n```bash\nmvn clean install -DskipTests\n```\n\n### Dependencies Issues\n\nUpdate Spring Boot Admin version in parent POM and rebuild.\n\n## Contributing\n\nTo add a new sample:\n\n1. Create directory under `spring-boot-admin-samples/`\n2. Follow existing sample structure\n3. Add `README.md` with specific instructions\n4. Include `docker-compose.yml` if applicable\n5. Add tests\n6. Update samples documentation\n\n## See Also\n\n- [Getting Started](../02-getting-started/) - Basic setup guide\n- [Server Configuration](../02-server/01-server.mdx) - Server configuration options\n- [Integration](../04-integration/) - Service discovery integration\n- [Customization](../06-customization/) - UI and server customization\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/10-reference/10-event-types.md",
    "content": "---\n\nsidebar_position: 10\nsidebar_custom_props:\n  icon: 'book'\n---\n\n# Event Types Reference\n\nComplete reference of all `InstanceEvent` types emitted by Spring Boot Admin Server during instance lifecycle.\n\n## Event Base Class\n\nAll events extend `InstanceEvent`:\n\n```java\npublic abstract class InstanceEvent implements Serializable {\n\tprivate final InstanceId instance;     // Unique instance identifier\n\tprivate final long version;            // Event version (incremental)\n\tprivate final Instant timestamp;       // When event occurred\n\tprivate final String type;             // Event type constant\n}\n```\n\n**Common Properties**:\n\n- `instance`: Unique ID of the instance (e.g., `\"abc123def456\"`)\n- `version`: Monotonically increasing version number\n- `timestamp`: ISO-8601 timestamp when event was created\n- `type`: String constant identifying the event type\n\n## Event Lifecycle\n\nTypical event sequence for an instance:\n\n```\n1. REGISTERED           → Instance first registers\n2. ENDPOINTS_DETECTED   → Actuator endpoints discovered\n3. STATUS_CHANGED       → Health status updated to UP\n4. INFO_CHANGED         → Info endpoint data loaded\n5. STATUS_CHANGED       → Status changes during lifecycle\n6. REGISTRATION_UPDATED → Registration info changes (optional)\n7. DEREGISTERED         → Instance unregisters\n```\n\n## Event Types\n\n### 1. REGISTERED\n\n**Class**: `InstanceRegisteredEvent`\n\n**Type Constant**: `\"REGISTERED\"`\n\n**When Emitted**: Instance first registers with Admin Server\n\n**Payload**:\n\n```java\npublic class InstanceRegisteredEvent extends InstanceEvent {\n\tRegistration registration;  // Complete registration info\n}\n```\n\n**Registration Contents**:\n\n- `name`: Application name\n- `managementUrl`: Actuator base URL\n- `healthUrl`: Health endpoint URL\n- `serviceUrl`: Application base URL\n- `source`: Registration source (e.g., \"http-api\", \"discovery\")\n- `metadata`: Custom metadata map\n\n**Example**:\n\n```json\n{\n  \"instance\": \"abc123def456\",\n  \"version\": 0,\n  \"timestamp\": \"2026-02-07T10:00:00Z\",\n  \"type\": \"REGISTERED\",\n  \"registration\": {\n    \"name\": \"my-service\",\n    \"managementUrl\": \"http://localhost:8080/actuator\",\n    \"healthUrl\": \"http://localhost:8080/actuator/health\",\n    \"serviceUrl\": \"http://localhost:8080\",\n    \"source\": \"http-api\",\n    \"metadata\": {\n      \"startup\": \"2026-02-07T09:59:55Z\"\n    }\n  }\n}\n```\n\n**Use Cases**:\n\n- Send welcome notifications\n- Initialize instance-specific monitoring\n- Log new instance registrations\n- Trigger discovery of endpoints\n\n**Example Listener**:\n\n```java\n\n@Component\npublic class RegistrationListener {\n\n\t@EventListener\n\tpublic void onInstanceRegistered(InstanceRegisteredEvent event) {\n\t\tlog.info(\"New instance registered: {} at {}\",\n\t\t\t\tevent.getRegistration().getName(),\n\t\t\t\tevent.getRegistration().getServiceUrl());\n\t}\n}\n```\n\n---\n\n### 2. REGISTRATION_UPDATED\n\n**Class**: `InstanceRegistrationUpdatedEvent`\n\n**Type Constant**: `\"REGISTRATION_UPDATED\"`\n\n**When Emitted**: Instance updates its registration (URL, metadata, etc.)\n\n**Payload**:\n\n```java\npublic class InstanceRegistrationUpdatedEvent extends InstanceEvent {\n\tRegistration registration;  // Updated registration info\n}\n```\n\n**Common Triggers**:\n\n- Instance IP address changes\n- Management port changes\n- Metadata updates\n- Health URL changes\n\n**Example**:\n\n```json\n{\n  \"instance\": \"abc123def456\",\n  \"version\": 5,\n  \"timestamp\": \"2026-02-07T11:00:00Z\",\n  \"type\": \"REGISTRATION_UPDATED\",\n  \"registration\": {\n    \"name\": \"my-service\",\n    \"managementUrl\": \"http://192.168.1.100:8080/actuator\",\n    \"healthUrl\": \"http://192.168.1.100:8080/actuator/health\",\n    \"serviceUrl\": \"http://192.168.1.100:8080\",\n    \"metadata\": {\n      \"version\": \"2.0.0\"\n    }\n  }\n}\n```\n\n**Use Cases**:\n\n- Detect instance migrations\n- Update monitoring endpoints\n- Track configuration changes\n- Trigger re-discovery of endpoints\n\n---\n\n### 3. DEREGISTERED\n\n**Class**: `InstanceDeregisteredEvent`\n\n**Type Constant**: `\"DEREGISTERED\"`\n\n**When Emitted**: Instance unregisters (shutdown, explicit deregistration)\n\n**Payload**:\n\n```java\npublic class InstanceDeregisteredEvent extends InstanceEvent {\n\t// No additional fields - just base InstanceEvent fields\n}\n```\n\n**Example**:\n\n```json\n{\n  \"instance\": \"abc123def456\",\n  \"version\": 10,\n  \"timestamp\": \"2026-02-07T12:00:00Z\",\n  \"type\": \"DEREGISTERED\"\n}\n```\n\n**Use Cases**:\n\n- Send shutdown notifications\n- Clean up instance-specific resources\n- Log instance lifecycle\n- Trigger alerts for unexpected shutdowns\n\n**Example Listener**:\n\n```java\n\n@EventListener\npublic void onInstanceDeregistered(InstanceDeregisteredEvent event) {\n\tInstant timestamp = event.getTimestamp();\n\tlong version = event.getVersion();\n\n\tlog.info(\"Instance {} deregistered after {} events\",\n\t\t\tevent.getInstance(), version);\n\n\t// Cleanup resources\n\tcleanupResourcesFor(event.getInstance());\n}\n```\n\n---\n\n### 4. STATUS_CHANGED\n\n**Class**: `InstanceStatusChangedEvent`\n\n**Type Constant**: `\"STATUS_CHANGED\"`\n\n**When Emitted**: Instance health status changes\n\n**Payload**:\n\n```java\npublic class InstanceStatusChangedEvent extends InstanceEvent {\n\tStatusInfo statusInfo;  // Current status information\n}\n```\n\n**StatusInfo Contents**:\n\n- `status`: Current status (`UP`, `DOWN`, `OUT_OF_SERVICE`, `UNKNOWN`, `OFFLINE`)\n- `details`: Map of health details from actuator\n\n**Status Values**:\n\n- `UP`: Application is healthy\n- `DOWN`: Application is unhealthy\n- `OUT_OF_SERVICE`: Temporarily unavailable\n- `UNKNOWN`: Status cannot be determined\n- `OFFLINE`: Instance not responding\n- `RESTRICTED`: Custom status\n\n**Example**:\n\n```json\n{\n  \"instance\": \"abc123def456\",\n  \"version\": 3,\n  \"timestamp\": \"2026-02-07T10:05:00Z\",\n  \"type\": \"STATUS_CHANGED\",\n  \"statusInfo\": {\n    \"status\": \"UP\",\n    \"details\": {\n      \"diskSpace\": {\n        \"status\": \"UP\",\n        \"total\": 500000000000,\n        \"free\": 250000000000\n      },\n      \"db\": {\n        \"status\": \"UP\",\n        \"database\": \"PostgreSQL\",\n        \"validationQuery\": \"isValid()\"\n      }\n    }\n  }\n}\n```\n\n**Use Cases**:\n\n- Send alerts on status changes (UP → DOWN)\n- Track uptime/downtime\n- Trigger automated recovery\n- Update dashboards\n\n**Example Listener**:\n\n```java\n\n@EventListener\npublic void onStatusChanged(InstanceStatusChangedEvent event) {\n\tStatusInfo statusInfo = event.getStatusInfo();\n\tString status = statusInfo.getStatus();\n\n\tif (\"DOWN\".equals(status)) {\n\t\talertService.sendAlert(\n\t\t\t\t\"Instance \" + event.getInstance() + \" is DOWN\",\n\t\t\t\tstatusInfo.getDetails()\n\t\t);\n\t}\n}\n```\n\n---\n\n### 5. ENDPOINTS_DETECTED\n\n**Class**: `InstanceEndpointsDetectedEvent`\n\n**Type Constant**: `\"ENDPOINTS_DETECTED\"`\n\n**When Emitted**: Actuator endpoints are discovered\n\n**Payload**:\n\n```java\npublic class InstanceEndpointsDetectedEvent extends InstanceEvent {\n\tEndpoints endpoints;  // Discovered endpoints\n}\n```\n\n**Endpoints Contents**:\nList of `Endpoint` objects, each containing:\n\n- `id`: Endpoint ID (e.g., \"health\", \"metrics\", \"env\")\n- `url`: Full endpoint URL\n\n**Example**:\n\n```json\n{\n  \"instance\": \"abc123def456\",\n  \"version\": 1,\n  \"timestamp\": \"2026-02-07T10:00:05Z\",\n  \"type\": \"ENDPOINTS_DETECTED\",\n  \"endpoints\": [\n    {\n      \"id\": \"health\",\n      \"url\": \"http://localhost:8080/actuator/health\"\n    },\n    {\n      \"id\": \"metrics\",\n      \"url\": \"http://localhost:8080/actuator/metrics\"\n    },\n    {\n      \"id\": \"env\",\n      \"url\": \"http://localhost:8080/actuator/env\"\n    },\n    {\n      \"id\": \"loggers\",\n      \"url\": \"http://localhost:8080/actuator/loggers\"\n    }\n  ]\n}\n```\n\n**Use Cases**:\n\n- Enable/disable UI views based on available endpoints\n- Start monitoring specific endpoints\n- Validate expected endpoints are present\n- Trigger custom endpoint polling\n\n**Example Listener**:\n\n```java\n\n@EventListener\npublic void onEndpointsDetected(InstanceEndpointsDetectedEvent event) {\n\tEndpoints endpoints = event.getEndpoints();\n\n\tboolean hasMetrics = endpoints.get(\"metrics\").isPresent();\n\tboolean hasLoggers = endpoints.get(\"loggers\").isPresent();\n\n\tif (hasMetrics && hasLoggers) {\n\t\t// Enable advanced monitoring\n\t\tadvancedMonitoring.enable(event.getInstance());\n\t}\n}\n```\n\n---\n\n### 6. INFO_CHANGED\n\n**Class**: `InstanceInfoChangedEvent`\n\n**Type Constant**: `\"INFO_CHANGED\"`\n\n**When Emitted**: Info endpoint data changes\n\n**Payload**:\n\n```java\npublic class InstanceInfoChangedEvent extends InstanceEvent {\n\tInfo info;  // Info endpoint data\n}\n```\n\n**Info Contents**:\nMap of arbitrary info data from `/actuator/info`, commonly including:\n\n- `build`: Build information (version, time, artifact)\n- `git`: Git information (commit, branch, time)\n- Custom application metadata\n\n**Example**:\n\n```json\n{\n  \"instance\": \"abc123def456\",\n  \"version\": 2,\n  \"timestamp\": \"2026-02-07T10:00:10Z\",\n  \"type\": \"INFO_CHANGED\",\n  \"info\": {\n    \"build\": {\n      \"version\": \"1.0.0\",\n      \"artifact\": \"my-service\",\n      \"name\": \"my-service\",\n      \"time\": \"2026-02-07T09:00:00Z\"\n    },\n    \"git\": {\n      \"branch\": \"main\",\n      \"commit\": {\n        \"id\": \"abc123\",\n        \"time\": \"2026-02-06T15:30:00Z\"\n      }\n    },\n    \"custom\": {\n      \"team\": \"Platform\",\n      \"environment\": \"production\"\n    }\n  }\n}\n```\n\n**Use Cases**:\n\n- Track deployed versions\n- Display build information in UI\n- Verify correct version deployed\n- Trigger version-specific logic\n\n---\n\n## Event Ordering\n\nEvents are ordered by `version` number, which is monotonically increasing per instance:\n\n```\nversion 0: REGISTERED\nversion 1: ENDPOINTS_DETECTED\nversion 2: STATUS_CHANGED (to UP)\nversion 3: INFO_CHANGED\nversion 4: STATUS_CHANGED (to DOWN)\nversion 5: STATUS_CHANGED (to UP)\nversion 6: DEREGISTERED\n```\n\n**Important**: Version numbers are unique per instance and always increase.\n\n## Event Persistence\n\nEvents are stored in the `InstanceEventStore`:\n\n- **InMemoryEventStore**: Non-persistent, lost on restart\n- **HazelcastEventStore**: Distributed, persisted across cluster\n\n**Event Compaction**: Old events are compacted to prevent unlimited growth:\n\n```yaml\nspring:\n  boot:\n    admin:\n      event-store:\n        max-log-size-per-aggregate: 100  # Keep last 100 events per instance\n```\n\n## Listening to Events\n\n### Spring Event Listener\n\n```java\n\n@Component\npublic class MyEventListener {\n\n\t@EventListener\n\tpublic void onAnyInstanceEvent(InstanceEvent event) {\n\t\tlog.info(\"Event: {} for instance {} at version {}\",\n\t\t\t\tevent.getType(), event.getInstance(), event.getVersion());\n\t}\n\n\t@EventListener\n\tpublic void onSpecificEvent(InstanceStatusChangedEvent event) {\n\t\t// Handle specific event type\n\t}\n}\n```\n\n### Custom Notifier\n\n```java\n\n@Component\npublic class CustomNotifier extends AbstractEventNotifier {\n\n\tpublic CustomNotifier(InstanceRepository repository) {\n\t\tsuper(repository);\n\t}\n\n\t@Override\n\tprotected Mono<Void> doNotify(InstanceEvent event, Instance instance) {\n\t\treturn Mono.fromRunnable(() -> {\n\t\t\tswitch (event.getType()) {\n\t\t\t\tcase \"STATUS_CHANGED\":\n\t\t\t\t\thandleStatusChange((InstanceStatusChangedEvent) event);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"REGISTERED\":\n\t\t\t\t\thandleRegistration((InstanceRegisteredEvent) event);\n\t\t\t\t\tbreak;\n\t\t\t\t// Handle other events\n\t\t\t}\n\t\t});\n\t}\n}\n```\n\n### Event Stream (SSE)\n\nSubscribe to event stream via REST API:\n\n```bash\ncurl -N http://localhost:8080/instances/events\n```\n\nReturns Server-Sent Events (SSE) stream:\n\n```\ndata:{\"instance\":\"abc123\",\"version\":0,\"type\":\"REGISTERED\",...}\n\ndata:{\"instance\":\"abc123\",\"version\":1,\"type\":\"ENDPOINTS_DETECTED\",...}\n\ndata:{\"instance\":\"abc123\",\"version\":2,\"type\":\"STATUS_CHANGED\",...}\n```\n\n## Event Filtering\n\nFilter events by type using `FilteringNotifier`:\n\n```java\n\n@Bean\npublic FilteringNotifier filteringNotifier(Notifier delegate,\n\t\t\t\t\t\t\t\t\t\t   InstanceRepository repository) {\n\tFilteringNotifier notifier = new FilteringNotifier(delegate, repository);\n\tnotifier.setFilterExpression(\"!(type == 'INFO_CHANGED')\");  // Exclude INFO_CHANGED\n\treturn notifier;\n}\n```\n\n**Filter Expression Language**: SpEL (Spring Expression Language)\n\n**Available Variables**:\n\n- `type`: Event type string\n- `instance`: Instance ID\n- `version`: Event version\n- Event-specific fields (e.g., `statusInfo.status` for STATUS_CHANGED)\n\n**Examples**:\n\n```java\n// Only DOWN events\n\"type == 'STATUS_CHANGED' && statusInfo.status == 'DOWN'\"\n\n// Exclude INFO_CHANGED and ENDPOINTS_DETECTED\n\t\t\"!(type == 'INFO_CHANGED' || type == 'ENDPOINTS_DETECTED')\"\n\n// Only production instances (via metadata)\n\t\t\"metadata['environment'] == 'production'\"\n```\n\n## Event Reminders\n\nUse `RemindingNotifier` to send periodic reminders:\n\n```java\n\n@Bean\npublic RemindingNotifier remindingNotifier(Notifier delegate,\n\t\t\t\t\t\t\t\t\t\t   InstanceRepository repository) {\n\tRemindingNotifier notifier = new RemindingNotifier(delegate, repository);\n\tnotifier.setReminderPeriod(Duration.ofMinutes(10));\n\tnotifier.setCheckReminderInverval(Duration.ofSeconds(60));\n\treturn notifier;\n}\n```\n\nSends reminders for instances still in non-UP status after configured period.\n\n## See Also\n\n- [Custom Notifiers](../02-server/notifications/90-custom-notifiers.md)\n- [Instance Registry](../02-server/40-instance-registry.md)\n- [REST API](./20-rest-api.md)\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/10-reference/20-rest-api.md",
    "content": "---\n\nsidebar_position: 20\nsidebar_custom_props:\n  icon: 'book'\n---\n\n# REST API Reference\n\nComplete HTTP API reference for Spring Boot Admin Server.\n\n## Base URL\n\nDefault: `http://localhost:8080`\n\nWith custom context path:\n\n```yaml\nspring:\n  boot:\n    admin:\n      context-path: /admin\n```\n\nBase URL becomes: `http://localhost:8080/admin`\n\n## Content Types\n\n- **Request**: `application/json`\n- **Response**: `application/json` or `application/hal+json`\n- **Streaming**: `text/event-stream` (Server-Sent Events)\n\n## Authentication\n\nIf Spring Security is enabled, all endpoints require authentication:\n\n```bash\ncurl -u user:password http://localhost:8080/instances\n```\n\nOr use token-based authentication as configured in your security setup.\n\n## Instances API\n\n### Register Instance\n\nRegister a new instance with the Admin Server.\n\n**Endpoint**: `POST /instances`\n\n**Request Body**:\n\n```json\n{\n  \"name\": \"my-service\",\n  \"managementUrl\": \"http://localhost:8081/actuator\",\n  \"healthUrl\": \"http://localhost:8081/actuator/health\",\n  \"serviceUrl\": \"http://localhost:8081\",\n  \"metadata\": {\n    \"startup\": \"2026-02-07T10:00:00Z\",\n    \"tags\": {\n      \"environment\": \"production\"\n    }\n  }\n}\n```\n\n**Response**: `201 Created`\n\n```json\n{\n  \"id\": \"abc123def456\"\n}\n```\n\n**Headers**:\n\n- `Location`: `/instances/abc123def456`\n\n**Example**:\n\n```bash\ncurl -X POST http://localhost:8080/instances \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"name\": \"my-service\",\n    \"managementUrl\": \"http://localhost:8081/actuator\",\n    \"healthUrl\": \"http://localhost:8081/actuator/health\",\n    \"serviceUrl\": \"http://localhost:8081\"\n  }'\n```\n\n---\n\n### List All Instances\n\nGet all registered instances.\n\n**Endpoint**: `GET /instances`\n\n**Response**: `200 OK`\n\n```json\n[\n  {\n    \"id\": \"abc123def456\",\n    \"version\": 5,\n    \"registration\": {\n      \"name\": \"my-service\",\n      \"managementUrl\": \"http://localhost:8081/actuator\",\n      \"healthUrl\": \"http://localhost:8081/actuator/health\",\n      \"serviceUrl\": \"http://localhost:8081\",\n      \"source\": \"http-api\",\n      \"metadata\": {}\n    },\n    \"registered\": true,\n    \"statusInfo\": {\n      \"status\": \"UP\",\n      \"details\": {}\n    },\n    \"statusTimestamp\": \"2026-02-07T10:05:00Z\",\n    \"info\": {},\n    \"endpoints\": [\n      {\n        \"id\": \"health\",\n        \"url\": \"http://localhost:8081/actuator/health\"\n      },\n      {\n        \"id\": \"metrics\",\n        \"url\": \"http://localhost:8081/actuator/metrics\"\n      }\n    ],\n    \"buildVersion\": \"1.0.0\",\n    \"tags\": {\n      \"environment\": \"production\"\n    }\n  }\n]\n```\n\n**Example**:\n\n```bash\ncurl http://localhost:8080/instances\n```\n\n---\n\n### List Instances by Name\n\nGet all instances with a specific name.\n\n**Endpoint**: `GET /instances?name={name}`\n\n**Parameters**:\n\n- `name` (required): Application name\n\n**Response**: `200 OK`\n\nSame as List All Instances, but filtered.\n\n**Example**:\n\n```bash\ncurl http://localhost:8080/instances?name=my-service\n```\n\n---\n\n### Get Single Instance\n\nGet details of a specific instance.\n\n**Endpoint**: `GET /instances/{id}`\n\n**Parameters**:\n\n- `id` (path): Instance ID\n\n**Response**: `200 OK`\n\n```json\n{\n  \"id\": \"abc123def456\",\n  \"version\": 5,\n  \"registration\": {\n    \"name\": \"my-service\",\n    \"managementUrl\": \"http://localhost:8081/actuator\",\n    \"healthUrl\": \"http://localhost:8081/actuator/health\",\n    \"serviceUrl\": \"http://localhost:8081\"\n  },\n  \"registered\": true,\n  \"statusInfo\": {\n    \"status\": \"UP\"\n  },\n  \"endpoints\": [...]\n}\n```\n\n**Example**:\n\n```bash\ncurl http://localhost:8080/instances/abc123def456\n```\n\n---\n\n### Deregister Instance\n\nRemove an instance from the registry.\n\n**Endpoint**: `DELETE /instances/{id}`\n\n**Parameters**:\n\n- `id` (path): Instance ID\n\n**Response**: `204 No Content`\n\n**Example**:\n\n```bash\ncurl -X DELETE http://localhost:8080/instances/abc123def456\n```\n\n---\n\n### Instance Event Stream\n\nSubscribe to real-time instance events via Server-Sent Events.\n\n**Endpoint**: `GET /instances/events`\n\n**Response**: `200 OK` (streaming)\n\n**Content-Type**: `text/event-stream`\n\n**Event Format**:\n\n```\ndata:{\"instance\":\"abc123\",\"version\":0,\"type\":\"REGISTERED\",\"timestamp\":\"2026-02-07T10:00:00Z\",\"registration\":{...}}\n\ndata:{\"instance\":\"abc123\",\"version\":1,\"type\":\"ENDPOINTS_DETECTED\",\"timestamp\":\"2026-02-07T10:00:05Z\",\"endpoints\":[...]}\n\ndata:{\"instance\":\"abc123\",\"version\":2,\"type\":\"STATUS_CHANGED\",\"timestamp\":\"2026-02-07T10:00:10Z\",\"statusInfo\":{\"status\":\"UP\"}}\n```\n\n**Example**:\n\n```bash\ncurl -N http://localhost:8080/instances/events\n```\n\n**JavaScript Client**:\n\n```javascript\nconst eventSource = new EventSource('http://localhost:8080/instances/events');\n\neventSource.onmessage = (event) => {\n  const instanceEvent = JSON.parse(event.data);\n  console.log('Event:', instanceEvent.type, 'for', instanceEvent.instance);\n};\n```\n\n**Heartbeat**: Ping comments sent every 10 seconds to keep connection alive.\n\n---\n\n### Instance Event Stream (Single Instance)\n\nSubscribe to events for a specific instance.\n\n**Endpoint**: `GET /instances/{id}/events`\n\n**Parameters**:\n\n- `id` (path): Instance ID\n\n**Response**: Same as `/instances/events`, but filtered to single instance.\n\n**Example**:\n\n```bash\ncurl -N http://localhost:8080/instances/abc123def456/events\n```\n\n---\n\n## Applications API\n\nApplications represent logical groups of instances with the same name.\n\n### List All Applications\n\nGet all applications (grouped instances).\n\n**Endpoint**: `GET /applications`\n\n**Response**: `200 OK`\n\n```json\n[\n  {\n    \"name\": \"my-service\",\n    \"buildVersion\": \"1.0.0\",\n    \"status\": \"UP\",\n    \"instances\": [\n      {\n        \"id\": \"abc123\",\n        \"healthUrl\": \"http://instance1:8081/actuator/health\",\n        \"statusInfo\": {\"status\": \"UP\"}\n      },\n      {\n        \"id\": \"def456\",\n        \"healthUrl\": \"http://instance2:8081/actuator/health\",\n        \"statusInfo\": {\"status\": \"UP\"}\n      }\n    ]\n  }\n]\n```\n\n**Example**:\n\n```bash\ncurl http://localhost:8080/applications\n```\n\n---\n\n### Get Single Application\n\nGet details of a specific application.\n\n**Endpoint**: `GET /applications/{name}`\n\n**Parameters**:\n\n- `name` (path): Application name\n\n**Response**: `200 OK`\n\n```json\n{\n  \"name\": \"my-service\",\n  \"buildVersion\": \"1.0.0\",\n  \"status\": \"UP\",\n  \"instances\": [...]\n}\n```\n\n**Response**: `404 Not Found` if application doesn't exist.\n\n**Example**:\n\n```bash\ncurl http://localhost:8080/applications/my-service\n```\n\n---\n\n### Application Event Stream\n\nSubscribe to application-level events.\n\n**Endpoint**: `GET /applications/events`\n\n**Response**: `200 OK` (streaming)\n\n**Content-Type**: `text/event-stream`\n\n**Example**:\n\n```bash\ncurl -N http://localhost:8080/applications/events\n```\n\n---\n\n### Refresh Applications\n\nTrigger manual refresh of all instances from service discovery.\n\n**Endpoint**: `POST /applications`\n\n**Response**: `200 OK`\n\n**Example**:\n\n```bash\ncurl -X POST http://localhost:8080/applications\n```\n\n**Use Case**: Force refresh when using service discovery (Eureka, Consul, etc.)\n\n---\n\n### Delete Application\n\nDeregister all instances of an application.\n\n**Endpoint**: `DELETE /applications/{name}`\n\n**Parameters**:\n\n- `name` (path): Application name\n\n**Response**: `204 No Content`\n\n**Example**:\n\n```bash\ncurl -X DELETE http://localhost:8080/applications/my-service\n```\n\n---\n\n## Instance Actuator Proxy\n\nAdmin Server proxies requests to instance actuator endpoints.\n\n### General Pattern\n\n**Endpoint**: `GET /instances/{id}/actuator/{endpoint}`\n\n**Parameters**:\n\n- `id` (path): Instance ID\n- `endpoint` (path): Actuator endpoint name\n\n**Response**: Proxied response from instance\n\n**Examples**:\n\n```bash\n# Get health\ncurl http://localhost:8080/instances/abc123/actuator/health\n\n# Get metrics\ncurl http://localhost:8080/instances/abc123/actuator/metrics\n\n# Get specific metric\ncurl http://localhost:8080/instances/abc123/actuator/metrics/jvm.memory.used\n\n# Get environment\ncurl http://localhost:8080/instances/abc123/actuator/env\n\n# Get loggers\ncurl http://localhost:8080/instances/abc123/actuator/loggers\n```\n\n### Common Endpoints\n\n| Endpoint                   | Description            |\n|----------------------------|------------------------|\n| `/actuator/health`         | Health status          |\n| `/actuator/info`           | Build and app info     |\n| `/actuator/metrics`        | Metrics list           |\n| `/actuator/metrics/{name}` | Specific metric        |\n| `/actuator/env`            | Environment properties |\n| `/actuator/loggers`        | Logger configuration   |\n| `/actuator/loggers/{name}` | Specific logger        |\n| `/actuator/httptrace`      | HTTP trace             |\n| `/actuator/threaddump`     | Thread dump            |\n| `/actuator/heapdump`       | Heap dump (binary)     |\n| `/actuator/jolokia`        | JMX via Jolokia        |\n\n### Modify Logger Level\n\n**Endpoint**: `POST /instances/{id}/actuator/loggers/{name}`\n\n**Request Body**:\n\n```json\n{\n  \"configuredLevel\": \"DEBUG\"\n}\n```\n\n**Example**:\n\n```bash\ncurl -X POST http://localhost:8080/instances/abc123/actuator/loggers/com.example \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"configuredLevel\":\"DEBUG\"}'\n```\n\n---\n\n## Instance Operations\n\n### Restart Instance\n\nRestart a Spring Boot application (requires `spring-boot-starter-actuator` with restart endpoint).\n\n**Endpoint**: `POST /instances/{id}/actuator/restart`\n\n**Response**: `200 OK`\n\n**Example**:\n\n```bash\ncurl -X POST http://localhost:8080/instances/abc123/actuator/restart\n```\n\n:::warning Dangerous Operation\nThis will restart the application. Ensure the restart endpoint is properly secured.\n:::\n\n---\n\n### Shutdown Instance\n\nGracefully shutdown a Spring Boot application.\n\n**Endpoint**: `POST /instances/{id}/actuator/shutdown`\n\n**Response**: `200 OK`\n\n```json\n{\n  \"message\": \"Shutting down, bye...\"\n}\n```\n\n**Example**:\n\n```bash\ncurl -X POST http://localhost:8080/instances/abc123/actuator/shutdown\n```\n\n:::warning Dangerous Operation\nThis will shut down the application. Ensure the shutdown endpoint is properly secured and consider it carefully in\nproduction.\n:::\n\n---\n\n## Error Responses\n\n### 400 Bad Request\n\nInvalid request body or parameters.\n\n```json\n{\n  \"error\": \"Bad Request\",\n  \"message\": \"Invalid registration data\",\n  \"status\": 400\n}\n```\n\n### 404 Not Found\n\nInstance or application not found.\n\n```json\n{\n  \"error\": \"Not Found\",\n  \"message\": \"Instance not found: abc123\",\n  \"status\": 404\n}\n```\n\n### 500 Internal Server Error\n\nServer error.\n\n```json\n{\n  \"error\": \"Internal Server Error\",\n  \"message\": \"Failed to register instance\",\n  \"status\": 500\n}\n```\n\n## CORS Support\n\nCross-Origin Resource Sharing (CORS) configuration:\n\n```yaml\nspring:\n  boot:\n    admin:\n      cors:\n        allowed-origins: \"http://localhost:3000\"\n        allowed-methods: \"GET,POST,DELETE\"\n        allowed-headers: \"*\"\n        exposed-headers: \"Location\"\n        allow-credentials: true\n        max-age: 3600\n```\n\n## Rate Limiting\n\nNo built-in rate limiting. Use reverse proxy (nginx, API gateway) for rate limiting if needed.\n\n## Pagination\n\nInstance and application lists are not paginated. For large deployments, consider filtering by name or using service\ndiscovery-based filtering.\n\n## Caching\n\nResponses are not cached by default. Add caching headers via reverse proxy if needed.\n\n## WebSocket Support\n\nNot supported. Use Server-Sent Events (SSE) for real-time updates.\n\n## API Clients\n\n### Java\n\n```java\nRestTemplate restTemplate = new RestTemplate();\n\n// Register instance\nRegistration registration = Registration.create(\"my-service\")\n\t\t.managementUrl(\"http://localhost:8081/actuator\")\n\t\t.healthUrl(\"http://localhost:8081/actuator/health\")\n\t\t.serviceUrl(\"http://localhost:8081\")\n\t\t.build();\n\nResponseEntity<Map> response = restTemplate.postForEntity(\n\t\t\"http://localhost:8080/instances\",\n\t\tregistration,\n\t\tMap.class\n);\n\nString instanceId = (String) response.getBody().get(\"id\");\n```\n\n### JavaScript/TypeScript\n\n```javascript\n// Register instance\nconst registration = {\n  name: 'my-service',\n  managementUrl: 'http://localhost:8081/actuator',\n  healthUrl: 'http://localhost:8081/actuator/health',\n  serviceUrl: 'http://localhost:8081'\n};\n\nconst response = await fetch('http://localhost:8080/instances', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify(registration)\n});\n\nconst { id } = await response.json();\nconsole.log('Instance ID:', id);\n```\n\n### Python\n\n```python\nimport requests\n\n# Register instance\nregistration = {\n    \"name\": \"my-service\",\n    \"managementUrl\": \"http://localhost:8081/actuator\",\n    \"healthUrl\": \"http://localhost:8081/actuator/health\",\n    \"serviceUrl\": \"http://localhost:8081\"\n}\n\nresponse = requests.post(\n    \"http://localhost:8080/instances\",\n    json=registration\n)\n\ninstance_id = response.json()[\"id\"]\nprint(f\"Instance ID: {instance_id}\")\n```\n\n## See Also\n\n- [Event Types](./10-event-types.md)\n- [Server Configuration](../02-server/01-server.mdx)\n- [Client Registration](../03-client/20-registration.md)\n- [Security](../05-security/)\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/10-reference/60-actuator-endpoints.mdx",
    "content": "---\nsidebar_custom_props:\n  icon: 'book'\n---\n# Supported Spring Boot Actuator Endpoints\n\nBelow is a comprehensive list of actuator endpoints which are supported by Spring Boot Admin.\n\n| Actuator Endpoint                 | Documentation Link                                                                                                                    |\n|-----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------|\n| `/actuator/auditevents`           | [Audit Events](https://docs.spring.io/spring-boot/api/rest/actuator/auditevents.html#page-title)                                      |\n| `/actuator/beans`                 | [Beans](https://docs.spring.io/spring-boot/api/rest/actuator/beans.html#page-title)                                                   |\n| `/actuator/conditions`            | [Conditions](https://docs.spring.io/spring-boot/api/rest/actuator/conditions.html#page-title)                                         |\n| `/actuator/configprops`           | [Config Props](https://docs.spring.io/spring-boot/api/rest/actuator/configprops.html#page-title)                                      |\n| `/actuator/env`                   | [Environment](https://docs.spring.io/spring-boot/api/rest/actuator/env.html#page-title)                                               |\n| `/actuator/flyway`                | [Flyway](https://docs.spring.io/spring-boot/api/rest/actuator/flyway.html#page-title)                                                 |\n| `/actuator/health`                | [Health](https://docs.spring.io/spring-boot/api/rest/actuator/health.html#page-title)                                                 |\n| `/actuator/httptrace`             | [HTTP Trace](https://docs.spring.io/spring-boot/docs/2.1.7.RELEASE/actuator-api/html/#http-trace)                                     |\n| `/actuator/httpexchanges`         | [HTTP Exchanges](https://docs.spring.io/spring-boot/api/rest/actuator/httpexchanges.html#page-title)                                  |\n| `/actuator/info`                  | [Info](https://docs.spring.io/spring-boot/api/rest/actuator/info.html#page-title)                                                     |\n| `/actuator/jolokia`               | [Jolokia](https://jolokia.org/reference/html/manual/spring.html)                                                                      |\n| `/actuator/liquibase`             | [Liquibase](https://docs.spring.io/spring-boot/api/rest/actuator/liquibase.html#page-title)                                           |\n| `/actuator/logfile`               | [Logfile](https://docs.spring.io/spring-boot/api/rest/actuator/logfile.html#page-title)                                               |\n| `/actuator/loggers`               | [Loggers](https://docs.spring.io/spring-boot/api/rest/actuator/loggers.html#page-title)                                               |\n| `/actuator/mappings`              | [Mappings](https://docs.spring.io/spring-boot/api/rest/actuator/mappings.html#page-title)                                             |\n| `/actuator/metrics`               | [Metrics](https://docs.spring.io/spring-boot/api/rest/actuator/metrics.html#page-title)                                               |\n| `/actuator/quartz`                | [Quartz](https://docs.spring.io/spring-boot/api/rest/actuator/quartz.html#page-title)                                                 |\n| `/actuator/quartz/jobs`           | [Quartz Jobs](https://docs.spring.io/spring-boot/api/rest/actuator/quartz.html#page-title)                                            |\n| `/actuator/quartz/triggers`       | [Quartz Triggers](https://docs.spring.io/spring-boot/api/rest/actuator/quartz.html#page-title)                                        |\n| `/actuator/sbom`                  | [SBOM](https://docs.spring.io/spring-boot/api/rest/actuator/sbom.html#page-title)                                                     |\n| `/actuator/scheduledtasks`        | [Scheduled Tasks](https://docs.spring.io/spring-boot/api/rest/actuator/scheduledtasks.html#page-title)                                |\n| `/actuator/sessions`              | [Sessions](https://docs.spring.io/spring-boot/api/rest/actuator/sessions.html#page-title)                                             |\n| `/actuator/shutdown`              | [Shutdown](https://docs.spring.io/spring-boot/api/rest/actuator/shutdown.html#page-title)                                             |\n| `/actuator/startup`               | [Startup](https://docs.spring.io/spring-boot/api/rest/actuator/startup.html#page-title)                                               |\n| `/actuator/threaddump`            | [Thread Dump](https://docs.spring.io/spring-boot/api/rest/actuator/threaddump.html#page-title)                                        |\n| `/actuator/refresh`               | [Refresh](https://docs.spring.io/spring-cloud-commons/reference/spring-cloud-commons/application-context-services.html#refresh-scope) |\n| `/actuator/busrefresh`            | [Bus Refresh](https://docs.spring.io/spring-cloud-bus/docs/current/reference/html/index.html)                                         |\n| `/actuator/caches`                | [Caches](https://docs.spring.io/spring-boot/api/rest/actuator/caches.html#page-title)                                                 |\n| `/actuator/restart`               | [Restart](https://docs.spring.io/spring-cloud-commons/reference/spring-cloud-commons/application-context-services.html#endpoints)     |\n| `/actuator/gateway/globalfilters` | [Global Filters](https://cloud.spring.io/spring-cloud-gateway/multi/multi__actuator_api.html)                                         |\n| `/actuator/gateway/routes`        | [Routes](https://cloud.spring.io/spring-cloud-gateway/multi/multi__actuator_api.html)                                                 |\n| `/actuator/gateway/refresh`       | [Refresh](https://cloud.spring.io/spring-cloud-gateway/multi/multi__actuator_api.html)                                                |\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/10-reference/_category_.json",
    "content": "{\n  \"position\": 10,\n  \"label\": \"Reference\"\n}\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/10-reference/index.md",
    "content": "---\n\nsidebar_position: 95\nsidebar_custom_props:\n  icon: 'book'\n---\n\n# Reference Documentation\n\nComprehensive reference material for Spring Boot Admin including event types, REST API, and configuration properties.\n\n## Contents\n\n### [Event Types](./10-event-types.md)\n\nComplete catalog of all `InstanceEvent` types emitted by Spring Boot Admin, including:\n\n- Event lifecycle and ordering\n- Event payloads and properties\n- Common use cases for each event\n- Example event listeners\n\n### [REST API](./20-rest-api.md)\n\nHTTP API reference for Spring Boot Admin Server (intended for internal use by the SPA, not for public consumption):\n\n- Instance registration endpoints\n- Application management endpoints\n- Event streaming\n- Instance operations (restart, shutdown, etc.)\n\n**Note**: The REST API is primarily designed for use by the built-in Single Page Application (SPA) and should not be\nconsidered a stable public API. Use at your own risk for external integrations.\n\n## Quick Links\n\n### Event Types\n\nAll events extend `InstanceEvent` base class:\n\n| Event Type             | Description                   |\n|------------------------|-------------------------------|\n| `REGISTERED`           | Instance first registered     |\n| `REGISTRATION_UPDATED` | Instance registration changed |\n| `DEREGISTERED`         | Instance unregistered         |\n| `STATUS_CHANGED`       | Health status changed         |\n| `ENDPOINTS_DETECTED`   | Actuator endpoints discovered |\n| `INFO_CHANGED`         | Info endpoint data changed    |\n\n### Common Properties\n\n#### Server Context Path\n\n```yaml\nspring:\n  boot:\n    admin:\n      context-path: /admin  # Default: /\n```\n\n#### Client Registration\n\n```yaml\nspring:\n  boot:\n    admin:\n      client:\n        url: http://localhost:8080\n        instance:\n          name: ${spring.application.name}\n```\n\n### Status Values\n\nHealth status values in order of precedence:\n\n1. `DOWN` - Application not healthy\n2. `OUT_OF_SERVICE` - Temporarily unavailable\n3. `OFFLINE` - Instance not responding\n4. `UNKNOWN` - Status cannot be determined\n5. `UP` - Application healthy\n6. `RESTRICTED` - Custom status (application-defined)\n\n## API Versioning\n\nSpring Boot Admin does not version its REST API. The API is primarily intended for internal use by the SPA and is not\nguaranteed to be stable for external integrations.\n\n**Base Path**: `{server.context-path}/instances` (default: `/instances`)\n\n**Content Type**: `application/json` (HAL JSON for collection endpoints)\n\n**Stability**: The REST API is designed for internal use by the built-in SPA and may change without notice. For stable\nintegrations, consider using the event notification system instead.\n\n## Property Prefixes\n\nAll Spring Boot Admin properties use these prefixes:\n\n### Server Properties\n\n- `spring.boot.admin.*` - Core server configuration\n- `spring.boot.admin.ui.*` - UI customization\n- `spring.boot.admin.discovery.*` - Service discovery\n- `spring.boot.admin.monitor.*` - Monitoring settings\n- `spring.boot.admin.notify.*` - Notification settings\n\n### Client Properties\n\n- `spring.boot.admin.client.*` - Client configuration\n- `spring.boot.admin.client.instance.*` - Instance metadata\n\n## Configuration Metadata\n\nSpring Boot Admin provides complete configuration metadata for IDE autocomplete:\n\n```xml\n<dependency>\n    <groupId>de.codecentric</groupId>\n    <artifactId>spring-boot-admin-server</artifactId>\n</dependency>\n```\n\nMetadata files:\n\n- `spring-configuration-metadata.json` (server)\n- `additional-spring-configuration-metadata.json` (client)\n\n## See Also\n\n- [Server Configuration](../02-server/01-server.mdx)\n- [Client Configuration](../03-client/20-registration.md)\n- [Customization](../06-customization/)\n- [Notifications](../02-server/notifications/)\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/11-upgrading/01-spring-boot-admin-4.md",
    "content": "---\nsidebar_custom_props:\n  icon: 'arrow-up'\n---\n\n# To Spring Boot Admin 4\n\nThis guide covers the breaking changes, deprecated features, and migration steps required to upgrade from Spring Boot\nAdmin 3.x to 4.x.\n\n## Prerequisites\n\nBefore upgrading to Spring Boot Admin 4, ensure your application meets these requirements:\n\n- **Spring Boot 4.0+** - Spring Boot Admin 4 requires Spring Boot 4.0 or higher\n- **Java 17+** - Minimum Java version is 17\n- **Review dependencies** - Check that all third-party dependencies are compatible with Spring Boot 4\n\n:::tip Java Version Compatibility\nSpring Boot Admin strives to support the same Java baseline version as the corresponding Spring Boot version. This\nmeans:\n\n- Spring Boot Admin 4.x supports the same minimum Java version as Spring Boot 4.x (Java 17+)\n- Future Spring Boot Admin releases will align with Spring Boot's Java requirements\n\nAlways check the [Spring Boot documentation](https://docs.spring.io/spring-boot/system-requirements.html) for the\nsupported Java versions of your Spring Boot version.\n:::\n\n---\n\n## Breaking Changes\n\n### 1. Nullable Annotations Change\n\n**What Changed:**\n\nSpring Boot Admin 4 replaces Spring's nullable annotations with JSpecify annotations for better null-safety across the\nJava ecosystem.\n\n**Migration:**\n\n```java\n// Before (Spring Boot Admin 3.x)\nimport org.springframework.lang.Nullable;\n\npublic class MyService {\n    public void process(@Nullable String value) {\n        // ...\n    }\n}\n```\n\n```java\n// After (Spring Boot Admin 4.x)\nimport org.jspecify.annotations.Nullable;\n\npublic class MyService {\n    public void process(@Nullable String value) {\n        // ...\n    }\n}\n```\n\n**Action Required:**\n\nIf you extend Spring Boot Admin classes or implement interfaces using `@Nullable` annotations:\n\n1. Add JSpecify dependency to your `pom.xml`:\n\n```xml\n<dependency>\n    <groupId>org.jspecify</groupId>\n    <artifactId>jspecify</artifactId>\n    <version>1.0.0</version>\n</dependency>\n```\n\n2. Update your imports from `org.springframework.lang.Nullable` to `org.jspecify.annotations.Nullable`\n\n---\n\n### 2. HTTP Client Configuration Changes\n\n**What Changed:**\n\nSpring Boot Admin 4 modernizes HTTP client usage:\n\n- **Client**: Now uses `RestClient` exclusively (replaces `WebClient` autoconfiguration)\n- **Server**: Uses `WebClient` for instance communication and `RestTemplate` for notifiers\n\n**Migration:**\n\n#### For Admin Client\n\nThe client autoconfiguration now provides `RestClient` instead of `WebClient`:\n\n```java\n// Before (Spring Boot Admin 3.x)\n@Bean\npublic WebClient.Builder webClientBuilder() {\n    return WebClient.builder()\n        .defaultHeader(\"X-Custom-Header\", \"value\");\n}\n```\n\n```java\n// After (Spring Boot Admin 4.x)\n@Bean\npublic RestClient.Builder restClientBuilder() {\n    return RestClient.builder()\n        .defaultHeader(\"X-Custom-Header\", \"value\");\n}\n```\n\n#### For Admin Server\n\nNo changes required - the server continues using `WebClient` for instance communication:\n\n```java\n// Server-side customization (unchanged)\n@Bean\npublic InstanceWebClient instanceWebClient(WebClient.Builder builder) {\n    return InstanceWebClient.builder(builder)\n        .connectTimeout(Duration.ofSeconds(5))\n        .build();\n}\n```\n\n**Action Required:**\n\n- If you customize the client's HTTP configuration, migrate from `WebClient.Builder` to `RestClient.Builder`\n- Update any custom beans that depend on `WebClient` in client applications\n\n---\n\n### 3. Property Rename: `prefer-ip` Removed\n\n**What Changed:**\n\nThe property `spring.boot.admin.client.instance.prefer-ip` has been removed in favor of the more flexible\n`spring.boot.admin.client.instance.service-host-type`.\n\n**Migration:**\n\n```yaml\n# Before (Spring Boot Admin 3.x)\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          prefer-ip: true\n```\n\n```yaml\n# After (Spring Boot Admin 4.x)\nspring:\n  boot:\n    admin:\n      client:\n        instance:\n          service-host-type: IP  # Options: IP, HOST_NAME, CANONICAL_HOST_NAME\n```\n\n**Available Options:**\n\n| Value                 | Description                                          |\n|-----------------------|------------------------------------------------------|\n| `IP`                  | Use IP address (equivalent to old `prefer-ip: true`) |\n| `HOST_NAME`           | Use hostname (equivalent to old `prefer-ip: false`)  |\n| `CANONICAL_HOST_NAME` | Use canonical hostname                               |\n\n**Action Required:**\n\n- Search your configuration files for `prefer-ip`\n- Replace with `service-host-type: IP` (if `prefer-ip: true`) or `service-host-type: HOST_NAME` (if `prefer-ip: false`)\n\n---\n\n### 4. Jolokia Compatibility\n\n**What Changed:**\n\nThe current stable Jolokia version (2.4.2) does not yet support Spring Boot 4. Spring Boot Admin 4 temporarily\ndowngrades to **Jolokia 2.1.0** for basic JMX functionality.\n\n**Limitations:**\n\n- Some advanced Jolokia features may not be available\n- JMX operations work but with reduced functionality compared to Jolokia 2.4.2\n\n**Future Outlook:**\n\nSpring Boot Admin will upgrade to a newer Jolokia version once Spring Boot 4 support is added. Monitor\nthe [Jolokia project](https://github.com/jolokia/jolokia) for updates on Spring Boot 4 compatibility.\n\n**Action Required:**\n\n- **No immediate action needed** - Jolokia 2.1.0 is included automatically and provides basic JMX functionality\n- Test your JMX operations to ensure they work with the limited feature set\n- If JMX functionality is critical, consider waiting for full Jolokia support before upgrading\n\n---\n\n## Migration Checklist\n\nFollow these steps to ensure a smooth upgrade:\n\n### Step 1: Update Dependencies\n\nUpdate your `pom.xml`:\n\n```xml\n<properties>\n    <spring-boot.version>4.0.0</spring-boot.version>\n    <spring-boot-admin.version>4.0.0</spring-boot-admin.version>\n</properties>\n\n<dependencies>\n    <!-- Admin Server -->\n    <dependency>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-starter-server</artifactId>\n        <version>${spring-boot-admin.version}</version>\n    </dependency>\n\n    <!-- Admin Client -->\n    <dependency>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-starter-client</artifactId>\n        <version>${spring-boot-admin.version}</version>\n    </dependency>\n</dependencies>\n```\n\n### Step 2: Update Configuration\n\n1. **Replace `prefer-ip` property:**\n\n```bash\n# Find and replace in all configuration files\ngrep -r \"prefer-ip\" src/main/resources/\n# Replace with service-host-type\n```\n\n2. **Review HTTP client customizations:**\n\n```bash\n# Check for WebClient customizations in client apps\ngrep -r \"WebClient.Builder\" src/main/java/\n```\n\n### Step 3: Update Code\n\n1. **Update nullable annotations:**\n\n```bash\n# Find all Spring nullable imports\nfind src -name \"*.java\" -exec grep -l \"org.springframework.lang.Nullable\" {} \\;\n\n# Replace with JSpecify\nsed -i 's/org.springframework.lang.Nullable/org.jspecify.annotations.Nullable/g' <files>\n```\n\n2. **Migrate client HTTP configuration:**\n\nReview and update any beans creating `WebClient.Builder` for the Admin Client.\n\n### Step 4: Test\n\n1. **Start the Admin Server:**\n\n```bash\nmvn spring-boot:run\n```\n\n2. **Register a client application**\n3. **Verify functionality:**\n    - Instance registration works\n    - Health checks update correctly\n    - Actuator endpoints are accessible\n    - Notifications fire properly\n    - JMX operations work (with Jolokia 2.1.0 limitations)\n\n### Step 5: Monitor Logs\n\nWatch for deprecation warnings or errors:\n\n```bash\ntail -f logs/spring-boot-admin.log | grep -i \"deprecat\\|error\\|warn\"\n```\n\n---\n\n## Getting Help\n\nIf you encounter issues during the upgrade:\n\n1. **Check the changelog**: Review detailed changes in\n   the [release notes](https://github.com/codecentric/spring-boot-admin/releases)\n2. **Search existing issues**: [GitHub Issues](https://github.com/codecentric/spring-boot-admin/issues)\n3. **Ask the community**: [Stack Overflow](https://stackoverflow.com/questions/tagged/spring-boot-admin) with tag\n   `spring-boot-admin`\n4. **Report bugs**: Create an issue on [GitHub](https://github.com/codecentric/spring-boot-admin/issues/new)\n\n---\n\n## Summary\n\n**Key Changes:**\n\n- ✅ Update to Spring Boot 4.0+\n- ✅ Replace `org.springframework.lang.Nullable` with `org.jspecify.annotations.Nullable`\n- ✅ Migrate client from `WebClient` to `RestClient`\n- ✅ Change `prefer-ip` to `service-host-type`\n- ⚠️ Accept Jolokia 2.1.0 limitations temporarily\n\nMost applications can upgrade with minimal code changes, primarily focused on configuration updates and dependency\nmanagement.\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/11-upgrading/_category_.json",
    "content": "{\n  \"position\": 11,\n  \"label\": \"Upgrading\"\n}\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/11-upgrading/index.md",
    "content": "---\nsidebar_custom_props:\n  icon: 'arrow-up'\n---\n\nimport DocCardList from '@theme/DocCardList';\n\n# Upgrading\n\n <DocCardList />\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/index.mdx",
    "content": "---\nsidebar_position: 0\nslug: /index\nid: index\ntitle: Start\nhide_title: true\nhide_table_of_contents: true\nsidebar_custom_props:\n  icon: 'home'\n---\n\nimport { HexMesh } from \"@sba/spring-boot-admin-docs/src/site/src/components/HexMesh\";\nimport Link from \"@docusaurus/Link\";\n\n<div className={\"styles.hexMeshContainer\"}>\n  <HexMesh\n    items={[\n      { title: 'Getting Started', description: 'Quick setup guide', link: '/docs/getting-started' },\n      { title: 'Server', description: 'Configure Admin Server', link: '/docs/server' },\n      { title: 'Client', description: 'Register applications', link: '/docs/client' },\n      { title: 'Integration', description: 'Service discovery', link: '/docs/integration' },\n      { title: 'Security', description: 'Authentication & authorization', link: '/docs/security' },\n      { title: 'Customization', description: 'Extend and customize', link: '/docs/customization' },\n      { title: 'Samples', description: 'Example projects', link: '/docs/samples' },\n      { title: 'Reference', description: 'API & configuration', link: '/docs/reference' },\n      { title: 'Upgrading', description: 'Migration guides', link: '/docs/upgrading' },\n    ]}\n    renderItem={(item) => (\n      <Link to={item.link} className={\"hex__body\"}>\n        <div className={\"hex__body__title\"}>{item.title}</div>\n        <div className={\"hex__body__description\"}>{item.description}</div>\n      </Link>\n    )}\n  />\n</div>\n\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docs/index.module.css",
    "content": ".hexMeshContainer {\n  height: 600px;\n  margin: 2rem 0;\n}\n\n.hexItem {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  text-align: center;\n  padding: 1rem;\n  color: var(--ifm-font-color-base);\n  text-decoration: none;\n  height: 100%;\n  width: 100%;\n}\n\n.hexItem:hover {\n  color: var(--ifm-color-primary);\n  text-decoration: none;\n}\n\n.hexIcon {\n  font-size: 2.5rem;\n  margin-bottom: 0.5rem;\n}\n\n.hexTitle {\n  font-size: 1.1rem;\n  font-weight: 600;\n  margin-bottom: 0.25rem;\n}\n\n.hexDescription {\n  font-size: 0.85rem;\n  opacity: 0.8;\n}\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/docusaurus.config.ts",
    "content": "import 'dotenv/config';\nimport { themes as prismThemes } from \"prism-react-renderer\";\nimport type { Config } from \"@docusaurus/types\";\nimport type * as Preset from \"@docusaurus/preset-classic\";\nimport path from \"path\";\n\nconst globalVariables = {\n  VERSION: process.env.VERSION,\n}\n\nconst config: Config = {\n  title: 'Spring Boot Admin',\n  favicon: 'img/favicon.png',\n  url: 'https://docs.spring-boot-admin.com/',\n  baseUrl: process.env.VERSION ? `/${process.env.VERSION}/` : '/',\n  organizationName: 'codecentric',\n  projectName: 'spring-boot-admin',\n  onBrokenLinks: 'warn',\n  onBrokenAnchors: 'warn',\n  i18n: {\n    defaultLocale: \"en\",\n    locales: [\"en\"]\n  },\n  presets: [\n    [\n      \"classic\",\n      {\n        theme: {\n          customCss: ['./src/css/custom.css'],\n        },\n        docs: {\n          sidebarPath: \"./sidebars.ts\",\n        },\n        blog: {\n          showReadingTime: true,\n          feedOptions: {\n            type: [\"rss\", \"atom\"],\n            xslt: true\n          },\n          onInlineTags: \"warn\",\n          onInlineAuthors: \"warn\",\n          onUntruncatedBlogPosts: \"warn\"\n        },\n      } satisfies Preset.Options\n    ]\n  ],\n  plugins: [\n    '@signalwire/docusaurus-plugin-llms-txt',\n    function () {\n      return {\n        name: 'custom-webpack-config',\n        configureWebpack() {\n          return {\n            resolve: {\n              alias: {\n                '@sba': path.resolve(__dirname, '../../..')\n              }\n            }\n          };\n        },\n      };\n    },\n  ],\n  markdown: {\n    hooks: {\n      onBrokenMarkdownLinks: \"throw\",\n      onBrokenMarkdownImages: \"throw\"\n    },\n    mermaid: true,\n    preprocessor: ({fileContent}) => {\n      let content = fileContent;\n      for (const variable in globalVariables) {\n        content = content.replaceAll('@'+variable+'@', globalVariables[variable]);\n      }\n\n      return content\n    },\n  },\n  themes: ['@docusaurus/theme-mermaid'],\n  themeConfig: {\n    image: \"img/social-card.jpg\",\n    tableOfContents: {\n      minHeadingLevel: 2,\n      maxHeadingLevel: 5\n    },\n    algolia: {\n      appId: \"GUDRYGX7B3\",\n      apiKey: \"d6ff502875993f3160598cbd257cc532\",\n      indexName: \"spring-boot-admin\",\n      contextualSearch: true,\n      searchParameters: {},\n      searchPagePath: false,\n      insights: false\n    },\n    navbar: {\n      title: \"Spring Boot Admin\",\n      logo: {\n        alt: \"Spring Boot logo with pulse line in front of it\",\n        src: \"img/logo.png\"\n      },\n      items: [\n        {\n          type: \"docSidebar\",\n          sidebarId: \"sidebar\",\n          position: \"left\",\n          label: \"Documentation\"\n        },\n        {\n          type: \"docSidebar\",\n          sidebarId: \"sidebar\",\n          position: \"left\",\n          label: \"FAQ\",\n          href: \"/faq\"\n        },\n        {\n          href: \"https://github.com/codecentric/spring-boot-admin\",\n          label: \"GitHub\",\n          position: \"right\"\n        }\n      ]\n    },\n    footer: {\n      style: \"dark\",\n      links: [\n        {\n          title: \"Docs\",\n          items: [\n            {\n              label: \"Overview\",\n              to: \"/docs/getting-started/\"\n            },\n            {\n              label: \"FAQ\",\n              to: \"/faq\"\n            }\n          ]\n        },\n        {\n          title: \"Community\",\n          items: [\n            {\n              label: \"Stack Overflow\",\n              href: \"https://stackoverflow.com/questions/tagged/spring-boot-admin\"\n            }\n          ]\n        },\n        {\n          title: \"More\",\n          items: [\n            {\n              label: \"GitHub\",\n              href: \"https://github.com/codecentric/spring-boot-admin\"\n            },\n            {\n              label: \"Impressum\",\n              to: \"/impressum\"\n            },\n            {\n              label: \"Privacy\",\n              to: \"/privacy\"\n            }\n          ]\n        }\n      ],\n      copyright: `© ${new Date().getFullYear()} <a href=\"https://www.codecentric.de/\" target='_blank'>codecentric AG</a>`\n    },\n    prism: {\n      theme: prismThemes.github,\n      darkTheme: prismThemes.vsDark,\n      additionalLanguages: [\"java\", \"bash\", \"javascript\", \"typescript\", \"docker\", \"gradle\", \"groovy\", \"yaml\"]\n    }\n  } satisfies Preset.ThemeConfig\n};\n\nexport default config;\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/package.json",
    "content": "{\n  \"name\": \"site\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"docusaurus\": \"docusaurus\",\n    \"start\": \"docusaurus start\",\n    \"swizzle\": \"docusaurus swizzle\",\n    \"build:current-version-redirect\": \"mkdir -p build/current/ && sed \\\"s/@@VERSION@@/$VERSION/g\\\" current/index.template.html > build/current/index.html && sed \\\"s/@@VERSION@@/$VERSION/g\\\" current/404.template.html > build/current/404.html\",\n    \"build\": \"docusaurus build\",\n    \"build:prod\": \"npm run build && npm run build:current-version-redirect && rsync -a --delete-before ./build ../../target/generated-docs\"\n  },\n  \"dependencies\": {\n    \"@docusaurus/core\": \"^3.6.3\",\n    \"@docusaurus/module-type-aliases\": \"^3.6.3\",\n    \"@docusaurus/preset-classic\": \"^3.6.3\",\n    \"@docusaurus/theme-mermaid\": \"^3.9.2\",\n    \"@docusaurus/tsconfig\": \"^3.6.3\",\n    \"@docusaurus/types\": \"^3.6.3\",\n    \"@iconify/react\": \"^6.0.0\",\n    \"@mdx-js/react\": \"^3.1.0\",\n    \"@signalwire/docusaurus-plugin-llms-txt\": \"^1.2.2\",\n    \"asciidoctor\": \"^3.0.4\",\n    \"clsx\": \"^2.1.1\",\n    \"dotenv\": \"^17.0.0\",\n    \"glob\": \"^13.0.0\",\n    \"lodash.merge\": \"^4.6.2\",\n    \"node-html-markdown\": \"^2.0.0\",\n    \"prism-react-renderer\": \"^2.4.0\",\n    \"react\": \"^19.0.0\",\n    \"react-dom\": \"^19.0.0\",\n    \"react-multi-carousel\": \"^2.8.5\",\n    \"remark-deflist\": \"^1.0.0\",\n    \"typescript\": \"~5.9.0\"\n  },\n  \"browserslist\": {\n    \"development\": [\n      \"last 3 chrome version\",\n      \"last 3 firefox version\",\n      \"last 5 safari version\"\n    ],\n    \"production\": [\n      \">0.5%\",\n      \"not dead\",\n      \"not op_mini all\"\n    ]\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/sidebars.ts",
    "content": "import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';\n\nconst sidebars: SidebarsConfig = {\n  sidebar: [\n    {type: 'autogenerated', dirName: '.'}\n  ],\n};\n\nexport default sidebars;\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/src/components/CopyButton.module.css",
    "content": ".copyButton {\n    display: inline-flex;\n    align-items: center;\n    gap: 6px;\n    padding: 5px 8px;\n    background-color: var(--ifm-color-primary-light); /* Spring Boot green */\n    color: #fff;\n    font-size: 14px;\n    font-weight: 500;\n    border: none;\n    border-radius: 6px;\n    cursor: pointer;\n    transition: background-color 0.2s ease-in-out, transform 0.1s ease-in-out;\n}\n\n.copyButton:hover {\n    background-color: var(--ifm-color-primary-dark); /* darker green on hover */\n}\n\n.copyButton:active {\n    transform: scale(0.97); /* subtle click effect */\n}\n\n.icon {\n    width: 12px;\n    aspect-ratio: 1;\n}\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/src/components/CopyButton.tsx",
    "content": "import React, { useState } from \"react\";\nimport styles from \"./CopyButton.module.css\";\n\nexport function CopyButton({ text }) {\n  const [copied, setCopied] = useState(false);\n\n  const handleCopy = async () => {\n    try {\n      await navigator.clipboard.writeText(text);\n      setCopied(true);\n      setTimeout(() => setCopied(false), 2000);\n    } catch (err) {\n      console.error(\"Failed to copy: \", err);\n    }\n  };\n\n  return (\n    <button className={styles.copyButton} onClick={handleCopy} title=\"Copy\">\n      {copied ? (\n        <>\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"16\"\n            height=\"16\"\n            fill=\"currentColor\"\n            viewBox=\"0 0 16 16\"\n            className={styles.icon}\n          >\n            <path d=\"M13.485 1.929a.75.75 0 0 1 0 1.06L6.06 10.414a.75.75 0 0 1-1.06 0L2.515 7.94a.75.75 0 1 1 1.06-1.06L6 9.293l6.425-6.425a.75.75 0 0 1 1.06 0z\" />\n          </svg>\n        </>\n      ) : (\n        <>\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"16\"\n            height=\"16\"\n            fill=\"currentColor\"\n            viewBox=\"0 0 16 16\"\n            className={styles.icon}\n          >\n            <path d=\"M10 1H2a1 1 0 0 0-1 1v11h2V3h7V1z\" />\n            <path d=\"M13 3H5a1 1 0 0 0-1 1v11h9a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1zM5 14V4h8v10H5z\" />\n          </svg>\n        </>\n      )}\n    </button>\n  );\n}\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/src/components/HexMesh.module.css",
    "content": ".hexMesh {\n    width: 100%;\n    height: 75vh;\n    display: flex;\n    justify-content: space-around;\n    align-items: center;\n}\n\n.hex {\n    --fill-color: #4a4a4a;\n    --stroke-color: transparent;\n\n    fill: var(--fill-color);\n    fill-opacity: 0.05;\n    stroke: var(--stroke-color);\n    stroke-width: 0.5;\n    stroke-opacity: 0.8;\n    pointer-events: none;\n\n    cursor: pointer;\n\n    &.hasContent {\n        cursor: pointer;\n        --fill-color: var(--ifm-color-primary);\n        --stroke-color: var(--ifm-color-primary);\n\n        &:hover {\n            fill-opacity: 0.25;\n            stroke-opacity: 1;\n            stroke-width: 2;\n        }\n    }\n\n    :hover path {\n        fill-opacity: 0.25;\n        stroke-opacity: 1;\n        stroke-width: 2;\n    }\n\n    :global(.hex__body) {\n        pointer-events: auto;\n        position: fixed;\n        z-index: 10;\n        font-size: 100%;\n        width: 100%;\n        height: 100%;\n        display: flex;\n        flex-direction: column;\n        justify-content: center;\n        align-items: center;\n        color: var(--ifm-font-color-base);\n\n        &:hover {\n            text-decoration: none;\n        }\n    }\n\n    :global(.hex__body::after) {\n        display: flex;\n        justify-content: center;\n        align-content: center;\n        font-size: 15em;\n        position: absolute;\n        z-index: -1;\n        width: 100%;\n    }\n\n    :global(.hex__body__title) {\n        width: 85%;\n        font-size: 1.75em;\n        font-weight: bold;\n        text-align: center;\n        line-height: 1em;\n    }\n\n    :global(.hex__body__description) {\n        font-style: italic;\n        font-size: 1em;\n    }\n}\n\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/src/components/HexMesh.tsx",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport React, { useEffect, useRef, useState, useMemo } from 'react';\nimport styles from './HexMesh.module.css';\n\ninterface HexMeshProps<T> {\n  items: T[];\n  classForItem?: (item: T | undefined) => string | undefined;\n  renderItem?: (item: T) => React.ReactNode;\n  onClick?: (item: T, event: React.MouseEvent) => void;\n}\n\ninterface Layout {\n  cols: number;\n  rows: number;\n  sideLength: number;\n}\n\nconst tileCount = (cols: number, rows: number): number => {\n  const shorterRows = Math.floor(rows / 2);\n  return rows * cols - shorterRows;\n};\n\nconst calcSideLength = (width: number, height: number, cols: number, rows: number): number => {\n  const fitToWidth = width / cols / Math.sqrt(3);\n  const fitToHeight = (height * 2) / (3 * rows + 1);\n  return Math.min(fitToWidth, fitToHeight);\n};\n\nconst calcLayout = (minTileCount: number, width: number, height: number): Layout => {\n  let cols = 1;\n  let rows = 1;\n  let sideLength = calcSideLength(width, height, cols, rows);\n\n  while (minTileCount > tileCount(cols, rows)) {\n    const sidelengthExtraCol = calcSideLength(width, height, cols + 1, rows);\n    const sidelengthExtraRow = calcSideLength(width, height, cols, rows + 1);\n    if (sidelengthExtraCol > sidelengthExtraRow) {\n      sideLength = sidelengthExtraCol;\n      cols++;\n    } else {\n      sideLength = sidelengthExtraRow;\n      rows++;\n    }\n  }\n  return {\n    cols,\n    rows,\n    sideLength,\n  };\n};\n\nexport function HexMesh<T>({ items, classForItem, renderItem, onClick }: HexMeshProps<T>) {\n  const rootRef = useRef<HTMLDivElement>(null);\n  const [layout, setLayout] = useState<Layout>({ cols: 1, rows: 1, sideLength: 1 });\n\n  const { cols, rows, sideLength } = layout;\n\n  const hexHeight = useMemo(() => sideLength * 2, [sideLength]);\n  const hexWidth = useMemo(() => sideLength * Math.sqrt(3), [sideLength]);\n  const meshWidth = useMemo(() => hexWidth * cols, [hexWidth, cols]);\n  const meshHeight = useMemo(() => sideLength * (2 + (rows - 1) * 1.5), [sideLength, rows]);\n\n  const point = (i: number): string => {\n    const innerSideLength = sideLength * 0.95;\n    const marginTop = hexHeight / 2;\n    const marginLeft = hexWidth / 2;\n    const x = marginLeft + innerSideLength * Math.cos(((1 + i * 2) * Math.PI) / 6);\n    const y = marginTop + innerSideLength * Math.sin(((1 + i * 2) * Math.PI) / 6);\n    return `${x},${y}`;\n  };\n\n  const hexPath = useMemo(() => {\n    const points = [point(0), point(1), point(2), point(3), point(4), point(5)];\n\n    // Radius for the rounded corners\n    const cornerRadius = sideLength * 0.05;\n\n    // Parse points into coordinate pairs\n    const coords = points.map((p) => {\n      const [x, y] = p.split(',').map(Number);\n      return { x, y };\n    });\n\n    // Helper function to calculate distance between two points\n    const distance = (p1: { x: number; y: number }, p2: { x: number; y: number }) =>\n      Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));\n\n    // Helper function to move along a line from p1 towards p2 by a given distance\n    const moveAlong = (p1: { x: number; y: number }, p2: { x: number; y: number }, dist: number) => {\n      const d = distance(p1, p2);\n      const ratio = dist / d;\n      return {\n        x: p1.x + (p2.x - p1.x) * ratio,\n        y: p1.y + (p2.y - p1.y) * ratio,\n      };\n    };\n\n    // Build the path\n    let path = '';\n\n    for (let i = 0; i < coords.length; i++) {\n      const current = coords[i];\n      const prev = coords[(i - 1 + coords.length) % coords.length];\n      const next = coords[(i + 1) % coords.length];\n\n      // Point after current corner (moving from current towards next)\n      const nextEdgeLength = distance(current, next);\n      const afterCorner = moveAlong(current, next, Math.min(cornerRadius, nextEdgeLength / 2));\n\n      // Point before current corner (moving from current towards prev)\n      const prevEdgeLength = distance(current, prev);\n      const beforeCorner = moveAlong(current, prev, Math.min(cornerRadius, prevEdgeLength / 2));\n\n      if (i === 0) {\n        // Start at the point after the first corner\n        path += `M ${afterCorner.x},${afterCorner.y} `;\n      } else {\n        // Draw line to the point before this corner\n        path += `L ${beforeCorner.x},${beforeCorner.y} `;\n        // Draw quadratic bezier curve around this corner using the corner as control point\n        path += `Q ${current.x},${current.y} ${afterCorner.x},${afterCorner.y} `;\n      }\n    }\n\n    // Close the path (draws line back to start and bezier around first corner)\n    const firstCorner = coords[0];\n    const lastCorner = coords[coords.length - 1];\n    const firstEdgeLength = distance(firstCorner, lastCorner);\n    const beforeFirstCorner = moveAlong(firstCorner, lastCorner, Math.min(cornerRadius, firstEdgeLength / 2));\n    path += `L ${beforeFirstCorner.x},${beforeFirstCorner.y} `;\n\n    const afterFirstCorner = moveAlong(\n      firstCorner,\n      coords[1],\n      Math.min(cornerRadius, distance(firstCorner, coords[1]) / 2)\n    );\n    path += `Q ${firstCorner.x},${firstCorner.y} ${afterFirstCorner.x},${afterFirstCorner.y} `;\n\n    path += 'Z';\n    return path;\n  }, [sideLength, hexHeight, hexWidth]);\n\n  const translate = (col: number, row: number): string => {\n    const x = (col - 1) * hexWidth + (row % 2 ? 0 : hexWidth / 2);\n    const y = (row - 1) * sideLength * 1.5;\n    return `translate(${x},${y})`;\n  };\n\n  const getItem = (col: number, row: number): T | undefined => {\n    const rowOffset = (row - 1) * cols - Math.max(Math.floor((row - 1) / 2), 0);\n    const index = rowOffset + col - 1;\n    return items[index];\n  };\n\n  const handleClick = (event: React.MouseEvent, col: number, row: number) => {\n    const item = getItem(col, row);\n    if (item && onClick) {\n      onClick(item, event);\n    }\n  };\n\n  const updateLayout = () => {\n    if (rootRef.current) {\n      const boundingClientRect = rootRef.current.getBoundingClientRect();\n      const newLayout = calcLayout(items.length, boundingClientRect.width, boundingClientRect.height);\n      setLayout(newLayout);\n    }\n  };\n\n  useEffect(() => {\n    updateLayout();\n  }, [items.length]);\n\n  useEffect(() => {\n    if (rootRef.current) {\n      rootRef.current.style.fontSize = `${sideLength / 9.5}px`;\n    }\n  }, [sideLength]);\n\n  useEffect(() => {\n    const resizeObserver = new ResizeObserver(() => {\n      updateLayout();\n    });\n\n    if (rootRef.current) {\n      resizeObserver.observe(rootRef.current);\n    }\n\n    return () => {\n      resizeObserver.disconnect();\n    };\n  }, [items.length]);\n\n  const hexes = useMemo(() => {\n    const result: React.ReactNode[] = [];\n    for (let row = 1; row <= rows; row++) {\n      const colCount = cols + (row % 2 ? 0 : -1);\n      for (let col = 1; col <= colCount; col++) {\n        const item = getItem(col, row);\n        const className = classForItem ? classForItem(item) : undefined;\n\n        result.push(\n          <g\n            key={`${col}-${row}`}\n            className={`${styles.hex} ${className || ''} ${item ? styles.hasContent : ''}`}\n            transform={translate(col, row)}\n            onClick={(e) => handleClick(e, col, row)}\n          >\n            <path d={hexPath} />\n            {item && renderItem && (\n              <foreignObject\n                height={hexHeight}\n                width={hexWidth}\n                style={{ pointerEvents: 'none' }}\n                x=\"0\"\n                y=\"0\"\n              >\n                {renderItem(item)}\n              </foreignObject>\n            )}\n          </g>\n        );\n      }\n    }\n    return result;\n  }, [rows, cols, hexPath, hexHeight, hexWidth, items, classForItem, renderItem]);\n\n  return (\n    <div ref={rootRef} className={styles.hexMesh}>\n      <svg height={meshHeight} width={meshWidth} xmlns=\"http://www.w3.org/2000/svg\">\n        <defs>\n          <clipPath id=\"hex-clip\">\n            <path d={hexPath} />\n          </clipPath>\n        </defs>\n        {hexes}\n      </svg>\n    </div>\n  );\n}\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/src/components/PropertyTable.module.css",
    "content": ".propertyTable {\n    display: table;\n    width: 100%;\n\n    caption {\n        text-align: left;\n        font-weight: bold;\n        padding: 0 0 1rem 0;\n    }\n\n    tr {\n        --description-background: var(--ifm-table-stripe-background);\n    }\n    tr:nth-child(2n) {\n        --description-background: var(--ifm-background-color);\n    }\n\n    code {\n        word-break: break-all;\n    }\n}\n\n.propertyBlock {\n    display: flex;\n    align-items: center;\n    gap: .5rem;\n    margin-bottom: .5rem;\n\n    code {\n        border: none;\n        background: none;\n        text-wrap: nowrap;\n    }\n}\n\n.descriptionBlock {\n    background: var(--description-background);\n    border-radius: 5px;\n    border: var(--ifm-table-border-width) solid var(--ifm-table-border-color);\n    padding: .5rem;\n    overflow: hidden;\n\n    p {\n        margin-bottom: .25rem;\n    }\n\n    dl {\n        margin-top: 0;\n        margin-bottom: 0;\n    }\n}\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/src/components/PropertyTable.tsx",
    "content": "import { filterPropertiesByName } from \"@site/src/propertiesUtil\";\nimport styles from \"./PropertyTable.module.css\";\nimport { CopyButton } from \"@site/src/components/CopyButton\";\n\ntype Props = {\n  title?: string;\n  properties: Array<SpringPropertyDefinition>;\n  filter?: Array<string>;\n  includeOnly?: boolean;\n  additionalProperties?: Array<SpringPropertyDefinition>;\n}\n\nfunction getFilteredProperties(properties: Array<SpringPropertyDefinition>, filter: Array<string>, includeOnly: boolean) {\n  if (filter.length === 0) {\n    return properties;\n  }\n\n  return filterPropertiesByName(properties, filter, includeOnly)\n    .filter((property, index, self) =>\n      index === self.findIndex((p) => p.name === property.name)\n    )\n    .sort((a, b) => {\n      return a.name.length - b.name.length || a.name.localeCompare(b.name);\n    });\n}\n\nexport function PropertyTable({\n                                title,\n                                properties,\n                                filter = [],\n                                includeOnly = true,\n                                additionalProperties = [] as Array<SpringPropertyDefinition>\n                              }: Readonly<Props>) {\n\n\n  const filteredProperties = getFilteredProperties(properties, filter, includeOnly);\n\n  const propertiesToShow = [\n    ...filteredProperties,\n    ...additionalProperties\n  ];\n\n  const hasDefaultValueOrType = (property: SpringPropertyDefinition) => {\n    return property.defaultValue || property.type;\n  };\n\n  return (\n    <table className={styles.propertyTable}>\n      {title && <caption>{title}</caption>}\n      <thead>\n      <tr>\n        <th>Property</th>\n      </tr>\n      </thead>\n      <tbody>\n      {propertiesToShow.map((a) => (\n        <>\n          <tr key={a.name}>\n            <td className={styles.propertyCell}>\n              <div className={styles.propertyBlock}>\n                <CopyButton text={a.name} />\n                <code>\n                  {a.name}\n                </code>\n              </div>\n              <div className={styles.descriptionBlock}>\n                <p dangerouslySetInnerHTML={{ __html: a.description }} />\n                {hasDefaultValueOrType(a) && (\n                  <dl>\n                    {a.type && (\n                      <div>\n                        <dt><span style={{ fontStyle: \"italic\" }}>Type:</span>&nbsp;</dt>\n                        <dd><code>{a.type}</code></dd>\n                      </div>\n                    )}\n                    {a.defaultValue && (\n                      <div>\n                        <dt><span style={{ fontStyle: \"italic\" }}>Default:</span>&nbsp;</dt>\n                        <dd><code>{JSON.stringify(a.defaultValue)}</code></dd>\n                      </div>\n                    )}\n                  </dl>\n                )}\n              </div>\n            </td>\n          </tr>\n        </>\n      ))}\n      </tbody>\n    </table>\n  );\n}\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/src/components/Screenshot.module.css",
    "content": ".screenshot {\n    position: relative;\n}\n\n.screenshot__description {\n    font-size: .9rem;\n    position: absolute;\n    text-align: center;\n    bottom: 2.4rem;\n    z-index: 1;\n    width: 90%;\n    backdrop-filter: blur(10px);\n    background: rgba(0,0,0,0.3);\n    color: #fff;\n    border-radius: 6px;\n    padding: 5px 10px;\n    left: 50%;\n    transform: translateX(-50%);\n}\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/src/components/Screenshot.tsx",
    "content": "import Carousel from \"react-multi-carousel\";\nimport \"react-multi-carousel/lib/styles.css\";\nimport styles from \"./Screenshot.module.css\";\n\nexport function Screenshot({ images }: { images: {src: string, description: string}[] }) {\n  return (\n    <Carousel\n      additionalTransfrom={0}\n      arrows\n      autoPlaySpeed={3000}\n      draggable\n      infinite\n      keyBoardControl\n      minimumTouchDrag={80}\n      pauseOnHover\n      responsive={{\n        desktop: {\n          breakpoint: {\n            max: 3000,\n            min: 1024\n          },\n          items: 1\n        },\n        mobile: {\n          breakpoint: {\n            max: 464,\n            min: 0\n          },\n          items: 1\n        },\n        tablet: {\n          breakpoint: {\n            max: 1024,\n            min: 464\n          },\n          items: 1\n        }\n      }}\n      shouldResetAutoplay\n      sliderClass=\"\"\n      slidesToSlide={1}\n      swipeable\n    >\n      {images.map((image, index) => {\n        const { default: imgSrc } = require(`../../assets/screens/${image.src}`);\n        return (\n          <div className={styles.screenshot}>\n            <div className={styles.screenshot__description}>\n              {image.description}\n            </div>\n            <img src={imgSrc} />\n          </div>\n        );\n      })}\n    </Carousel>\n  );\n}\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/src/css/custom.css",
    "content": ":root {\n    --ifm-color-primary: #18674e;\n    --ifm-color-primary-dark: #165d46;\n    --ifm-color-primary-darker: #145842;\n    --ifm-color-primary-darkest: #114837;\n    --ifm-color-primary-light: #1a7156;\n    --ifm-color-primary-lighter: #1c765a;\n    --ifm-color-primary-lightest: #1f8665;\n    --ifm-background-color: #fff;\n}\n\n[data-theme='dark'] {\n    --ifm-color-primary: #45d3a6;\n    --ifm-color-primary-dark: #30cc9a;\n    --ifm-color-primary-darker: #2ec092;\n    --ifm-color-primary-darkest: #259f78;\n    --ifm-color-primary-light: #5cd8b1;\n    --ifm-color-primary-lighter: #67dbb6;\n    --ifm-color-primary-lightest: #89e3c7;\n}\n\nmain[class^='docMainContainer'] {\n    position: relative;\n}\n\nmain > .container {\n    max-width: initial !important;\n}\n\n.dl-horizontal {\n    * {\n        margin: 0;\n    }\n    div {\n        display: grid;\n        gap: .5rem;\n        grid-template-columns: repeat(3, 1fr);\n\n        dd {\n            grid-column: span 2 / span 2;\n        }\n    }\n}\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/src/global.d.ts",
    "content": "export {};\n\ndeclare global {\n  type SpringPropertyDefinition = {\n    name: string;\n    description: string;\n    type?: string;\n    defaultValue?: string;\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/src/pages/faq.md",
    "content": "--- \ntoc_min_heading_level: 2\ntoc_max_heading_level: 2\n---\n# FAQ\n\nThis FAQ covers common questions and troubleshooting scenarios encountered when using Spring Boot Admin.\n\n---\n\n## General Questions\n\n### Can I include spring-boot-admin into my business application?\n\n**tl;dr** You can, but you shouldn't.\n\nYou can set `spring.boot.admin.context-path` to alter the path where the UI and REST-API is served, but depending on the complexity of your application you might get in trouble. On the other hand in my opinion it makes no sense for an application to monitor itself. In case your application goes down your monitoring tool also does.\n\n### Can I change or reload Spring Boot properties at runtime?\n\nYes, you can refresh the entire environment or set/update individual properties for both single instances as well as for the entire application.\n\nNote, however, that the Spring Boot application needs to have [Spring Cloud Commons](https://docs.spring.io/spring-cloud-commons/docs/current/reference/html/#endpoints) and `management.endpoint.env.post.enabled=true` in place.\n\nAlso check the details of `@RefreshScope` https://docs.spring.io/spring-cloud-commons/docs/current/reference/html/#refresh-scope.\n\n### Which Spring Boot Admin version should I use?\n\nSpring Boot Admin's version matches the major and minor versions of Spring Boot:\n\n- **Spring Boot Admin 2.x** → **Spring Boot 2.x**\n- **Spring Boot Admin 3.x** → **Spring Boot 3.x**\n- **Spring Boot Admin 4.x** → **Spring Boot 4.x**\n\nAlways match the major and minor version numbers. For example, if you're using Spring Boot 3.2.x, use Spring Boot Admin 3.2.x.\n\n---\n\n## Client Registration Issues\n\n### My client application is not registering with the Admin Server\n\n> **Related Issues:** [#918](https://github.com/codecentric/spring-boot-admin/issues/918), [#2039](https://github.com/codecentric/spring-boot-admin/issues/2039), [#797](https://github.com/codecentric/spring-boot-admin/issues/797)\n> **Stack Overflow:** [spring-boot-admin](https://stackoverflow.com/questions/tagged/spring-boot-admin+registration)\n\n**Common causes:**\n\n1. **Incorrect Admin Server URL**\n\nVerify your client's `application.properties`:\n\n```properties\nspring.boot.admin.client.url=http://localhost:8080\n```\n\nMake sure the URL points to the running Admin Server.\n\n2. **Missing dependency**\n\nEnsure you have the client starter in your `pom.xml`:\n\n```xml\n<dependency>\n    <groupId>de.codecentric</groupId>\n    <artifactId>spring-boot-admin-starter-client</artifactId>\n    <version>${spring-boot-admin.version}</version>\n</dependency>\n```\n\n3. **Network connectivity**\n\nTest if the client can reach the admin server:\n\n```bash\ncurl http://localhost:8080/actuator/health\n```\n\n### I get \"401 Unauthorized\" errors during registration\n\n> **Related Issues:** [#803](https://github.com/codecentric/spring-boot-admin/issues/803), [#1190](https://github.com/codecentric/spring-boot-admin/issues/1190), [#470](https://github.com/codecentric/spring-boot-admin/issues/470)\n> **Stack Overflow:** [spring-boot-admin+security](https://stackoverflow.com/questions/tagged/spring-boot-admin+spring-security)\n\nThis occurs when the Admin Server has security enabled but the client doesn't provide credentials.\n\n**Solution:** Add credentials to your client configuration:\n\n```properties\nspring.boot.admin.client.username=admin\nspring.boot.admin.client.password=secret\n```\n\n### Registration works but client shows as \"OFFLINE\" immediately\n\n> **Related Issues:** [#319](https://github.com/codecentric/spring-boot-admin/issues/319), [#136](https://github.com/codecentric/spring-boot-admin/issues/136)\n> **Stack Overflow:** [spring-boot-actuator](https://stackoverflow.com/questions/tagged/spring-boot-actuator+spring-boot-admin)\n\nThis typically happens when:\n\n1. **Health endpoint is not accessible**\n\nEnsure the health endpoint is exposed:\n\n```properties\nmanagement.endpoints.web.exposure.include=health,info\n```\n\n2. **Client has security but Admin Server can't access it**\n\nProvide credentials via metadata:\n\n```properties\nspring.boot.admin.client.instance.metadata.user.name=actuator-user\nspring.boot.admin.client.instance.metadata.user.password=actuator-password\n```\n\n### Client registration works in local development but fails in Docker/Kubernetes\n\n> **Related Issues:** [#1537](https://github.com/codecentric/spring-boot-admin/issues/1537), [#1665](https://github.com/codecentric/spring-boot-admin/issues/1665)\n> **Stack Overflow:** [spring-boot+docker](https://stackoverflow.com/questions/tagged/spring-boot+docker), [spring-boot+kubernetes](https://stackoverflow.com/questions/tagged/spring-boot+kubernetes)\n\nThis is often due to hostname resolution issues.\n\n**Solution:** Use IP addresses instead of hostnames:\n\n```properties\nspring.boot.admin.client.instance.service-host-type=IP\n```\n\nOr specify the service URL explicitly:\n\n```properties\nspring.boot.admin.client.instance.service-base-url=http://my-service:8080\n```\n\n---\n\n## Actuator Endpoints\n\n### Only \"Health\" and \"Info\" endpoints are visible in the UI\n\n> **Related Issues:** [#1102](https://github.com/codecentric/spring-boot-admin/issues/1102)\n> **Stack Overflow:** [spring-boot-actuator+endpoints](https://stackoverflow.com/questions/tagged/spring-boot-actuator+endpoints)\n\nStarting with Spring Boot 2.x, most actuator endpoints are not exposed by default.\n\n**Solution:** Expose all endpoints in your client's `application.properties`:\n\n```properties\nmanagement.endpoints.web.exposure.include=*\n```\n\nFor production, be more selective:\n\n```properties\nmanagement.endpoints.web.exposure.include=health,info,metrics,env,loggers\n```\n\n### How do I verify endpoints are accessible?\n\nVisit the actuator discovery endpoint directly on your client application:\n\n```\nhttp://localhost:8080/actuator\n```\n\nYou should see a JSON response with links to all available endpoints.\n\n### Endpoints work locally but not through Spring Boot Admin\n\nCheck if security is blocking the Admin Server from accessing client endpoints:\n\n1. **Verify the Admin Server can access endpoints directly:**\n\n```bash\ncurl -u user:password http://client-host:8080/actuator/metrics\n```\n\n2. **Configure instance authentication:**\n\n```properties\n# Client application\nspring.boot.admin.client.instance.metadata.user.name=actuator\nspring.boot.admin.client.instance.metadata.user.password=secret\n```\n\n---\n\n## Service Discovery (Eureka, Consul, Kubernetes)\n\n### Applications registered in Eureka don't appear in Spring Boot Admin\n\n> **Related Issues:** [#1327](https://github.com/codecentric/spring-boot-admin/issues/1327), [#152](https://github.com/codecentric/spring-boot-admin/issues/152)\n> **Stack Overflow:** [spring-cloud-eureka](https://stackoverflow.com/questions/tagged/spring-cloud-eureka+spring-boot-admin)\n\n**Solution:** Enable registry fetching in your Admin Server:\n\n```properties\neureka.client.fetch-registry=true\neureka.client.registry-fetch-interval-seconds=5\n```\n\nAlso ensure your Admin Server has `@EnableDiscoveryClient`:\n\n```java\n@SpringBootApplication\n@EnableAdminServer\n@EnableDiscoveryClient\npublic class AdminServerApplication {\n    static void main(String[] args) {\n        SpringApplication.run(AdminServerApplication.class, args);\n    }\n}\n```\n\n### Service discovery takes too long (1.5+ minutes)\n\n> **Related Issues:** [#1327](https://github.com/codecentric/spring-boot-admin/issues/1327)\n\nThis is due to default registry fetch intervals.\n\n**Solution:** Speed up discovery:\n\n```properties\neureka.client.registry-fetch-interval-seconds=5\neureka.instance.lease-renewal-interval-in-seconds=10\n```\n\n### Services disappear from Admin Server when they go DOWN\n\n> **Related Issues:** [#1472](https://github.com/codecentric/spring-boot-admin/issues/1472)\n> **Stack Overflow:** [spring-cloud-discovery](https://stackoverflow.com/questions/tagged/spring-cloud+service-discovery)\n\nThis is a known issue with Eureka's `DiscoveryClient` implementation - it filters out non-UP services.\n\n**Workaround:** Use client registration instead of service discovery for critical monitoring, or implement a custom `ServiceInstanceConverter`.\n\n### Multiple instances of the same application only show one in Admin Server\n\n> **Related Issues:** [#856](https://github.com/codecentric/spring-boot-admin/issues/856), [#552](https://github.com/codecentric/spring-boot-admin/issues/552)\n> **Stack Overflow:** [spring-cloud+multiple-instances](https://stackoverflow.com/questions/tagged/spring-cloud)\n\nThis can happen with certain cloud platforms (PCF, Kubernetes) when instances share the same hostname.\n\n**Solution:** Ensure each instance has a unique instance ID:\n\n```properties\nspring.boot.admin.client.instance.metadata.instanceId=${spring.application.name}:${random.value}\n```\n\n---\n\n## Security & Authentication\n\n### How do I secure the Admin Server UI?\n\nAdd Spring Security dependency and configure authentication:\n\n```xml\n<dependency>\n    <groupId>org.springframework.boot</groupId>\n    <artifactId>spring-boot-starter-security</artifactId>\n</dependency>\n```\n\n```java\n@Configuration\npublic class SecurityConfig {\n\n    @Bean\n    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n        http\n            .authorizeHttpRequests(auth -> auth\n                .requestMatchers(\"/assets/**\", \"/login\").permitAll()\n                .anyRequest().authenticated()\n            )\n            .formLogin(form -> form.loginPage(\"/login\"))\n            .logout(logout -> logout.logoutUrl(\"/logout\"))\n            .httpBasic(withDefaults());\n        return http.build();\n    }\n}\n```\n\n### CORS errors when accessing client applications\n\n> **Related Issues:** [#1362](https://github.com/codecentric/spring-boot-admin/issues/1362), [#1691](https://github.com/codecentric/spring-boot-admin/issues/1691)\n> **Stack Overflow:** [spring-boot+cors](https://stackoverflow.com/questions/tagged/spring-boot+cors)\n\nWhen client applications run on different domains, browsers make preflight requests that can fail.\n\n**Solution:** Configure CORS on the Admin Server:\n\n```java\n@Bean\npublic WebMvcConfigurer corsConfigurer() {\n    return new WebMvcConfigurer() {\n        @Override\n        public void addCorsMappings(CorsRegistry registry) {\n            registry.addMapping(\"/**\")\n                .allowedOrigins(\"http://localhost:3000\")\n                .allowedMethods(\"GET\", \"POST\", \"PUT\", \"DELETE\")\n                .allowCredentials(true);\n        }\n    };\n}\n```\n\n### CSRF protection is blocking client registration\n\nBy default, Spring Security's CSRF protection can block registration requests.\n\n**Solution:** Exempt the registration endpoint:\n\n```java\nhttp.csrf(csrf -> csrf\n    .ignoringRequestMatchers(\"/instances\", \"/actuator/**\")\n);\n```\n\n---\n\n## Notifications\n\n### Mail notifications are not working\n\n> **Related Issues:** [#507](https://github.com/codecentric/spring-boot-admin/issues/507)\n> **Stack Overflow:** [spring-boot+email](https://stackoverflow.com/questions/tagged/spring-boot+email)\n\n**Checklist:**\n\n1. **Add mail dependency:**\n\n```xml\n<dependency>\n    <groupId>org.springframework.boot</groupId>\n    <artifactId>spring-boot-starter-mail</artifactId>\n</dependency>\n```\n\n2. **Configure mail properties:**\n\n```properties\nspring.boot.admin.notify.mail.enabled=true\nspring.boot.admin.notify.mail.from=admin@example.com\nspring.boot.admin.notify.mail.to=alerts@example.com\n\nspring.mail.host=smtp.example.com\nspring.mail.port=587\nspring.mail.username=user\nspring.mail.password=secret\nspring.mail.properties.mail.smtp.auth=true\nspring.mail.properties.mail.smtp.starttls.enable=true\n```\n\n3. **Test mail configuration separately** to ensure SMTP settings are correct.\n\n### Slack notifications are not sending\n\n> **Related Issues:** [#202](https://github.com/codecentric/spring-boot-admin/issues/202), [#356](https://github.com/codecentric/spring-boot-admin/issues/356)\n> **Stack Overflow:** [spring-boot+slack](https://stackoverflow.com/questions/tagged/spring-boot+slack)\n\n**Solution:** Configure Slack webhook:\n\n```properties\nspring.boot.admin.notify.slack.enabled=true\nspring.boot.admin.notify.slack.webhook-url=https://hooks.slack.com/services/YOUR/WEBHOOK/URL\nspring.boot.admin.notify.slack.channel=monitoring\n```\n\nNote: The channel name should not include the `#` prefix.\n\n### I'm receiving too many notifications\n\n> **Related Issues:** [#402](https://github.com/codecentric/spring-boot-admin/issues/402)\n\n**Solution:** Filter notifications by status changes:\n\n```properties\n# Ignore specific status transitions\nspring.boot.admin.notify.mail.ignore-changes=UNKNOWN:UP,UNKNOWN:OFFLINE\n```\n\nOr create a custom filtered notifier:\n\n```java\n@Bean\n@Primary\npublic FilteringNotifier filteringNotifier(Notifier delegate, InstanceRepository repository) {\n    return new FilteringNotifier(delegate, repository);\n}\n```\n\n---\n\n## Kubernetes & Cloud Deployments\n\n### Health checks fail with 401 errors in Kubernetes\n\n> **Related Issues:** [#1325](https://github.com/codecentric/spring-boot-admin/issues/1325)\n> **Stack Overflow:** [kubernetes+spring-boot](https://stackoverflow.com/questions/tagged/kubernetes+spring-boot+health-check)\n\nWhen health endpoints are secured in Kubernetes, the Admin Server cannot access them.\n\n**Solution:** Either:\n\n1. **Make health endpoint public** (for Kubernetes probes):\n\n```properties\nmanagement.endpoint.health.show-details=when-authorized\nmanagement.endpoints.web.exposure.include=health,info\n```\n\n2. **Configure separate ports** for management endpoints:\n\n```properties\nmanagement.server.port=8081\n```\n\nThen configure Kubernetes probes to use the management port.\n\n### Spring Boot Admin creates wrong health URL in Kubernetes\n\n> **Related Issues:** [#1522](https://github.com/codecentric/spring-boot-admin/issues/1522), [#437](https://github.com/codecentric/spring-boot-admin/issues/437)\n> **Stack Overflow:** [kubernetes+spring-boot-admin](https://stackoverflow.com/questions/tagged/kubernetes+spring-boot)\n\nThis happens with multi-port services (e.g., HTTP + gRPC).\n\n**Solution:** Explicitly configure the management base URL:\n\n```properties\nspring.boot.admin.client.instance.management-base-url=http://my-service:8081/actuator\n```\n\n### Liveness probe failures causing cascading restarts\n\n**Important:** Never configure liveness probes to depend on external system health checks.\n\n```yaml\n# Bad - includes external dependencies\nlivenessProbe:\n  httpGet:\n    path: /actuator/health\n\n# Good - only internal application health\nlivenessProbe:\n  httpGet:\n    path: /actuator/health/liveness\n```\n\nConfigure Spring Boot to separate liveness and readiness:\n\n```properties\nmanagement.health.probes.enabled=true\nmanagement.endpoint.health.group.liveness.include=ping\nmanagement.endpoint.health.group.readiness.include=db,redis\n```\n\n---\n\n## UI Customization\n\n### How do I add custom views to the Admin UI?\n\n> **Related Issues:** [#683](https://github.com/codecentric/spring-boot-admin/issues/683), [#867](https://github.com/codecentric/spring-boot-admin/issues/867)\n> **Stack Overflow:** [spring-boot-admin+customization](https://stackoverflow.com/questions/tagged/spring-boot-admin)\n\nCustom views must be implemented as Vue.js components and placed at:\n\n```\n/META-INF/spring-boot-admin-server-ui/extensions/{name}/\n```\n\n**Example registration:**\n\n```javascript\nSBA.use({\n  install({viewRegistry}) {\n    viewRegistry.addView({\n      name: 'custom-view',\n      path: '/custom',\n      component: CustomComponent,\n      label: 'Custom View',\n      order: 1000,\n    });\n  }\n});\n```\n\nFor development, configure the extension location:\n\n```properties\nspring.boot.admin.ui.extension-resource-locations=file:///path/to/custom-ui/target/dist/\n```\n\n### Can I conditionally show custom views based on instance metadata?\n\n> **Related Issues:** [#1385](https://github.com/codecentric/spring-boot-admin/issues/1385)\n\nYes, use the `isEnabled` function in view registration:\n\n```javascript\nviewRegistry.addView({\n  name: 'custom-view',\n  path: '/custom',\n  component: CustomComponent,\n  isEnabled: ({instance}) => instance.hasTag('custom-enabled')\n});\n```\n\n---\n\n## Performance & Troubleshooting\n\n### Admin Server is slow or uses too much memory\n\n**Common causes:**\n\n1. **Too many instances being monitored**\n2. **Aggressive monitoring intervals**\n3. **Event store growing too large**\n\n**Solutions:**\n\n1. **Adjust monitoring intervals:**\n\n```properties\nspring.boot.admin.monitor.status-interval=30s\nspring.boot.admin.monitor.info-interval=1m\n```\n\n2. **Use Hazelcast for clustered deployments:**\n\n```xml\n<dependency>\n    <groupId>de.codecentric</groupId>\n    <artifactId>spring-boot-admin-server-cloud</artifactId>\n</dependency>\n<dependency>\n    <groupId>com.hazelcast</groupId>\n    <artifactId>hazelcast</artifactId>\n</dependency>\n```\n\n3. **Increase JVM memory:**\n\n```bash\njava -Xmx1g -Xms512m -jar admin-server.jar\n```\n\n### How do I enable DEBUG logging for troubleshooting?\n\nAdd to `application.properties`:\n\n```properties\n# General Admin Server logging\nlogging.level.de.codecentric.boot.admin=DEBUG\n\n# Client registration logging\nlogging.level.de.codecentric.boot.admin.server.services.InstanceRegistry=DEBUG\n\n# HTTP client logging\nlogging.level.org.springframework.web.reactive.function.client=DEBUG\n```\n\n### Where can I get help?\n\n1. **Check the changelog:** [GitHub Releases](https://github.com/codecentric/spring-boot-admin/releases)\n2. **Search existing issues:** [GitHub Issues](https://github.com/codecentric/spring-boot-admin/issues)\n3. **Ask the community:**\n   - [Stack Overflow](https://stackoverflow.com/questions/tagged/spring-boot-admin) - Questions tagged `spring-boot-admin`\n   - [Stack Overflow Search](https://stackoverflow.com/search?q=spring-boot-admin) - Search all Spring Boot Admin discussions\n4. **Report bugs:** [Create an issue](https://github.com/codecentric/spring-boot-admin/issues/new)\n\n:::note Community Resources\n**For questions and troubleshooting:** Use [Stack Overflow](https://stackoverflow.com/questions/tagged/spring-boot-admin) with the `spring-boot-admin` tag. The FAQ entries above reference related Stack Overflow tags for each topic.\n\n**For bug reports and feature requests:** Use [GitHub Issues](https://github.com/codecentric/spring-boot-admin/issues). The FAQ entries reference specific GitHub issues where bugs were reported and resolved.\n\nFor broader Spring ecosystem questions, also check:\n- [Spring Boot on Stack Overflow](https://stackoverflow.com/questions/tagged/spring-boot)\n- [Spring Security on Stack Overflow](https://stackoverflow.com/questions/tagged/spring-security) (for security-related questions)\n- [Spring Cloud on Stack Overflow](https://stackoverflow.com/questions/tagged/spring-cloud) (for Eureka/Discovery questions)\n:::\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/src/pages/impressum.md",
    "content": "# Impressum\n\n## Hauptsitz der Gesellschaft\ncodecentric AG  \nHochstraße 11  \n42697 Solingen  \nTelefon: +49 (0) 212 23 36 28 0  \nTelefax: +49 (0) 212.23 36 28 79  \nE-Mail: info@codecentric.de  \n\n## Vorstand  \nRainer Vehns (Vorsitzender)  \nVerena Deller  \nStefan Riedel  \nLars Rückemann  \n\n## Handelsregister\nAmtsgericht Wuppertal, HRB 25917\n\n## Umsatzsteueridentifikationsnummer\nDE 119437798\n\n## Inhaltlich Verantwortlicher\nInhaltlich Verantwortlicher gemäß § 18 Abs. 2 MStV: Rainer Vehns\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/src/pages/index.tsx",
    "content": "import { Redirect } from \"@docusaurus/router\";\n\nexport default function Home() {\n  return <Redirect to='docs/index' />;\n}\n\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/src/pages/markdown-page.md",
    "content": "---\ntitle: Markdown page example\n---\n\n# Markdown page example\n\nYou don't need React to write simple standalone pages.\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/src/pages/privacy.md",
    "content": "# Datenschutzerklärung nach der DSGVO\n\n## Name und Anschrift der Verantwortlichen\n\nDie Verantwortliche im Sinne der Datenschutz-Grundverordnung und anderer nationaler Datenschutzgesetze der Mitgliedsstaaten sowie sonstiger datenschutzrechtlicher Bestimmungen ist die:\n\n*codecentric AG*  \nHochstraße 11  \n42697 Solingen  \nDeutschland  \nTel.: +49 [0] 212 23 36 28 0  \nE-Mail: office@codecentric.de  \nWebsite: www.codecentric.de  \n\n## Anschrift des Datenschutzbeauftragten\n\nDer Datenschutzbeauftragte der Verantwortlichen ist wie folgt zu erreichen:\n\n*codecentric AG*  \nHochstraße 11  \n42697 Solingen  \nDeutschland  \nE-Mail: datenschutz@codecentric.de  \n\n## Allgemeines zur Datenverarbeitung\n\n### Umfang der Verarbeitung personenbezogener Daten\n\nWir verarbeiten personenbezogene Daten unserer Nutzer grundsätzlich nur, soweit dies zur Bereitstellung einer funktionsfähigen Website sowie unserer Inhalte und Leistungen erforderlich ist. Die Verarbeitung personenbezogener Daten unserer Nutzer erfolgt regelmäßig nur nach Einwilligung des Nutzers. Eine Ausnahme gilt in solchen Fällen, in denen eine vorherige Einholung einer Einwilligung aus tatsächlichen Gründen nicht möglich ist und die Verarbeitung der Daten durch gesetzliche Vorschriften gestattet ist.\n\n### Rechtsgrundlage für die Verarbeitung personenbezogener Daten\n\nSoweit wir für Verarbeitungsvorgänge personenbezogener Daten eine Einwilligung der betroffenen Person einholen, dient Art. 6 Abs. 1 lit. a EU-Datenschutzgrundverordnung (DSGVO) als Rechtsgrundlage.\n\nBei der Verarbeitung von personenbezogenen Daten, die zur Erfüllung eines Vertrages, dessen Vertragspartei die betroffene Person ist, erforderlich ist, dient Art. 6 Abs. 1 lit. b DSGVO als Rechtsgrundlage. Dies gilt auch für Verarbeitungsvorgänge, die zur Durchführung vorvertraglicher Maßnahmen erforderlich sind.\n\nSoweit eine Verarbeitung personenbezogener Daten zur Erfüllung einer rechtlichen Verpflichtung erforderlich ist, der unser Unternehmen unterliegt, dient Art. 6 Abs. 1 lit. c DSGVO als Rechtsgrundlage.\n\nFür den Fall, dass lebenswichtige Interessen der betroffenen Person oder einer anderen natürlichen Person eine Verarbeitung personenbezogener Daten erforderlich machen, dient Art. 6 Abs. 1 lit. d DSGVO als Rechtsgrundlage.\n\nIst die Verarbeitung zur Wahrung eines berechtigten Interesses unseres Unternehmens oder eines Dritten erforderlich und überwiegen die Interessen, Grundrechte und Grundfreiheiten des Betroffenen das erstgenannte Interesse nicht, so dient Art. 6 Abs. 1 lit. f DSGVO als Rechtsgrundlage für die Verarbeitung.\n\n### Datenlöschung und Speicherdauer\n\nDie personenbezogenen Daten der betroffenen Person werden gelöscht oder gesperrt, sobald der Zweck der Speicherung entfällt. Eine Speicherung kann darüber hinaus erfolgen, wenn dies durch den europäischen oder nationalen Gesetzgeber in unionsrechtlichen Verordnungen, Gesetzen oder sonstigen Vorschriften, denen der Verantwortliche unterliegt, vorgesehen wurde. Eine Sperrung oder Löschung der Daten erfolgt auch dann, wenn eine durch die genannten Normen vorgeschriebene Speicherfrist abläuft, es sei denn, dass eine Erforderlichkeit zur weiteren Speicherung der Daten für einen Vertragsabschluss oder eine Vertragserfüllung besteht.\n\n## Bereitstellung der Website und Erstellung von Logfiles\n\n### Beschreibung und Umfang der Datenverarbeitung\n\nBei jedem Aufruf unserer Internetseite erfasst unser System automatisiert Daten und Informationen vom Computersystem des aufrufenden Rechners.\n\nFolgende Daten werden hierbei erhoben:\n\n1. Informationen über den Browsertyp und die verwendete Version\n2. Das Betriebssystem des Nutzers\n3. Den Internet-Service-Provider des Nutzers\n4. Die IP-Adresse des Nutzers\n5. Datum und Uhrzeit des Zugriffs\n6. Websites, von denen das System des Nutzers auf unsere Internetseite gelangt\n7. Websites, die vom System des Nutzers über unsere Website aufgerufen werden\n\nDie Daten werden ebenfalls in den Logfiles unseres Systems gespeichert. Eine Speicherung dieser Daten zusammen mit anderen personenbezogenen Daten des Nutzers findet nicht statt.\n\n### Rechtsgrundlage für die Datenverarbeitung\n\nRechtsgrundlage für die vorübergehende Speicherung der Daten und der Logfiles ist Art. 6 Abs. 1 lit. f DSGVO.\n\n### Zweck der Datenverarbeitung\n\nDie vorübergehende Speicherung der IP-Adresse durch das System ist notwendig, um eine Auslieferung der Website an den Rechner des Nutzers zu ermöglichen. Hierfür muss die IP-Adresse des Nutzers für die Dauer der Sitzung gespeichert bleiben.\n\nDie Speicherung in Logfiles erfolgt, um die Funktionsfähigkeit der Website sicherzustellen. Zudem dienen uns die Daten zur technischen Optimierung der Website und zur Sicherstellung der Sicherheit unserer informationstechnischen Systeme. Eine Auswertung der Daten zu Marketingzwecken findet in diesem Zusammenhang nicht statt.\n\nIn diesen Zwecken liegt auch unser berechtigtes Interesse an der Datenverarbeitung nach Art. 6 Abs. 1 lit. f DSGVO.\n\n### Dauer der Speicherung\n\nDie Daten werden gelöscht, sobald sie für die Erreichung des Zweckes ihrer Erhebung nicht mehr erforderlich sind. Im Falle der Erfassung der Daten zur Bereitstellung der Website ist dies der Fall, wenn die jeweilige Sitzung beendet ist.\n\nIm Falle der Speicherung der Daten in Logfiles ist dies nach spätestens sieben Tagen der Fall. Eine darüberhinausgehende Speicherung ist möglich. In diesem Fall werden die IP-Adressen der Nutzer gelöscht oder verfremdet, sodass eine Zuordnung des aufrufenden Clients nicht mehr möglich ist.\n\n### Widerspruchs- und Beseitigungsmöglichkeit\n\nDie Erfassung der Daten zur Bereitstellung der Website und die Speicherung der Daten in Logfiles ist für den Betrieb der Internetseite zwingend erforderlich. Es besteht folglich seitens des Nutzers keine Widerspruchsmöglichkeit.\n\n## Rechte der betroffenen Person\n\nWerden personenbezogene Daten von Ihnen verarbeitet, sind Sie Betroffener i.S.d. DSGVO und es stehen Ihnen folgende Rechte gegenüber dem Verantwortlichen zu:\n\n### Auskunftsrecht\n\nSie können von dem Verantwortlichen eine Bestätigung darüber verlangen, ob personenbezogene Daten, die Sie betreffen, von uns verarbeitet werden.\n\nLiegt eine solche Verarbeitung vor, können Sie von dem Verantwortlichen über folgende Informationen Auskunft verlangen:\n\n (1) die Zwecke, zu denen die personenbezogenen Daten verarbeitet werden;\n\n (2) die Kategorien von personenbezogenen Daten, welche verarbeitet werden;\n\n (3) die Empfänger bzw. die Kategorien von Empfängern, gegenüber denen die Sie betreffenden personenbezogenen Daten offengelegt wurden oder noch offengelegt werden;\n\n (4) die geplante Dauer der Speicherung der Sie betreffenden personenbezogenen Daten oder, falls konkrete Angaben hierzu nicht möglich sind, Kriterien für die Festlegung der Speicherdauer;\n\n (5) das Bestehen eines Rechts auf Berichtigung oder Löschung der Sie betreffenden personenbezogenen Daten, eines Rechts auf Einschränkung der Verarbeitung durch den Verantwortlichen oder eines Widerspruchsrechts gegen diese Verarbeitung;\n\n (6) das Bestehen eines Beschwerderechts bei einer Aufsichtsbehörde;\n\n (7) alle verfügbaren Informationen über die Herkunft der Daten, wenn die personenbezogenen Daten nicht bei der betroffenen Person erhoben werden;\n\n (8) das Bestehen einer automatisierten Entscheidungsfindung, einschließlich Profiling gemäß Art. 22 Abs. 1 und 4 DSGVO und – zumindest in diesen Fällen – aussagekräftige Informationen über die involvierte Logik sowie die Tragweite und die angestrebten Auswirkungen einer derartigen Verarbeitung für die betroffene Person.\n\nIhnen steht das Recht zu, Auskunft darüber zu verlangen, ob die Sie betreffenden personenbezogenen Daten in ein Drittland oder an eine internationale Organisation übermittelt werden. In diesem Zusammenhang können Sie verlangen, über die geeigneten Garantien gem. Art. 46 DSGVO im Zusammenhang mit der Übermittlung unterrichtet zu werden.\n\n### Recht auf Berichtigung\n\nSie haben ein Recht auf Berichtigung und/oder Vervollständigung gegenüber dem Verantwortlichen, sofern die verarbeiteten personenbezogenen Daten, die Sie betreffen, unrichtig oder unvollständig sind. Der Verantwortliche hat die Berichtigung unverzüglich vorzunehmen.\n\n### Recht auf Einschränkung der Verarbeitung\n\nUnter den folgenden Voraussetzungen können Sie die Einschränkung der Verarbeitung der Sie betreffenden personenbezogenen Daten verlangen:\n\n (1) wenn Sie die Richtigkeit der Sie betreffenden personenbezogenen für eine Dauer bestreiten, die es dem Verantwortlichen ermöglicht, die Richtigkeit der personenbezogenen Daten zu überprüfen;\n\n (2) die Verarbeitung unrechtmäßig ist und Sie die Löschung der personenbezogenen Daten ablehnen und stattdessen die Einschränkung der Nutzung der personenbezogenen Daten verlangen;\n\n (3) der Verantwortliche die personenbezogenen Daten für die Zwecke der Verarbeitung nicht länger benötigt, Sie diese jedoch zur Geltendmachung, Ausübung oder Verteidigung von Rechtsansprüchen benötigen, oder\n\n (4) wenn Sie Widerspruch gegen die Verarbeitung gemäß Art. 21 Abs. 1 DSGVO eingelegt haben und noch nicht feststeht, ob die berechtigten Gründe des Verantwortlichen gegenüber Ihren Gründen überwiegen.\n\nWurde die Verarbeitung der Sie betreffenden personenbezogenen Daten eingeschränkt, dürfen diese Daten – von ihrer Speicherung abgesehen – nur mit Ihrer Einwilligung oder zur Geltendmachung, Ausübung oder Verteidigung von Rechtsansprüchen oder zum Schutz der Rechte einer anderen natürlichen oder juristischen Person oder aus Gründen eines wichtigen öffentlichen Interesses der Union oder eines Mitgliedstaats verarbeitet werden.\n\nWurde die Einschränkung der Verarbeitung nach den o.g. Voraussetzungen eingeschränkt, werden Sie von dem Verantwortlichen unterrichtet, bevor die Einschränkung aufgehoben wird.\n\n### Recht auf Löschung\n\n#### Löschungspflicht\n\nSie können von dem Verantwortlichen verlangen, dass die Sie betreffenden personenbezogenen Daten unverzüglich gelöscht werden, und der Verantwortliche ist verpflichtet, diese Daten unverzüglich zu löschen, sofern einer der folgenden Gründe zutrifft:\n\n (1) Die Sie betreffenden personenbezogenen Daten sind für die Zwecke, für die sie erhoben oder auf sonstige Weise verarbeitet wurden, nicht mehr notwendig.\n\n (2) Sie widerrufen Ihre Einwilligung, auf die sich die Verarbeitung gem. Art. 6 Abs. 1 lit. a oder Art. 9 Abs. 2 lit. a DSGVO stützte, und es fehlt an einer anderweitigen Rechtsgrundlage für die Verarbeitung.\n\n (3) Sie legen gem. Art. 21 Abs. 1 DSGVO Widerspruch gegen die Verarbeitung ein und es liegen keine vorrangigen berechtigten Gründe für die Verarbeitung vor, oder Sie legen gem. Art. 21 Abs. 2 DSGVO Widerspruch gegen die Verarbeitung ein.\n\n (4) Die Sie betreffenden personenbezogenen Daten wurden unrechtmäßig verarbeitet.\n\n (5) Die Löschung der Sie betreffenden personenbezogenen Daten ist zur Erfüllung einer rechtlichen Verpflichtung nach dem Unionsrecht oder dem Recht der Mitgliedstaaten erforderlich, dem der Verantwortliche unterliegt.\n\n (6) Die Sie betreffenden personenbezogenen Daten wurden in Bezug auf angebotene Dienste der Informationsgesellschaft gemäß Art. 8 Abs. 1 DSGVO erhoben.\n\n#### Information an Dritte\n\nHat der Verantwortliche die Sie betreffenden personenbezogenen Daten öffentlich gemacht und ist er gem. Art. 17 Abs. 1 DSGVO zu deren Löschung verpflichtet, so trifft er unter Berücksichtigung der verfügbaren Technologie und der Implementierungskosten angemessene Maßnahmen, auch technischer Art, um für die Datenverarbeitung Verantwortliche, die die personenbezogenen Daten verarbeiten, darüber zu informieren, dass Sie als betroffene Person von ihnen die Löschung aller Links zu diesen personenbezogenen Daten oder von Kopien oder Replikationen dieser personenbezogenen Daten verlangt haben.\n\n#### Ausnahmen\n\nDas Recht auf Löschung besteht nicht, soweit die Verarbeitung erforderlich ist\n\n (1) zur Ausübung des Rechts auf freie Meinungsäußerung und Information;\n\n (2) zur Erfüllung einer rechtlichen Verpflichtung, die die Verarbeitung nach dem Recht der Union oder der Mitgliedstaaten, dem der Verantwortliche unterliegt, erfordert, oder zur Wahrnehmung einer Aufgabe, die im öffentlichen Interesse liegt oder in Ausübung öffentlicher Gewalt erfolgt, die dem Verantwortlichen übertragen wurde;\n\n (3) aus Gründen des öffentlichen Interesses im Bereich der öffentlichen Gesundheit gemäß Art. 9 Abs. 2 lit. h und i sowie Art. 9 Abs. 3 DSGVO;\n\n (4) für im öffentlichen Interesse liegende Archivzwecke, wissenschaftliche oder historische Forschungszwecke oder für statistische Zwecke gem. Art. 89 Abs. 1 DSGVO, soweit das unter Abschnitt a) genannte Recht voraussichtlich die Verwirklichung der Ziele dieser Verarbeitung unmöglich macht oder ernsthaft beeinträchtigt, oder\n\n (5) zur Geltendmachung, Ausübung oder Verteidigung von Rechtsansprüchen.\n\n### Recht auf Unterrichtung\n\nHaben Sie das Recht auf Berichtigung, Löschung oder Einschränkung der Verarbeitung gegenüber dem Verantwortlichen geltend gemacht, ist dieser verpflichtet, allen Empfängern, denen die Sie betreffenden personenbezogenen Daten offengelegt wurden, diese Berichtigung oder Löschung der Daten oder Einschränkung der Verarbeitung mitzuteilen, es sei denn, dies erweist sich als unmöglich oder ist mit einem unverhältnismäßigen Aufwand verbunden.\n\nIhnen steht gegenüber dem Verantwortlichen das Recht zu, über diese Empfänger unterrichtet zu werden.\n\n### Recht auf Datenübertragbarkeit\n\nSie haben das Recht, die Sie betreffenden personenbezogenen Daten, die Sie dem Verantwortlichen bereitgestellt haben, in einem strukturierten, gängigen und maschinenlesbaren Format zu erhalten. Außerdem haben Sie das Recht diese Daten einem anderen Verantwortlichen ohne Behinderung durch den Verantwortlichen, dem die personenbezogenen Daten bereitgestellt wurden, zu übermitteln, sofern\n\n (1) die Verarbeitung auf einer Einwilligung gem. Art. 6 Abs. 1 lit. a DSGVO oder Art. 9 Abs. 2 lit. a DSGVO oder auf einem Vertrag gem. Art. 6 Abs. 1 lit. b DSGVO beruht und\n\n (2) die Verarbeitung mithilfe automatisierter Verfahren erfolgt.\n\nIn Ausübung dieses Rechts haben Sie ferner das Recht, zu erwirken, dass die Sie betreffenden personenbezogenen Daten direkt von einem Verantwortlichen einem anderen Verantwortlichen übermittelt werden, soweit dies technisch machbar ist. Freiheiten und Rechte anderer Personen dürfen hierdurch nicht beeinträchtigt werden.\n\nDas Recht auf Datenübertragbarkeit gilt nicht für eine Verarbeitung personenbezogener Daten, die für die Wahrnehmung einer Aufgabe erforderlich ist, die im öffentlichen Interesse liegt oder in Ausübung öffentlicher Gewalt erfolgt, die dem Verantwortlichen übertragen wurde.\n\n### Widerspruchsrecht\n\nSie haben das Recht, aus Gründen, die sich aus ihrer besonderen Situation ergeben, jederzeit gegen die Verarbeitung der Sie betreffenden personenbezogenen Daten, die aufgrund von Art. 6 Abs. 1 lit. e oder f DSGVO erfolgt, Widerspruch einzulegen; dies gilt auch für ein auf diese Bestimmungen gestütztes Profiling.\n\nDer Verantwortliche verarbeitet die Sie betreffenden personenbezogenen Daten nicht mehr, es sei denn, er kann zwingende schutzwürdige Gründe für die Verarbeitung nachweisen, die Ihre Interessen, Rechte und Freiheiten überwiegen, oder die Verarbeitung dient der Geltendmachung, Ausübung oder Verteidigung von Rechtsansprüchen.\n\nWerden die Sie betreffenden personenbezogenen Daten verarbeitet, um Direktwerbung zu betreiben, haben Sie das Recht, jederzeit Widerspruch gegen die Verarbeitung der Sie betreffenden personenbezogenen Daten zum Zwecke derartiger Werbung einzulegen; dies gilt auch für das Profiling, soweit es mit solcher Direktwerbung in Verbindung steht.\n\nWidersprechen Sie der Verarbeitung für Zwecke der Direktwerbung, so werden die Sie betreffenden personenbezogenen Daten nicht mehr für diese Zwecke verarbeitet.\n\nSie haben die Möglichkeit, im Zusammenhang mit der Nutzung von Diensten der Informationsgesellschaft – ungeachtet der Richtlinie 2002/58/EG – Ihr Widerspruchsrecht mittels automatisierter Verfahren auszuüben, bei denen technische Spezifikationen verwendet werden.\n\n### Recht auf Widerruf der datenschutzrechtlichen Einwilligungserklärung\n\nSie haben das Recht, Ihre datenschutzrechtliche Einwilligungserklärung jederzeit zu widerrufen. Durch den Widerruf der Einwilligung wird die Rechtmäßigkeit der aufgrund der Einwilligung bis zum Widerruf erfolgten Verarbeitung nicht berührt.\n\n### Automatisierte Entscheidung im Einzelfall einschließlich Profiling\n\nSie haben das Recht, nicht einer ausschließlich auf einer automatisierten Verarbeitung – einschließlich Profiling – beruhenden Entscheidung unterworfen zu werden, die Ihnen gegenüber rechtliche Wirkung entfaltet oder Sie in ähnlicher Weise erheblich beeinträchtigt. Dies gilt nicht, wenn die Entscheidung\n\n (1) für den Abschluss oder die Erfüllung eines Vertrags zwischen Ihnen und dem Verantwortlichen erforderlich ist,\n\n (2) aufgrund von Rechtsvorschriften der Union oder der Mitgliedstaaten, denen der Verantwortliche unterliegt, zulässig ist und diese Rechtsvorschriften angemessene Maßnahmen zur Wahrung Ihrer Rechte und Freiheiten sowie Ihrer berechtigten Interessen enthalten oder\n\n (3) mit Ihrer ausdrücklichen Einwilligung erfolgt.\n\nAllerdings dürfen diese Entscheidungen nicht auf besonderen Kategorien personenbezogener Daten nach Art. 9 Abs. 1 DSGVO beruhen, sofern nicht Art. 9 Abs. 2 lit. a oder g DSGVO gilt und angemessene Maßnahmen zum Schutz der Rechte und Freiheiten sowie Ihrer berechtigten Interessen getroffen wurden.\n\nHinsichtlich der in (1) und (3) genannten Fälle trifft der Verantwortliche angemessene Maßnahmen, um die Rechte und Freiheiten sowie Ihre berechtigten Interessen zu wahren, wozu mindestens das Recht auf Erwirkung des Eingreifens einer Person seitens des Verantwortlichen, auf Darlegung des eigenen Standpunkts und auf Anfechtung der Entscheidung gehört.\n\n### Recht auf Beschwerde bei einer Aufsichtsbehörde\n\nUnbeschadet eines anderweitigen verwaltungsrechtlichen oder gerichtlichen Rechtsbehelfs steht Ihnen das Recht auf Beschwerde bei einer Aufsichtsbehörde, insbesondere in dem Mitgliedstaat ihres Aufenthaltsorts, ihres Arbeitsplatzes oder des Orts des mutmaßlichen Verstoßes, zu, wenn Sie der Ansicht sind, dass die Verarbeitung der Sie betreffenden personenbezogenen Daten gegen die DSGVO verstößt.\n\nDie Aufsichtsbehörde, bei der die Beschwerde eingereicht wurde, unterrichtet den Beschwerdeführer über den Stand und die Ergebnisse der Beschwerde, einschließlich der Möglichkeit eines gerichtlichen Rechtsbehelfs nach Art. 78 DSGVO.\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/src/propertiesUtil.ts",
    "content": "/**\n * Filters an array of Spring property definitions by their names based on provided keywords.\n *\n * @param properties - The array of Spring property definitions to filter\n * @param keywords - The array of keywords to search for in property names\n * @param includeOnly - If true, includes only properties matching keywords; if false, excludes matching properties (default: false)\n * @returns A filtered array of property definitions\n */\nexport const filterPropertiesByName = (\n  properties: Array<SpringPropertyDefinition>,\n  keywords: string[],\n  includeOnly: boolean = false\n) => {\n  if (!includeOnly) {\n    return properties.filter(property => !containsKeywordIgnoreCase(property.name, keywords));\n  }\n\n  return properties.filter(property => containsKeywordIgnoreCase(property.name, keywords));\n};\n\nfunction containsKeywordIgnoreCase(str: string, keywords: string[]): boolean {\n  const searchContext = str.toLowerCase();\n  return keywords.some(keyword => {\n    const searchTerm = keyword.toLowerCase();\n    const isIncluded = searchContext.includes(searchTerm);\n    return isIncluded;\n  });\n}\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/src/theme/DocCard/index.js",
    "content": "import React from \"react\";\nimport clsx from \"clsx\";\nimport Link from \"@docusaurus/Link\";\nimport { findFirstSidebarItemLink, useDocById } from \"@docusaurus/plugin-content-docs/client\";\nimport { usePluralForm } from \"@docusaurus/theme-common\";\nimport isInternalUrl from \"@docusaurus/isInternalUrl\";\nimport { translate } from \"@docusaurus/Translate\";\nimport Heading from \"@theme/Heading\";\nimport styles from \"./styles.module.css\";\nimport { Icon } from \"@iconify/react\";\n\nconst ICON_MAP = {\n  apps: <Icon icon='mdi:apps' height='24' />,\n  \"arrow-up\": <Icon icon='mdi:arrow-up' height='24' />,\n  bell: <Icon icon='mdi:bell' height='24' />,\n  book: <Icon icon='mdi:book-open-page-variant' height='24' />,\n  category: <Icon icon='mdi:folder-outline' height='24' />,\n  cloud: <Icon icon='mdi:cloud' height='24' />,\n  configuration: <Icon icon='mdi:wrench' height='24' />,\n  database: <Icon icon='mdi:database' height='24' />,\n  features: <Icon icon='mdi:function-variant' height='24' />,\n  \"file-code\": <Icon icon='mdi:file-code' height='24' />,\n  home: <Icon icon='mdi:home' height='24' />,\n  http: <Icon icon='mdi:web' height='24' />,\n  link: <Icon icon='mdi:link-variant' height='24' />,\n  notifications: <Icon icon='mdi:bell-ring' height='24' />,\n  package: <Icon icon='mdi:package-variant' height='24' />,\n  properties: <Icon icon='mdi:cog' height='24' />,\n  puzzle: <Icon icon='mdi:puzzle' height='24' />,\n  python: <Icon icon='mdi:language-python' height='24' />,\n  rocket: <Icon icon='mdi:rocket-launch' height='24' />,\n  server: <Icon icon='mdi:server-outline' height='24' />,\n  shield: <Icon icon='mdi:shield' height='24' />,\n  ui: <Icon icon='mdi:monitor-dashboard' height='24' />,\n  wrench: <Icon icon='mdi:wrench' height='24' />\n};\n\nfunction useCategoryItemsPlural() {\n  const { selectMessage } = usePluralForm();\n  return (count) =>\n    selectMessage(\n      count,\n      translate(\n        {\n          message: \"1 item|{count} items\",\n          id: \"theme.docs.DocCard.categoryDescription.plurals\",\n          description:\n            \"The default description for a category card in the generated index about how many items this category includes\"\n        },\n        { count }\n      )\n    );\n}\n\nfunction CardContainer({ href, children }) {\n  return (\n    <Link\n      href={href}\n      className={clsx(\"card padding--lg\", styles.cardContainer)}\n    >\n      {children}\n    </Link>\n  );\n}\n\nfunction CardLayout({ href, icon, title, description }) {\n  return (\n    <CardContainer href={href}>\n      <Heading\n        as='h2'\n        className={clsx(\"text--truncate\", styles.cardTitle)}\n        title={title}\n      >\n        {icon} {title}\n      </Heading>\n      {description && (\n        <p\n          className={clsx(\"text--truncate\", styles.cardDescription)}\n          title={description}\n        >\n          {description}\n        </p>\n      )}\n    </CardContainer>\n  );\n}\n\nexport default function DocCard({ item }) {\n  switch (item.type) {\n    case \"link\":\n      return <CardLink item={item} />;\n    case \"category\":\n      return <CardCategory item={item} />;\n    default:\n      throw new Error(`unknown item type ${JSON.stringify(item)}`);\n  }\n}\n\nfunction CardCategory({ item }) {\n  const href = findFirstSidebarItemLink(item);\n  const categoryItemsPlural = useCategoryItemsPlural();\n  // Unexpected: categories that don't have a link have been filtered upfront\n  if (!href) {\n    return null;\n  }\n  return (\n    <CardLayout\n      href={href}\n      icon={ICON_MAP[\"category\"]}\n      title={item.label}\n      description={item.description ?? categoryItemsPlural(item.items.length)}\n    />\n  );\n}\n\nfunction CardLink({ item }) {\n  const doc = useDocById(item.docId ?? undefined);\n\n  return (\n    <>\n      <CardLayout\n        href={item.href}\n        icon={ICON_MAP[item?.customProps?.icon] ?? (isInternalUrl(item.href) ? \"📄️\" : \"🔗\")}\n        title={item.label}\n        description={item.description ?? doc?.description}\n      />\n    </>\n  );\n}\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/src/theme/DocCard/styles.module.css",
    "content": ".cardContainer {\n    --ifm-link-color: var(--ifm-color-emphasis-800);\n    --ifm-link-hover-color: var(--ifm-color-emphasis-700);\n    --ifm-link-hover-decoration: none;\n\n    box-shadow: 0 1.5px 3px 0 rgb(0 0 0 / 15%);\n    border: 1px solid var(--ifm-color-emphasis-200);\n    transition: all var(--ifm-transition-fast) ease;\n    transition-property: border, box-shadow;\n}\n\n.cardContainer:hover {\n    border-color: var(--ifm-color-primary);\n    box-shadow: 0 3px 6px 0 rgb(0 0 0 / 20%);\n}\n\n.cardContainer *:last-child {\n    margin-bottom: 0;\n}\n\n.cardTitle {\n    font-size: 1.2rem;\n    display: inline-flex;\n    gap: 0.25rem;\n}\n\n.cardDescription {\n    font-size: 0.8rem;\n}\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/src/theme/MDXComponents.ts",
    "content": "import MDXComponents from '@theme-original/MDXComponents';\nimport { Icon } from '@iconify/react';\n\nexport default {\n  // Re-use the default mapping\n  ...MDXComponents,\n  IIcon: Icon, // Make the iconify Icon component available in MDX as <icon />.\n};\n"
  },
  {
    "path": "spring-boot-admin-docs/src/site/static/.nojekyll",
    "content": ""
  },
  {
    "path": "spring-boot-admin-docs/src/site/tsconfig.json",
    "content": "{\n  // This file is not used in compilation. It is here just for a nice editor experience.\n  \"extends\": \"@docusaurus/tsconfig\",\n  \"compilerOptions\": {\n    \"target\": \"es2023\",\n    \"lib\": [\n      \"es2023\",\n      \"dom\"\n    ],\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@sba\": [\n        \"<rootDir>/../../..\"\n      ]\n    }\n  },\n  \"include\": [\n    \"**/*.mdx\"\n  ]\n}\n"
  },
  {
    "path": "spring-boot-admin-samples/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright 2014-2019 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>spring-boot-admin-samples</artifactId>\n\n    <packaging>pom</packaging>\n\n    <name>Spring Boot Admin Samples</name>\n    <description>Spring Boot Admin Samples</description>\n\n    <parent>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-build</artifactId>\n        <version>${revision}</version>\n        <relativePath>../spring-boot-admin-build</relativePath>\n    </parent>\n\n    <modules>\n        <module>spring-boot-admin-sample-custom-ui</module>\n        <module>spring-boot-admin-sample-servlet</module>\n        <module>spring-boot-admin-sample-reactive</module>\n        <module>spring-boot-admin-sample-war</module>\n        <module>spring-boot-admin-sample-hazelcast</module>\n    </modules>\n\n    <dependencyManagement>\n        <dependencies>\n            <dependency>\n                <groupId>de.codecentric</groupId>\n                <artifactId>spring-boot-admin-sample-custom-ui</artifactId>\n                <version>${revision}</version>\n            </dependency>\n        </dependencies>\n    </dependencyManagement>\n\n    <build>\n        <!-- Turn on filtering by default for application properties -->\n        <resources>\n            <resource>\n                <directory>${project.basedir}/src/main/resources</directory>\n                <filtering>true</filtering>\n                <includes>\n                    <include>**/application*.yml</include>\n                    <include>**/application*.yaml</include>\n                    <include>**/application*.properties</include>\n                </includes>\n            </resource>\n            <resource>\n                <directory>${project.basedir}/src/main/resources</directory>\n                <excludes>\n                    <exclude>**/application*.yml</exclude>\n                    <exclude>**/application*.yaml</exclude>\n                    <exclude>**/application*.properties</exclude>\n                </excludes>\n            </resource>\n        </resources>\n\n        <pluginManagement>\n            <plugins>\n                <plugin>\n                    <groupId>org.apache.maven.plugins</groupId>\n                    <artifactId>maven-javadoc-plugin</artifactId>\n                    <configuration>\n                        <doclint>none</doclint>\n                    </configuration>\n                </plugin>\n            </plugins>\n        </pluginManagement>\n    </build>\n\n    <profiles>\n        <profile>\n            <id>include-cloud</id>\n            <activation>\n                <property>\n                    <name>!excludeSpringCloud</name>\n                </property>\n            </activation>\n            <modules>\n                <module>spring-boot-admin-sample-eureka</module>\n                <module>spring-boot-admin-sample-consul</module>\n                <module>spring-boot-admin-sample-zookeeper</module>\n            </modules>\n        </profile>\n        <profile>\n            <id>travis</id>\n            <activation>\n                <property>\n                    <name>env.TRAVIS</name>\n                    <value>true</value>\n                </property>\n            </activation>\n            <build>\n                <plugins>\n                    <plugin>\n                        <groupId>org.jacoco</groupId>\n                        <artifactId>jacoco-maven-plugin</artifactId>\n                        <configuration>\n                            <skip>true</skip>\n                        </configuration>\n                    </plugin>\n                </plugins>\n            </build>\n        </profile>\n    </profiles>\n</project>\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-consul/docker-compose.yml",
    "content": "services:\n  consul:\n    image: hashicorp/consul\n    ports:\n      - \"8500:8500\"\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-consul/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright 2014-2019 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>spring-boot-admin-sample-consul</artifactId>\n\n    <name>Spring Boot Admin Sample Consul</name>\n    <description>Spring Boot Admin Sample using Consul</description>\n\n    <parent>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-samples</artifactId>\n        <version>${revision}</version>\n        <relativePath>../pom.xml</relativePath>\n    </parent>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-webmvc</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-security</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>de.codecentric</groupId>\n            <artifactId>spring-boot-admin-starter-server</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-consul-discovery</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <finalName>${project.artifactId}</finalName>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>repackage</goal>\n                            <goal>build-info</goal>\n                        </goals>\n                    </execution>\n                </executions>\n                <configuration>\n                    <mainClass>de.codecentric.boot.admin.sample.SpringBootAdminConsulApplication</mainClass>\n                    <addResources>false</addResources>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-consul/src/main/java/de/codecentric/boot/admin/sample/SpringBootAdminConsulApplication.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.sample;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Profile;\nimport org.springframework.security.config.Customizer;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.web.SecurityFilterChain;\nimport org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;\nimport org.springframework.security.web.csrf.CookieCsrfTokenRepository;\nimport org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;\n\nimport de.codecentric.boot.admin.server.config.AdminServerProperties;\nimport de.codecentric.boot.admin.server.config.EnableAdminServer;\n\nimport static org.springframework.http.HttpMethod.DELETE;\nimport static org.springframework.http.HttpMethod.POST;\n\n@SpringBootApplication\n@EnableDiscoveryClient\n@EnableAdminServer\npublic class SpringBootAdminConsulApplication {\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(SpringBootAdminConsulApplication.class, args);\n\t}\n\n\t@Profile(\"insecure\")\n\t@Configuration(proxyBeanMethods = false)\n\tpublic static class SecurityPermitAllConfig {\n\n\t\tprivate final String adminContextPath;\n\n\t\tpublic SecurityPermitAllConfig(AdminServerProperties adminServerProperties) {\n\t\t\tthis.adminContextPath = adminServerProperties.getContextPath();\n\t\t}\n\n\t\t@Bean\n\t\tprotected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n\t\t\thttp.authorizeHttpRequests((authorizeRequests) -> authorizeRequests.anyRequest().permitAll())\n\t\t\t\t.csrf((csrf) -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())\n\t\t\t\t\t.ignoringRequestMatchers(\n\t\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults()\n\t\t\t\t\t\t\t\t.matcher(POST, this.adminContextPath + \"/instances\"),\n\t\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults()\n\t\t\t\t\t\t\t\t.matcher(DELETE, this.adminContextPath + \"/instances/*\"),\n\t\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults().matcher(this.adminContextPath + \"/actuator/**\")));\n\t\t\treturn http.build();\n\t\t}\n\n\t}\n\n\t@Profile(\"secure\")\n\t@Configuration(proxyBeanMethods = false)\n\tpublic static class SecuritySecureConfig {\n\n\t\tprivate final String adminContextPath;\n\n\t\tpublic SecuritySecureConfig(AdminServerProperties adminServerProperties) {\n\t\t\tthis.adminContextPath = adminServerProperties.getContextPath();\n\t\t}\n\n\t\t@Bean\n\t\tprotected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n\t\t\tSavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();\n\t\t\tsuccessHandler.setTargetUrlParameter(\"redirectTo\");\n\t\t\tsuccessHandler.setDefaultTargetUrl(this.adminContextPath + \"/\");\n\n\t\t\thttp.authorizeHttpRequests((authorizeRequests) -> authorizeRequests\n\t\t\t\t.requestMatchers(PathPatternRequestMatcher.withDefaults().matcher(this.adminContextPath + \"/assets/**\"))\n\t\t\t\t.permitAll()\n\t\t\t\t.requestMatchers(PathPatternRequestMatcher.withDefaults().matcher(this.adminContextPath + \"/login\"))\n\t\t\t\t.permitAll()\n\t\t\t\t.anyRequest()\n\t\t\t\t.authenticated())\n\t\t\t\t.formLogin((formLogin) -> formLogin.loginPage(this.adminContextPath + \"/login\")\n\t\t\t\t\t.successHandler(successHandler))\n\t\t\t\t.logout((logout) -> logout.logoutUrl(this.adminContextPath + \"/logout\"))\n\t\t\t\t.httpBasic(Customizer.withDefaults())\n\t\t\t\t.csrf((csrf) -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())\n\t\t\t\t\t.ignoringRequestMatchers(\n\t\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults()\n\t\t\t\t\t\t\t\t.matcher(POST, this.adminContextPath + \"/instances\"),\n\t\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults()\n\t\t\t\t\t\t\t\t.matcher(DELETE, this.adminContextPath + \"/instances/*\"),\n\t\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults().matcher(this.adminContextPath + \"/actuator/**\")));\n\n\t\t\treturn http.build();\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-consul/src/main/resources/application-dev.yml",
    "content": "spring:\n  boot:\n    admin:\n      ui:\n        cache:\n          no-cache: true\n        template-location: file:../../spring-boot-admin-server-ui/target/dist/\n        resource-locations: file:../../spring-boot-admin-server-ui/target/dist/\n        cache-templates: false\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-consul/src/main/resources/application-insecure.yml",
    "content": "info.tags.security: insecure\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-consul/src/main/resources/application-secure.yml",
    "content": "spring:\n  security:\n    user:\n      name: \"user\"\n      password: \"password\"\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-consul/src/main/resources/application.yml",
    "content": "spring:\n  application:\n    name: consul-example\n  cloud:\n    config:\n      enabled: false\n    consul:\n      host: localhost\n      port: 8500\n      discovery:\n        metadata:\n          management-context-path: /foo\n          health-path: /ping\n          user-name: user\n          user-password: password\n  profiles:\n    active:\n      - secure\n  boot:\n    admin:\n      discovery:\n        ignored-services: consul\n\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n      path-mapping:\n        health: /ping\n      base-path: /foo\n  endpoint:\n    health:\n      show-details: ALWAYS\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-consul/src/test/java/de/codecentric/boot/admin/sample/SpringBootAdminConsulApplicationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.sample;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\n\n@ExtendWith(SpringExtension.class)\n@SpringBootTest(classes = { SpringBootAdminConsulApplication.class },\n\t\tproperties = { \"spring.cloud.consul.enabled=false\" })\nclass SpringBootAdminConsulApplicationTest {\n\n\t@Test\n\tvoid contextLoads() {\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-custom-ui/.gitignore",
    "content": ".DS_Store\nnode_modules\n/dist\n\n# local env files\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw*\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-custom-ui/README.md",
    "content": "spring-boot-admin-sample-custom-ui\n================================\n\n### Building this module\nThe jar **can be build with Maven** using the frontend-maven-plugin. This will download node.js and npm automatically.\nIf you don't want to use the maven exec run the following commands:\n\n### Running Spring Boot Admin Server for development\nTo develop the ui on an running server the best to do is\n\n1. Running the ui build in watch mode so the resources get updated:\n```shell\nnpm run build:watch\n```\n2. Run a Spring Boot Admin Server instances with the template-location and resource-location pointing to the build output and disable caching:\n```\nspring.boot.admin.ui.cache.no-cache: true\nspring.boot.admin.ui.extension-resource-locations: file:../spring-boot-admin-sample-custom-ui/target/dist/\nspring.boot.admin.ui.cache-templates: false\n```\nOr just start the [spring-boot-admin-sample-servlet](../spring-boot-admin-sample-servlet) project using the `dev` profile.\n\n### Build\n```shell\nnpm install\nnpm run build\n```\n\nRepeated build with watching the files:\n```shell\nnpm run build:watch\n```\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-custom-ui/babel.config.js",
    "content": "module.exports = {\n  presets: [\n    '@vue/cli-plugin-babel/preset'\n  ]\n}\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-custom-ui/package.json",
    "content": "{\n  \"name\": \"spring-boot-admin-sample-custom-ui\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"vite build\",\n    \"build:dev\": \"NODE_ENV=development vite build --emptyOutDir --sourcemap --mode development\",\n    \"build:watch\": \"NODE_ENV=development vite build --emptyOutDir --watch --sourcemap --mode development\",\n    \"preview\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"vue\": \"3.5.30\"\n  },\n  \"devDependencies\": {\n    \"@vitejs/plugin-vue\": \"6.0.5\",\n    \"vite\": \"7.3.1\",\n    \"vite-plugin-static-copy\": \"^3.0.0\"\n  },\n  \"browserslist\": [\n    \"> 1%\",\n    \"last 2 versions\",\n    \"not ie <= 8\"\n  ]\n}\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-custom-ui/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright 2014-2018 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>spring-boot-admin-sample-custom-ui</artifactId>\n\n    <name>Spring Boot Admin Server custom UI</name>\n    <description>Spring Boot Admin Server custom UI</description>\n\n    <parent>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-samples</artifactId>\n        <version>${revision}</version>\n        <relativePath>../pom.xml</relativePath>\n    </parent>\n\n    <dependencies>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>com.github.eirslett</groupId>\n                <artifactId>frontend-maven-plugin</artifactId>\n                <executions>\n                    <execution>\n                        <id>install-node-and-npm</id>\n                        <goals>\n                            <goal>install-node-and-npm</goal>\n                        </goals>\n                        <configuration>\n                            <nodeVersion>${node.version}</nodeVersion>\n                        </configuration>\n                    </execution>\n                    <execution>\n                        <id>npm-install</id>\n                        <goals>\n                            <goal>npm</goal>\n                        </goals>\n                        <configuration>\n                            <arguments>ci --prefer-offline --no-progress --no-audit --silent</arguments>\n                        </configuration>\n                    </execution>\n                    <execution>\n                        <id>npm-build</id>\n                        <goals>\n                            <goal>npm</goal>\n                        </goals>\n                        <configuration>\n                            <arguments>run build</arguments>\n                            <environmentVariables>\n                                <PROJECT_VERSION>${project.version}</PROJECT_VERSION>\n                            </environmentVariables>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-resources-plugin</artifactId>\n                <executions>\n                    <execution>\n                        <id>copy-resources</id>\n                        <phase>process-resources</phase>\n                        <goals>\n                            <goal>copy-resources</goal>\n                        </goals>\n                        <configuration>\n                            <outputDirectory>\n                                ${project.build.outputDirectory}/META-INF/spring-boot-admin-server-ui/extensions/custom\n                            </outputDirectory>\n                            <resources>\n                                <resource>\n                                    <directory>${project.build.directory}/dist</directory>\n                                    <filtering>false</filtering>\n                                </resource>\n                            </resources>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-custom-ui/src/custom-endpoint.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITION S OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div class=\"custom\">\n    <p>Instance: <span v-text=\"instance.id\" /></p>\n    <p>Output: <span v-html=\"text\" /></p>\n  </div>\n</template>\n\n<script>\nexport default {\n  props: {\n    instance: {\n      //<1>\n      type: Object,\n      required: true,\n    },\n  },\n  data: () => ({\n    text: \"\",\n  }),\n  async created() {\n    console.log(this.instance);\n    const response = await this.instance.axios.get(\"actuator/custom\"); //<2>\n    this.text = response.data;\n  },\n};\n</script>\n\n<style lang=\"css\" scoped>\n.custom {\n  font-size: 20px;\n  width: 80%;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-custom-ui/src/custom-subitem.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITION S OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div class=\"custom\">\n    <h1 v-text=\"t('custom_sub.hello')\"></h1>\n  </div>\n</template>\n\n<script setup>\n/* global:SBA */\nconst { t, mergeLocaleMessage } = SBA.useI18n();\n\nmergeLocaleMessage(\"en\", {\n  custom_sub: {\n    hello: \"Hello from the submenu item\",\n  },\n});\nmergeLocaleMessage(\"de\", {\n  custom_sub: {\n    hello: \"Hallo, ich bin ein Sub-Menü-Element!\",\n  },\n});\n</script>\n\n<style lang=\"css\" scoped>\n.custom {\n  font-size: 20px;\n  width: 80%;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-custom-ui/src/custom.css",
    "content": ".mx-1 {\n   margin-left: .5rem;\n    margin-right: .5rem;\n}\n\n.m-4 {\n    margin: 2rem;\n}\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-custom-ui/src/custom.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div class=\"m-4\">\n    <template v-for=\"application in applications\" :key=\"application.name\">\n      <sba-panel :title=\"application.name\">\n        This application has the following instances:\n\n        <ul>\n          <template v-for=\"instance in application.instances\">\n            <li>\n              <span class=\"mx-1\" v-text=\"instance.registration.name\"></span>\n\n              <!-- SBA components are registered globally and can be used without importing them! -->\n              <!-- They are defined in spring-boot-admin-server-ui -->\n              <sba-status :status=\"instance.statusInfo.status\" class=\"mx-1\" />\n              <sba-tag :value=\"instance.id\" class=\"mx-1\" label=\"id\" />\n            </li>\n          </template>\n        </ul>\n      </sba-panel>\n    </template>\n    <pre v-text=\"applications\"></pre>\n  </div>\n</template>\n\n<script>\n/* global SBA */\nimport \"./custom.css\";\n\nexport default {\n  setup() {\n    const { applications } = SBA.useApplicationStore(); //<1>\n    return {\n      applications,\n    };\n  },\n  methods: {\n    stringify: JSON.stringify,\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-custom-ui/src/handle.vue",
    "content": "<!--\n  - Copyright 2014-2019 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <span>\n    <span v-text=\"t('custom.label')\" />\n    <span>&nbsp;(UP: {{ upCount }})</span>\n  </span>\n</template>\n\n<script setup>\n/* global SBA */\nimport { computed } from \"vue\";\n\nconst { applications } = SBA.useApplicationStore();\nconst { t } = SBA.useI18n();\nconst upCount = computed({\n  get() {\n    return applications.value.reduce((current, next) => {\n      return (\n        current +\n        next.instances.filter((instance) => instance.statusInfo.status === \"UP\")\n          .length\n      );\n    }, 0);\n  },\n});\n</script>\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-custom-ui/src/index.js",
    "content": "/*\n * Copyright 2014-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/* global SBA */\nimport customEndpoint from \"./custom-endpoint.vue\";\nimport customSubitem from \"./custom-subitem.vue\";\nimport custom from \"./custom.vue\";\nimport handle from \"./handle.vue\";\n\n// tag::customization-ui-toplevel[]\nSBA.use({\n  install({ viewRegistry, i18n }) {\n    viewRegistry.addView({\n      name: \"custom\", //<1>\n      path: \"/custom\", //<2>\n      component: custom, //<3>\n      group: \"custom\", //<4>\n      handle, //<5>\n      order: 1000, //<6>\n    });\n    i18n.mergeLocaleMessage(\"en\", {\n      custom: {\n        label: \"My Extensions\", //<7>\n      },\n    });\n    i18n.mergeLocaleMessage(\"de\", {\n      custom: {\n        label: \"Meine Erweiterung\",\n      },\n    });\n  },\n});\n// end::customization-ui-toplevel[]\n\n// tag::customization-ui-child[]\nSBA.viewRegistry.addView({\n  name: \"customSub\",\n  parent: \"custom\", // <1>\n  path: \"/customSub\", // <2>\n  component: customSubitem,\n  label: \"Custom Sub\",\n  order: 1000,\n});\n// end::customization-ui-child[]\n\nSBA.viewRegistry.addView({\n  name: \"customSubUser\",\n  parent: \"user\", // <1>\n  path: \"/customSub\", // <2>\n  component: customSubitem,\n  label: \"Custom Sub In Usermenu\",\n  order: 1000,\n});\n\n// tag::customization-ui-endpoint[]\nSBA.viewRegistry.addView({\n  name: \"instances/custom\",\n  parent: \"instances\", // <1>\n  path: \"custom\",\n  component: customEndpoint,\n  label: \"Custom\",\n  group: \"custom\", // <2>\n  order: 1000,\n  isEnabled: ({ instance }) => {\n    return instance.hasEndpoint(\"custom\");\n  }, // <3>\n});\n// end::customization-ui-endpoint[]\n\n// tag::customization-ui-groups[]\nSBA.viewRegistry.setGroupIcon(\n  \"custom\", //<1>\n  `<svg xmlns='http://www.w3.org/2000/svg'\n        class='h-5 mr-3'\n        viewBox='0 0 576 512'><path d='M512 80c8.8 0 16 7.2 16 16V416c0 8.8-7.2 16-16 16H64c-8.8 0-16-7.2-16-16V96c0-8.8 7.2-16 16-16H512zM64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H512c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zM200 208c14.2 0 27 6.1 35.8 16c8.8 9.9 24 10.7 33.9 1.9s10.7-24 1.9-33.9c-17.5-19.6-43.1-32-71.5-32c-53 0-96 43-96 96s43 96 96 96c28.4 0 54-12.4 71.5-32c8.8-9.9 8-25-1.9-33.9s-25-8-33.9 1.9c-8.8 9.9-21.6 16-35.8 16c-26.5 0-48-21.5-48-48s21.5-48 48-48zm144 48c0-26.5 21.5-48 48-48c14.2 0 27 6.1 35.8 16c8.8 9.9 24 10.7 33.9 1.9s10.7-24 1.9-33.9c-17.5-19.6-43.1-32-71.5-32c-53 0-96 43-96 96s43 96 96 96c28.4 0 54-12.4 71.5-32c8.8-9.9 8-25-1.9-33.9s-25-8-33.9 1.9c-8.8 9.9-21.6 16-35.8 16c-26.5 0-48-21.5-48-48z'/>\n  </svg>` //<2>\n);\n// end::customization-ui-groups[]\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-custom-ui/src/routes.txt",
    "content": "/custom/**\n/customSub/**\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-custom-ui/vite.config.js",
    "content": "import vue from \"@vitejs/plugin-vue\";\nimport path from \"path\";\nimport { defineConfig } from \"vite\";\nimport { viteStaticCopy } from \"vite-plugin-static-copy\";\n\nexport default defineConfig({\n  plugins: [\n    vue(),\n    viteStaticCopy({\n      targets: [\n        {\n          src: \"src/routes.txt\",\n          dest: \"./\",\n        },\n      ],\n    }),\n  ],\n  build: {\n    target: \"es2015\",\n    sourcemap: true,\n    minify: false,\n    outDir: \"target/dist\",\n    lib: {\n      entry: path.resolve(__dirname, \"src/index.js\"),\n      name: \"CustomUi\",\n      formats: [\"umd\"],\n      fileName: () => \"custom-ui.js\",\n    },\n    define: {\n      __VUE_PROD_DEVTOOLS__: process.env.NODE_ENV === \"development\",\n    },\n    rollupOptions: {\n      external: [\"vue\"],\n      output: {\n        globals: {\n          vue: \"Vue\",\n        },\n      },\n    },\n  },\n});\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-eureka/docker-compose.yml",
    "content": "version: '2'\n\n\nservices:\n  eureka:\n    image: springcloud/eureka\n    container_name: eureka\n    ports:\n      - \"8761:8761\"\n    networks:\n      - \"discovery\"\n    environment:\n      - EUREKA_INSTANCE_PREFERIPADDRESS=true\n\n  admin:\n    build:\n      context: .\n      dockerfile: ./src/main/docker/Dockerfile\n    depends_on:\n      - eureka\n    container_name: admin\n    ports:\n     - \"8080:8080\"\n    networks:\n     - \"discovery\"\n    environment:\n      - EUREKA_SERVICE_URL=http://eureka:8761\n      - EUREKA_INSTANCE_PREFER_IP_ADDRESS=true\n      - LOGGING_FILE=/tmp/admin.log\n\n  config:\n    image: springcloud/configserver\n    container_name: config\n    depends_on:\n      - eureka\n    ports:\n      - \"8888:8888\"\n    networks:\n      - \"discovery\"\n    environment:\n      - EUREKA_SERVICE_URL=http://eureka:8761\n      - EUREKA_INSTANCE_PREFER_IP_ADDRESS=true\n\n  customers:\n    image: springcloud/customers\n    depends_on:\n      - config\n      - rabbit\n    networks:\n      - \"discovery\"\n    environment:\n      - EUREKA_INSTANCE_PREFER_IP_ADDRESS=true\n      - CONFIG_SERVER_URI=http://config:8888\n      - RABBITMQ_HOST=rabbit\n      - RABBITMQ_PORT=5672\n\n  stores:\n    image: springcloud/stores\n    depends_on:\n      - config\n      - rabbit\n      - mongodb\n    networks:\n      - \"discovery\"\n    environment:\n      - EUREKA_INSTANCE_PREFER_IP_ADDRESS=true\n      - CONFIG_SERVER_URI=http://config:8888\n      - RABBITMQ_HOST=rabbit\n      - RABBITMQ_PORT=5672\n      - MONGODB_HOST=mongodb\n      - MONGODB_PORT=27017\n\n  customersui:\n    image: springcloud/customersui\n    depends_on:\n      - config\n      - customers\n      - stores\n    ports:\n      - \"80:80\"\n    links:\n      - \"config\"\n    networks:\n      - \"discovery\"\n    environment:\n      - SERVER_PORT=80\n      - EUREKA_INSTANCE_PREFER_IP_ADDRESS=true\n      - CONFIG_SERVER_URI=http://config:8888\n\n  mongodb:\n    image: tutum/mongodb\n    container_name: mongodb\n    ports:\n      - \"27017:27017\"\n    networks:\n      - \"discovery\"\n    environment:\n      - AUTH=no\n\n  rabbit:\n    image: \"rabbitmq:4\"\n    container_name: rabbit\n    ports:\n     - \"5672:5672\"\n    networks:\n      - \"discovery\"\n\nnetworks:\n  discovery:\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-eureka/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright 2014-2019 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>spring-boot-admin-sample-eureka</artifactId>\n\n    <name>Spring Boot Admin Sample Eureka</name>\n    <description>Spring Boot Admin Sample using Eureka</description>\n\n    <parent>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-samples</artifactId>\n        <version>${revision}</version>\n        <relativePath>../pom.xml</relativePath>\n    </parent>\n\n    <dependencies>\n        <dependency>\n            <groupId>de.codecentric</groupId>\n            <artifactId>spring-boot-admin-starter-server</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-webmvc</artifactId>\n            <exclusions>\n                <exclusion>\n                    <groupId>org.springframework.boot</groupId>\n                    <artifactId>spring-boot-starter-tomcat</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-security</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <finalName>${project.artifactId}</finalName>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>repackage</goal>\n                            <goal>build-info</goal>\n                        </goals>\n                    </execution>\n                </executions>\n                <configuration>\n                    <mainClass>de.codecentric.boot.admin.SpringBootAdminEurekaApplication</mainClass>\n                    <addResources>false</addResources>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-eureka/src/main/docker/Dockerfile",
    "content": "FROM eclipse-temurin:25\nVOLUME /tmp\nADD target/spring-boot-admin-sample-eureka.jar /app.jar\nRUN bash -c 'touch /app.jar'\nEXPOSE 8080\nENTRYPOINT [\"java\",\"-jar\",\"/app.jar\"]\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-eureka/src/main/java/de/codecentric/boot/admin/SpringBootAdminEurekaApplication.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin;\n\nimport java.net.URI;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.EnableAutoConfiguration;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Profile;\nimport org.springframework.security.config.Customizer;\nimport org.springframework.security.config.web.server.ServerHttpSecurity;\nimport org.springframework.security.web.server.SecurityWebFilterChain;\nimport org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler;\nimport org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;\nimport org.springframework.security.web.server.authentication.logout.RedirectServerLogoutSuccessHandler;\nimport org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;\n\nimport de.codecentric.boot.admin.server.config.AdminServerProperties;\nimport de.codecentric.boot.admin.server.config.EnableAdminServer;\n\n@Configuration(proxyBeanMethods = false)\n@EnableAutoConfiguration\n@EnableDiscoveryClient\n@EnableAdminServer\npublic class SpringBootAdminEurekaApplication {\n\n\tprivate final AdminServerProperties adminServer;\n\n\tpublic SpringBootAdminEurekaApplication(AdminServerProperties adminServer) {\n\t\tthis.adminServer = adminServer;\n\t}\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(SpringBootAdminEurekaApplication.class, args);\n\t}\n\n\t@Bean\n\t@Profile(\"insecure\")\n\tpublic SecurityWebFilterChain securityWebFilterChainPermitAll(ServerHttpSecurity http) {\n\t\treturn http.authorizeExchange((authorizeExchange) -> authorizeExchange.anyExchange().permitAll())\n\t\t\t.csrf(ServerHttpSecurity.CsrfSpec::disable)\n\t\t\t.build();\n\t}\n\n\t@Bean\n\t@Profile(\"secure\")\n\tpublic SecurityWebFilterChain securityWebFilterChainSecure(ServerHttpSecurity http) {\n\t\treturn http\n\t\t\t.authorizeExchange(\n\t\t\t\t\t(authorizeExchange) -> authorizeExchange.pathMatchers(this.adminServer.path(\"/assets/**\"))\n\t\t\t\t\t\t.permitAll()\n\t\t\t\t\t\t.pathMatchers(\"/actuator/health/**\")\n\t\t\t\t\t\t.permitAll()\n\t\t\t\t\t\t.pathMatchers(this.adminServer.path(\"/login\"))\n\t\t\t\t\t\t.permitAll()\n\t\t\t\t\t\t.anyExchange()\n\t\t\t\t\t\t.authenticated())\n\t\t\t.formLogin((formLogin) -> formLogin.loginPage(this.adminServer.path(\"/login\"))\n\t\t\t\t.authenticationSuccessHandler(loginSuccessHandler(this.adminServer.path(\"/\"))))\n\t\t\t.logout((logout) -> logout.logoutUrl(this.adminServer.path(\"/logout\"))\n\t\t\t\t.logoutSuccessHandler(logoutSuccessHandler(this.adminServer.path(\"/login?logout\"))))\n\t\t\t.httpBasic(Customizer.withDefaults())\n\t\t\t.csrf(ServerHttpSecurity.CsrfSpec::disable)\n\t\t\t.build();\n\t}\n\n\t// The following two methods are only required when setting a custom base-path (see\n\t// 'basepath' profile in application.yml)\n\tprivate ServerLogoutSuccessHandler logoutSuccessHandler(String uri) {\n\t\tRedirectServerLogoutSuccessHandler successHandler = new RedirectServerLogoutSuccessHandler();\n\t\tsuccessHandler.setLogoutSuccessUrl(URI.create(uri));\n\t\treturn successHandler;\n\t}\n\n\tprivate ServerAuthenticationSuccessHandler loginSuccessHandler(String uri) {\n\t\tRedirectServerAuthenticationSuccessHandler successHandler = new RedirectServerAuthenticationSuccessHandler();\n\t\tsuccessHandler.setLocation(URI.create(uri));\n\t\treturn successHandler;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-eureka/src/main/resources/application-dev.yml",
    "content": "spring:\n  boot:\n    admin:\n      ui:\n        cache:\n          no-cache: true\n        template-location: file:../../spring-boot-admin-server-ui/target/dist/\n        resource-locations: file:../../spring-boot-admin-server-ui/target/dist/\n        cache-templates: false\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-eureka/src/main/resources/application-insecure.yml",
    "content": "info.tags.security: insecure\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-eureka/src/main/resources/application-secure.yml",
    "content": "spring:\n  security:\n    user:\n      name: \"user\"\n      password: \"password\"\n  boot:\n    admin:\n      client:\n        username: \"user\"       #These two are needed so that the client\n        password: \"password\"   #can register at the protected server api\n        instance:\n          metadata:\n            user.name: \"user\"         #These two are needed so that the server\n            user.password: \"password\" #can access the protected client endpoints\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-eureka/src/main/resources/application.yml",
    "content": "spring:\n  application:\n    name: spring-boot-admin-sample-eureka\n  profiles:\n    active:\n      - secure\n\n\n# tag::configuration-eureka[]\neureka:   #<1>\n  instance:\n    leaseRenewalIntervalInSeconds: 10\n    health-check-url-path: /actuator/health\n    metadata-map:\n      startup: ${random.int}    #needed to trigger info and endpoint update after restart\n  client:\n    registryFetchIntervalSeconds: 5\n    serviceUrl:\n      defaultZone: ${EUREKA_SERVICE_URL:http://localhost:8761}/eureka/\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"  #<2>\n  endpoint:\n    health:\n      show-details: ALWAYS\n# end::configuration-eureka[]\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-eureka/src/test/java/de/codecentric/boot/admin/SpringBootAdminEurekaApplicationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\n\n@ExtendWith(SpringExtension.class)\n@SpringBootTest(classes = { SpringBootAdminEurekaApplication.class })\nclass SpringBootAdminEurekaApplicationTest {\n\n\t@Test\n\tvoid contextLoads() {\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-hazelcast/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright 2014-2019 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>spring-boot-admin-sample-hazelcast</artifactId>\n\n    <name>Spring Boot Admin Sample Hazelcast</name>\n    <description>Spring Boot Admin Sample using Hazelcast</description>\n\n    <parent>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-samples</artifactId>\n        <version>${revision}</version>\n        <relativePath>../pom.xml</relativePath>\n    </parent>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-webmvc</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-security</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>de.codecentric</groupId>\n            <artifactId>spring-boot-admin-starter-server</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>de.codecentric</groupId>\n            <artifactId>spring-boot-admin-starter-client</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.hazelcast</groupId>\n            <artifactId>hazelcast</artifactId>\n        </dependency>\n        <!-- Test -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <finalName>${project.artifactId}</finalName>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>repackage</goal>\n                            <goal>build-info</goal>\n                        </goals>\n                    </execution>\n                </executions>\n                <configuration>\n                    <mainClass>de.codecentric.boot.admin.sample.SpringBootAdminHazelcastApplication</mainClass>\n                    <addResources>false</addResources>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-hazelcast/src/main/java/de/codecentric/boot/admin/sample/SpringBootAdminHazelcastApplication.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.sample;\n\nimport com.hazelcast.config.Config;\nimport com.hazelcast.config.EvictionConfig;\nimport com.hazelcast.config.EvictionPolicy;\nimport com.hazelcast.config.InMemoryFormat;\nimport com.hazelcast.config.MapConfig;\nimport com.hazelcast.config.MaxSizePolicy;\nimport com.hazelcast.config.MergePolicyConfig;\nimport com.hazelcast.config.TcpIpConfig;\nimport com.hazelcast.spi.merge.PutIfAbsentMergePolicy;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Profile;\nimport org.springframework.security.config.Customizer;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.web.SecurityFilterChain;\nimport org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;\nimport org.springframework.security.web.csrf.CookieCsrfTokenRepository;\nimport org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.config.AdminServerProperties;\nimport de.codecentric.boot.admin.server.config.EnableAdminServer;\nimport de.codecentric.boot.admin.server.notify.Notifier;\n\nimport static de.codecentric.boot.admin.server.config.AdminServerHazelcastAutoConfiguration.DEFAULT_NAME_EVENT_STORE_MAP;\nimport static de.codecentric.boot.admin.server.config.AdminServerHazelcastAutoConfiguration.DEFAULT_NAME_SENT_NOTIFICATIONS_MAP;\nimport static java.util.Collections.singletonList;\nimport static org.springframework.http.HttpMethod.DELETE;\nimport static org.springframework.http.HttpMethod.POST;\n\n@SpringBootApplication\n@EnableAdminServer\npublic class SpringBootAdminHazelcastApplication {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(SpringBootAdminHazelcastApplication.class);\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(SpringBootAdminHazelcastApplication.class, args);\n\t}\n\n\t// tag::application-hazelcast[]\n\t@Bean\n\tpublic Config hazelcastConfig() {\n\t\t// This map is used to store the events.\n\t\t// It should be configured to reliably hold all the data,\n\t\t// Spring Boot Admin will compact the events, if there are too many\n\t\tMapConfig eventStoreMap = new MapConfig(DEFAULT_NAME_EVENT_STORE_MAP).setInMemoryFormat(InMemoryFormat.OBJECT)\n\t\t\t.setBackupCount(1)\n\t\t\t.setMergePolicyConfig(new MergePolicyConfig(PutIfAbsentMergePolicy.class.getName(), 100));\n\n\t\t// This map is used to deduplicate the notifications.\n\t\t// If data in this map gets lost it should not be a big issue as it will utmost\n\t\t// lead to\n\t\t// the same notification to be sent by multiple instances\n\t\tMapConfig sentNotificationsMap = new MapConfig(DEFAULT_NAME_SENT_NOTIFICATIONS_MAP)\n\t\t\t.setInMemoryFormat(InMemoryFormat.OBJECT)\n\t\t\t.setBackupCount(1)\n\t\t\t.setEvictionConfig(\n\t\t\t\t\tnew EvictionConfig().setEvictionPolicy(EvictionPolicy.LRU).setMaxSizePolicy(MaxSizePolicy.PER_NODE))\n\t\t\t.setMergePolicyConfig(new MergePolicyConfig(PutIfAbsentMergePolicy.class.getName(), 100));\n\n\t\tConfig config = new Config();\n\t\tconfig.addMapConfig(eventStoreMap);\n\t\tconfig.addMapConfig(sentNotificationsMap);\n\t\tconfig.setProperty(\"hazelcast.jmx\", \"true\");\n\n\t\t// WARNING: This setups a local cluster, you change it to fit your needs.\n\t\tconfig.getNetworkConfig().getJoin().getMulticastConfig().setEnabled(false);\n\t\tTcpIpConfig tcpIpConfig = config.getNetworkConfig().getJoin().getTcpIpConfig();\n\t\ttcpIpConfig.setEnabled(true);\n\t\ttcpIpConfig.setMembers(singletonList(\"127.0.0.1\"));\n\t\treturn config;\n\t}\n\t// end::application-hazelcast[]\n\n\t@Bean\n\tpublic Notifier loggingNotifier() {\n\t\treturn (event) -> Mono.fromRunnable(() -> log.info(\"Event occurred: {}\", event));\n\t}\n\n\t@Profile(\"insecure\")\n\t@Configuration(proxyBeanMethods = false)\n\tpublic static class SecurityPermitAllConfig {\n\n\t\tprivate final AdminServerProperties adminServer;\n\n\t\tpublic SecurityPermitAllConfig(AdminServerProperties adminServer) {\n\t\t\tthis.adminServer = adminServer;\n\t\t}\n\n\t\t@Bean\n\t\tprotected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n\t\t\thttp.authorizeHttpRequests((authorizeRequests) -> authorizeRequests.anyRequest().permitAll())\n\t\t\t\t.csrf((csrf) -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())\n\t\t\t\t\t.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())\n\t\t\t\t\t.ignoringRequestMatchers(\n\t\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults().matcher(POST, this.adminServer.path(\"/instances\")),\n\t\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults()\n\t\t\t\t\t\t\t\t.matcher(DELETE, this.adminServer.path(\"/instances/*\")),\n\t\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults().matcher(this.adminServer.path(\"/actuator/**\"))));\n\n\t\t\treturn http.build();\n\t\t}\n\n\t}\n\n\t@Profile(\"secure\")\n\t@Configuration(proxyBeanMethods = false)\n\tpublic static class SecuritySecureConfig {\n\n\t\tprivate final AdminServerProperties adminServer;\n\n\t\tpublic SecuritySecureConfig(AdminServerProperties adminServer) {\n\t\t\tthis.adminServer = adminServer;\n\t\t}\n\n\t\t@Bean\n\t\tprotected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n\t\t\tSavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();\n\t\t\tsuccessHandler.setTargetUrlParameter(\"redirectTo\");\n\t\t\tsuccessHandler.setDefaultTargetUrl(this.adminServer.path(\"/\"));\n\n\t\t\thttp.authorizeHttpRequests((authorizeRequests) -> authorizeRequests\n\t\t\t\t.requestMatchers(PathPatternRequestMatcher.withDefaults().matcher(this.adminServer.path(\"/assets/**\")))\n\t\t\t\t.permitAll()\n\t\t\t\t.requestMatchers(PathPatternRequestMatcher.withDefaults().matcher(this.adminServer.path(\"/login\")))\n\t\t\t\t.permitAll()\n\t\t\t\t.anyRequest()\n\t\t\t\t.authenticated())\n\t\t\t\t.formLogin((formLogin) -> formLogin.loginPage(this.adminServer.path(\"/login\"))\n\t\t\t\t\t.successHandler(successHandler))\n\t\t\t\t.logout((logout) -> logout.logoutUrl(this.adminServer.path(\"/logout\")))\n\t\t\t\t.httpBasic(Customizer.withDefaults())\n\t\t\t\t.csrf((csrf) -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())\n\t\t\t\t\t.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())\n\t\t\t\t\t.ignoringRequestMatchers(\n\t\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults().matcher(POST, this.adminServer.path(\"/instances\")),\n\t\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults()\n\t\t\t\t\t\t\t\t.matcher(DELETE, this.adminServer.path(\"/instances/*\")),\n\t\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults().matcher(this.adminServer.path(\"/actuator/**\"))));\n\n\t\t\treturn http.build();\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-hazelcast/src/main/resources/application-dev.yml",
    "content": "spring:\n  boot:\n    admin:\n      ui:\n        cache:\n          no-cache: true\n        template-location: file:../../spring-boot-admin-server-ui/target/dist/\n        resource-locations: file:../../spring-boot-admin-server-ui/target/dist/\n        cache-templates: false\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-hazelcast/src/main/resources/application-insecure.yml",
    "content": "info.tags.security: insecure\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-hazelcast/src/main/resources/application-secure.yml",
    "content": "spring:\n  security:\n    user:\n      name: \"user\"\n      password: \"password\"\n  boot:\n    admin:\n      client:\n        username: \"user\"       #These two are needed so that the client\n        password: \"password\"   #can register at the protected server api\n        instance:\n          metadata:\n            user.name: \"user\"         #These two are needed so that the server\n            user.password: \"password\" #can access the protected client endpoints\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-hazelcast/src/main/resources/application.yml",
    "content": "---\nlogging:\n  file:\n    name: \"target/boot-admin-sample-hazelcast.log\"\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n  endpoint:\n    health:\n      show-details: ALWAYS\n\nspring:\n  application:\n    name: spring-boot-admin-sample-hazelcast\n  boot:\n    admin:\n      client:\n        url: http://localhost:${server.port:8080}\n  profiles:\n    active:\n      - insecure\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-hazelcast/src/test/java/de/codecentric/boot/admin/sample/SpringBootAdminHazelcastApplicationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.sample;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\n\n@ExtendWith(SpringExtension.class)\n@SpringBootTest(classes = { SpringBootAdminHazelcastApplication.class })\nclass SpringBootAdminHazelcastApplicationTest {\n\n\t@Test\n\tvoid contextLoads() {\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-reactive/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright 2014-2019 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>spring-boot-admin-sample-reactive</artifactId>\n\n    <name>Spring Boot Admin Sample Reactive</name>\n    <description>Spring Boot Admin Sample Reactive</description>\n\n    <parent>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-samples</artifactId>\n        <version>${revision}</version>\n        <relativePath>../pom.xml</relativePath>\n    </parent>\n\n    <dependencies>\n        <dependency>\n            <groupId>de.codecentric</groupId>\n            <artifactId>spring-boot-admin-starter-server</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-security</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>de.codecentric</groupId>\n            <artifactId>spring-boot-admin-starter-client</artifactId>\n        </dependency>\n        <!-- Test -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-devtools</artifactId>\n            <optional>true</optional>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <finalName>${project.artifactId}</finalName>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>repackage</goal>\n                            <goal>build-info</goal>\n                        </goals>\n                    </execution>\n                </executions>\n                <configuration>\n                    <mainClass>de.codecentric.boot.admin.sample.SpringBootAdminReactiveApplication</mainClass>\n                    <addResources>false</addResources>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-reactive/src/main/java/de/codecentric/boot/admin/sample/SpringBootAdminReactiveApplication.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.sample;\n\nimport java.net.URI;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Profile;\nimport org.springframework.security.config.Customizer;\nimport org.springframework.security.config.web.server.ServerHttpSecurity;\nimport org.springframework.security.web.server.SecurityWebFilterChain;\nimport org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler;\nimport org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;\nimport org.springframework.security.web.server.authentication.logout.RedirectServerLogoutSuccessHandler;\nimport org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.config.AdminServerProperties;\nimport de.codecentric.boot.admin.server.config.EnableAdminServer;\nimport de.codecentric.boot.admin.server.notify.Notifier;\n\n@SpringBootApplication\n@EnableAdminServer\npublic class SpringBootAdminReactiveApplication {\n\n\tprivate final AdminServerProperties adminServer;\n\n\tpublic SpringBootAdminReactiveApplication(AdminServerProperties adminServer) {\n\t\tthis.adminServer = adminServer;\n\t}\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(SpringBootAdminReactiveApplication.class, args);\n\t}\n\n\t@Bean\n\t@Profile(\"insecure\")\n\tpublic SecurityWebFilterChain securityWebFilterChainPermitAll(ServerHttpSecurity http) {\n\t\treturn http.authorizeExchange((authorizeExchange) -> authorizeExchange.anyExchange().permitAll())\n\t\t\t.csrf(ServerHttpSecurity.CsrfSpec::disable)\n\t\t\t.build();\n\t}\n\n\t@Bean\n\t@Profile(\"secure\")\n\tpublic SecurityWebFilterChain securityWebFilterChainSecure(ServerHttpSecurity http) {\n\t\treturn http\n\t\t\t.authorizeExchange(\n\t\t\t\t\t(authorizeExchange) -> authorizeExchange.pathMatchers(this.adminServer.path(\"/assets/**\"))\n\t\t\t\t\t\t.permitAll()\n\t\t\t\t\t\t.pathMatchers(\"/actuator/health/**\")\n\t\t\t\t\t\t.permitAll()\n\t\t\t\t\t\t.pathMatchers(this.adminServer.path(\"/login\"))\n\t\t\t\t\t\t.permitAll()\n\t\t\t\t\t\t.anyExchange()\n\t\t\t\t\t\t.authenticated())\n\t\t\t.formLogin((formLogin) -> formLogin.loginPage(this.adminServer.path(\"/login\"))\n\t\t\t\t.authenticationSuccessHandler(loginSuccessHandler(this.adminServer.path(\"/\"))))\n\t\t\t.logout((logout) -> logout.logoutUrl(this.adminServer.path(\"/logout\"))\n\t\t\t\t.logoutSuccessHandler(logoutSuccessHandler(this.adminServer.path(\"/login?logout\"))))\n\t\t\t.httpBasic(Customizer.withDefaults())\n\t\t\t.csrf(ServerHttpSecurity.CsrfSpec::disable)\n\t\t\t.build();\n\t}\n\n\t// The following two methods are only required when setting a custom base-path (see\n\t// 'basepath' profile in application.yml)\n\tprivate ServerLogoutSuccessHandler logoutSuccessHandler(String uri) {\n\t\tRedirectServerLogoutSuccessHandler successHandler = new RedirectServerLogoutSuccessHandler();\n\t\tsuccessHandler.setLogoutSuccessUrl(URI.create(uri));\n\t\treturn successHandler;\n\t}\n\n\tprivate ServerAuthenticationSuccessHandler loginSuccessHandler(String uri) {\n\t\tRedirectServerAuthenticationSuccessHandler successHandler = new RedirectServerAuthenticationSuccessHandler();\n\t\tsuccessHandler.setLocation(URI.create(uri));\n\t\treturn successHandler;\n\t}\n\n\t@Bean\n\tpublic Notifier notifier() {\n\t\treturn (e) -> Mono.empty();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-reactive/src/main/resources/application-dev.yml",
    "content": "spring:\n  boot:\n    admin:\n      ui:\n        cache:\n          no-cache: true\n        template-location: file:../../spring-boot-admin-server-ui/target/dist/\n        resource-locations: file:../../spring-boot-admin-server-ui/target/dist/\n        cache-templates: false\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-reactive/src/main/resources/application-insecure.yml",
    "content": "info.tags.security: insecure\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-reactive/src/main/resources/application-secure.yml",
    "content": "spring:\n  security:\n    user:\n      name: \"user\"\n      password: \"password\"\n  boot:\n    admin:\n      client:\n        username: \"user\"       #These two are needed so that the client\n        password: \"password\"   #can register at the protected server api\n        instance:\n          metadata:\n            user.name: \"user\"         #These two are needed so that the server\n            user.password: \"password\" #can access the protected client endpoints\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-reactive/src/main/resources/application.yml",
    "content": "info:\n  scm-url: \"@scm.url@\"\n  build-url: \"https://travis-ci.org/codecentric/spring-boot-admin\"\n\nlogging:\n  file:\n    name: \"target/boot-admin-sample-reactive.log\"\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n  endpoint:\n    health:\n      show-details: ALWAYS\n\nspring:\n  application:\n    name: spring-boot-admin-sample-reactive\n  boot:\n    admin:\n      client:\n        url: http://localhost:8080\n  profiles:\n    active:\n      - insecure\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-reactive/src/test/java/de/codecentric/boot/admin/sample/SpringBootAdminReactiveApplicationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.sample;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\n\n@ExtendWith(SpringExtension.class)\n@SpringBootTest(classes = { SpringBootAdminReactiveApplication.class })\nclass SpringBootAdminReactiveApplicationTest {\n\n\t@Test\n\tvoid contextLoads() {\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-servlet/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright 2014-2019 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>spring-boot-admin-sample-servlet</artifactId>\n\n    <name>Spring Boot Admin Sample Servlet</name>\n    <description>Spring Boot Admin Sample Servlet</description>\n\n    <parent>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-samples</artifactId>\n        <version>${revision}</version>\n        <relativePath>../pom.xml</relativePath>\n    </parent>\n\n    <dependencies>\n        <dependency>\n            <groupId>de.codecentric</groupId>\n            <artifactId>spring-boot-admin-sample-custom-ui</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>de.codecentric</groupId>\n            <artifactId>spring-boot-admin-starter-server</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-security</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-webmvc</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-mail</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>de.codecentric</groupId>\n            <artifactId>spring-boot-admin-starter-client</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.session</groupId>\n            <artifactId>spring-session-core</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.session</groupId>\n            <artifactId>spring-session-jdbc</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-config</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.hsqldb</groupId>\n            <artifactId>hsqldb</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.jolokia</groupId>\n            <artifactId>jolokia-support-springboot</artifactId>\n        </dependency>\n        <!-- Test -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <finalName>${project.artifactId}</finalName>\n        <plugins>\n            <plugin>\n                <groupId>org.cyclonedx</groupId>\n                <artifactId>cyclonedx-maven-plugin</artifactId>\n                <executions>\n                    <execution>\n                        <phase>generate-resources</phase>\n                        <goals>\n                            <goal>makeAggregateBom</goal>\n                        </goals>\n                        <configuration>\n                            <projectType>application</projectType>\n                            <outputDirectory>${project.build.outputDirectory}</outputDirectory>\n                            <outputName>bom</outputName>\n                            <outputFormat>json</outputFormat>\n                            <skipAttach>true</skipAttach>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>repackage</goal>\n                            <goal>build-info</goal>\n                        </goals>\n                    </execution>\n                </executions>\n                <configuration>\n                    <mainClass>de.codecentric.boot.admin.sample.SpringBootAdminServletApplication</mainClass>\n                    <addResources>false</addResources>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/sample/CustomCsrfFilter.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.sample;\n\nimport java.io.IOException;\n\nimport jakarta.servlet.FilterChain;\nimport jakarta.servlet.ServletException;\nimport jakarta.servlet.http.Cookie;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.springframework.security.web.csrf.CsrfToken;\nimport org.springframework.web.filter.OncePerRequestFilter;\nimport org.springframework.web.util.WebUtils;\n\npublic class CustomCsrfFilter extends OncePerRequestFilter {\n\n\tpublic static final String CSRF_COOKIE_NAME = \"XSRF-TOKEN\";\n\n\t@Override\n\tprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)\n\t\t\tthrows ServletException, IOException {\n\n\t\tCsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());\n\n\t\tif (csrf != null) {\n\n\t\t\tCookie cookie = WebUtils.getCookie(request, CSRF_COOKIE_NAME);\n\t\t\tString token = csrf.getToken();\n\n\t\t\tif (cookie == null || token != null && !token.equals(cookie.getValue())) {\n\t\t\t\tcookie = new Cookie(CSRF_COOKIE_NAME, token);\n\t\t\t\tcookie.setPath(\"/\");\n\t\t\t\tresponse.addCookie(cookie);\n\t\t\t}\n\t\t}\n\n\t\tfilterChain.doFilter(request, response);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/sample/CustomEndpoint.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.sample;\n\nimport org.springframework.boot.actuate.endpoint.annotation.Endpoint;\nimport org.springframework.boot.actuate.endpoint.annotation.ReadOperation;\n\n@Endpoint(id = \"custom\")\npublic class CustomEndpoint {\n\n\t@ReadOperation\n\tpublic String invoke() {\n\t\treturn \"Hello World!\";\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/sample/CustomNotifier.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.sample;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.notify.AbstractEventNotifier;\n\n// tag::customization-notifiers[]\npublic class CustomNotifier extends AbstractEventNotifier {\n\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(CustomNotifier.class);\n\n\tpublic CustomNotifier(InstanceRepository repository) {\n\t\tsuper(repository);\n\t}\n\n\t@Override\n\tprotected Mono<Void> doNotify(InstanceEvent event, Instance instance) {\n\t\treturn Mono.fromRunnable(() -> {\n\t\t\tif (event instanceof InstanceStatusChangedEvent statusChangedEvent) {\n\t\t\t\tLOGGER.info(\"Instance {} ({}) is {}\", instance.getRegistration().getName(), event.getInstance(),\n\t\t\t\t\t\tstatusChangedEvent.getStatusInfo().getStatus());\n\t\t\t}\n\t\t\telse {\n\t\t\t\tLOGGER.info(\"Instance {} ({}) {}\", instance.getRegistration().getName(), event.getInstance(),\n\t\t\t\t\t\tevent.getType());\n\t\t\t}\n\t\t});\n\t}\n\n}\n// end::customization-notifiers[]\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/sample/NotifierConfig.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.sample;\n\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.List;\n\nimport org.springframework.beans.factory.ObjectProvider;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Primary;\n\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.notify.CompositeNotifier;\nimport de.codecentric.boot.admin.server.notify.Notifier;\nimport de.codecentric.boot.admin.server.notify.RemindingNotifier;\nimport de.codecentric.boot.admin.server.notify.filter.FilteringNotifier;\n\n// tag::configuration-filtering-notifier[]\n@Configuration(proxyBeanMethods = false)\npublic class NotifierConfig {\n\n\tprivate final InstanceRepository repository;\n\n\tprivate final ObjectProvider<List<Notifier>> otherNotifiers;\n\n\tpublic NotifierConfig(InstanceRepository repository, ObjectProvider<List<Notifier>> otherNotifiers) {\n\t\tthis.repository = repository;\n\t\tthis.otherNotifiers = otherNotifiers;\n\t}\n\n\t@Bean\n\tpublic FilteringNotifier filteringNotifier() { // <1>\n\t\tCompositeNotifier delegate = new CompositeNotifier(this.otherNotifiers.getIfAvailable(Collections::emptyList));\n\t\treturn new FilteringNotifier(delegate, this.repository);\n\t}\n\n\t@Primary\n\t@Bean(initMethod = \"start\", destroyMethod = \"stop\")\n\tpublic RemindingNotifier remindingNotifier() { // <2>\n\t\tRemindingNotifier notifier = new RemindingNotifier(filteringNotifier(), this.repository);\n\t\tnotifier.setReminderPeriod(Duration.ofMinutes(10));\n\t\tnotifier.setCheckReminderInverval(Duration.ofSeconds(10));\n\t\treturn notifier;\n\t}\n\n}\n// end::configuration-filtering-notifier[]\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/sample/SecurityPermitAllConfig.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.sample;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Profile;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.web.SecurityFilterChain;\nimport org.springframework.security.web.authentication.www.BasicAuthenticationFilter;\nimport org.springframework.security.web.csrf.CookieCsrfTokenRepository;\nimport org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;\nimport org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;\n\nimport de.codecentric.boot.admin.server.config.AdminServerProperties;\n\nimport static org.springframework.http.HttpMethod.DELETE;\nimport static org.springframework.http.HttpMethod.POST;\n\n@Profile(\"insecure\")\n@Configuration(proxyBeanMethods = false)\npublic class SecurityPermitAllConfig {\n\n\tprivate final AdminServerProperties adminServer;\n\n\tpublic SecurityPermitAllConfig(AdminServerProperties adminServer) {\n\t\tthis.adminServer = adminServer;\n\t}\n\n\t@Bean\n\tprotected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n\n\t\thttp.authorizeHttpRequests((authorizeRequest) -> authorizeRequest.anyRequest().permitAll());\n\n\t\thttp.addFilterAfter(new CustomCsrfFilter(), BasicAuthenticationFilter.class)\n\t\t\t.csrf((csrf) -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())\n\t\t\t\t.csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())\n\t\t\t\t.ignoringRequestMatchers(\n\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults().matcher(POST, this.adminServer.path(\"/instances\")),\n\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults()\n\t\t\t\t\t\t\t.matcher(POST, this.adminServer.path(\"/notifications/**\")),\n\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults()\n\t\t\t\t\t\t\t.matcher(DELETE, this.adminServer.path(\"/notifications/**\")),\n\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults().matcher(DELETE, this.adminServer.path(\"/instances/*\")),\n\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults().matcher(this.adminServer.path(\"/actuator/**\"))));\n\n\t\treturn http.build();\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/sample/SecuritySecureConfig.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.sample;\n\nimport java.util.UUID;\n\nimport jakarta.servlet.DispatcherType;\nimport org.springframework.boot.security.autoconfigure.SecurityProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Profile;\nimport org.springframework.security.config.Customizer;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.core.userdetails.User;\nimport org.springframework.security.core.userdetails.UserDetails;\nimport org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;\nimport org.springframework.security.crypto.password.PasswordEncoder;\nimport org.springframework.security.provisioning.InMemoryUserDetailsManager;\nimport org.springframework.security.web.SecurityFilterChain;\nimport org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;\nimport org.springframework.security.web.authentication.www.BasicAuthenticationFilter;\nimport org.springframework.security.web.csrf.CookieCsrfTokenRepository;\nimport org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;\nimport org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;\n\nimport de.codecentric.boot.admin.server.config.AdminServerProperties;\n\nimport static org.springframework.http.HttpMethod.DELETE;\nimport static org.springframework.http.HttpMethod.POST;\n\n@Profile(\"secure\")\n// tag::configuration-spring-security[]\n@Configuration(proxyBeanMethods = false)\npublic class SecuritySecureConfig {\n\n\tprivate final AdminServerProperties adminServer;\n\n\tprivate final SecurityProperties security;\n\n\tpublic SecuritySecureConfig(AdminServerProperties adminServer, SecurityProperties security) {\n\t\tthis.adminServer = adminServer;\n\t\tthis.security = security;\n\t}\n\n\t@Bean\n\tprotected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n\t\tSavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();\n\t\tsuccessHandler.setTargetUrlParameter(\"redirectTo\");\n\t\tsuccessHandler.setDefaultTargetUrl(this.adminServer.path(\"/\"));\n\n\t\thttp.authorizeHttpRequests((authorizeRequests) -> authorizeRequests //\n\t\t\t.requestMatchers(PathPatternRequestMatcher.withDefaults().matcher(this.adminServer.path(\"/assets/**\")))\n\t\t\t.permitAll() // <1>\n\t\t\t.requestMatchers(\n\t\t\t\t\tPathPatternRequestMatcher.withDefaults().matcher((this.adminServer.path(\"/actuator/info\"))))\n\t\t\t.permitAll()\n\t\t\t.requestMatchers(PathPatternRequestMatcher.withDefaults().matcher(adminServer.path(\"/actuator/health\")))\n\t\t\t.permitAll()\n\t\t\t.requestMatchers(PathPatternRequestMatcher.withDefaults().matcher(this.adminServer.path(\"/login\")))\n\t\t\t.permitAll()\n\t\t\t.dispatcherTypeMatchers(DispatcherType.ASYNC)\n\t\t\t.permitAll() // https://github.com/spring-projects/spring-security/issues/11027\n\t\t\t.anyRequest()\n\t\t\t.authenticated()) // <2>\n\t\t\t.formLogin(\n\t\t\t\t\t(formLogin) -> formLogin.loginPage(this.adminServer.path(\"/login\")).successHandler(successHandler)) // <3>\n\t\t\t.logout((logout) -> logout.logoutUrl(this.adminServer.path(\"/logout\")))\n\t\t\t.httpBasic(Customizer.withDefaults()); // <4>\n\n\t\thttp.addFilterAfter(new CustomCsrfFilter(), BasicAuthenticationFilter.class) // <5>\n\t\t\t.csrf((csrf) -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())\n\t\t\t\t.csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())\n\t\t\t\t.ignoringRequestMatchers(\n\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults().matcher(POST, this.adminServer.path(\"/instances\")), // <6>\n\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults().matcher(DELETE, this.adminServer.path(\"/instances/*\")), // <6>\n\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults().matcher(this.adminServer.path(\"/actuator/**\")) // <7>\n\t\t\t\t));\n\n\t\thttp.rememberMe((rememberMe) -> rememberMe.key(UUID.randomUUID().toString()).tokenValiditySeconds(1209600));\n\n\t\treturn http.build();\n\n\t}\n\n\t// Required to provide UserDetailsService for \"remember functionality\"\n\t@Bean\n\tpublic InMemoryUserDetailsManager userDetailsService(PasswordEncoder passwordEncoder) {\n\t\tUserDetails user = User.withUsername(\"user\").password(passwordEncoder.encode(\"password\")).roles(\"USER\").build();\n\t\treturn new InMemoryUserDetailsManager(user);\n\t}\n\n\t@Bean\n\tpublic PasswordEncoder passwordEncoder() {\n\t\treturn new BCryptPasswordEncoder();\n\t}\n\n}\n// end::configuration-spring-security[]\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/sample/SpringBootAdminServletApplication.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.sample;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.actuate.audit.AuditEventRepository;\nimport org.springframework.boot.actuate.audit.InMemoryAuditEventRepository;\nimport org.springframework.boot.actuate.web.exchanges.HttpExchangeRepository;\nimport org.springframework.boot.actuate.web.exchanges.InMemoryHttpExchangeRepository;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;\nimport org.springframework.cache.CacheManager;\nimport org.springframework.cache.annotation.EnableCaching;\nimport org.springframework.cache.concurrent.ConcurrentMapCacheManager;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Lazy;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;\nimport org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;\nimport org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;\n\nimport de.codecentric.boot.admin.server.config.EnableAdminServer;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.web.client.HttpHeadersProvider;\nimport de.codecentric.boot.admin.server.web.client.InstanceExchangeFilterFunction;\n\n@SpringBootApplication\n@EnableAdminServer\n@Lazy(false)\n@EnableCaching\npublic class SpringBootAdminServletApplication {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(SpringBootAdminServletApplication.class);\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication app = new SpringApplication(SpringBootAdminServletApplication.class);\n\t\tapp.setApplicationStartup(new BufferingApplicationStartup(1500));\n\t\tapp.run(args);\n\t}\n\n\t@Bean\n\tpublic CacheManager cacheManager() {\n\t\treturn new ConcurrentMapCacheManager(\"books\");\n\t}\n\n\t// tag::customization-instance-exchange-filter-function[]\n\t@Bean\n\tpublic InstanceExchangeFilterFunction auditLog() {\n\t\treturn (instance, request, next) -> next.exchange(request).doOnSubscribe((s) -> {\n\t\t\tif (HttpMethod.DELETE.equals(request.method()) || HttpMethod.POST.equals(request.method())) {\n\t\t\t\tlog.info(\"{} for {} on {}\", request.method(), instance.getId(), request.url());\n\t\t\t}\n\t\t});\n\t}\n\t// end::customization-instance-exchange-filter-function[]\n\n\t@Bean\n\tpublic CustomNotifier customNotifier(InstanceRepository repository) {\n\t\treturn new CustomNotifier(repository);\n\t}\n\n\t@Bean\n\tpublic CustomEndpoint customEndpoint() {\n\t\treturn new CustomEndpoint();\n\t}\n\n\t// tag::customization-http-headers-providers[]\n\t@Bean\n\tpublic HttpHeadersProvider customHttpHeadersProvider() {\n\t\treturn (instance) -> {\n\t\t\tHttpHeaders httpHeaders = new HttpHeaders();\n\t\t\thttpHeaders.add(\"X-CUSTOM\", \"My Custom Value\");\n\t\t\treturn httpHeaders;\n\t\t};\n\t}\n\t// end::customization-http-headers-providers[]\n\n\t@Bean\n\tpublic HttpExchangeRepository httpTraceRepository() {\n\t\treturn new InMemoryHttpExchangeRepository();\n\t}\n\n\t@Bean\n\tpublic AuditEventRepository auditEventRepository() {\n\t\treturn new InMemoryAuditEventRepository();\n\t}\n\n\t@Bean\n\tpublic EmbeddedDatabase dataSource() {\n\t\treturn new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL)\n\t\t\t.addScript(\"org/springframework/session/jdbc/schema-hsqldb.sql\")\n\t\t\t.build();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/resources/application-dev.yml",
    "content": "spring:\n  boot:\n    admin:\n      ui:\n        cache:\n          no-cache: true\n        template-location: file:../../spring-boot-admin-server-ui/target/dist/\n        resource-locations: file:../../spring-boot-admin-server-ui/target/dist/\n        cache-templates: false\n        extension-resource-locations: file:../spring-boot-admin-sample-custom-ui/target/dist/\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/resources/application-insecure.yml",
    "content": "info.tags.security: insecure\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/resources/application-secure.yml",
    "content": "spring:\n  security:\n    user:\n      name: \"user\"\n      password: \"password\"\n  boot:\n    admin:\n      client:\n        username: \"user\"       #These two are needed so that the client\n        password: \"password\"   #can register at the protected server api\n        instance:\n          metadata:\n            user.name: \"user\"         #These two are needed so that the server\n            user.password: \"password\" #can access the protected client endpoints\n\ninfo.tags.security: secured\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/resources/application-themed.yml",
    "content": "spring:\n  boot:\n    admin:\n      ui:\n        theme:\n          color: \"#4A1420\"\n          palette:\n            50: \"#F8EBE4\"\n            100: \"#F2D7CC\"\n            200: \"#E5AC9C\"\n            300: \"#D87B6C\"\n            400: \"#CB463B\"\n            500: \"#9F2A2A\"\n            600: \"#83232A\"\n            700: \"#661B26\"\n            800: \"#4A1420\"\n            900: \"#2E0C16\"\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/resources/application.yml",
    "content": "---\ninfo:\n  scm-url: \"@scm.url@\"\n  build-url: \"https://travis-ci.org/codecentric/spring-boot-admin\"\n\nlogging:\n  level:\n    ROOT: info\n    de.codecentric: info\n    org.springframework.web: info\n  file:\n    name: \"target/boot-admin-sample-servlet.log\"\n  pattern:\n    file: \"%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID}){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx\"\n  group:\n    Spring Boot Admin:\n      - de.codecentric.boot.admin.server\n      - de.codecentric.boot.admin.client\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n  endpoint:\n    refresh:\n      enabled: true\n    restart:\n      enabled: true\n    shutdown:\n      enabled: true\n    env:\n      post:\n        enabled: true\n    health:\n      show-details: ALWAYS\n\n\nspring:\n  application:\n    name: spring-boot-admin-sample-servlet\n  cloud:\n    discovery:\n      client:\n        simple:\n          instances:\n            SBA:\n              - uri: http://localhost:8080\n                metadata:\n                  management.context-path: /actuator\n                  service-url: http://localhost:8080\n                  service-path: /\n  boot:\n    admin:\n      client:\n        url: http://localhost:8080\n        instance:\n          service-host-type: IP\n          metadata:\n            service-path: /foo\n            service-url: http://localhost:8080\n            hide-url: true\n            tags:\n              environment: test\n              de-service-test-1: A large content\n              de-service-test-2: A large content\n              de-service-test-3: A large content\n              de-service-test-4: A large content\n              de-service-test-5: A large content\n              de-service-test-6: A large content\n            kubectl.kubernetes.io/last-applied-configuration: '{\"name\":\"jvm.threads.peak\",\"description\":\"The peak live thread count since the Java virtual machine started or peak was reset\",\"baseUnit\":\"threads\",\"measurements\":[{\"statistic\":\"VALUE\",\"value\":64.0}],\"availableTags\":[]}'\n  config:\n    import: optional:configserver:http://localhost:8888/\n  jmx:\n    enabled: true\n  main:\n    lazy-initialization: true\n---\n# tag::customization-external-views-simple-link[]\nspring:\n  boot:\n    admin:\n      ui:\n        external-views:\n          - label: \"🚀\" #<1>\n            url: \"https://codecentric.de\" #<2>\n            order: 2000 #<3>\n# end::customization-external-views-simple-link[]\n---\n# tag::customization-external-views-dropdown-with-links[]\nspring:\n  boot:\n    admin:\n      ui:\n        external-views:\n          - label: Link w/o children\n            children:\n              - label: \"📖 Docs\"\n                url: https://codecentric.github.io/spring-boot-admin/current/\n              - label: \"📦 Maven\"\n                url: https://search.maven.org/search?q=g:de.codecentric%20AND%20a:spring-boot-admin-starter-server\n              - label: \"🐙 GitHub\"\n                url: https://github.com/codecentric/spring-boot-admin\n# end::customization-external-views-dropdown-with-links[]\n---\n# tag::customization-external-views-dropdown-is-link-with-links-as-children[]\nspring:\n  boot:\n    admin:\n      ui:\n        external-views:\n          - label: Link w children\n            url: https://codecentric.de  #<1>\n            children:\n              - label: \"📖 Docs\"\n                url: https://codecentric.github.io/spring-boot-admin/current/\n              - label: \"📦 Maven\"\n                url: https://search.maven.org/search?q=g:de.codecentric%20AND%20a:spring-boot-admin-starter-server\n              - label: \"🐙 GitHub\"\n                url: https://github.com/codecentric/spring-boot-admin\n          - label: \"🎅 Is it christmas\"\n            url: https://isitchristmas.com\n            iframe: true\n# end::customization-external-views-dropdown-is-link-with-links-as-children[]\n---\n# tag::customization-view-settings[]\nspring:\n  boot:\n    admin:\n      ui:\n        view-settings:\n          - name: \"journal\"\n            enabled: false\n# end::customization-view-settings[]\n\nmanagement:\n  endpoint:\n    sbom:\n      application:\n        location: optional:classpath:bom.json\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/test/java/de/codecentric/boot/admin/sample/SpringBootAdminServletApplicationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.sample;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\n\n@ExtendWith(SpringExtension.class)\n@SpringBootTest(classes = { SpringBootAdminServletApplication.class })\nclass SpringBootAdminServletApplicationTest {\n\n\t@Test\n\tvoid contextLoads() {\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-servlet-graalvm/Readme.md",
    "content": "# Spring Boot Admin GraalVM sample application\n\nThis is a sample project running a Spring Boot Admin server which works with GraalVM and Native Image Builder.\n\nIn order to show basic functionalities, the server itself is registered as a client.\n\n## Build project\n\nMake sure to use a GraalVM with a v17-BaseJDK to build the project (e.g. GraalVM Oracle 17.0.8).\nIf you're using sdkman:\n```bash\nsdk install java 17.0.8-graal\n```\nBuild the application with the `native` profile:\n```bash\nmvn -Pnative native:compile\n```\nThe native application will now be build in the target folder.\n```bash\ncd target\n./spring-boot-admin-sample-servlet-graalvm\n```\nYou should now be able to access Spring Boot Admin locally under http://localhost:8080/\n\n## Build an OCI image that can be run with Docker\n\n```bash\nmvn spring-boot:build-image -Pnative -Dspring-boot.build-image.imageName=spring-boot-admin-sample-servlet-graalvm:latest\n```\nDepending on your OS, you might want to change the builder in your `pom.xml`.\n\nRight now, `<builder>dashaun/native-builder:focal-arm64</builder>` is a good choice for ARM64.\n\nIn most other cases `<builder>paketobuildpacks/builder:tiny</builder>` should do the job.\n\n## Running the example\n\n```bash\ndocker run --rm -p 8080:8080 docker.io/library/spring-boot-admin-sample-servlet-graalvm:latest\n```\nYou should now be able to access Spring Boot Admin locally under http://localhost:8080/\n\n## Current limitations of Spring Boot's native image build feature\n\nKeep in mind that currently not all Spring modules have built-in support. Therefore, you might need to tell the AOT compiler about the usage of reflection, dynamic proxies etc. There are several ways to deal with these concerns. A good starting point for specifying additional native configuration can be found in the official [Spring documentation](https://docs.spring.io/spring-framework/docs/6.0.0/reference/html/core.html#aot-hints).\nSome features like gc and memory metrics are not supported by GraalVM yet. So some views (e.g. gc-details) are currently not working.\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-servlet-graalvm/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright 2014-2019 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>spring-boot-admin-sample-servlet-graalvm</artifactId>\n\n    <name>Spring Boot Admin Sample Servlet GraalVM</name>\n    <description>Spring Boot Admin Sample Servlet</description>\n\n    <parent>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-samples</artifactId>\n        <version>${revision}</version>\n        <relativePath>../pom.xml</relativePath>\n    </parent>\n\n    <properties>\n        <zstd-jni.version>1.5.7-7</zstd-jni.version>\n        <native-build-tools-plugin.version>0.11.5</native-build-tools-plugin.version>\n\n        <maven.javadoc.skip>true</maven.javadoc.skip>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>de.codecentric</groupId>\n            <artifactId>spring-boot-admin-starter-server</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>de.codecentric</groupId>\n            <artifactId>spring-boot-admin-starter-client</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>com.github.luben</groupId>\n            <artifactId>zstd-jni</artifactId>\n            <version>${zstd-jni.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>io.netty</groupId>\n            <artifactId>netty-tcnative-boringssl-static</artifactId>\n            <scope>compile</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <finalName>${project.artifactId}</finalName>\n        <pluginManagement>\n            <plugins>\n                <plugin>\n                    <groupId>org.graalvm.buildtools</groupId>\n                    <artifactId>native-maven-plugin</artifactId>\n                    <version>${native-build-tools-plugin.version}</version>\n                    <extensions>true</extensions>\n                </plugin>\n            </plugins>\n        </pluginManagement>\n    </build>\n\n    <profiles>\n        <profile>\n            <id>native</id>\n            <build>\n                <plugins>\n                    <plugin>\n                        <groupId>org.springframework.boot</groupId>\n                        <artifactId>spring-boot-maven-plugin</artifactId>\n                        <configuration>\n                            <excludes>\n                                <exclude>\n                                    <groupId>org.projectlombok</groupId>\n                                    <artifactId>lombok</artifactId>\n                                </exclude>\n                            </excludes>\n                            <image>\n                                <builder>dashaun/native-builder:focal-arm64</builder>\n                                <env>\n                                    <BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>\n                                </env>\n                            </image>\n                        </configuration>\n                        <executions>\n                            <execution>\n                                <goals>\n                                    <goal>repackage</goal>\n                                    <goal>build-info</goal>\n                                </goals>\n                            </execution>\n                            <execution>\n                                <id>process-aot</id>\n                                <goals>\n                                    <goal>process-aot</goal>\n                                </goals>\n                            </execution>\n                        </executions>\n                    </plugin>\n                    <plugin>\n                        <groupId>org.graalvm.buildtools</groupId>\n                        <artifactId>native-maven-plugin</artifactId>\n                        <configuration>\n                            <skipNativeTests>true</skipNativeTests>\n                            <buildArgs>\n                                <arg>-H:+IncludeAllLocales</arg>\n                                <arg>-H:-CheckToolchain</arg>\n                                <arg>-H:+ReportExceptionStackTraces</arg>\n                            </buildArgs>\n                            <classesDirectory>${project.build.outputDirectory}</classesDirectory>\n                            <metadataRepository>\n                                <enabled>true</enabled>\n                            </metadataRepository>\n                        </configuration>\n                        <executions>\n                            <execution>\n                                <id>add-reachability-metadata</id>\n                                <goals>\n                                    <goal>add-reachability-metadata</goal>\n                                </goals>\n                            </execution>\n                        </executions>\n                    </plugin>\n                </plugins>\n            </build>\n        </profile>\n        <profile>\n            <id>nativeTest</id>\n            <dependencies>\n                <dependency>\n                    <groupId>org.junit.platform</groupId>\n                    <artifactId>junit-platform-launcher</artifactId>\n                    <scope>test</scope>\n                </dependency>\n            </dependencies>\n            <build>\n                <plugins>\n                    <plugin>\n                        <groupId>org.springframework.boot</groupId>\n                        <artifactId>spring-boot-maven-plugin</artifactId>\n                        <executions>\n                            <execution>\n                                <id>process-test-aot</id>\n                                <goals>\n                                    <goal>process-test-aot</goal>\n                                </goals>\n                            </execution>\n                        </executions>\n                    </plugin>\n                    <plugin>\n                        <groupId>org.graalvm.buildtools</groupId>\n                        <artifactId>native-maven-plugin</artifactId>\n                        <configuration>\n                            <classesDirectory>${project.build.outputDirectory}</classesDirectory>\n                            <metadataRepository>\n                                <enabled>true</enabled>\n                            </metadataRepository>\n                        </configuration>\n                        <executions>\n                            <execution>\n                                <id>native-test</id>\n                                <goals>\n                                    <goal>test</goal>\n                                </goals>\n                            </execution>\n                        </executions>\n                    </plugin>\n                </plugins>\n            </build>\n        </profile>\n    </profiles>\n\n</project>\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-servlet-graalvm/src/main/java/de/codecentric/boot/admin/sample/SpringBootAdminServletApplication.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.sample;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\n\nimport de.codecentric.boot.admin.server.config.EnableAdminServer;\n\n@SpringBootApplication\n@EnableAdminServer\npublic class SpringBootAdminServletApplication {\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(SpringBootAdminServletApplication.class, args);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-servlet-graalvm/src/main/resources/application.yml",
    "content": "---\ninfo:\n  scm-url: \"@scm.url@\"\n  build-url: \"https://travis-ci.org/codecentric/spring-boot-admin\"\n\nlogging:\n  level:\n    ROOT: info\n    de.codecentric: trace\n    org.springframework.web: debug\n  file:\n    name: \"target/spring-boot-admin-3-graalvm.log\"\n  pattern:\n    file: \"%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID}){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx\"\n\nmanagement:\n  info:\n    java:\n      enabled: true\n      env:\n        enabled: true\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n\nspring:\n  application:\n    name: spring-boot-admin-3-graalvm\n  main:\n    lazy-initialization: true\n  boot:\n    admin:\n      client:\n        url: http://localhost:8080\n      ui:\n        external-views:\n          - label: 'Is it Christmas yet?'\n            url: https://isitchristmas.com/\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-war/.gitignore",
    "content": "/.springBeans\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-war/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright 2014-2018 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>spring-boot-admin-sample-war</artifactId>\n\n    <packaging>war</packaging>\n\n    <name>Spring Boot Admin Sample War</name>\n    <description>Spring Boot Admin Sample packaged as war</description>\n\n    <parent>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-samples</artifactId>\n        <version>${revision}</version>\n        <relativePath>../pom.xml</relativePath>\n    </parent>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-tomcat</artifactId>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-webmvc</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-security</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>de.codecentric</groupId>\n            <artifactId>spring-boot-admin-starter-client</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>de.codecentric</groupId>\n            <artifactId>spring-boot-admin-server</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>de.codecentric</groupId>\n            <artifactId>spring-boot-admin-server-ui</artifactId>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <finalName>${project.artifactId}</finalName>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-war-plugin</artifactId>\n                <configuration>\n                    <failOnMissingWebXml>false</failOnMissingWebXml>\n                    <archive>\n                        <manifest>\n                            <addDefaultImplementationEntries>true</addDefaultImplementationEntries>\n                        </manifest>\n                    </archive>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>repackage</goal>\n                            <goal>build-info</goal>\n                        </goals>\n                    </execution>\n                </executions>\n                <configuration>\n                    <addResources>false</addResources>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-war/src/main/java/de/codecentric/boot/admin/sample/SpringBootAdminWarApplication.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.sample;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.boot.builder.SpringApplicationBuilder;\nimport org.springframework.boot.web.servlet.support.SpringBootServletInitializer;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Profile;\nimport org.springframework.security.config.Customizer;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.web.SecurityFilterChain;\nimport org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;\nimport org.springframework.security.web.csrf.CookieCsrfTokenRepository;\nimport org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;\n\nimport de.codecentric.boot.admin.server.config.AdminServerProperties;\nimport de.codecentric.boot.admin.server.config.EnableAdminServer;\n\nimport static org.springframework.http.HttpMethod.DELETE;\nimport static org.springframework.http.HttpMethod.POST;\n\n@SpringBootApplication\n@EnableAdminServer\npublic class SpringBootAdminWarApplication extends SpringBootServletInitializer {\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(SpringBootAdminWarApplication.class, args);\n\t}\n\n\t@Override\n\tprotected SpringApplicationBuilder configure(SpringApplicationBuilder application) {\n\t\treturn application;\n\t}\n\n\t@Profile(\"insecure\")\n\t@Configuration(proxyBeanMethods = false)\n\tpublic static class SecurityPermitAllConfig {\n\n\t\tprivate final AdminServerProperties adminServer;\n\n\t\tpublic SecurityPermitAllConfig(AdminServerProperties adminServer) {\n\t\t\tthis.adminServer = adminServer;\n\t\t}\n\n\t\t@Bean\n\t\tpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n\t\t\thttp.authorizeHttpRequests((authorizeRequests) -> authorizeRequests.anyRequest().permitAll())\n\t\t\t\t.csrf((csrf) -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())\n\t\t\t\t\t.ignoringRequestMatchers(\n\t\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults().matcher(POST, this.adminServer.path(\"/instances\")),\n\t\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults()\n\t\t\t\t\t\t\t\t.matcher(DELETE, this.adminServer.path(\"/instances/*\")),\n\t\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults().matcher(this.adminServer.path(\"/actuator/**\"))));\n\t\t\treturn http.build();\n\t\t}\n\n\t}\n\n\t@Profile(\"secure\")\n\t@Configuration(proxyBeanMethods = false)\n\tpublic static class SecuritySecureConfig {\n\n\t\tprivate final AdminServerProperties adminServer;\n\n\t\tpublic SecuritySecureConfig(AdminServerProperties adminServer) {\n\t\t\tthis.adminServer = adminServer;\n\t\t}\n\n\t\t@Bean\n\t\tprotected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n\t\t\tSavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();\n\t\t\tsuccessHandler.setTargetUrlParameter(\"redirectTo\");\n\t\t\tsuccessHandler.setDefaultTargetUrl(this.adminServer.path(\"/\"));\n\n\t\t\thttp.authorizeHttpRequests((authorizeRequests) -> authorizeRequests\n\t\t\t\t.requestMatchers(PathPatternRequestMatcher.withDefaults().matcher(this.adminServer.path(\"/assets/**\")))\n\t\t\t\t.permitAll()\n\t\t\t\t.requestMatchers(PathPatternRequestMatcher.withDefaults().matcher(this.adminServer.path(\"/login\")))\n\t\t\t\t.permitAll()\n\t\t\t\t.anyRequest()\n\t\t\t\t.authenticated())\n\n\t\t\t\t.formLogin((formLogin) -> formLogin.loginPage(this.adminServer.path(\"/login\"))\n\t\t\t\t\t.successHandler(successHandler))\n\t\t\t\t.logout((logout) -> logout.logoutUrl(this.adminServer.path(\"/logout\")))\n\t\t\t\t.httpBasic(Customizer.withDefaults())\n\t\t\t\t.csrf((csrf) -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())\n\t\t\t\t\t.ignoringRequestMatchers(\n\t\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults().matcher(POST, this.adminServer.path(\"/instances\")),\n\t\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults()\n\t\t\t\t\t\t\t\t.matcher(DELETE, this.adminServer.path(\"/instances/*\")),\n\t\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults().matcher(this.adminServer.path(\"/actuator/**\"))));\n\n\t\t\treturn http.build();\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-war/src/main/resources/application-dev.yml",
    "content": "spring:\n  boot:\n    admin:\n      ui:\n        cache:\n          no-cache: true\n        template-location: file:../../spring-boot-admin-server-ui/target/dist/\n        resource-locations: file:../../spring-boot-admin-server-ui/target/dist/\n        cache-templates: false\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-war/src/main/resources/application-insecure.yml",
    "content": "info.tags.security: insecure\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-war/src/main/resources/application-secure.yml",
    "content": "spring:\n  security:\n    user:\n      name: \"user\"\n      password: \"password\"\n  boot:\n    admin:\n      client:\n        username: \"user\"       #These two are needed so that the client\n        password: \"password\"   #can register at the protected server api\n        instance:\n          metadata:\n            user.name: \"user\"         #These two are needed so that the server\n            user.password: \"password\" #can access the protected client endpoints\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-war/src/main/resources/application.yml",
    "content": "spring:\n  application:\n    name: spring-boot-admin-sample-war\n  boot:\n    admin:\n      client:\n        url: http://localhost:8080\n        instance:\n          service-base-url: http://localhost:8080\n  profiles:\n    active:\n      - secure\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n  endpoint:\n    health:\n      show-details: ALWAYS\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-zookeeper/docker-compose.yml",
    "content": "services:\n  zookeeper:\n    image: zookeeper\n    ports:\n      - \"2181:2181\"\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-zookeeper/pom.xml",
    "content": "<!--\n  ~ Copyright 2014-2019 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>spring-boot-admin-sample-zookeeper</artifactId>\n\n    <name>Spring Boot Admin Sample Zookeeper</name>\n    <description>Spring Boot Admin Sample using Zookeeper</description>\n\n    <parent>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-samples</artifactId>\n        <version>${revision}</version>\n        <relativePath>../pom.xml</relativePath>\n    </parent>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-webmvc</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-security</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>de.codecentric</groupId>\n            <artifactId>spring-boot-admin-starter-server</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-context</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <finalName>${project.artifactId}</finalName>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>repackage</goal>\n                            <goal>build-info</goal>\n                        </goals>\n                    </execution>\n                </executions>\n                <configuration>\n                    <mainClass>de.codecentric.boot.admin.sample.SpringBootAdminZookeeperApplication</mainClass>\n                    <addResources>false</addResources>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-zookeeper/src/main/java/de/codecentric/boot/admin/sample/SpringBootAdminZookeeperApplication.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.sample;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Profile;\nimport org.springframework.security.config.Customizer;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.web.SecurityFilterChain;\nimport org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;\nimport org.springframework.security.web.csrf.CookieCsrfTokenRepository;\nimport org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;\n\nimport de.codecentric.boot.admin.server.config.AdminServerProperties;\nimport de.codecentric.boot.admin.server.config.EnableAdminServer;\n\nimport static org.springframework.http.HttpMethod.DELETE;\nimport static org.springframework.http.HttpMethod.POST;\n\n@SpringBootApplication\n@EnableDiscoveryClient\n@EnableAdminServer\npublic class SpringBootAdminZookeeperApplication {\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(SpringBootAdminZookeeperApplication.class, args);\n\t}\n\n\t@Profile(\"insecure\")\n\t@Configuration(proxyBeanMethods = false)\n\tpublic static class SecurityPermitAllConfig {\n\n\t\tprivate final AdminServerProperties adminServer;\n\n\t\tpublic SecurityPermitAllConfig(AdminServerProperties adminServer) {\n\t\t\tthis.adminServer = adminServer;\n\t\t}\n\n\t\t@Bean\n\t\tprotected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n\t\t\thttp.authorizeHttpRequests((authorizeRequests) -> authorizeRequests.anyRequest().permitAll())\n\t\t\t\t.csrf((csrf) -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())\n\t\t\t\t\t.ignoringRequestMatchers(\n\t\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults().matcher(POST, this.adminServer.path(\"/instances\")),\n\t\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults()\n\t\t\t\t\t\t\t\t.matcher(DELETE, this.adminServer.path(\"/instances/*\")),\n\t\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults().matcher(this.adminServer.path(\"/actuator/**\"))));\n\n\t\t\treturn http.build();\n\t\t}\n\n\t}\n\n\t@Profile(\"secure\")\n\t@Configuration(proxyBeanMethods = false)\n\tpublic static class SecuritySecureConfig {\n\n\t\tprivate final AdminServerProperties adminServer;\n\n\t\tpublic SecuritySecureConfig(AdminServerProperties adminServer) {\n\t\t\tthis.adminServer = adminServer;\n\t\t}\n\n\t\t@Bean\n\t\tprotected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n\t\t\tSavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();\n\t\t\tsuccessHandler.setTargetUrlParameter(\"redirectTo\");\n\t\t\tsuccessHandler.setDefaultTargetUrl(this.adminServer.path(\"/\"));\n\n\t\t\thttp.authorizeHttpRequests((authorizeRequests) -> authorizeRequests\n\t\t\t\t.requestMatchers(PathPatternRequestMatcher.withDefaults().matcher(this.adminServer.path(\"/assets/**\")))\n\t\t\t\t.permitAll()\n\t\t\t\t.requestMatchers(PathPatternRequestMatcher.withDefaults().matcher(this.adminServer.path(\"/login\")))\n\t\t\t\t.permitAll()\n\t\t\t\t.anyRequest()\n\t\t\t\t.authenticated())\n\t\t\t\t.formLogin((formLogin) -> formLogin.loginPage(this.adminServer.path(\"/login\"))\n\t\t\t\t\t.successHandler(successHandler))\n\t\t\t\t.logout((logout) -> logout.logoutUrl(this.adminServer.path(\"/logout\")))\n\t\t\t\t.httpBasic(Customizer.withDefaults())\n\t\t\t\t.csrf((csrf) -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())\n\t\t\t\t\t.ignoringRequestMatchers(\n\t\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults().matcher(POST, this.adminServer.path(\"/instances\")),\n\t\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults()\n\t\t\t\t\t\t\t\t.matcher(DELETE, this.adminServer.path(\"/instances/*\")),\n\t\t\t\t\t\t\tPathPatternRequestMatcher.withDefaults().matcher(this.adminServer.path(\"/actuator/**\"))));\n\n\t\t\treturn http.build();\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-zookeeper/src/main/resources/application.yml",
    "content": "spring:\n  application:\n    name: zookeeper-example\n  cloud:\n    config:\n      enabled: false\n    zookeeper:\n      connect-string: localhost:2181\n      discovery:\n        metadata:\n          management.context-path: /foo\n          health.path: /ping\n          user.name: user\n          user.password: password\n  profiles:\n    active:\n      - secure\n\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"\n      path-mapping:\n        health: /ping\n      base-path: /foo\n  endpoint:\n    health:\n      show-details: ALWAYS\n\n---\nspring:\n  config:\n    activate:\n      on-profile: insecure\n---\nspring:\n  security:\n    user:\n      name: \"user\"\n      password: \"password\"\n  config:\n    activate:\n      on-profile: secure\n\n"
  },
  {
    "path": "spring-boot-admin-samples/spring-boot-admin-sample-zookeeper/src/test/java/de/codecentric/boot/admin/sample/SpringBootAdminZookeeperApplicationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.sample;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\n\n@ExtendWith(SpringExtension.class)\n@SpringBootTest(classes = { SpringBootAdminZookeeperApplication.class },\n\t\tproperties = { \"spring.cloud.zookeeper.enabled=false\" })\nclass SpringBootAdminZookeeperApplicationTest {\n\n\t@Test\n\tvoid contextLoads() {\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright 2014-2020 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>spring-boot-admin-server</artifactId>\n\n    <name>Spring Boot Admin Server</name>\n    <description>Spring Boot Admin Server</description>\n\n    <parent>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-build</artifactId>\n        <version>${revision}</version>\n        <relativePath>../spring-boot-admin-build</relativePath>\n    </parent>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-webclient</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-hazelcast</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-webflux</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-webmvc</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-thymeleaf</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-actuator</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.httpcomponents.client5</groupId>\n            <artifactId>httpclient5</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>io.projectreactor.addons</groupId>\n            <artifactId>reactor-extra</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <!-- Optional Mail Starter for mail-notifications -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-mail</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <!-- Optional Hazelcast-Support -->\n        <dependency>\n            <groupId>com.hazelcast</groupId>\n            <artifactId>hazelcast</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-autoconfigure-processor</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <!-- Optional Configuration Processor for metadata -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-configuration-processor</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <!-- Test -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n            <exclusions>\n                <exclusion>\n                    <groupId>org.junit.vintage</groupId>\n                    <artifactId>junit-vintage-engine</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-security</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>tools.jackson.datatype</groupId>\n            <artifactId>jackson-datatype-json-org</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>io.projectreactor</groupId>\n            <artifactId>reactor-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.hazelcast</groupId>\n            <artifactId>hazelcast</artifactId>\n            <classifier>tests</classifier>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.wiremock</groupId>\n            <artifactId>wiremock-standalone</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.eclipse.jetty</groupId>\n            <artifactId>jetty-alpn-server</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.awaitility</groupId>\n            <artifactId>awaitility</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.testcontainers</groupId>\n            <artifactId>testcontainers</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.testcontainers</groupId>\n            <artifactId>testcontainers-junit-jupiter</artifactId>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/config/AdminServerAutoConfiguration.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.config;\n\nimport java.time.Duration;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.reactivestreams.Publisher;\nimport org.springframework.boot.autoconfigure.AutoConfigureAfter;\nimport org.springframework.boot.autoconfigure.ImportAutoConfiguration;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Conditional;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Lazy;\n\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.entities.SnapshottingInstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.eventstore.InMemoryEventStore;\nimport de.codecentric.boot.admin.server.eventstore.InstanceEventPublisher;\nimport de.codecentric.boot.admin.server.eventstore.InstanceEventStore;\nimport de.codecentric.boot.admin.server.services.ApiMediaTypeHandler;\nimport de.codecentric.boot.admin.server.services.ApplicationRegistry;\nimport de.codecentric.boot.admin.server.services.EndpointDetectionTrigger;\nimport de.codecentric.boot.admin.server.services.EndpointDetector;\nimport de.codecentric.boot.admin.server.services.HashingInstanceUrlIdGenerator;\nimport de.codecentric.boot.admin.server.services.InfoUpdateTrigger;\nimport de.codecentric.boot.admin.server.services.InfoUpdater;\nimport de.codecentric.boot.admin.server.services.InstanceFilter;\nimport de.codecentric.boot.admin.server.services.InstanceIdGenerator;\nimport de.codecentric.boot.admin.server.services.InstanceRegistry;\nimport de.codecentric.boot.admin.server.services.StatusUpdateTrigger;\nimport de.codecentric.boot.admin.server.services.StatusUpdater;\nimport de.codecentric.boot.admin.server.services.endpoints.ChainingStrategy;\nimport de.codecentric.boot.admin.server.services.endpoints.ProbeEndpointsStrategy;\nimport de.codecentric.boot.admin.server.services.endpoints.QueryIndexEndpointStrategy;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\n\n@Configuration(proxyBeanMethods = false)\n@Conditional(SpringBootAdminServerEnabledCondition.class)\n@ConditionalOnBean(AdminServerMarkerConfiguration.Marker.class)\n@EnableConfigurationProperties(AdminServerProperties.class)\n@ImportAutoConfiguration({ AdminServerInstanceWebClientConfiguration.class, AdminServerWebConfiguration.class })\n@AutoConfigureAfter({ WebClientAutoConfiguration.class })\n@Slf4j\n@Lazy(false)\npublic class AdminServerAutoConfiguration {\n\n\tprivate final AdminServerProperties adminServerProperties;\n\n\tpublic AdminServerAutoConfiguration(AdminServerProperties adminServerProperties) {\n\t\tthis.adminServerProperties = adminServerProperties;\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean\n\tpublic InstanceFilter instanceFilter() {\n\t\treturn (instance) -> true;\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean\n\tpublic InstanceRegistry instanceRegistry(InstanceRepository instanceRepository,\n\t\t\tInstanceIdGenerator instanceIdGenerator, InstanceFilter instanceFilter) {\n\t\treturn new InstanceRegistry(instanceRepository, instanceIdGenerator, instanceFilter);\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean\n\tpublic ApplicationRegistry applicationRegistry(InstanceRegistry instanceRegistry,\n\t\t\tInstanceEventPublisher instanceEventPublisher) {\n\t\treturn new ApplicationRegistry(instanceRegistry, instanceEventPublisher);\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean\n\tpublic InstanceIdGenerator instanceIdGenerator() {\n\t\treturn new HashingInstanceUrlIdGenerator();\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean\n\tpublic StatusUpdater statusUpdater(InstanceRepository instanceRepository,\n\t\t\tInstanceWebClient.Builder instanceWebClientBuilder) {\n\t\treturn new StatusUpdater(instanceRepository, instanceWebClientBuilder.build(), new ApiMediaTypeHandler());\n\t}\n\n\t@Bean(initMethod = \"start\", destroyMethod = \"stop\")\n\t@ConditionalOnMissingBean\n\tpublic StatusUpdateTrigger statusUpdateTrigger(StatusUpdater statusUpdater, Publisher<InstanceEvent> events) {\n\t\tAdminServerProperties.MonitorProperties monitorProperties = this.adminServerProperties.getMonitor();\n\n\t\tDuration defaultTimeout = monitorProperties.getDefaultTimeout();\n\t\tDuration statusInterval = monitorProperties.getStatusInterval();\n\n\t\tif (defaultTimeout.compareTo(statusInterval) > 0) {\n\t\t\tlog.warn(\n\t\t\t\t\t\"Default timeout ({}) is larger than status interval ({}), hence status interval will be used as timeout.\",\n\t\t\t\t\tdefaultTimeout, statusInterval);\n\t\t}\n\n\t\treturn new StatusUpdateTrigger(statusUpdater, events, monitorProperties.getStatusInterval(),\n\t\t\t\tmonitorProperties.getStatusLifetime(), monitorProperties.getStatusMaxBackoff());\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean\n\tpublic EndpointDetector endpointDetector(InstanceRepository instanceRepository,\n\t\t\tInstanceWebClient.Builder instanceWebClientBuilder) {\n\t\tInstanceWebClient instanceWebClient = instanceWebClientBuilder.build();\n\t\tChainingStrategy strategy = new ChainingStrategy(\n\t\t\t\tnew QueryIndexEndpointStrategy(instanceWebClient, new ApiMediaTypeHandler()),\n\t\t\t\tnew ProbeEndpointsStrategy(instanceWebClient, this.adminServerProperties.getProbedEndpoints()));\n\t\treturn new EndpointDetector(instanceRepository, strategy);\n\t}\n\n\t@Bean(initMethod = \"start\", destroyMethod = \"stop\")\n\t@ConditionalOnMissingBean\n\tpublic EndpointDetectionTrigger endpointDetectionTrigger(EndpointDetector endpointDetector,\n\t\t\tPublisher<InstanceEvent> events) {\n\t\treturn new EndpointDetectionTrigger(endpointDetector, events);\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean\n\tpublic InfoUpdater infoUpdater(InstanceRepository instanceRepository,\n\t\t\tInstanceWebClient.Builder instanceWebClientBuilder) {\n\t\treturn new InfoUpdater(instanceRepository, instanceWebClientBuilder.build(), new ApiMediaTypeHandler());\n\t}\n\n\t@Bean(initMethod = \"start\", destroyMethod = \"stop\")\n\t@ConditionalOnMissingBean\n\tpublic InfoUpdateTrigger infoUpdateTrigger(InfoUpdater infoUpdater, Publisher<InstanceEvent> events) {\n\t\treturn new InfoUpdateTrigger(infoUpdater, events, this.adminServerProperties.getMonitor().getInfoInterval(),\n\t\t\t\tthis.adminServerProperties.getMonitor().getInfoLifetime(),\n\t\t\t\tthis.adminServerProperties.getMonitor().getInfoMaxBackoff());\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean(InstanceEventStore.class)\n\tpublic InMemoryEventStore eventStore() {\n\t\treturn new InMemoryEventStore();\n\t}\n\n\t@Bean(initMethod = \"start\", destroyMethod = \"stop\")\n\t@ConditionalOnMissingBean(InstanceRepository.class)\n\tpublic SnapshottingInstanceRepository instanceRepository(InstanceEventStore eventStore) {\n\t\treturn new SnapshottingInstanceRepository(eventStore);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/config/AdminServerCloudFoundryAutoConfiguration.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.config;\n\nimport org.springframework.boot.autoconfigure.AutoConfigureBefore;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.cloud.CloudPlatform;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Lazy;\n\nimport de.codecentric.boot.admin.server.services.CloudFoundryInstanceIdGenerator;\nimport de.codecentric.boot.admin.server.services.HashingInstanceUrlIdGenerator;\nimport de.codecentric.boot.admin.server.services.InstanceIdGenerator;\nimport de.codecentric.boot.admin.server.web.client.CloudFoundryHttpHeaderProvider;\n\n@Configuration(proxyBeanMethods = false)\n@ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY)\n@AutoConfigureBefore({ AdminServerAutoConfiguration.class })\n@Lazy(false)\npublic class AdminServerCloudFoundryAutoConfiguration {\n\n\t@Bean\n\t@ConditionalOnMissingBean\n\tpublic InstanceIdGenerator instanceIdGenerator() {\n\t\treturn new CloudFoundryInstanceIdGenerator(new HashingInstanceUrlIdGenerator());\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean\n\tpublic CloudFoundryHttpHeaderProvider cloudFoundryHttpHeaderProvider() {\n\t\treturn new CloudFoundryHttpHeaderProvider();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/config/AdminServerHazelcastAutoConfiguration.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.config;\n\nimport java.util.List;\n\nimport com.hazelcast.core.HazelcastInstance;\nimport com.hazelcast.map.IMap;\nimport org.reactivestreams.Publisher;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.boot.autoconfigure.AutoConfigureAfter;\nimport org.springframework.boot.autoconfigure.AutoConfigureBefore;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;\nimport org.springframework.boot.hazelcast.autoconfigure.HazelcastAutoConfiguration;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Lazy;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.eventstore.HazelcastEventStore;\nimport de.codecentric.boot.admin.server.eventstore.InstanceEventStore;\nimport de.codecentric.boot.admin.server.notify.HazelcastNotificationTrigger;\nimport de.codecentric.boot.admin.server.notify.NotificationTrigger;\nimport de.codecentric.boot.admin.server.notify.Notifier;\n\n@Configuration(proxyBeanMethods = false)\n@ConditionalOnBean(AdminServerMarkerConfiguration.Marker.class)\n@ConditionalOnSingleCandidate(HazelcastInstance.class)\n@ConditionalOnProperty(prefix = \"spring.boot.admin.hazelcast\", name = \"enabled\", matchIfMissing = true)\n@AutoConfigureBefore({ AdminServerAutoConfiguration.class, AdminServerNotifierAutoConfiguration.class })\n@AutoConfigureAfter(HazelcastAutoConfiguration.class)\n@Lazy(false)\npublic class AdminServerHazelcastAutoConfiguration {\n\n\tpublic static final String DEFAULT_NAME_EVENT_STORE_MAP = \"spring-boot-admin-event-store\";\n\n\tpublic static final String DEFAULT_NAME_SENT_NOTIFICATIONS_MAP = \"spring-boot-admin-sent-notifications\";\n\n\t@Value(\"${spring.boot.admin.hazelcast.event-store:\" + DEFAULT_NAME_EVENT_STORE_MAP + \"}\")\n\tprivate final String nameEventStoreMap = DEFAULT_NAME_EVENT_STORE_MAP;\n\n\t@Bean\n\t@ConditionalOnMissingBean(InstanceEventStore.class)\n\tpublic HazelcastEventStore eventStore(HazelcastInstance hazelcastInstance) {\n\t\tIMap<InstanceId, List<InstanceEvent>> map = hazelcastInstance.getMap(this.nameEventStoreMap);\n\t\treturn new HazelcastEventStore(map);\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnBean(Notifier.class)\n\tpublic static class NotifierTriggerConfiguration {\n\n\t\t@Value(\"${spring.boot.admin.hazelcast.sent-notifications:\" + DEFAULT_NAME_SENT_NOTIFICATIONS_MAP + \"}\")\n\t\tprivate final String nameSentNotificationsMap = DEFAULT_NAME_SENT_NOTIFICATIONS_MAP;\n\n\t\t@Bean(initMethod = \"start\", destroyMethod = \"stop\")\n\t\t@ConditionalOnMissingBean(NotificationTrigger.class)\n\t\tpublic NotificationTrigger notificationTrigger(HazelcastInstance hazelcastInstance, Notifier notifier,\n\t\t\t\tPublisher<InstanceEvent> events) {\n\t\t\treturn new HazelcastNotificationTrigger(notifier, events,\n\t\t\t\t\thazelcastInstance.getMap(this.nameSentNotificationsMap));\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/config/AdminServerInstanceWebClientConfiguration.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.config;\n\nimport java.net.CookiePolicy;\nimport java.util.List;\n\nimport org.reactivestreams.Publisher;\nimport org.springframework.beans.factory.ObjectProvider;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Lazy;\nimport org.springframework.context.annotation.Scope;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.web.reactive.function.client.WebClient;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.web.client.BasicAuthHttpHeaderProvider;\nimport de.codecentric.boot.admin.server.web.client.CompositeHttpHeadersProvider;\nimport de.codecentric.boot.admin.server.web.client.HttpHeadersProvider;\nimport de.codecentric.boot.admin.server.web.client.InstanceExchangeFilterFunction;\nimport de.codecentric.boot.admin.server.web.client.InstanceExchangeFilterFunctions;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClientCustomizer;\nimport de.codecentric.boot.admin.server.web.client.LegacyEndpointConverter;\nimport de.codecentric.boot.admin.server.web.client.LegacyEndpointConverters;\nimport de.codecentric.boot.admin.server.web.client.cookies.CookieStoreCleanupTrigger;\nimport de.codecentric.boot.admin.server.web.client.cookies.JdkPerInstanceCookieStore;\nimport de.codecentric.boot.admin.server.web.client.cookies.PerInstanceCookieStore;\nimport de.codecentric.boot.admin.server.web.client.reactive.CompositeReactiveHttpHeadersProvider;\nimport de.codecentric.boot.admin.server.web.client.reactive.ReactiveHttpHeadersProvider;\n\n@Configuration(proxyBeanMethods = false)\n@Lazy(false)\npublic class AdminServerInstanceWebClientConfiguration {\n\n\tprivate final InstanceWebClient.Builder instanceWebClientBuilder;\n\n\tpublic AdminServerInstanceWebClientConfiguration(ObjectProvider<InstanceWebClientCustomizer> customizers,\n\t\t\tWebClient.Builder webClient) {\n\t\tthis.instanceWebClientBuilder = InstanceWebClient.builder(webClient);\n\t\tcustomizers.orderedStream().forEach((customizer) -> customizer.customize(this.instanceWebClientBuilder));\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean\n\t@Scope(\"prototype\")\n\tpublic InstanceWebClient.Builder instanceWebClientBuilder() {\n\t\treturn this.instanceWebClientBuilder.clone();\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\tprotected static class InstanceExchangeFiltersConfiguration {\n\n\t\t@Bean\n\t\t@ConditionalOnBean(InstanceExchangeFilterFunction.class)\n\t\t@ConditionalOnMissingBean(name = \"filterInstanceWebClientCustomizer\")\n\t\tpublic InstanceWebClientCustomizer filterInstanceWebClientCustomizer(\n\t\t\t\tList<InstanceExchangeFilterFunction> filters) {\n\t\t\treturn (builder) -> builder.filters((f) -> f.addAll(filters));\n\t\t}\n\n\t\t@Configuration(proxyBeanMethods = false)\n\t\tprotected static class DefaultInstanceExchangeFiltersConfiguration {\n\n\t\t\t@Bean\n\t\t\t@Order(0)\n\t\t\t@ConditionalOnBean(HttpHeadersProvider.class)\n\t\t\t@ConditionalOnMissingBean(name = \"addHeadersInstanceExchangeFilter\")\n\t\t\tpublic InstanceExchangeFilterFunction addHeadersInstanceExchangeFilter(\n\t\t\t\t\tList<HttpHeadersProvider> headersProviders) {\n\t\t\t\treturn InstanceExchangeFilterFunctions.addHeaders(new CompositeHttpHeadersProvider(headersProviders));\n\t\t\t}\n\n\t\t\t@Bean\n\t\t\t@Order(0)\n\t\t\t@ConditionalOnBean(ReactiveHttpHeadersProvider.class)\n\t\t\t@ConditionalOnMissingBean(name = \"addReactiveHeadersInstanceExchangeFilter\")\n\t\t\tpublic InstanceExchangeFilterFunction addReactiveHeadersInstanceExchangeFilter(\n\t\t\t\t\tList<ReactiveHttpHeadersProvider> reactiveHeadersProviders) {\n\t\t\t\treturn InstanceExchangeFilterFunctions\n\t\t\t\t\t.addHeadersReactive(new CompositeReactiveHttpHeadersProvider(reactiveHeadersProviders));\n\t\t\t}\n\n\t\t\t@Bean\n\t\t\t@Order(10)\n\t\t\t@ConditionalOnMissingBean(name = \"rewriteEndpointUrlInstanceExchangeFilter\")\n\t\t\tpublic InstanceExchangeFilterFunction rewriteEndpointUrlInstanceExchangeFilter() {\n\t\t\t\treturn InstanceExchangeFilterFunctions.rewriteEndpointUrl();\n\t\t\t}\n\n\t\t\t@Bean\n\t\t\t@Order(20)\n\t\t\t@ConditionalOnMissingBean(name = \"setDefaultAcceptHeaderInstanceExchangeFilter\")\n\t\t\tpublic InstanceExchangeFilterFunction setDefaultAcceptHeaderInstanceExchangeFilter() {\n\t\t\t\treturn InstanceExchangeFilterFunctions.setDefaultAcceptHeader();\n\t\t\t}\n\n\t\t\t@Bean\n\t\t\t@Order(30)\n\t\t\t@ConditionalOnBean(LegacyEndpointConverter.class)\n\t\t\t@ConditionalOnMissingBean(name = \"legacyEndpointConverterInstanceExchangeFilter\")\n\t\t\tpublic InstanceExchangeFilterFunction legacyEndpointConverterInstanceExchangeFilter(\n\t\t\t\t\tList<LegacyEndpointConverter> converters) {\n\t\t\t\treturn InstanceExchangeFilterFunctions.convertLegacyEndpoints(converters);\n\t\t\t}\n\n\t\t\t@Bean\n\t\t\t@Order(40)\n\t\t\t@ConditionalOnMissingBean(name = \"logfileAcceptWorkaround\")\n\t\t\tpublic InstanceExchangeFilterFunction logfileAcceptWorkaround() {\n\t\t\t\treturn InstanceExchangeFilterFunctions.logfileAcceptWorkaround();\n\t\t\t}\n\n\t\t\t@Bean\n\t\t\t@Order(50)\n\t\t\t@ConditionalOnMissingBean(name = \"cookieHandlingInstanceExchangeFilter\")\n\t\t\tpublic InstanceExchangeFilterFunction cookieHandlingInstanceExchangeFilter(\n\t\t\t\t\tfinal PerInstanceCookieStore store) {\n\t\t\t\treturn InstanceExchangeFilterFunctions.handleCookies(store);\n\t\t\t}\n\n\t\t\t@Bean\n\t\t\t@Order(100)\n\t\t\t@ConditionalOnMissingBean(name = \"retryInstanceExchangeFilter\")\n\t\t\tpublic InstanceExchangeFilterFunction retryInstanceExchangeFilter(\n\t\t\t\t\tAdminServerProperties adminServerProperties) {\n\t\t\t\tAdminServerProperties.MonitorProperties monitor = adminServerProperties.getMonitor();\n\t\t\t\treturn InstanceExchangeFilterFunctions.retry(monitor.getDefaultRetries(), monitor.getRetries());\n\t\t\t}\n\n\t\t\t@Bean\n\t\t\t@Order(200)\n\t\t\t@ConditionalOnMissingBean(name = \"timeoutInstanceExchangeFilter\")\n\t\t\tpublic InstanceExchangeFilterFunction timeoutInstanceExchangeFilter(\n\t\t\t\t\tAdminServerProperties adminServerProperties) {\n\t\t\t\tAdminServerProperties.MonitorProperties monitor = adminServerProperties.getMonitor();\n\t\t\t\treturn InstanceExchangeFilterFunctions.timeout(monitor.getDefaultTimeout(), monitor.getTimeout());\n\t\t\t}\n\n\t\t}\n\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\tprotected static class HttpHeadersProviderConfiguration {\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\tpublic BasicAuthHttpHeaderProvider basicAuthHttpHeadersProvider(AdminServerProperties adminServerProperties) {\n\t\t\tAdminServerProperties.InstanceAuthProperties instanceAuth = adminServerProperties.getInstanceAuth();\n\n\t\t\tif (instanceAuth.isEnabled()) {\n\t\t\t\treturn new BasicAuthHttpHeaderProvider(instanceAuth.getDefaultUserName(),\n\t\t\t\t\t\tinstanceAuth.getDefaultPassword(), instanceAuth.getServiceMap());\n\t\t\t}\n\t\t\telse {\n\t\t\t\treturn new BasicAuthHttpHeaderProvider();\n\t\t\t}\n\t\t}\n\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\tprotected static class LegacyEndpointConvertersConfiguration {\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean(name = \"healthLegacyEndpointConverter\")\n\t\tpublic LegacyEndpointConverter healthLegacyEndpointConverter() {\n\t\t\treturn LegacyEndpointConverters.health();\n\t\t}\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean(name = \"infoLegacyEndpointConverter\")\n\t\tpublic LegacyEndpointConverter infoLegacyEndpointConverter() {\n\t\t\treturn LegacyEndpointConverters.info();\n\t\t}\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean(name = \"envLegacyEndpointConverter\")\n\t\tpublic LegacyEndpointConverter envLegacyEndpointConverter() {\n\t\t\treturn LegacyEndpointConverters.env();\n\t\t}\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean(name = \"httptraceLegacyEndpointConverter\")\n\t\tpublic LegacyEndpointConverter httptraceLegacyEndpointConverter() {\n\t\t\treturn LegacyEndpointConverters.httptrace();\n\t\t}\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean(name = \"threaddumpLegacyEndpointConverter\")\n\t\tpublic LegacyEndpointConverter threaddumpLegacyEndpointConverter() {\n\t\t\treturn LegacyEndpointConverters.threaddump();\n\t\t}\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean(name = \"liquibaseLegacyEndpointConverter\")\n\t\tpublic LegacyEndpointConverter liquibaseLegacyEndpointConverter() {\n\t\t\treturn LegacyEndpointConverters.liquibase();\n\t\t}\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean(name = \"flywayLegacyEndpointConverter\")\n\t\tpublic LegacyEndpointConverter flywayLegacyEndpointConverter() {\n\t\t\treturn LegacyEndpointConverters.flyway();\n\t\t}\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean(name = \"beansLegacyEndpointConverter\")\n\t\tpublic LegacyEndpointConverter beansLegacyEndpointConverter() {\n\t\t\treturn LegacyEndpointConverters.beans();\n\t\t}\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean(name = \"configpropsLegacyEndpointConverter\")\n\t\tpublic LegacyEndpointConverter configpropsLegacyEndpointConverter() {\n\t\t\treturn LegacyEndpointConverters.configprops();\n\t\t}\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean(name = \"mappingsLegacyEndpointConverter\")\n\t\tpublic LegacyEndpointConverter mappingsLegacyEndpointConverter() {\n\t\t\treturn LegacyEndpointConverters.mappings();\n\t\t}\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean(name = \"startupLegacyEndpointConverter\")\n\t\tpublic LegacyEndpointConverter startupLegacyEndpointConverter() {\n\t\t\treturn LegacyEndpointConverters.startup();\n\t\t}\n\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\tprotected static class CookieStoreConfiguration {\n\n\t\t/**\n\t\t * Creates a default {@link PerInstanceCookieStore} that should be used.\n\t\t * @return the cookie store\n\t\t */\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\tpublic PerInstanceCookieStore cookieStore() {\n\t\t\treturn new JdkPerInstanceCookieStore(CookiePolicy.ACCEPT_ORIGINAL_SERVER);\n\t\t}\n\n\t\t/**\n\t\t * Creates a default trigger to cleanup the cookie store on deregistering of an\n\t\t * {@link de.codecentric.boot.admin.server.domain.entities.Instance}.\n\t\t * @param publisher publisher of {@link InstanceEvent}s events\n\t\t * @param cookieStore the store to inform about deregistration of an\n\t\t * {@link de.codecentric.boot.admin.server.domain.entities.Instance}\n\t\t * @return a new trigger\n\t\t */\n\t\t@Bean(initMethod = \"start\", destroyMethod = \"stop\")\n\t\t@ConditionalOnMissingBean\n\t\tpublic CookieStoreCleanupTrigger cookieStoreCleanupTrigger(final Publisher<InstanceEvent> publisher,\n\t\t\t\tfinal PerInstanceCookieStore cookieStore) {\n\t\t\treturn new CookieStoreCleanupTrigger(publisher, cookieStore);\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/config/AdminServerMarkerConfiguration.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.config;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration(proxyBeanMethods = false)\npublic class AdminServerMarkerConfiguration {\n\n\t@Bean\n\tpublic Marker adminServerMarker() {\n\t\treturn new Marker();\n\t}\n\n\tpublic static class Marker {\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/config/AdminServerNotifierAutoConfiguration.java",
    "content": "/*\n * Copyright 2014-2024 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.config;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.List;\n\nimport org.apache.hc.client5.http.auth.AuthScope;\nimport org.apache.hc.client5.http.auth.Credentials;\nimport org.apache.hc.client5.http.auth.UsernamePasswordCredentials;\nimport org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;\nimport org.apache.hc.client5.http.impl.classic.HttpClientBuilder;\nimport org.apache.hc.core5.http.HttpHost;\nimport org.reactivestreams.Publisher;\nimport org.springframework.boot.autoconfigure.AutoConfigureAfter;\nimport org.springframework.boot.autoconfigure.AutoConfigureBefore;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;\nimport org.springframework.boot.autoconfigure.condition.NoneNestedConditions;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Conditional;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Lazy;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.http.client.HttpComponentsClientHttpRequestFactory;\nimport org.springframework.mail.MailSender;\nimport org.springframework.mail.javamail.JavaMailSender;\nimport org.springframework.web.client.RestTemplate;\nimport org.thymeleaf.TemplateEngine;\nimport org.thymeleaf.spring6.SpringTemplateEngine;\nimport org.thymeleaf.templatemode.TemplateMode;\nimport org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;\n\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.notify.CompositeNotifier;\nimport de.codecentric.boot.admin.server.notify.DingTalkNotifier;\nimport de.codecentric.boot.admin.server.notify.DiscordNotifier;\nimport de.codecentric.boot.admin.server.notify.FeiShuNotifier;\nimport de.codecentric.boot.admin.server.notify.HipchatNotifier;\nimport de.codecentric.boot.admin.server.notify.LetsChatNotifier;\nimport de.codecentric.boot.admin.server.notify.MailNotifier;\nimport de.codecentric.boot.admin.server.notify.MattermostNotifier;\nimport de.codecentric.boot.admin.server.notify.MicrosoftTeamsNotifier;\nimport de.codecentric.boot.admin.server.notify.NotificationTrigger;\nimport de.codecentric.boot.admin.server.notify.Notifier;\nimport de.codecentric.boot.admin.server.notify.NotifierProxyProperties;\nimport de.codecentric.boot.admin.server.notify.OpsGenieNotifier;\nimport de.codecentric.boot.admin.server.notify.PagerdutyNotifier;\nimport de.codecentric.boot.admin.server.notify.RocketChatNotifier;\nimport de.codecentric.boot.admin.server.notify.SlackNotifier;\nimport de.codecentric.boot.admin.server.notify.TelegramNotifier;\nimport de.codecentric.boot.admin.server.notify.WebexNotifier;\nimport de.codecentric.boot.admin.server.notify.filter.FilteringNotifier;\nimport de.codecentric.boot.admin.server.notify.filter.web.NotificationFilterController;\n\n@Configuration(proxyBeanMethods = false)\n@Conditional(SpringBootAdminServerEnabledCondition.class)\n@EnableConfigurationProperties(NotifierProxyProperties.class)\n@AutoConfigureAfter(name = { \"org.springframework.boot.mail.autoconfigure.MailSenderAutoConfiguration\" })\npublic class AdminServerNotifierAutoConfiguration {\n\n\tprivate static RestTemplate createNotifierRestTemplate(NotifierProxyProperties proxyProperties) {\n\t\tRestTemplate restTemplate = new RestTemplate();\n\t\tif (proxyProperties.getHost() != null) {\n\t\t\tHttpClientBuilder builder = HttpClientBuilder.create();\n\t\t\tbuilder.setProxy(new HttpHost(proxyProperties.getHost(), proxyProperties.getPort()));\n\n\t\t\tif (proxyProperties.getUsername() != null && proxyProperties.getPassword() != null) {\n\t\t\t\tAuthScope authScope = new AuthScope(proxyProperties.getHost(), proxyProperties.getPort());\n\t\t\t\tCredentials usernamePasswordCredentials = new UsernamePasswordCredentials(proxyProperties.getUsername(),\n\t\t\t\t\t\tproxyProperties.getPassword().toCharArray());\n\n\t\t\t\tBasicCredentialsProvider credsProvider = new BasicCredentialsProvider();\n\t\t\t\tcredsProvider.setCredentials(authScope, usernamePasswordCredentials);\n\t\t\t\tbuilder.setDefaultCredentialsProvider(credsProvider);\n\t\t\t}\n\n\t\t\trestTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(builder.build()));\n\t\t}\n\t\treturn restTemplate;\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnBean(Notifier.class)\n\t@Lazy(false)\n\tpublic static class NotifierTriggerConfiguration {\n\n\t\t@Bean(initMethod = \"start\", destroyMethod = \"stop\")\n\t\t@ConditionalOnMissingBean(NotificationTrigger.class)\n\t\tpublic NotificationTrigger notificationTrigger(Notifier notifier, Publisher<InstanceEvent> events) {\n\t\t\treturn new NotificationTrigger(notifier, events);\n\t\t}\n\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnBean(Notifier.class)\n\t@AutoConfigureBefore({ NotifierTriggerConfiguration.class })\n\t@Lazy(false)\n\tpublic static class CompositeNotifierConfiguration {\n\n\t\t@Bean\n\t\t@Primary\n\t\t@Conditional(NoSingleNotifierCandidateCondition.class)\n\t\tpublic CompositeNotifier compositeNotifier(List<Notifier> notifiers) {\n\t\t\treturn new CompositeNotifier(notifiers);\n\t\t}\n\n\t\tstatic class NoSingleNotifierCandidateCondition extends NoneNestedConditions {\n\n\t\t\tNoSingleNotifierCandidateCondition() {\n\t\t\t\tsuper(ConfigurationPhase.REGISTER_BEAN);\n\t\t\t}\n\n\t\t\t@ConditionalOnSingleCandidate(Notifier.class)\n\t\t\tstatic class HasSingleNotifierInstance {\n\n\t\t\t}\n\n\t\t}\n\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnSingleCandidate(FilteringNotifier.class)\n\t@Lazy(false)\n\tpublic static class FilteringNotifierWebConfiguration {\n\n\t\tprivate final FilteringNotifier filteringNotifier;\n\n\t\tpublic FilteringNotifierWebConfiguration(FilteringNotifier filteringNotifier) {\n\t\t\tthis.filteringNotifier = filteringNotifier;\n\t\t}\n\n\t\t@Bean\n\t\tpublic NotificationFilterController notificationFilterController() {\n\t\t\treturn new NotificationFilterController(this.filteringNotifier);\n\t\t}\n\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@AutoConfigureBefore({ NotifierTriggerConfiguration.class, CompositeNotifierConfiguration.class })\n\t@ConditionalOnBean(MailSender.class)\n\t@Lazy(false)\n\tpublic static class MailNotifierConfiguration {\n\n\t\tprivate final ApplicationContext applicationContext;\n\n\t\tpublic MailNotifierConfiguration(ApplicationContext applicationContext) {\n\t\t\tthis.applicationContext = applicationContext;\n\t\t}\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\t@ConfigurationProperties(\"spring.boot.admin.notify.mail\")\n\t\tpublic MailNotifier mailNotifier(JavaMailSender mailSender, InstanceRepository repository,\n\t\t\t\tTemplateEngine mailNotifierTemplateEngine) {\n\t\t\treturn new MailNotifier(mailSender, repository, mailNotifierTemplateEngine);\n\t\t}\n\n\t\t@Bean\n\t\tpublic TemplateEngine mailNotifierTemplateEngine() {\n\t\t\tClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();\n\t\t\tresolver.setTemplateMode(TemplateMode.HTML);\n\t\t\tresolver.setCharacterEncoding(StandardCharsets.UTF_8.name());\n\n\t\t\tSpringTemplateEngine templateEngine = new SpringTemplateEngine();\n\t\t\ttemplateEngine.setTemplateResolver(resolver);\n\t\t\treturn templateEngine;\n\t\t}\n\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnProperty(prefix = \"spring.boot.admin.notify.hipchat\", name = \"url\")\n\t@AutoConfigureBefore({ NotifierTriggerConfiguration.class, CompositeNotifierConfiguration.class })\n\t@Lazy(false)\n\tpublic static class HipchatNotifierConfiguration {\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\t@ConfigurationProperties(\"spring.boot.admin.notify.hipchat\")\n\t\tpublic HipchatNotifier hipchatNotifier(InstanceRepository repository, NotifierProxyProperties proxyProperties) {\n\t\t\treturn new HipchatNotifier(repository, createNotifierRestTemplate(proxyProperties));\n\t\t}\n\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnProperty(prefix = \"spring.boot.admin.notify.slack\", name = \"webhook-url\")\n\t@AutoConfigureBefore({ NotifierTriggerConfiguration.class, CompositeNotifierConfiguration.class })\n\t@Lazy(false)\n\tpublic static class SlackNotifierConfiguration {\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\t@ConfigurationProperties(\"spring.boot.admin.notify.slack\")\n\t\tpublic SlackNotifier slackNotifier(InstanceRepository repository, NotifierProxyProperties proxyProperties) {\n\t\t\treturn new SlackNotifier(repository, createNotifierRestTemplate(proxyProperties));\n\t\t}\n\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnProperty(prefix = \"spring.boot.admin.notify.mattermost\", name = \"api-url\")\n\t@AutoConfigureBefore({ NotifierTriggerConfiguration.class, CompositeNotifierConfiguration.class })\n\t@Lazy(false)\n\tpublic static class MattermostNotifierConfiguration {\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\t@ConfigurationProperties(\"spring.boot.admin.notify.mattermost\")\n\t\tpublic MattermostNotifier mattermostNotifier(InstanceRepository repository,\n\t\t\t\tNotifierProxyProperties proxyProperties) {\n\t\t\treturn new MattermostNotifier(repository, createNotifierRestTemplate(proxyProperties));\n\t\t}\n\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnProperty(prefix = \"spring.boot.admin.notify.letschat\", name = \"url\")\n\t@AutoConfigureBefore({ NotifierTriggerConfiguration.class, CompositeNotifierConfiguration.class })\n\t@Lazy(false)\n\tpublic static class LetsChatNotifierConfiguration {\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\t@ConfigurationProperties(\"spring.boot.admin.notify.letschat\")\n\t\tpublic LetsChatNotifier letsChatNotifier(InstanceRepository repository,\n\t\t\t\tNotifierProxyProperties proxyProperties) {\n\t\t\treturn new LetsChatNotifier(repository, createNotifierRestTemplate(proxyProperties));\n\t\t}\n\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnProperty(prefix = \"spring.boot.admin.notify.pagerduty\", name = \"service-key\")\n\t@AutoConfigureBefore({ NotifierTriggerConfiguration.class, CompositeNotifierConfiguration.class })\n\t@Lazy(false)\n\tpublic static class PagerdutyNotifierConfiguration {\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\t@ConfigurationProperties(\"spring.boot.admin.notify.pagerduty\")\n\t\tpublic PagerdutyNotifier pagerdutyNotifier(InstanceRepository repository,\n\t\t\t\tNotifierProxyProperties proxyProperties) {\n\t\t\treturn new PagerdutyNotifier(repository, createNotifierRestTemplate(proxyProperties));\n\t\t}\n\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnProperty(prefix = \"spring.boot.admin.notify.opsgenie\", name = \"api-key\")\n\t@AutoConfigureBefore({ NotifierTriggerConfiguration.class, CompositeNotifierConfiguration.class })\n\t@Lazy(false)\n\tpublic static class OpsGenieNotifierConfiguration {\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\t@ConfigurationProperties(\"spring.boot.admin.notify.opsgenie\")\n\t\tpublic OpsGenieNotifier opsgenieNotifier(InstanceRepository repository,\n\t\t\t\tNotifierProxyProperties proxyProperties) {\n\t\t\treturn new OpsGenieNotifier(repository, createNotifierRestTemplate(proxyProperties));\n\t\t}\n\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnProperty(prefix = \"spring.boot.admin.notify.ms-teams\", name = \"webhook-url\")\n\t@AutoConfigureBefore({ NotifierTriggerConfiguration.class, CompositeNotifierConfiguration.class })\n\t@Lazy(false)\n\tpublic static class MicrosoftTeamsNotifierConfiguration {\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\t@ConfigurationProperties(\"spring.boot.admin.notify.ms-teams\")\n\t\tpublic MicrosoftTeamsNotifier microsoftTeamsNotifier(InstanceRepository repository,\n\t\t\t\tNotifierProxyProperties proxyProperties) {\n\t\t\treturn new MicrosoftTeamsNotifier(repository, createNotifierRestTemplate(proxyProperties));\n\t\t}\n\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnProperty(prefix = \"spring.boot.admin.notify.telegram\", name = \"auth-token\")\n\t@AutoConfigureBefore({ NotifierTriggerConfiguration.class, CompositeNotifierConfiguration.class })\n\t@Lazy(false)\n\tpublic static class TelegramNotifierConfiguration {\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\t@ConfigurationProperties(\"spring.boot.admin.notify.telegram\")\n\t\tpublic TelegramNotifier telegramNotifier(InstanceRepository repository,\n\t\t\t\tNotifierProxyProperties proxyProperties) {\n\t\t\treturn new TelegramNotifier(repository, createNotifierRestTemplate(proxyProperties));\n\t\t}\n\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnProperty(prefix = \"spring.boot.admin.notify.discord\", name = \"webhook-url\")\n\t@AutoConfigureBefore({ NotifierTriggerConfiguration.class, CompositeNotifierConfiguration.class })\n\t@Lazy(false)\n\tpublic static class DiscordNotifierConfiguration {\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\t@ConfigurationProperties(\"spring.boot.admin.notify.discord\")\n\t\tpublic DiscordNotifier discordNotifier(InstanceRepository repository, NotifierProxyProperties proxyProperties) {\n\t\t\treturn new DiscordNotifier(repository, createNotifierRestTemplate(proxyProperties));\n\t\t}\n\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnProperty(prefix = \"spring.boot.admin.notify.dingtalk\", name = \"webhook-url\")\n\t@AutoConfigureBefore({ NotifierTriggerConfiguration.class, CompositeNotifierConfiguration.class })\n\t@Lazy(false)\n\tpublic static class DingTalkNotifierConfiguration {\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\t@ConfigurationProperties(\"spring.boot.admin.notify.dingtalk\")\n\t\tpublic DingTalkNotifier dingTalkNotifier(InstanceRepository repository,\n\t\t\t\tNotifierProxyProperties proxyProperties) {\n\t\t\treturn new DingTalkNotifier(repository, createNotifierRestTemplate(proxyProperties));\n\t\t}\n\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnProperty(prefix = \"spring.boot.admin.notify.rocketchat\", name = \"url\")\n\t@AutoConfigureBefore({ NotifierTriggerConfiguration.class, CompositeNotifierConfiguration.class })\n\t@Lazy(false)\n\tpublic static class RocketChatNotifierConfiguration {\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\t@ConfigurationProperties(\"spring.boot.admin.notify.rocketchat\")\n\t\tpublic RocketChatNotifier rocketChatNotifier(InstanceRepository repository,\n\t\t\t\tNotifierProxyProperties proxyProperties) {\n\t\t\treturn new RocketChatNotifier(repository, createNotifierRestTemplate(proxyProperties));\n\t\t}\n\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnProperty(prefix = \"spring.boot.admin.notify.feishu\", name = \"webhook-url\")\n\t@AutoConfigureBefore({ NotifierTriggerConfiguration.class, CompositeNotifierConfiguration.class })\n\t@Lazy(false)\n\tpublic static class FeiShuNotifierConfiguration {\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\t@ConfigurationProperties(\"spring.boot.admin.notify.feishu\")\n\t\tpublic FeiShuNotifier feiShuNotifier(InstanceRepository repository, NotifierProxyProperties proxyProperties) {\n\t\t\treturn new FeiShuNotifier(repository, createNotifierRestTemplate(proxyProperties));\n\t\t}\n\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnProperty(prefix = \"spring.boot.admin.notify.webex\", name = \"auth-token\")\n\t@AutoConfigureBefore({ NotifierTriggerConfiguration.class, CompositeNotifierConfiguration.class })\n\t@Lazy(false)\n\tpublic static class WebexNotifierConfiguration {\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\t@ConfigurationProperties(\"spring.boot.admin.notify.webex\")\n\t\tpublic WebexNotifier webexNotifier(InstanceRepository repository, NotifierProxyProperties proxyProperties) {\n\t\t\treturn new WebexNotifier(repository, createNotifierRestTemplate(proxyProperties));\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/config/AdminServerProperties.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.config;\n\nimport java.time.Duration;\nimport java.time.temporal.ChronoUnit;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Set;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.boot.convert.DurationUnit;\n\nimport de.codecentric.boot.admin.server.web.PathUtils;\nimport de.codecentric.boot.admin.server.web.client.BasicAuthHttpHeaderProvider.InstanceCredentials;\n\nimport static java.util.Arrays.asList;\n\n@lombok.Data\n@ConfigurationProperties(\"spring.boot.admin\")\npublic class AdminServerProperties {\n\n\t/**\n\t * The context-path prefixes the path where the Admin Servers static assets and api\n\t * should be served, relative to the Dispatcher-Servlet.\n\t */\n\tprivate String contextPath = \"\";\n\n\tprivate ServerProperties server = new ServerProperties();\n\n\tprivate MonitorProperties monitor = new MonitorProperties();\n\n\tprivate InstanceAuthProperties instanceAuth = new InstanceAuthProperties();\n\n\tprivate InstanceProxyProperties instanceProxy = new InstanceProxyProperties();\n\n\t/**\n\t * The metadata keys which should be sanitized when serializing to json\n\t */\n\tprivate String[] metadataKeysToSanitize = new String[] { \".*password$\", \".*secret$\", \".*key$\", \".*token$\",\n\t\t\t\".*credentials.*\", \".*vcap_services$\" };\n\n\t/**\n\t * For Spring Boot 2.x applications the endpoints should be discovered automatically\n\t * using the actuator links. For Spring Boot 1.x applications SBA probes for the\n\t * specified endpoints using an OPTIONS request. If the path differs from the id you\n\t * can specify this as id:path (e.g. health:ping).\n\t */\n\tprivate String[] probedEndpoints = { \"health\", \"env\", \"metrics\", \"httptrace:trace\", \"httptrace\", \"threaddump:dump\",\n\t\t\t\"threaddump\", \"jolokia\", \"info\", \"logfile\", \"refresh\", \"flyway\", \"liquibase\", \"heapdump\", \"loggers\",\n\t\t\t\"auditevents\", \"mappings\", \"scheduledtasks\", \"configprops\", \"caches\", \"beans\" };\n\n\tpublic void setContextPath(String contextPath) {\n\t\tthis.contextPath = PathUtils.normalizePath(contextPath);\n\t}\n\n\t/**\n\t * @param path the partial path within the admin context-path\n\t * @return the full path within the admin context-path\n\t */\n\tpublic String path(String path) {\n\t\treturn this.contextPath + path;\n\t}\n\n\t@lombok.Data\n\tpublic static class ServerProperties {\n\n\t\t/**\n\t\t * Enable Spring Boot Admin Server Default: true\n\t\t */\n\t\tprivate boolean enabled = true;\n\n\t}\n\n\t@lombok.Data\n\tpublic static class MonitorProperties {\n\n\t\t/**\n\t\t * Time interval to check the status of instances, must be greater than 1 second.\n\t\t */\n\t\t@DurationUnit(ChronoUnit.MILLIS)\n\t\tprivate Duration statusInterval = Duration.ofMillis(10_000L);\n\n\t\t/**\n\t\t * Lifetime of status. The status won't be updated as long the last status isn't\n\t\t * expired.\n\t\t */\n\t\t@DurationUnit(ChronoUnit.MILLIS)\n\t\tprivate Duration statusLifetime = Duration.ofMillis(10_000L);\n\n\t\t/**\n\t\t * The maximal backoff for status check retries (retry after error has exponential\n\t\t * backoff, minimum backoff is 1 second).\n\t\t */\n\t\t@DurationUnit(ChronoUnit.MILLIS)\n\t\tprivate Duration statusMaxBackoff = Duration.ofMillis(60_000L);\n\n\t\t/**\n\t\t * Time interval to check the info of instances,\n\t\t */\n\t\t@DurationUnit(ChronoUnit.MILLIS)\n\t\tprivate Duration infoInterval = Duration.ofMinutes(1L);\n\n\t\t/**\n\t\t * The maximal backoff for info check retries (retry after error has exponential\n\t\t * backoff, minimum backoff is 1 second).\n\t\t */\n\t\t@DurationUnit(ChronoUnit.MILLIS)\n\t\tprivate Duration infoMaxBackoff = Duration.ofMinutes(10);\n\n\t\t/**\n\t\t * Lifetime of info. The info won't be updated as long the last info isn't\n\t\t * expired.\n\t\t */\n\t\t@DurationUnit(ChronoUnit.MILLIS)\n\t\tprivate Duration infoLifetime = Duration.ofMinutes(1L);\n\n\t\t/**\n\t\t * Default number of retries for failed requests. Individual values for specific\n\t\t * endpoints can be overriden using `spring.boot.admin.monitor.retries.*`.\n\t\t */\n\t\tprivate int defaultRetries = 0;\n\n\t\t/**\n\t\t * Number of retries per endpointId. Defaults to default-retry.\n\t\t */\n\t\tprivate Map<String, Integer> retries = new HashMap<>();\n\n\t\t/**\n\t\t * Default timeout when making requests. Individual values for specific endpoints\n\t\t * can be overriden using `spring.boot.admin.monitor.timeout.*`.\n\t\t */\n\t\t@DurationUnit(ChronoUnit.MILLIS)\n\t\tprivate Duration defaultTimeout = Duration.ofMillis(10_000L);\n\n\t\t/**\n\t\t * timeout per endpointId. Defaults to default-timeout.\n\t\t */\n\t\t@DurationUnit(ChronoUnit.MILLIS)\n\t\tprivate Map<String, Duration> timeout = new HashMap<>();\n\n\t}\n\n\t@lombok.Data\n\tpublic static class InstanceAuthProperties {\n\n\t\t/**\n\t\t * Whether or not to use configuration properties as a source for instance\n\t\t * credentials <br/>\n\t\t * Default: true\n\t\t */\n\t\tprivate boolean enabled = true;\n\n\t\t/**\n\t\t * Default username used for authentication to each instance. Individual values\n\t\t * for specific instances can be overriden using\n\t\t * `spring.boot.admin.instance-auth.service-map.*.user-name`. <br/>\n\t\t * Default: null\n\t\t */\n\t\tprivate String defaultUserName = null;\n\n\t\t/**\n\t\t * Default userpassword used for authentication to each instance. Individual\n\t\t * values for specific instances can be overriden using\n\t\t * `spring.boot.admin.instance-auth.service-map.*.user-password`. <br/>\n\t\t * Default: null\n\t\t */\n\t\tprivate String defaultPassword = null;\n\n\t\t/**\n\t\t * Map of instance credentials per registered service name\n\t\t */\n\t\tprivate Map<String, InstanceCredentials> serviceMap = new HashMap<>();\n\n\t}\n\n\t@lombok.Data\n\tpublic static class InstanceProxyProperties {\n\n\t\t/**\n\t\t * Headers not to be forwarded when making requests to clients.\n\t\t */\n\t\tprivate Set<String> ignoredHeaders = new HashSet<>(asList(\"Cookie\", \"Set-Cookie\", \"Authorization\"));\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/config/AdminServerWebConfiguration.java",
    "content": "/*\n * Copyright 2014-2024 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.config;\n\nimport org.springframework.boot.autoconfigure.AutoConfigureAfter;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;\nimport org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration;\nimport org.springframework.context.ApplicationEventPublisher;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.web.accept.ContentNegotiationManager;\nimport org.springframework.web.reactive.accept.RequestedContentTypeResolver;\nimport tools.jackson.databind.module.SimpleModule;\n\nimport de.codecentric.boot.admin.server.eventstore.InstanceEventStore;\nimport de.codecentric.boot.admin.server.services.ApplicationRegistry;\nimport de.codecentric.boot.admin.server.services.InstanceRegistry;\nimport de.codecentric.boot.admin.server.utils.jackson.AdminServerModule;\nimport de.codecentric.boot.admin.server.web.ApplicationsController;\nimport de.codecentric.boot.admin.server.web.InstancesController;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\n\n@Configuration(proxyBeanMethods = false)\npublic class AdminServerWebConfiguration {\n\n\tprivate final AdminServerProperties adminServerProperties;\n\n\tpublic AdminServerWebConfiguration(AdminServerProperties adminServerProperties) {\n\t\tthis.adminServerProperties = adminServerProperties;\n\t}\n\n\t@Bean\n\tpublic SimpleModule adminJacksonModule() {\n\t\treturn new AdminServerModule(this.adminServerProperties.getMetadataKeysToSanitize());\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean\n\tpublic InstancesController instancesController(InstanceRegistry instanceRegistry, InstanceEventStore eventStore) {\n\t\treturn new InstancesController(instanceRegistry, eventStore);\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean\n\tpublic ApplicationsController applicationsController(ApplicationRegistry applicationRegistry,\n\t\t\tApplicationEventPublisher applicationEventPublisher) {\n\t\treturn new ApplicationsController(applicationRegistry, applicationEventPublisher);\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)\n\tpublic static class ReactiveRestApiConfiguration {\n\n\t\tprivate final AdminServerProperties adminServerProperties;\n\n\t\tpublic ReactiveRestApiConfiguration(AdminServerProperties adminServerProperties) {\n\t\t\tthis.adminServerProperties = adminServerProperties;\n\t\t}\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\tpublic de.codecentric.boot.admin.server.web.reactive.InstancesProxyController instancesProxyController(\n\t\t\t\tInstanceRegistry instanceRegistry, InstanceWebClient.Builder instanceWebClientBuilder) {\n\t\t\treturn new de.codecentric.boot.admin.server.web.reactive.InstancesProxyController(\n\t\t\t\t\tthis.adminServerProperties.getContextPath(),\n\t\t\t\t\tthis.adminServerProperties.getInstanceProxy().getIgnoredHeaders(), instanceRegistry,\n\t\t\t\t\tinstanceWebClientBuilder.build());\n\t\t}\n\n\t\t@Bean\n\t\tpublic org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping adminHandlerMapping(\n\t\t\t\tRequestedContentTypeResolver webFluxContentTypeResolver) {\n\t\t\torg.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping mapping = new de.codecentric.boot.admin.server.web.reactive.AdminControllerHandlerMapping(\n\t\t\t\t\tthis.adminServerProperties.getContextPath());\n\t\t\tmapping.setOrder(0);\n\t\t\tmapping.setContentTypeResolver(webFluxContentTypeResolver);\n\t\t\treturn mapping;\n\t\t}\n\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)\n\t@AutoConfigureAfter(WebMvcAutoConfiguration.class)\n\tpublic static class ServletRestApiConfiguration {\n\n\t\tprivate final AdminServerProperties adminServerProperties;\n\n\t\tpublic ServletRestApiConfiguration(AdminServerProperties adminServerProperties) {\n\t\t\tthis.adminServerProperties = adminServerProperties;\n\t\t}\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\tpublic de.codecentric.boot.admin.server.web.servlet.InstancesProxyController instancesProxyController(\n\t\t\t\tInstanceRegistry instanceRegistry, InstanceWebClient.Builder instanceWebClientBuilder) {\n\t\t\treturn new de.codecentric.boot.admin.server.web.servlet.InstancesProxyController(\n\t\t\t\t\tthis.adminServerProperties.getContextPath(),\n\t\t\t\t\tthis.adminServerProperties.getInstanceProxy().getIgnoredHeaders(), instanceRegistry,\n\t\t\t\t\tinstanceWebClientBuilder.build());\n\t\t}\n\n\t\t@Bean\n\t\tpublic org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping adminHandlerMapping(\n\t\t\t\tContentNegotiationManager contentNegotiationManager) {\n\t\t\torg.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping mapping = new de.codecentric.boot.admin.server.web.servlet.AdminControllerHandlerMapping(\n\t\t\t\t\tthis.adminServerProperties.getContextPath());\n\t\t\tmapping.setOrder(0);\n\t\t\tmapping.setContentNegotiationManager(contentNegotiationManager);\n\t\t\treturn mapping;\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/config/EnableAdminServer.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.config;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\nimport org.springframework.context.annotation.Import;\n\n/**\n * @author Dennis Schulte\n */\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@Import(AdminServerMarkerConfiguration.class)\npublic @interface EnableAdminServer {\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/config/SpringBootAdminServerEnabledCondition.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.config;\n\nimport org.springframework.boot.autoconfigure.condition.ConditionOutcome;\nimport org.springframework.boot.autoconfigure.condition.SpringBootCondition;\nimport org.springframework.boot.context.properties.bind.Bindable;\nimport org.springframework.boot.context.properties.bind.Binder;\nimport org.springframework.context.annotation.ConditionContext;\nimport org.springframework.core.type.AnnotatedTypeMetadata;\n\n/**\n * This condition checks if the sever should be enabled. Property\n * spring.boot.admin.server.enabled is checked.\n */\npublic class SpringBootAdminServerEnabledCondition extends SpringBootCondition {\n\n\t@Override\n\tpublic ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata annotatedTypeMetadata) {\n\t\tAdminServerProperties serverProperties = getClientProperties(context);\n\n\t\tif (!serverProperties.getServer().isEnabled()) {\n\t\t\treturn ConditionOutcome\n\t\t\t\t.noMatch(\"Spring Boot Server is disabled, because 'spring.boot.admin.server.enabled' is false.\");\n\t\t}\n\n\t\treturn ConditionOutcome.match();\n\t}\n\n\tprivate AdminServerProperties getClientProperties(ConditionContext context) {\n\t\tAdminServerProperties serverProperties = new AdminServerProperties();\n\t\tBinder.get(context.getEnvironment()).bind(\"spring.boot.admin\", Bindable.ofInstance(serverProperties));\n\t\treturn serverProperties;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/config/package-info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Spring Boot Admin Server - core configuration package.\n@NullMarked\npackage de.codecentric.boot.admin.server.config;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/entities/Application.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.entities;\n\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport org.jspecify.annotations.Nullable;\nimport org.springframework.util.Assert;\n\nimport de.codecentric.boot.admin.server.domain.values.BuildVersion;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\n@lombok.Data\npublic final class Application {\n\n\tprivate final String name;\n\n\t@Nullable private final BuildVersion buildVersion;\n\n\tprivate final String status;\n\n\tprivate final Instant statusTimestamp;\n\n\tprivate final List<Instance> instances;\n\n\t@lombok.Builder(builderClassName = \"Builder\", toBuilder = true)\n\tprivate Application(String name, @Nullable BuildVersion buildVersion, @Nullable String status,\n\t\t\t@Nullable Instant statusTimestamp, List<Instance> instances) {\n\t\tAssert.notNull(name, \"'name' must not be null\");\n\t\tthis.name = name;\n\t\tthis.buildVersion = buildVersion;\n\t\tthis.status = (status != null) ? status : StatusInfo.STATUS_UNKNOWN;\n\t\tthis.statusTimestamp = (statusTimestamp != null) ? statusTimestamp : Instant.now();\n\t\tif (instances.isEmpty()) {\n\t\t\tthis.instances = Collections.emptyList();\n\t\t}\n\t\telse {\n\t\t\tthis.instances = new ArrayList<>(instances);\n\n\t\t}\n\t}\n\n\tpublic static Application.Builder create(String name) {\n\t\treturn builder().name(name);\n\t}\n\n\tpublic static class Builder {\n\n\t\t// Will be generated by lombok\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/entities/EventsourcingInstanceRepository.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.entities;\n\nimport java.util.function.BiFunction;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.util.retry.Retry;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.eventstore.InstanceEventStore;\nimport de.codecentric.boot.admin.server.eventstore.OptimisticLockingException;\n\n/**\n * InstanceRepository storing instances using an event log.\n *\n * @author Johannes Edmeier\n */\npublic class EventsourcingInstanceRepository implements InstanceRepository {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(EventsourcingInstanceRepository.class);\n\n\tprivate final InstanceEventStore eventStore;\n\n\tprivate final Retry retryOptimisticLockException = Retry.max(10)\n\t\t.doBeforeRetry((s) -> log.debug(\"Retrying after OptimisticLockingException\", s.failure()))\n\t\t.filter(OptimisticLockingException.class::isInstance);\n\n\tpublic EventsourcingInstanceRepository(InstanceEventStore eventStore) {\n\t\tthis.eventStore = eventStore;\n\t}\n\n\t@Override\n\tpublic Mono<Instance> save(Instance instance) {\n\t\treturn this.eventStore.append(instance.getUnsavedEvents()).then(Mono.just(instance.clearUnsavedEvents()));\n\t}\n\n\t@Override\n\tpublic Flux<Instance> findAll() {\n\t\treturn this.eventStore.findAll()\n\t\t\t.groupBy(InstanceEvent::getInstance)\n\t\t\t.flatMap((f) -> f.reduce(Instance.create(f.key()), Instance::apply));\n\t}\n\n\t@Override\n\tpublic Mono<Instance> find(InstanceId id) {\n\t\treturn this.eventStore.find(id)\n\t\t\t.collectList()\n\t\t\t.filter((e) -> !e.isEmpty())\n\t\t\t.map((e) -> Instance.create(id).apply(e));\n\t}\n\n\t@Override\n\tpublic Flux<Instance> findByName(String name) {\n\t\treturn findAll().filter((a) -> a.isRegistered() && name.equals(a.getRegistration().getName()));\n\t}\n\n\t@Override\n\tpublic Mono<Instance> compute(InstanceId id, BiFunction<InstanceId, Instance, Mono<Instance>> remappingFunction) {\n\t\treturn this.find(id)\n\t\t\t.flatMap((application) -> remappingFunction.apply(id, application))\n\t\t\t.switchIfEmpty(Mono.defer(() -> remappingFunction.apply(id, null)))\n\t\t\t.flatMap(this::save)\n\t\t\t.retryWhen(this.retryOptimisticLockException);\n\t}\n\n\t@Override\n\tpublic Mono<Instance> computeIfPresent(InstanceId id,\n\t\t\tBiFunction<InstanceId, Instance, Mono<Instance>> remappingFunction) {\n\t\treturn this.find(id)\n\t\t\t.flatMap((application) -> remappingFunction.apply(id, application))\n\t\t\t.flatMap(this::save)\n\t\t\t.retryWhen(this.retryOptimisticLockException);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/entities/Instance.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.entities;\n\nimport java.io.Serializable;\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\n\nimport org.jspecify.annotations.Nullable;\nimport org.springframework.util.Assert;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceDeregisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEndpointsDetectedEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceInfoChangedEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegistrationUpdatedEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.BuildVersion;\nimport de.codecentric.boot.admin.server.domain.values.Endpoint;\nimport de.codecentric.boot.admin.server.domain.values.Endpoints;\nimport de.codecentric.boot.admin.server.domain.values.Info;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\nimport de.codecentric.boot.admin.server.domain.values.Tags;\n\nimport static java.util.Collections.emptyList;\nimport static java.util.Collections.emptyMap;\nimport static java.util.Collections.unmodifiableList;\n\n/**\n * The aggregate representing a registered application instance.\n *\n * @author Johannes Edmeier\n */\n@lombok.Data\n@lombok.EqualsAndHashCode(exclude = { \"unsavedEvents\", \"statusTimestamp\" })\n@lombok.ToString(exclude = \"unsavedEvents\")\npublic final class Instance implements Serializable {\n\n\tprivate final InstanceId id;\n\n\tprivate final long version;\n\n\t@Nullable private final Registration registration;\n\n\tprivate final boolean registered;\n\n\tprivate final StatusInfo statusInfo;\n\n\tprivate final Instant statusTimestamp;\n\n\tprivate final Info info;\n\n\tprivate final List<InstanceEvent> unsavedEvents;\n\n\tprivate final Endpoints endpoints;\n\n\t@Nullable private final BuildVersion buildVersion;\n\n\tprivate final Tags tags;\n\n\tprivate Instance(InstanceId id) {\n\t\tthis(id, -1L, null, false, StatusInfo.ofUnknown(), Instant.EPOCH, Info.empty(), Endpoints.empty(), null,\n\t\t\t\tTags.empty(), emptyList());\n\t}\n\n\tprivate Instance(InstanceId id, long version, @Nullable Registration registration, boolean registered,\n\t\t\tStatusInfo statusInfo, Instant statusTimestamp, Info info, Endpoints endpoints,\n\t\t\t@Nullable BuildVersion buildVersion, Tags tags, List<InstanceEvent> unsavedEvents) {\n\t\tAssert.notNull(id, \"'id' must not be null\");\n\t\tAssert.notNull(endpoints, \"'endpoints' must not be null\");\n\t\tAssert.notNull(info, \"'info' must not be null\");\n\t\tAssert.notNull(statusInfo, \"'statusInfo' must not be null\");\n\t\tthis.id = id;\n\t\tthis.version = version;\n\t\tthis.registration = registration;\n\t\tthis.registered = registered;\n\t\tthis.statusInfo = statusInfo;\n\t\tthis.statusTimestamp = statusTimestamp;\n\t\tthis.info = info;\n\t\tthis.endpoints = (registered && (registration != null))\n\t\t\t\t? endpoints.withEndpoint(Endpoint.HEALTH, registration.getHealthUrl()) : endpoints;\n\t\tthis.unsavedEvents = unsavedEvents;\n\t\tthis.buildVersion = buildVersion;\n\t\tthis.tags = tags;\n\t}\n\n\tpublic static Instance create(InstanceId id) {\n\t\tAssert.notNull(id, \"'id' must not be null\");\n\t\treturn new Instance(id);\n\t}\n\n\tpublic Instance register(Registration registration) {\n\t\tAssert.notNull(registration, \"'registration' must not be null\");\n\t\tif (!this.isRegistered()) {\n\t\t\treturn this.apply(new InstanceRegisteredEvent(this.id, this.nextVersion(), registration), true);\n\t\t}\n\n\t\tif (!Objects.equals(this.registration, registration)) {\n\t\t\treturn this.apply(new InstanceRegistrationUpdatedEvent(this.id, this.nextVersion(), registration), true);\n\t\t}\n\n\t\treturn this;\n\t}\n\n\tpublic Instance deregister() {\n\t\tif (this.isRegistered()) {\n\t\t\treturn this.apply(new InstanceDeregisteredEvent(this.id, this.nextVersion()), true);\n\t\t}\n\t\treturn this;\n\t}\n\n\tpublic Instance withInfo(Info info) {\n\t\tAssert.notNull(info, \"'info' must not be null\");\n\t\tif (Objects.equals(this.info, info)) {\n\t\t\treturn this;\n\t\t}\n\t\treturn this.apply(new InstanceInfoChangedEvent(this.id, this.nextVersion(), info), true);\n\t}\n\n\tpublic Instance withStatusInfo(StatusInfo statusInfo) {\n\t\tAssert.notNull(statusInfo, \"'statusInfo' must not be null\");\n\t\tif (Objects.equals(this.statusInfo.getStatus(), statusInfo.getStatus())) {\n\t\t\treturn this;\n\t\t}\n\t\treturn this.apply(new InstanceStatusChangedEvent(this.id, this.nextVersion(), statusInfo), true);\n\t}\n\n\tpublic Instance withEndpoints(Endpoints endpoints) {\n\t\tAssert.notNull(endpoints, \"'endpoints' must not be null\");\n\t\tEndpoints endpointsWithHealth = (this.registration != null)\n\t\t\t\t? endpoints.withEndpoint(Endpoint.HEALTH, this.registration.getHealthUrl()) : endpoints;\n\t\tif (Objects.equals(this.endpoints, endpointsWithHealth)) {\n\t\t\treturn this;\n\t\t}\n\t\treturn this.apply(new InstanceEndpointsDetectedEvent(this.id, this.nextVersion(), endpoints), true);\n\t}\n\n\tpublic boolean isRegistered() {\n\t\treturn this.registered;\n\t}\n\n\tpublic Registration getRegistration() {\n\t\tif (this.registration == null) {\n\t\t\tthrow new IllegalStateException(\"Application '\" + this.id + \"' has no valid registration.\");\n\t\t}\n\t\treturn this.registration;\n\t}\n\n\tList<InstanceEvent> getUnsavedEvents() {\n\t\treturn unmodifiableList(this.unsavedEvents);\n\t}\n\n\tInstance clearUnsavedEvents() {\n\t\treturn new Instance(this.id, this.version, this.registration, this.registered, this.statusInfo,\n\t\t\t\tthis.statusTimestamp, this.info, this.endpoints, this.buildVersion, this.tags, emptyList());\n\t}\n\n\tInstance apply(Collection<InstanceEvent> events) {\n\t\tAssert.notNull(events, \"'events' must not be null\");\n\t\tInstance instance = this;\n\t\tfor (InstanceEvent event : events) {\n\t\t\tinstance = instance.apply(event);\n\t\t}\n\t\treturn instance;\n\t}\n\n\tInstance apply(InstanceEvent event) {\n\t\treturn this.apply(event, false);\n\t}\n\n\tprivate Instance apply(InstanceEvent event, boolean isNewEvent) {\n\t\tAssert.notNull(event, \"'event' must not be null\");\n\t\tAssert.isTrue(this.id.equals(event.getInstance()), \"'event' must refer the same instance\");\n\t\tAssert.isTrue(event.getVersion() >= this.nextVersion(),\n\t\t\t\t() -> \"Event \" + event.getVersion() + \" must be greater or equal to \" + this.nextVersion());\n\n\t\tList<InstanceEvent> unsavedEvents = appendToEvents(event, isNewEvent);\n\n\t\tif (event instanceof InstanceRegisteredEvent registeredEvent) {\n\t\t\tRegistration registration = registeredEvent.getRegistration();\n\t\t\treturn new Instance(this.id, event.getVersion(), registration, true, StatusInfo.ofUnknown(),\n\t\t\t\t\tevent.getTimestamp(), Info.empty(), Endpoints.empty(),\n\t\t\t\t\tupdateBuildVersion(registration.getMetadata()), updateTags(registration.getMetadata()),\n\t\t\t\t\tunsavedEvents);\n\n\t\t}\n\t\telse if (event instanceof InstanceRegistrationUpdatedEvent updatedEvent) {\n\t\t\tRegistration registration = updatedEvent.getRegistration();\n\t\t\treturn new Instance(this.id, event.getVersion(), registration, this.registered, this.statusInfo,\n\t\t\t\t\tthis.statusTimestamp, this.info, this.endpoints,\n\t\t\t\t\tupdateBuildVersion(registration.getMetadata(), this.info.getValues()),\n\t\t\t\t\tupdateTags(registration.getMetadata(), this.info.getValues()), unsavedEvents);\n\n\t\t}\n\t\telse if (event instanceof InstanceStatusChangedEvent statusChangedEvent) {\n\t\t\tStatusInfo statusInfo = statusChangedEvent.getStatusInfo();\n\t\t\treturn new Instance(this.id, event.getVersion(), this.registration, this.registered, statusInfo,\n\t\t\t\t\tevent.getTimestamp(), this.info, this.endpoints, this.buildVersion, this.tags, unsavedEvents);\n\n\t\t}\n\t\telse if (event instanceof InstanceEndpointsDetectedEvent endpointsDetectedEvent) {\n\t\t\tEndpoints endpoints = endpointsDetectedEvent.getEndpoints();\n\t\t\treturn new Instance(this.id, event.getVersion(), this.registration, this.registered, this.statusInfo,\n\t\t\t\t\tthis.statusTimestamp, this.info, endpoints, this.buildVersion, this.tags, unsavedEvents);\n\n\t\t}\n\t\telse if (event instanceof InstanceInfoChangedEvent infoChangedEvent) {\n\t\t\tInfo info = infoChangedEvent.getInfo();\n\t\t\tMap<String, ?> metaData = (this.registration != null) ? this.registration.getMetadata() : emptyMap();\n\t\t\treturn new Instance(this.id, event.getVersion(), this.registration, this.registered, this.statusInfo,\n\t\t\t\t\tthis.statusTimestamp, info, this.endpoints, updateBuildVersion(metaData, info.getValues()),\n\t\t\t\t\tupdateTags(metaData, info.getValues()), unsavedEvents);\n\n\t\t}\n\t\telse if (event instanceof InstanceDeregisteredEvent) {\n\t\t\treturn new Instance(this.id, event.getVersion(), this.registration, false, StatusInfo.ofUnknown(),\n\t\t\t\t\tevent.getTimestamp(), Info.empty(), Endpoints.empty(), null, Tags.empty(), unsavedEvents);\n\t\t}\n\n\t\treturn this;\n\t}\n\n\tprivate long nextVersion() {\n\t\treturn this.version + 1L;\n\t}\n\n\tprivate List<InstanceEvent> appendToEvents(InstanceEvent event, boolean isNewEvent) {\n\t\tif (!isNewEvent) {\n\t\t\treturn this.unsavedEvents;\n\t\t}\n\t\tArrayList<InstanceEvent> events = new ArrayList<>(this.unsavedEvents.size() + 1);\n\t\tevents.addAll(this.unsavedEvents);\n\t\tevents.add(event);\n\t\treturn events;\n\t}\n\n\t@Nullable\n\t@SafeVarargs\n\tprivate BuildVersion updateBuildVersion(Map<String, ?>... sources) {\n\t\treturn Arrays.stream(sources).map(BuildVersion::from).filter(Objects::nonNull).findFirst().orElse(null);\n\t}\n\n\t@SafeVarargs\n\tprivate Tags updateTags(Map<String, ?>... sources) {\n\t\treturn Arrays.stream(sources).map((source) -> Tags.from(source, \"tags\")).reduce(Tags.empty(), Tags::append);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/entities/InstanceRepository.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.entities;\n\nimport java.util.function.BiFunction;\n\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\n/**\n * Responsible for storing instances.\n *\n * @author Johannes Edmeier\n */\npublic interface InstanceRepository {\n\n\t/**\n\t * Saves the Instance\n\t * @param app instance to save\n\t * @return the saved instance\n\t */\n\tMono<Instance> save(Instance app);\n\n\t/**\n\t * @return all instances in the repository;\n\t */\n\tFlux<Instance> findAll();\n\n\t/**\n\t * @param id the instances id\n\t * @return the instance with the specified id;\n\t */\n\tMono<Instance> find(InstanceId id);\n\n\t/**\n\t * @param name the instances name\n\t * @return all instance with the specified name;\n\t */\n\tFlux<Instance> findByName(String name);\n\n\t/**\n\t * Updates the instance associated with the id using the remapping function. If there\n\t * is no associated instance the function will be called with the id and null.\n\t * @param id instance to update\n\t * @param remappingFunction function to apply\n\t * @return the saved istance\n\t */\n\tMono<Instance> compute(InstanceId id, BiFunction<InstanceId, Instance, Mono<Instance>> remappingFunction);\n\n\t/**\n\t * Updates the instance associated with the id using the remapping function. If there\n\t * is no associated instance the function will not be called.\n\t * @param id instance to update\n\t * @param remappingFunction function to apply\n\t * @return the saved istance\n\t */\n\tMono<Instance> computeIfPresent(InstanceId id, BiFunction<InstanceId, Instance, Mono<Instance>> remappingFunction);\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/entities/SnapshottingInstanceRepository.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.entities;\n\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\nimport java.util.function.Function;\n\nimport org.jspecify.annotations.Nullable;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport reactor.core.Disposable;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.eventstore.InstanceEventStore;\nimport de.codecentric.boot.admin.server.eventstore.OptimisticLockingException;\n\n/**\n * InstanceRepository storing instances using an event log.\n *\n * @author Johannes Edmeier\n */\npublic class SnapshottingInstanceRepository extends EventsourcingInstanceRepository {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(SnapshottingInstanceRepository.class);\n\n\tprivate final ConcurrentMap<InstanceId, Instance> snapshots = new ConcurrentHashMap<>();\n\n\tprivate final Set<InstanceId> outdatedSnapshots = ConcurrentHashMap.newKeySet();\n\n\tprivate final InstanceEventStore eventStore;\n\n\t@Nullable private Disposable subscription;\n\n\tpublic SnapshottingInstanceRepository(InstanceEventStore eventStore) {\n\t\tsuper(eventStore);\n\t\tthis.eventStore = eventStore;\n\t}\n\n\t@Override\n\tpublic Flux<Instance> findAll() {\n\t\treturn Mono.fromSupplier(this.snapshots::values).flatMapIterable(Function.identity());\n\t}\n\n\t@Override\n\tpublic Mono<Instance> find(InstanceId id) {\n\t\treturn Mono.defer(() -> {\n\t\t\tif (!this.outdatedSnapshots.contains(id)) {\n\t\t\t\treturn Mono.justOrEmpty(this.snapshots.get(id));\n\t\t\t}\n\t\t\telse {\n\t\t\t\treturn rehydrateSnapshot(id).doOnSuccess((v) -> this.outdatedSnapshots.remove(v.getId()));\n\t\t\t}\n\t\t});\n\t}\n\n\t@Override\n\tpublic Mono<Instance> save(Instance instance) {\n\t\treturn super.save(instance).doOnError(OptimisticLockingException.class,\n\t\t\t\t(e) -> this.outdatedSnapshots.add(instance.getId()));\n\t}\n\n\tpublic void start() {\n\t\tthis.subscription = this.eventStore.findAll().concatWith(this.eventStore).subscribe(this::updateSnapshot);\n\t}\n\n\tpublic void stop() {\n\t\tif (this.subscription != null) {\n\t\t\tthis.subscription.dispose();\n\t\t\tthis.subscription = null;\n\t\t}\n\t}\n\n\tprotected Mono<Instance> rehydrateSnapshot(InstanceId id) {\n\t\treturn super.find(id).map((instance) -> this.snapshots.compute(id, (key, snapshot) -> {\n\t\t\t// check if the loaded version hasn't been already outdated by a snapshot\n\t\t\tif (snapshot == null || instance.getVersion() >= snapshot.getVersion()) {\n\t\t\t\treturn instance;\n\t\t\t}\n\t\t\telse {\n\t\t\t\treturn snapshot;\n\t\t\t}\n\t\t}));\n\t}\n\n\tprotected void updateSnapshot(InstanceEvent event) {\n\t\ttry {\n\t\t\tthis.snapshots.compute(event.getInstance(), (key, old) -> {\n\t\t\t\tInstance instance = (old != null) ? old : Instance.create(key);\n\t\t\t\tif (event.getVersion() > instance.getVersion()) {\n\t\t\t\t\treturn instance.apply(event);\n\t\t\t\t}\n\t\t\t\treturn instance;\n\t\t\t});\n\t\t}\n\t\tcatch (Exception ex) {\n\t\t\tlog.warn(\"Error while updating the snapshot with event {}\", event, ex);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/entities/package-info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Spring Boot Admin Server - entity package.\n\n@NullMarked\npackage de.codecentric.boot.admin.server.domain.entities;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceDeregisteredEvent.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.events;\n\nimport java.io.Serial;\nimport java.time.Instant;\n\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\n/**\n * This event gets emitted when an instance is unregistered.\n *\n * @author Johannes Edmeier\n */\n@lombok.Value\n@lombok.EqualsAndHashCode(callSuper = true)\n@lombok.ToString(callSuper = true)\npublic class InstanceDeregisteredEvent extends InstanceEvent {\n\n\tpublic static final String TYPE = \"DEREGISTERED\";\n\n\t@Serial\n\tprivate static final long serialVersionUID = 1L;\n\n\tpublic InstanceDeregisteredEvent(InstanceId instance, long version) {\n\t\tthis(instance, version, Instant.now());\n\t}\n\n\tpublic InstanceDeregisteredEvent(InstanceId instance, long version, Instant timestamp) {\n\t\tsuper(instance, version, TYPE, timestamp);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceEndpointsDetectedEvent.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.events;\n\nimport java.io.Serial;\nimport java.time.Instant;\n\nimport de.codecentric.boot.admin.server.domain.values.Endpoints;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\n/**\n * This event gets emitted when all instance's endpoints are discovered.\n *\n * @author Johannes Edmeier\n */\n@lombok.Value\n@lombok.EqualsAndHashCode(callSuper = true)\n@lombok.ToString(callSuper = true)\npublic class InstanceEndpointsDetectedEvent extends InstanceEvent {\n\n\tpublic static final String TYPE = \"ENDPOINTS_DETECTED\";\n\n\t@Serial\n\tprivate static final long serialVersionUID = 1L;\n\n\tEndpoints endpoints;\n\n\tpublic InstanceEndpointsDetectedEvent(InstanceId instance, long version, Endpoints endpoints) {\n\t\tthis(instance, version, Instant.now(), endpoints);\n\t}\n\n\tpublic InstanceEndpointsDetectedEvent(InstanceId instance, long version, Instant timestamp, Endpoints endpoints) {\n\t\tsuper(instance, version, TYPE, timestamp);\n\t\tthis.endpoints = endpoints;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceEvent.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.events;\n\nimport java.io.Serial;\nimport java.io.Serializable;\nimport java.time.Instant;\n\nimport org.springframework.util.Assert;\n\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\n/**\n * Abstract Event regarding registered instances\n *\n * @author Johannes Edmeier\n */\n@lombok.Data\npublic abstract class InstanceEvent implements Serializable {\n\n\t@Serial\n\tprivate static final long serialVersionUID = 1L;\n\n\tprivate final InstanceId instance;\n\n\tprivate final long version;\n\n\tprivate final Instant timestamp;\n\n\tprivate final String type;\n\n\tprotected InstanceEvent(InstanceId instance, long version, String type, Instant timestamp) {\n\t\tAssert.notNull(instance, \"'instance' must not be null\");\n\t\tAssert.notNull(timestamp, \"'timestamp' must not be null\");\n\t\tAssert.hasText(type, \"'type' must not be empty\");\n\t\tthis.instance = instance;\n\t\tthis.version = version;\n\t\tthis.timestamp = timestamp;\n\t\tthis.type = type;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceInfoChangedEvent.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.events;\n\nimport java.io.Serial;\nimport java.time.Instant;\n\nimport de.codecentric.boot.admin.server.domain.values.Info;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\n/**\n * This event gets emitted when an instance information changes.\n *\n * @author Johannes Edmeier\n */\n@lombok.Value\n@lombok.EqualsAndHashCode(callSuper = true)\n@lombok.ToString(callSuper = true)\npublic class InstanceInfoChangedEvent extends InstanceEvent {\n\n\tpublic static final String TYPE = \"INFO_CHANGED\";\n\n\t@Serial\n\tprivate static final long serialVersionUID = 1L;\n\n\tInfo info;\n\n\tpublic InstanceInfoChangedEvent(InstanceId instance, long version, Info info) {\n\t\tthis(instance, version, Instant.now(), info);\n\t}\n\n\tpublic InstanceInfoChangedEvent(InstanceId instance, long version, Instant timestamp, Info info) {\n\t\tsuper(instance, version, TYPE, timestamp);\n\t\tthis.info = info;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceRegisteredEvent.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.events;\n\nimport java.io.Serial;\nimport java.time.Instant;\n\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\n\n/**\n * This event gets emitted when an instance is registered.\n *\n * @author Johannes Edmeier\n */\n@lombok.Value\n@lombok.EqualsAndHashCode(callSuper = true)\n@lombok.ToString(callSuper = true)\npublic class InstanceRegisteredEvent extends InstanceEvent {\n\n\tpublic static final String TYPE = \"REGISTERED\";\n\n\t@Serial\n\tprivate static final long serialVersionUID = 1L;\n\n\tRegistration registration;\n\n\tpublic InstanceRegisteredEvent(InstanceId instance, long version, Registration registration) {\n\t\tthis(instance, version, Instant.now(), registration);\n\t}\n\n\tpublic InstanceRegisteredEvent(InstanceId instance, long version, Instant timestamp, Registration registration) {\n\t\tsuper(instance, version, TYPE, timestamp);\n\t\tthis.registration = registration;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceRegistrationUpdatedEvent.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.events;\n\nimport java.io.Serial;\nimport java.time.Instant;\n\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\n\n/**\n * This event gets emitted when an instance updates it's registration.\n *\n * @author Johannes Edmeier\n */\n@lombok.Value\n@lombok.EqualsAndHashCode(callSuper = true)\n@lombok.ToString(callSuper = true)\npublic class InstanceRegistrationUpdatedEvent extends InstanceEvent {\n\n\tpublic static final String TYPE = \"REGISTRATION_UPDATED\";\n\n\t@Serial\n\tprivate static final long serialVersionUID = 1L;\n\n\tRegistration registration;\n\n\tpublic InstanceRegistrationUpdatedEvent(InstanceId instance, long version, Registration registration) {\n\t\tthis(instance, version, Instant.now(), registration);\n\t}\n\n\tpublic InstanceRegistrationUpdatedEvent(InstanceId instance, long version, Instant timestamp,\n\t\t\tRegistration registration) {\n\t\tsuper(instance, version, TYPE, timestamp);\n\t\tthis.registration = registration;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceStatusChangedEvent.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.events;\n\nimport java.io.Serial;\nimport java.time.Instant;\n\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\n/**\n * This event gets emitted when an instance changes its status.\n *\n * @author Johannes Edmeier\n */\n@lombok.Value\n@lombok.EqualsAndHashCode(callSuper = true)\n@lombok.ToString(callSuper = true)\npublic class InstanceStatusChangedEvent extends InstanceEvent {\n\n\tpublic static final String TYPE = \"STATUS_CHANGED\";\n\n\t@Serial\n\tprivate static final long serialVersionUID = 1L;\n\n\tStatusInfo statusInfo;\n\n\tpublic InstanceStatusChangedEvent(InstanceId instance, long version, StatusInfo statusInfo) {\n\t\tthis(instance, version, Instant.now(), statusInfo);\n\t}\n\n\tpublic InstanceStatusChangedEvent(InstanceId instance, long version, Instant timestamp, StatusInfo statusInfo) {\n\t\tsuper(instance, version, TYPE, timestamp);\n\t\tthis.statusInfo = statusInfo;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/package-info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Spring Boot Admin Server - events package.\n\n@NullMarked\npackage de.codecentric.boot.admin.server.domain.events;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/values/BuildVersion.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.values;\n\nimport java.io.Serializable;\nimport java.util.Map;\nimport java.util.Scanner;\n\nimport org.jspecify.annotations.Nullable;\nimport org.springframework.util.StringUtils;\n\n@lombok.Data\npublic final class BuildVersion implements Serializable, Comparable<BuildVersion> {\n\n\tprivate static final String DEFAULT_VERSION = \"UNKNOWN\";\n\n\tprivate final String value;\n\n\tprivate BuildVersion(String value) {\n\t\tif (!StringUtils.hasText(value)) {\n\t\t\tthis.value = DEFAULT_VERSION;\n\t\t}\n\t\telse {\n\t\t\tthis.value = value;\n\t\t}\n\t}\n\n\tpublic static BuildVersion valueOf(String s) {\n\t\treturn new BuildVersion(s);\n\t}\n\n\t@Nullable public static BuildVersion from(Map<String, ?> map) {\n\t\tif (map.isEmpty()) {\n\t\t\treturn null;\n\t\t}\n\n\t\tObject build = map.get(\"build\");\n\t\tif (build instanceof Map) {\n\t\t\tObject version = ((Map<?, ?>) build).get(\"version\");\n\t\t\tif (version instanceof String versionString) {\n\t\t\t\treturn valueOf(versionString);\n\t\t\t}\n\t\t}\n\n\t\tObject version = map.get(\"build.version\");\n\t\tif (version instanceof String versionString) {\n\t\t\treturn valueOf(versionString);\n\t\t}\n\n\t\tversion = map.get(\"version\");\n\t\tif (version instanceof String versionString) {\n\t\t\treturn valueOf(versionString);\n\t\t}\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn this.value;\n\t}\n\n\t@Override\n\tpublic int compareTo(BuildVersion other) {\n\t\ttry (Scanner s1 = new Scanner(this.value); Scanner s2 = new Scanner(other.value)) {\n\t\t\ts1.useDelimiter(\"[.\\\\-+]\");\n\t\t\ts2.useDelimiter(\"[.\\\\-+]\");\n\n\t\t\twhile (s1.hasNext() && s2.hasNext()) {\n\t\t\t\tint c;\n\t\t\t\tif (s1.hasNextInt() && s2.hasNextInt()) {\n\t\t\t\t\tc = Integer.compare(s1.nextInt(), s2.nextInt());\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tc = s1.next().compareTo(s2.next());\n\t\t\t\t}\n\t\t\t\tif (c != 0) {\n\t\t\t\t\treturn c;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (s1.hasNext()) {\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\telse if (s2.hasNext()) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\t\treturn 0;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/values/Endpoint.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.values;\n\nimport java.io.Serializable;\n\nimport org.springframework.util.Assert;\n\n@lombok.Data\npublic final class Endpoint implements Serializable {\n\n\tpublic static final String INFO = \"info\";\n\n\tpublic static final String HEALTH = \"health\";\n\n\tpublic static final String LOGFILE = \"logfile\";\n\n\tpublic static final String ENV = \"env\";\n\n\tpublic static final String HTTPTRACE = \"httptrace\";\n\n\tpublic static final String HTTPEXCHANGES = \"httpexchanges\";\n\n\tpublic static final String THREADDUMP = \"threaddump\";\n\n\tpublic static final String LIQUIBASE = \"liquibase\";\n\n\tpublic static final String FLYWAY = \"flyway\";\n\n\tpublic static final String ACTUATOR_INDEX = \"actuator-index\";\n\n\tpublic static final String BEANS = \"beans\";\n\n\tpublic static final String CONFIGPROPS = \"configprops\";\n\n\tpublic static final String MAPPINGS = \"mappings\";\n\n\tpublic static final String STARTUP = \"startup\";\n\n\tprivate final String id;\n\n\tprivate final String url;\n\n\tprivate Endpoint(String id, String url) {\n\t\tAssert.hasText(id, \"'id' must not be empty.\");\n\t\tAssert.hasText(url, \"'url' must not be empty.\");\n\t\tthis.id = id;\n\t\tthis.url = url;\n\t}\n\n\tpublic static Endpoint of(String id, String url) {\n\t\treturn new Endpoint(id, url);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/values/Endpoints.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.values;\n\nimport java.io.Serializable;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.function.Function;\nimport java.util.stream.Stream;\n\nimport org.jspecify.annotations.Nullable;\n\nimport static java.util.stream.Collectors.toMap;\n\n@lombok.EqualsAndHashCode\n@lombok.ToString\npublic final class Endpoints implements Iterable<Endpoint>, Serializable {\n\n\tprivate static final Endpoints EMPTY = new Endpoints(Collections.emptyList());\n\n\tprivate final Map<String, Endpoint> endpoints;\n\n\tprivate Endpoints(Collection<Endpoint> endpoints) {\n\t\tif (endpoints.isEmpty()) {\n\t\t\tthis.endpoints = Collections.emptyMap();\n\t\t}\n\t\telse {\n\t\t\tthis.endpoints = endpoints.stream().collect(toMap(Endpoint::getId, Function.identity()));\n\t\t}\n\t}\n\n\tpublic static Endpoints empty() {\n\t\treturn EMPTY;\n\t}\n\n\tpublic static Endpoints single(String id, String url) {\n\t\treturn new Endpoints(Collections.singletonList(Endpoint.of(id, url)));\n\t}\n\n\tpublic static Endpoints of(@Nullable Collection<Endpoint> endpoints) {\n\t\tif (endpoints == null || endpoints.isEmpty()) {\n\t\t\treturn empty();\n\t\t}\n\t\treturn new Endpoints(endpoints);\n\t}\n\n\tpublic Optional<Endpoint> get(String id) {\n\t\treturn Optional.ofNullable(this.endpoints.get(id));\n\t}\n\n\tpublic boolean isPresent(String id) {\n\t\treturn this.endpoints.containsKey(id);\n\t}\n\n\t@Override\n\tpublic Iterator<Endpoint> iterator() {\n\t\treturn new UnmodifiableIterator<>(this.endpoints.values().iterator());\n\t}\n\n\tpublic Endpoints withEndpoint(String id, String url) {\n\t\tEndpoint endpoint = Endpoint.of(id, url);\n\t\tHashMap<String, Endpoint> newEndpoints = new HashMap<>(this.endpoints);\n\t\tnewEndpoints.put(endpoint.getId(), endpoint);\n\t\treturn new Endpoints(newEndpoints.values());\n\t}\n\n\tpublic Stream<Endpoint> stream() {\n\t\treturn this.endpoints.values().stream();\n\t}\n\n\tprivate record UnmodifiableIterator<T>(Iterator<T> delegate) implements Iterator<T> {\n\n\t\t@Override\n\t\tpublic boolean hasNext() {\n\t\t\treturn this.delegate.hasNext();\n\t\t}\n\n\t\t@Override\n\t\tpublic T next() {\n\t\t\treturn this.delegate.next();\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/values/Info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.values;\n\nimport java.io.Serializable;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\nimport com.fasterxml.jackson.annotation.JsonAnyGetter;\nimport com.fasterxml.jackson.annotation.JsonAnySetter;\nimport org.jspecify.annotations.Nullable;\n\n/**\n * Represents the info fetched from the info actuator endpoint\n *\n * @author Johannes Edmeier\n */\n@lombok.Data\npublic final class Info implements Serializable {\n\n\tprivate static final Info EMPTY = new Info(Collections.emptyMap());\n\n\tprivate Map<String, Object> values = new HashMap<>();\n\n\tpublic Info() {\n\t}\n\n\tprivate Info(Map<String, Object> values) {\n\t\tif (!values.isEmpty()) {\n\t\t\tthis.values = Collections.unmodifiableMap(new LinkedHashMap<>(values));\n\t\t}\n\t}\n\n\tpublic static Info from(@Nullable Map<String, Object> values) {\n\t\tif (values == null || values.isEmpty()) {\n\t\t\treturn empty();\n\t\t}\n\t\treturn new Info(values);\n\t}\n\n\tpublic static Info empty() {\n\t\treturn EMPTY;\n\t}\n\n\t@JsonAnySetter\n\tpublic void put(String key, Object value) {\n\t\tthis.values.put(key, value);\n\t}\n\n\t@JsonAnyGetter\n\tpublic Map<String, Object> getValues() {\n\t\treturn Collections.unmodifiableMap(values);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/values/InstanceId.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.values;\n\nimport java.io.Serializable;\n\nimport org.springframework.util.Assert;\n\n/**\n * Value type for the instance identifier\n */\n@lombok.Data\npublic final class InstanceId implements Serializable, Comparable<InstanceId> {\n\n\tprivate final String value;\n\n\tprivate InstanceId(String value) {\n\t\tAssert.hasText(value, \"'value' must have text\");\n\t\tthis.value = value;\n\t}\n\n\tpublic static InstanceId of(String value) {\n\t\treturn new InstanceId(value);\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn value;\n\t}\n\n\t@Override\n\tpublic int compareTo(InstanceId that) {\n\t\treturn this.value.compareTo(that.value);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/values/Registration.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.values;\n\nimport java.io.Serializable;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.util.Collections;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\nimport lombok.ToString;\nimport org.jspecify.annotations.Nullable;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.util.Assert;\nimport org.springframework.util.StringUtils;\n\n/**\n * Registration info for the instance registers with (including metadata)\n *\n * @author Johannes Edmeier\n */\n@lombok.Data\n@ToString(exclude = \"metadata\")\npublic final class Registration implements Serializable {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(Registration.class);\n\n\tprivate final String name;\n\n\t/**\n\t * Base URL of the Actuator (management) endpoints. May run on a different port or\n\t * context than the service itself. Must be an absolute URL when present. Example:\n\t * <code>https://example.com/actuator</code>\n\t */\n\t@Nullable private final String managementUrl;\n\n\t/**\n\t * Absolute URL of the Actuator health endpoint. Required and used by Spring Boot\n\t * Admin to determine the instance status. Example:\n\t * <code>https://example.com/actuator/health</code>\n\t */\n\tprivate final String healthUrl;\n\n\t/**\n\t * Public base URL of the business application (not the Actuator base). Used by Spring\n\t * Boot Admin to link to the running app (e.g., \"Open application\"). Must be an\n\t * absolute URL; can be overridden via metadata keys \"service-url\" and \"service-path\".\n\t */\n\t@Nullable private final String serviceUrl;\n\n\tprivate final String source;\n\n\tprivate final Map<String, String> metadata;\n\n\t@lombok.Builder(builderClassName = \"Builder\", toBuilder = true)\n\tprivate Registration(String name, @Nullable String managementUrl, String healthUrl, @Nullable String serviceUrl,\n\t\t\tString source, @lombok.Singular(\"metadata\") Map<String, String> metadata) {\n\t\tAssert.hasText(name, \"'name' must not be empty.\");\n\t\tAssert.hasText(healthUrl, \"'healthUrl' must not be empty.\");\n\t\tAssert.isTrue(checkUrl(healthUrl), \"'healthUrl' is not valid: \" + healthUrl);\n\t\tAssert.isTrue(!StringUtils.hasText(managementUrl) || checkUrl(managementUrl),\n\t\t\t\t\"'managementUrl' is not valid: \" + managementUrl);\n\t\tAssert.isTrue(!StringUtils.hasText(serviceUrl) || checkUrl(serviceUrl),\n\t\t\t\t\"'serviceUrl' is not valid: \" + serviceUrl);\n\n\t\tthis.name = name;\n\t\tthis.managementUrl = managementUrl;\n\t\tthis.healthUrl = healthUrl;\n\t\tthis.serviceUrl = this.getServiceUrl(serviceUrl, metadata);\n\t\tthis.source = source;\n\t\tthis.metadata = new LinkedHashMap<>();\n\t\tfor (Map.Entry<String, String> entry : metadata.entrySet()) {\n\t\t\tString key = entry.getKey();\n\t\t\tString value = entry.getValue();\n\t\t\tthis.metadata.put(key, value);\n\t\t}\n\t}\n\n\tpublic static Registration.Builder create(String name, String healthUrl) {\n\t\treturn builder().name(name).healthUrl(healthUrl);\n\t}\n\n\tpublic static Registration.Builder copyOf(Registration registration) {\n\t\treturn registration.toBuilder();\n\t}\n\n\t/**\n\t * Determines the service url. It might be overriden by metadata entries to override\n\t * the service url.\n\t * @param serviceUrl original serviceUrl\n\t * @param metadata metadata information of registered instance\n\t * @return the actual service url\n\t */\n\t@Nullable private String getServiceUrl(@Nullable String serviceUrl, Map<String, String> metadata) {\n\t\tif (serviceUrl == null) {\n\t\t\treturn null;\n\t\t}\n\t\tString url = metadata.getOrDefault(\"service-url\", serviceUrl);\n\n\t\ttry {\n\t\t\tURI baseUri = new URI(url);\n\t\t\treturn baseUri.toString();\n\t\t}\n\t\tcatch (URISyntaxException ex) {\n\t\t\tlog.warn(\"Invalid service url: \" + serviceUrl, ex);\n\t\t}\n\n\t\treturn serviceUrl;\n\t}\n\n\tpublic Map<String, String> getMetadata() {\n\t\treturn Collections.unmodifiableMap(this.metadata);\n\t}\n\n\t/**\n\t * Checks the syntax of the given URL.\n\t * @param url the URL.\n\t * @return true, if valid.\n\t */\n\tprivate boolean checkUrl(String url) {\n\t\ttry {\n\t\t\tURI uri = new URI(url);\n\t\t\treturn uri.isAbsolute();\n\t\t}\n\t\tcatch (URISyntaxException ex) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tpublic static class Builder {\n\n\t\t// Will be generated by lombok\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/values/StatusInfo.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.values;\n\nimport java.io.Serializable;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.jspecify.annotations.Nullable;\nimport org.springframework.util.Assert;\n\nimport static java.util.Arrays.asList;\n\n/**\n * Instance status with details fetched from the info endpoint.\n *\n * @author Johannes Edmeier\n */\n@lombok.Data\npublic final class StatusInfo implements Serializable {\n\n\tpublic static final String STATUS_UNKNOWN = \"UNKNOWN\";\n\n\tpublic static final String STATUS_OUT_OF_SERVICE = \"OUT_OF_SERVICE\";\n\n\tpublic static final String STATUS_UP = \"UP\";\n\n\tpublic static final String STATUS_DOWN = \"DOWN\";\n\n\tpublic static final String STATUS_OFFLINE = \"OFFLINE\";\n\n\tpublic static final String STATUS_RESTRICTED = \"RESTRICTED\";\n\n\tprivate static final List<String> STATUS_ORDER = asList(STATUS_DOWN, STATUS_OUT_OF_SERVICE, STATUS_OFFLINE,\n\t\t\tSTATUS_UNKNOWN, STATUS_RESTRICTED, STATUS_UP);\n\n\tprivate final String status;\n\n\tprivate final Map<String, Object> details;\n\n\tprivate StatusInfo(String status, @Nullable Map<String, ?> details) {\n\t\tAssert.hasText(status, \"'status' must not be empty.\");\n\t\tthis.status = status.toUpperCase();\n\t\tthis.details = (details != null) ? new HashMap<>(details) : Collections.emptyMap();\n\t}\n\n\tpublic static StatusInfo valueOf(String statusCode, @Nullable Map<String, ?> details) {\n\t\treturn new StatusInfo(statusCode, details);\n\t}\n\n\tpublic static StatusInfo valueOf(String statusCode) {\n\t\treturn valueOf(statusCode, null);\n\t}\n\n\tpublic static StatusInfo ofUnknown() {\n\t\treturn valueOf(STATUS_UNKNOWN, null);\n\t}\n\n\tpublic static StatusInfo ofUp() {\n\t\treturn ofUp(null);\n\t}\n\n\tpublic static StatusInfo ofDown() {\n\t\treturn ofDown(null);\n\t}\n\n\tpublic static StatusInfo ofOffline() {\n\t\treturn ofOffline(null);\n\t}\n\n\tpublic static StatusInfo ofOutOfService() {\n\t\treturn ofOutOfService(null);\n\t}\n\n\tpublic static StatusInfo ofRestricted() {\n\t\treturn ofRestricted(null);\n\t}\n\n\tpublic static StatusInfo ofUp(@Nullable Map<String, Object> details) {\n\t\treturn valueOf(STATUS_UP, details);\n\t}\n\n\tpublic static StatusInfo ofDown(@Nullable Map<String, Object> details) {\n\t\treturn valueOf(STATUS_DOWN, details);\n\t}\n\n\tpublic static StatusInfo ofOffline(@Nullable Map<String, Object> details) {\n\t\treturn valueOf(STATUS_OFFLINE, details);\n\t}\n\n\tpublic static StatusInfo ofOutOfService(@Nullable Map<String, Object> details) {\n\t\treturn valueOf(STATUS_OUT_OF_SERVICE, details);\n\t}\n\n\tpublic static StatusInfo ofRestricted(@Nullable Map<String, Object> details) {\n\t\treturn valueOf(STATUS_RESTRICTED, details);\n\t}\n\n\tpublic Map<String, Object> getDetails() {\n\t\treturn Collections.unmodifiableMap(details);\n\t}\n\n\tpublic boolean isUp() {\n\t\treturn STATUS_UP.equals(status);\n\t}\n\n\tpublic boolean isOffline() {\n\t\treturn STATUS_OFFLINE.equals(status);\n\t}\n\n\tpublic boolean isDown() {\n\t\treturn STATUS_DOWN.equals(status);\n\t}\n\n\tpublic boolean isUnknown() {\n\t\treturn STATUS_UNKNOWN.equals(status);\n\t}\n\n\tpublic boolean isOutOfService() {\n\t\treturn STATUS_OUT_OF_SERVICE.equals(status);\n\t}\n\n\tpublic boolean isRestricted() {\n\t\treturn STATUS_RESTRICTED.equals(status);\n\t}\n\n\tpublic static Comparator<String> severity() {\n\t\treturn Comparator.comparingInt(STATUS_ORDER::indexOf);\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic static StatusInfo from(Map<String, ?> body) {\n\t\tMap<String, ?> details = Collections.emptyMap();\n\n\t\t/*\n\t\t * Key \"details\" is present when accessing Spring Boot Actuator Health using\n\t\t * Accept-Header {@link org.springframework.boot.actuate.endpoint.ApiVersion#V2}.\n\t\t */\n\t\tif (body.containsKey(\"details\")) {\n\t\t\tdetails = (Map<String, ?>) body.get(\"details\");\n\t\t}\n\t\telse if (body.containsKey(\"components\")) {\n\t\t\tdetails = (Map<String, ?>) body.get(\"components\");\n\t\t}\n\n\t\treturn StatusInfo.valueOf((String) body.get(\"status\"), details);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/values/Tags.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.values;\n\nimport java.io.Serializable;\nimport java.util.Collections;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.function.Function;\nimport java.util.stream.Collector;\n\nimport lombok.Getter;\nimport org.jspecify.annotations.Nullable;\nimport org.springframework.util.StringUtils;\n\nimport static java.util.stream.Collectors.toMap;\n\n@Getter\n@lombok.EqualsAndHashCode\n@lombok.ToString\npublic final class Tags implements Serializable {\n\n\tprivate static final Tags EMPTY = new Tags(Collections.emptyMap());\n\n\tprivate final Map<String, String> values;\n\n\tprivate Tags(Map<String, String> tags) {\n\t\tif (tags.isEmpty()) {\n\t\t\tthis.values = Collections.emptyMap();\n\t\t}\n\t\telse {\n\t\t\tthis.values = Collections.unmodifiableMap(new LinkedHashMap<>(tags));\n\t\t}\n\t}\n\n\tpublic static Tags empty() {\n\t\treturn EMPTY;\n\t}\n\n\tpublic static Tags from(Map<String, ?> map) {\n\t\treturn from(map, null);\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic static Tags from(Map<String, ?> map, @Nullable String prefix) {\n\t\tif (map.isEmpty()) {\n\t\t\treturn empty();\n\t\t}\n\n\t\tif (StringUtils.hasText(prefix)) {\n\t\t\tObject nestedTags = map.get(prefix);\n\t\t\tif (nestedTags instanceof Map) {\n\t\t\t\treturn from((Map<String, Object>) nestedTags);\n\t\t\t}\n\n\t\t\tString flatPrefix = prefix + \".\";\n\t\t\treturn from(map.entrySet()\n\t\t\t\t.stream()\n\t\t\t\t.filter((e) -> e.getKey() != null)\n\t\t\t\t.filter((e) -> e.getKey().toLowerCase().startsWith(flatPrefix))\n\t\t\t\t.collect(toLinkedHashMap((e) -> e.getKey().substring(flatPrefix.length()), Map.Entry::getValue)));\n\t\t}\n\n\t\treturn new Tags(map.entrySet()\n\t\t\t.stream()\n\t\t\t.filter((e) -> e.getKey() != null)\n\t\t\t.collect(toLinkedHashMap(Map.Entry::getKey, (e) -> Objects.toString(e.getValue()))));\n\t}\n\n\tprivate static <T, K, U> Collector<T, ?, LinkedHashMap<K, U>> toLinkedHashMap(\n\t\t\tFunction<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper) {\n\t\treturn toMap(keyMapper, valueMapper, (u, v) -> {\n\t\t\tthrow new IllegalStateException(String.format(\"Duplicate key %s\", u));\n\t\t}, LinkedHashMap::new);\n\t}\n\n\tpublic Tags append(Tags other) {\n\t\tMap<String, String> newTags = new LinkedHashMap<>(this.values);\n\t\tnewTags.putAll(other.values);\n\t\treturn new Tags(newTags);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/values/package-info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Spring Boot Admin Server - domain objects package.\n\n@NullMarked\npackage de.codecentric.boot.admin.server.domain.values;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/eventstore/ConcurrentMapEventStore.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.eventstore;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.concurrent.ConcurrentMap;\nimport java.util.function.BinaryOperator;\nimport java.util.function.Function;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\nimport static java.util.Comparator.comparing;\nimport static java.util.stream.Collectors.groupingBy;\nimport static java.util.stream.Collectors.reducing;\n\npublic abstract class ConcurrentMapEventStore extends InstanceEventPublisher implements InstanceEventStore {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(ConcurrentMapEventStore.class);\n\n\tprivate static final Comparator<InstanceEvent> byTimestampAndIdAndVersion = comparing(InstanceEvent::getTimestamp)\n\t\t.thenComparing(InstanceEvent::getInstance)\n\t\t.thenComparing(InstanceEvent::getVersion);\n\n\tprivate final int maxLogSizePerAggregate;\n\n\tprivate final ConcurrentMap<InstanceId, List<InstanceEvent>> eventLog;\n\n\tprotected ConcurrentMapEventStore(int maxLogSizePerAggregate,\n\t\t\tConcurrentMap<InstanceId, List<InstanceEvent>> eventLog) {\n\t\tthis.eventLog = eventLog;\n\t\tthis.maxLogSizePerAggregate = maxLogSizePerAggregate;\n\t}\n\n\t@Override\n\tpublic Flux<InstanceEvent> findAll() {\n\t\treturn Flux.defer(() -> Flux.fromIterable(eventLog.values())\n\t\t\t.flatMapIterable(Function.identity())\n\t\t\t.sort(byTimestampAndIdAndVersion));\n\t}\n\n\t@Override\n\tpublic Flux<InstanceEvent> find(InstanceId id) {\n\t\treturn Flux.defer(() -> Flux.fromIterable(eventLog.getOrDefault(id, Collections.emptyList())));\n\t}\n\n\t@Override\n\tpublic Mono<Void> append(List<InstanceEvent> events) {\n\t\treturn Mono.fromRunnable(() -> {\n\t\t\twhile (true) {\n\t\t\t\tif (doAppend(events)) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\tprotected boolean doAppend(List<InstanceEvent> events) {\n\t\tif (events.isEmpty()) {\n\t\t\treturn true;\n\t\t}\n\n\t\tInstanceId id = events.get(0).getInstance();\n\t\tif (!events.stream().allMatch((event) -> event.getInstance().equals(id))) {\n\t\t\tthrow new IllegalArgumentException(\"'events' must only refer to the same instance.\");\n\t\t}\n\n\t\tList<InstanceEvent> oldEvents = eventLog.computeIfAbsent(id,\n\t\t\t\t(key) -> new ArrayList<>(maxLogSizePerAggregate + 1));\n\n\t\tlong lastVersion = getLastVersion(oldEvents);\n\t\tif (lastVersion >= events.get(0).getVersion()) {\n\t\t\tthrow createOptimisticLockException(events.get(0), lastVersion);\n\t\t}\n\n\t\tList<InstanceEvent> newEvents = new ArrayList<>(oldEvents);\n\t\tnewEvents.addAll(events);\n\n\t\tif (newEvents.size() > maxLogSizePerAggregate) {\n\t\t\tlog.debug(\"Threshold for {} reached. Compacting events\", id);\n\t\t\tcompact(newEvents);\n\t\t}\n\n\t\tif (eventLog.replace(id, oldEvents, newEvents)) {\n\t\t\tlog.debug(\"Events appended to log {}\", events);\n\t\t\treturn true;\n\t\t}\n\n\t\tlog.debug(\"Unsuccessful attempt append the events {} \", events);\n\t\treturn false;\n\t}\n\n\tprivate void compact(List<InstanceEvent> events) {\n\t\tBinaryOperator<InstanceEvent> latestEvent = (e1, e2) -> (e1.getVersion() > e2.getVersion()) ? e1 : e2;\n\t\tMap<Class<?>, Optional<InstanceEvent>> latestPerType = events.stream()\n\t\t\t.collect(groupingBy(InstanceEvent::getClass, reducing(latestEvent)));\n\t\tevents.removeIf((e) -> !Objects.equals(e, latestPerType.get(e.getClass()).orElse(null)));\n\t}\n\n\tprivate OptimisticLockingException createOptimisticLockException(InstanceEvent event, long lastVersion) {\n\t\treturn new OptimisticLockingException(\n\t\t\t\t\"Version \" + event.getVersion() + \" was overtaken by \" + lastVersion + \" for \" + event.getInstance());\n\t}\n\n\tprotected static long getLastVersion(List<InstanceEvent> events) {\n\t\treturn events.isEmpty() ? -1 : events.get(events.size() - 1).getVersion();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/eventstore/HazelcastEventStore.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.eventstore;\n\nimport java.util.List;\n\nimport com.hazelcast.core.EntryAdapter;\nimport com.hazelcast.core.EntryEvent;\nimport com.hazelcast.map.IMap;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\n/**\n * Event-Store backed by a Hazelcast-map.\n *\n * @author Johannes Edmeier\n */\npublic class HazelcastEventStore extends ConcurrentMapEventStore {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(HazelcastEventStore.class);\n\n\tpublic HazelcastEventStore(IMap<InstanceId, List<InstanceEvent>> eventLogs) {\n\t\tthis(100, eventLogs);\n\t}\n\n\tpublic HazelcastEventStore(int maxLogSizePerAggregate, IMap<InstanceId, List<InstanceEvent>> eventLog) {\n\t\tsuper(maxLogSizePerAggregate, eventLog);\n\n\t\teventLog.addEntryListener(new EntryAdapter<InstanceId, List<InstanceEvent>>() {\n\t\t\t@Override\n\t\t\tpublic void entryUpdated(EntryEvent<InstanceId, List<InstanceEvent>> event) {\n\t\t\t\tlog.debug(\"Updated {}\", event);\n\t\t\t\tlong lastKnownVersion = getLastVersion(event.getOldValue());\n\t\t\t\tList<InstanceEvent> newEvents = event.getValue()\n\t\t\t\t\t.stream()\n\t\t\t\t\t.filter((e) -> e.getVersion() > lastKnownVersion)\n\t\t\t\t\t.toList();\n\t\t\t\tHazelcastEventStore.this.publish(newEvents);\n\t\t\t}\n\t\t}, true);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/eventstore/InMemoryEventStore.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.eventstore;\n\nimport java.util.List;\nimport java.util.concurrent.ConcurrentHashMap;\n\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\n\n/**\n * Event-Store backed by a ConcurrentHashMap.\n *\n * @author Johannes Edmeier\n */\npublic class InMemoryEventStore extends ConcurrentMapEventStore {\n\n\tpublic InMemoryEventStore() {\n\t\tthis(100);\n\t}\n\n\tpublic InMemoryEventStore(int maxLogSizePerAggregate) {\n\t\tsuper(maxLogSizePerAggregate, new ConcurrentHashMap<>());\n\t}\n\n\t@Override\n\tpublic Mono<Void> append(List<InstanceEvent> events) {\n\t\treturn super.append(events).then(Mono.fromRunnable(() -> this.publish(events)));\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/eventstore/InstanceEventPublisher.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.eventstore;\n\nimport java.util.List;\n\nimport org.reactivestreams.Publisher;\nimport org.reactivestreams.Subscriber;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Sinks;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\n\npublic class InstanceEventPublisher implements Publisher<InstanceEvent> {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(InstanceEventPublisher.class);\n\n\tprivate final Flux<InstanceEvent> publishedFlux;\n\n\tprivate final Sinks.Many<InstanceEvent> unicast;\n\n\tprivate final Sinks.EmitFailureHandler emitFailureHandler = (signalType, emitResult) -> emitResult\n\t\t.equals(Sinks.EmitResult.FAIL_NON_SERIALIZED);\n\n\tprotected InstanceEventPublisher() {\n\t\tthis.unicast = Sinks.many().unicast().onBackpressureBuffer();\n\t\tthis.publishedFlux = this.unicast.asFlux().publish().autoConnect(0);\n\t}\n\n\tprotected void publish(List<InstanceEvent> events) {\n\t\tevents.forEach((event) -> {\n\t\t\tlog.debug(\"Event published {}\", event);\n\t\t\tthis.unicast.emitNext(event, emitFailureHandler);\n\t\t});\n\t}\n\n\t@Override\n\tpublic void subscribe(Subscriber<? super InstanceEvent> s) {\n\t\tthis.publishedFlux.subscribe(s);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/eventstore/InstanceEventStore.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.eventstore;\n\nimport java.util.List;\n\nimport org.reactivestreams.Publisher;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\n/**\n * Interface for storing all instance related Events\n *\n * @author Johannes Edmeier\n */\npublic interface InstanceEventStore extends Publisher<InstanceEvent> {\n\n\tFlux<InstanceEvent> findAll();\n\n\tFlux<InstanceEvent> find(InstanceId id);\n\n\tMono<Void> append(List<InstanceEvent> events);\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/eventstore/OptimisticLockingException.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.eventstore;\n\npublic class OptimisticLockingException extends RuntimeException {\n\n\tpublic OptimisticLockingException(String message) {\n\t\tsuper(message);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/eventstore/package-info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Spring Boot Admin Server - event store package.\n\n@NullMarked\npackage de.codecentric.boot.admin.server.eventstore;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/AbstractEventNotifier.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\n\n/**\n * Abstract Notifier which allows disabling and filtering of events.\n *\n * @author Johannes Edmeier\n */\npublic abstract class AbstractEventNotifier implements Notifier {\n\n\tprivate final InstanceRepository repository;\n\n\t/**\n\t * Enables the notification.\n\t */\n\tprivate boolean enabled = true;\n\n\tprotected AbstractEventNotifier(InstanceRepository repository) {\n\t\tthis.repository = repository;\n\t}\n\n\t@Override\n\tpublic Mono<Void> notify(InstanceEvent event) {\n\t\tif (!enabled) {\n\t\t\treturn Mono.empty();\n\t\t}\n\n\t\treturn repository.find(event.getInstance())\n\t\t\t.filter((instance) -> shouldNotify(event, instance))\n\t\t\t.flatMap((instance) -> doNotify(event, instance))\n\t\t\t.doOnError((ex) -> getLogger().error(\"Couldn't notify for event {} \", event, ex))\n\t\t\t.then();\n\t}\n\n\tprotected boolean shouldNotify(InstanceEvent event, Instance instance) {\n\t\treturn true;\n\t}\n\n\tprotected abstract Mono<Void> doNotify(InstanceEvent event, Instance instance);\n\n\tprivate Logger getLogger() {\n\t\treturn LoggerFactory.getLogger(this.getClass());\n\t}\n\n\tpublic void setEnabled(boolean enabled) {\n\t\tthis.enabled = enabled;\n\t}\n\n\tpublic boolean isEnabled() {\n\t\treturn enabled;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/AbstractStatusChangeNotifier.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceDeregisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\n/**\n * Abstract Notifier for status change which allows filtering of certain status changes.\n *\n * @author Johannes Edmeier\n */\npublic abstract class AbstractStatusChangeNotifier extends AbstractEventNotifier {\n\n\tprivate final Map<InstanceId, String> lastStatuses = new HashMap<>();\n\n\t/**\n\t * List of changes to ignore. Must be in Format OLD:NEW, for any status use * as\n\t * wildcard, e.g. *:UP or OFFLINE:*\n\t */\n\tprivate String[] ignoreChanges = { \"UNKNOWN:UP\" };\n\n\tpublic AbstractStatusChangeNotifier(InstanceRepository repository) {\n\t\tsuper(repository);\n\t}\n\n\t@Override\n\tpublic Mono<Void> notify(InstanceEvent event) {\n\t\treturn super.notify(event).then(Mono.fromRunnable(() -> updateLastStatus(event)));\n\t}\n\n\t@Override\n\tprotected boolean shouldNotify(InstanceEvent event, Instance instance) {\n\t\tif (event instanceof InstanceStatusChangedEvent statusChangedEvent) {\n\t\t\tString from = getLastStatus(event.getInstance());\n\t\t\tString to = statusChangedEvent.getStatusInfo().getStatus();\n\t\t\treturn Arrays.binarySearch(ignoreChanges, from + \":\" + to) < 0\n\t\t\t\t\t&& Arrays.binarySearch(ignoreChanges, \"*:\" + to) < 0\n\t\t\t\t\t&& Arrays.binarySearch(ignoreChanges, from + \":*\") < 0;\n\t\t}\n\t\treturn false;\n\t}\n\n\tprotected final String getLastStatus(InstanceId instanceId) {\n\t\treturn lastStatuses.getOrDefault(instanceId, \"UNKNOWN\");\n\t}\n\n\tprotected void updateLastStatus(InstanceEvent event) {\n\t\tif (event instanceof InstanceDeregisteredEvent) {\n\t\t\tlastStatuses.remove(event.getInstance());\n\t\t}\n\t\tif (event instanceof InstanceStatusChangedEvent statusChangedEvent) {\n\t\t\tlastStatuses.put(event.getInstance(), statusChangedEvent.getStatusInfo().getStatus());\n\t\t}\n\t}\n\n\tpublic void setIgnoreChanges(String[] ignoreChanges) {\n\t\tString[] copy = Arrays.copyOf(ignoreChanges, ignoreChanges.length);\n\t\tArrays.sort(copy);\n\t\tthis.ignoreChanges = copy;\n\t}\n\n\tpublic String[] getIgnoreChanges() {\n\t\treturn ignoreChanges;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/CompositeNotifier.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.util.Assert;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\n\n/**\n * A notifier delegating notifications to all specified notifiers.\n *\n * @author Sebastian Meiser\n */\npublic class CompositeNotifier implements Notifier {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(CompositeNotifier.class);\n\n\tprivate final Iterable<Notifier> delegates;\n\n\tpublic CompositeNotifier(Iterable<Notifier> delegates) {\n\t\tAssert.notNull(delegates, \"'delegates' must not be null!\");\n\t\tthis.delegates = delegates;\n\t}\n\n\t@Override\n\tpublic Mono<Void> notify(InstanceEvent event) {\n\t\treturn Flux.fromIterable(delegates).flatMap((d) -> d.notify(event).onErrorResume((error) -> {\n\t\t\tlog.warn(\"Unexpected exception while triggering notifications. Notification might not be sent.\", error);\n\t\t\treturn Mono.empty();\n\t\t})).then();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/DingTalkNotifier.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.net.URLEncoder;\nimport java.nio.charset.StandardCharsets;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport javax.crypto.Mac;\nimport javax.crypto.spec.SecretKeySpec;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.hc.client5.http.utils.Base64;\nimport org.jspecify.annotations.Nullable;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.MediaType;\nimport org.springframework.web.client.RestTemplate;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.notify.filter.AbstractContentNotifier;\n\n/**\n * Notifier submitting events to DingTalk.\n *\n * @author Mask\n */\n@Slf4j\npublic class DingTalkNotifier extends AbstractContentNotifier {\n\n\tprivate static final String DEFAULT_MESSAGE = \"#{name} #{id} is #{status}\";\n\n\tprivate RestTemplate restTemplate;\n\n\t/**\n\t * Webhook URI for the DingTalk API.\n\t */\n\tprivate String webhookUrl;\n\n\t/**\n\t * Secret for DingTalk.\n\t */\n\t@Nullable private String secret;\n\n\tpublic DingTalkNotifier(InstanceRepository repository, RestTemplate restTemplate) {\n\t\tsuper(repository);\n\t\tthis.restTemplate = restTemplate;\n\t}\n\n\t@Override\n\tprotected Mono<Void> doNotify(InstanceEvent event, Instance instance) {\n\t\treturn Mono\n\t\t\t.fromRunnable(() -> restTemplate.postForEntity(buildUrl(), createMessage(event, instance), Void.class));\n\t}\n\n\tprivate String buildUrl() {\n\t\tLong timestamp = System.currentTimeMillis();\n\t\treturn String.format(\"%s&timestamp=%s&sign=%s\", webhookUrl, timestamp, getSign(timestamp));\n\t}\n\n\tprotected Object createMessage(InstanceEvent event, Instance instance) {\n\t\tMap<String, Object> messageJson = new HashMap<>();\n\t\tmessageJson.put(\"msgtype\", \"text\");\n\n\t\tMap<String, Object> content = new HashMap<>();\n\t\tcontent.put(\"content\", createContent(event, instance));\n\t\tmessageJson.put(\"text\", content);\n\n\t\tHttpHeaders headers = new HttpHeaders();\n\t\theaders.setContentType(MediaType.APPLICATION_JSON);\n\t\treturn new HttpEntity<>(messageJson, headers);\n\t}\n\n\t@Override\n\tprotected String getDefaultMessage() {\n\t\treturn DEFAULT_MESSAGE;\n\t}\n\n\tprivate String getSign(Long timestamp) {\n\t\ttry {\n\t\t\tString stringToSign = timestamp + \"\\n\" + secret;\n\t\t\tMac mac = Mac.getInstance(\"HmacSHA256\");\n\t\t\tmac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), \"HmacSHA256\"));\n\t\t\tbyte[] signData = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));\n\t\t\treturn URLEncoder.encode(new String(Base64.encodeBase64(signData)), StandardCharsets.UTF_8);\n\t\t}\n\t\tcatch (Exception ex) {\n\t\t\tlog.warn(\"Failed to sign message\", ex);\n\t\t}\n\t\treturn \"\";\n\t}\n\n\tpublic void setRestTemplate(RestTemplate restTemplate) {\n\t\tthis.restTemplate = restTemplate;\n\t}\n\n\tpublic String getWebhookUrl() {\n\t\treturn webhookUrl;\n\t}\n\n\tpublic void setWebhookUrl(String webhookUrl) {\n\t\tthis.webhookUrl = webhookUrl;\n\t}\n\n\t@Nullable public String getSecret() {\n\t\treturn secret;\n\t}\n\n\tpublic void setSecret(@Nullable String secret) {\n\t\tthis.secret = secret;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/DiscordNotifier.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.net.URI;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.jspecify.annotations.Nullable;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.MediaType;\nimport org.springframework.web.client.RestTemplate;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.notify.filter.AbstractContentNotifier;\n\n/**\n * Notifier submitting events to Discord by webhooks.\n *\n * @author Movitz Sunar\n * @see <a href=\n * \"https://discordapp.com/developers/docs/resources/webhook#execute-webhook\">https://discordapp.com/developers/docs/resources/webhook#execute-webhook</a>\n */\npublic class DiscordNotifier extends AbstractContentNotifier {\n\n\tprivate static final String DEFAULT_MESSAGE = \"*#{name}* (#{id}) is *#{status}*\";\n\n\tprivate RestTemplate restTemplate;\n\n\t/**\n\t * Webhook URI for the Discord API (i.e.\n\t * https://discordapp.com/api/webhooks/{webhook.id}/{webhook.token})\n\t */\n\t@Nullable private URI webhookUrl;\n\n\t/**\n\t * If the message is a text to speech message. False by default.\n\t */\n\tprivate boolean tts = false;\n\n\t/**\n\t * Optional username. Default is set in Discord.\n\t */\n\t@Nullable private String username;\n\n\t/**\n\t * Optional URL to avatar.\n\t */\n\t@Nullable private String avatarUrl;\n\n\tpublic DiscordNotifier(InstanceRepository repository, RestTemplate restTemplate) {\n\t\tsuper(repository);\n\t\tthis.restTemplate = restTemplate;\n\t}\n\n\t@Override\n\tprotected Mono<Void> doNotify(InstanceEvent event, Instance instance) {\n\t\tif (webhookUrl == null) {\n\t\t\treturn Mono.error(new IllegalStateException(\"'webhookUrl' must not be null.\"));\n\t\t}\n\t\treturn Mono.fromRunnable(\n\t\t\t\t() -> restTemplate.postForEntity(webhookUrl, createDiscordNotification(event, instance), Void.class));\n\t}\n\n\tprotected Object createDiscordNotification(InstanceEvent event, Instance instance) {\n\t\tMap<String, Object> body = new HashMap<>();\n\t\tbody.put(\"content\", createContent(event, instance));\n\t\tbody.put(\"tts\", tts);\n\n\t\tif (avatarUrl != null) {\n\t\t\tbody.put(\"avatar_url\", avatarUrl);\n\t\t}\n\t\tif (username != null) {\n\t\t\tbody.put(\"username\", username);\n\t\t}\n\n\t\tHttpHeaders headers = new HttpHeaders();\n\t\theaders.setContentType(MediaType.APPLICATION_JSON);\n\t\theaders.add(HttpHeaders.USER_AGENT, \"RestTemplate\");\n\t\treturn new HttpEntity<>(body, headers);\n\t}\n\n\t@Override\n\tprotected String getDefaultMessage() {\n\t\treturn DEFAULT_MESSAGE;\n\t}\n\n\t@Nullable public URI getWebhookUrl() {\n\t\treturn webhookUrl;\n\t}\n\n\tpublic void setWebhookUrl(@Nullable URI webhookUrl) {\n\t\tthis.webhookUrl = webhookUrl;\n\t}\n\n\tpublic boolean isTts() {\n\t\treturn tts;\n\t}\n\n\tpublic void setTts(boolean tts) {\n\t\tthis.tts = tts;\n\t}\n\n\t@Nullable public String getUsername() {\n\t\treturn username;\n\t}\n\n\tpublic void setUsername(@Nullable String username) {\n\t\tthis.username = username;\n\t}\n\n\t@Nullable public String getAvatarUrl() {\n\t\treturn avatarUrl;\n\t}\n\n\tpublic void setAvatarUrl(@Nullable String avatarUrl) {\n\t\tthis.avatarUrl = avatarUrl;\n\t}\n\n\tpublic void setRestTemplate(RestTemplate restTemplate) {\n\t\tthis.restTemplate = restTemplate;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/FeiShuNotifier.java",
    "content": "/*\n * Copyright 2014-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.net.URI;\nimport java.nio.charset.StandardCharsets;\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.Base64;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\n\nimport javax.crypto.Mac;\nimport javax.crypto.spec.SecretKeySpec;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.MediaType;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.util.StringUtils;\nimport org.springframework.web.client.RestTemplate;\nimport reactor.core.publisher.Mono;\nimport tools.jackson.databind.json.JsonMapper;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.notify.filter.AbstractContentNotifier;\n\n/**\n * Notifier submitting events to FeiShu by webhooks.\n *\n * @author sweeter\n * @see <a href=\n * \"https://open.feishu.cn/document/ukTMukTMukTM/ucTM5YjL3ETO24yNxkjN\">https://open.feishu.cn/document/ukTMukTMukTM/ucTM5YjL3ETO24yNxkjN</a>\n *\n */\n@Slf4j\npublic class FeiShuNotifier extends AbstractContentNotifier {\n\n\tprivate RestTemplate restTemplate;\n\n\t/**\n\t * Webhook URL for the FeiShu(飞书) chat group API (i.e.\n\t * https://open.feishu.cn/open-apis/bot/v2/hook/xxx).\n\t */\n\tprivate URI webhookUrl;\n\n\t/**\n\t * {@literal @}all\n\t */\n\tprivate boolean atAll = true;\n\n\t/**\n\t * The secret of the chat group robot from the FeiShu setup.\n\t */\n\tprivate String secret;\n\n\t/**\n\t * FeiShu message type: text(文本) interactive(消息卡片)\n\t */\n\tprivate MessageType messageType = MessageType.interactive;\n\n\t/**\n\t * Card theme message\n\t */\n\tprivate Card card = new Card();\n\n\tpublic FeiShuNotifier(InstanceRepository repository, RestTemplate restTemplate) {\n\t\tsuper(repository);\n\t\tthis.restTemplate = restTemplate;\n\t}\n\n\t@Override\n\tprotected Mono<Void> doNotify(InstanceEvent event, Instance instance) {\n\t\tif (webhookUrl == null) {\n\t\t\treturn Mono.error(new IllegalStateException(\"'webhookUrl' must not be null.\"));\n\t\t}\n\t\treturn Mono.fromRunnable(() -> {\n\t\t\tResponseEntity<String> responseEntity = this.restTemplate.postForEntity(this.webhookUrl,\n\t\t\t\t\tthis.createNotification(event, instance), String.class);\n\t\t\tlog.debug(\"Send a notification message to the FeiShu group,returns the parameter：{}\",\n\t\t\t\t\tresponseEntity.getBody());\n\t\t});\n\t}\n\n\tprivate String generateSign(String secret, long timestamp) {\n\t\ttry {\n\t\t\tString stringToSign = timestamp + \"\\n\" + secret;\n\t\t\tMac mac = Mac.getInstance(\"HmacSHA256\");\n\t\t\tmac.init(new SecretKeySpec(stringToSign.getBytes(StandardCharsets.UTF_8), \"HmacSHA256\"));\n\t\t\tbyte[] signData = mac.doFinal(new byte[] {});\n\t\t\treturn new String(Base64.getEncoder().encode(signData));\n\t\t}\n\t\tcatch (Exception ex) {\n\t\t\tlog.error(\"Description Failed to generate the Webhook signature of the FeiShu：{}\", ex.getMessage());\n\t\t}\n\t\treturn null;\n\t}\n\n\tprotected HttpEntity<Map<String, Object>> createNotification(InstanceEvent event, Instance instance) {\n\t\tMap<String, Object> body = new HashMap<>();\n\t\tbody.put(\"receive_id\", UUID.randomUUID().toString());\n\t\tif (StringUtils.hasText(this.secret)) {\n\t\t\tlong timestamp = Instant.now().getEpochSecond();\n\t\t\tbody.put(\"timestamp\", timestamp);\n\t\t\tbody.put(\"sign\", this.generateSign(this.secret, timestamp));\n\t\t}\n\t\tbody.put(\"msg_type\", this.messageType);\n\t\tswitch (this.messageType) {\n\t\t\tcase interactive:\n\t\t\t\tbody.put(\"card\", this.createCardContent(event, instance));\n\t\t\t\tbreak;\n\t\t\tcase text:\n\t\t\tdefault:\n\t\t\t\tbody.put(\"content\", this.createTextContent(event, instance));\n\t\t}\n\t\tHttpHeaders headers = new HttpHeaders();\n\t\theaders.setContentType(MediaType.APPLICATION_JSON);\n\t\theaders.add(\"User-Agent\", \"Codecentric's Spring Boot Admin\");\n\t\treturn new HttpEntity<>(body, headers);\n\t}\n\n\t@Override\n\tprotected Map<String, Object> buildContentModel(InstanceEvent event, Instance instance) {\n\t\tMap<String, Object> root = new HashMap<>();\n\t\troot.put(\"event\", event);\n\t\troot.put(\"instance\", instance);\n\t\troot.put(\"lastStatus\", this.getLastStatus(event.getInstance()));\n\t\treturn root;\n\t}\n\n\t@Override\n\tprotected String getDefaultMessage() {\n\t\treturn \"ServiceName: #{instance.registration.name}(#{instance.id}) \\nServiceUrl: #{instance.registration.serviceUrl} \\nStatus: changed status from [#{lastStatus}] to [#{event.statusInfo.status}]\";\n\t}\n\n\tprivate String createTextContent(InstanceEvent event, Instance instance) {\n\t\tMap<String, Object> textContent = new HashMap<>();\n\t\tString content = this.createContent(event, instance);\n\t\tif (this.atAll) {\n\t\t\tcontent += \"\\n<at user_id=\\\"all\\\">@all</at>\";\n\t\t}\n\t\ttextContent.put(\"text\", content);\n\t\treturn this.toJsonString(textContent);\n\t}\n\n\tprivate String createCardContent(InstanceEvent event, Instance instance) {\n\t\tString content = this.createContent(event, instance);\n\n\t\tMap<String, Object> header = new HashMap<>();\n\t\theader.put(\"template\", StringUtils.hasText(this.card.getThemeColor()) ? \"red\" : this.card.getThemeColor());\n\t\tMap<String, String> titleContent = new HashMap<>();\n\t\ttitleContent.put(\"tag\", \"plain_text\");\n\t\ttitleContent.put(\"content\", this.card.getTitle());\n\t\theader.put(\"title\", titleContent);\n\n\t\tList<Map<String, Object>> elements = new ArrayList<>();\n\t\tMap<String, Object> item = new HashMap<>();\n\t\titem.put(\"tag\", \"div\");\n\n\t\tMap<String, String> text = new HashMap<>();\n\t\ttext.put(\"tag\", \"plain_text\");\n\t\ttext.put(\"content\", content);\n\t\titem.put(\"text\", text);\n\t\telements.add(item);\n\n\t\tif (this.atAll) {\n\t\t\tMap<String, Object> atItem = new HashMap<>();\n\t\t\tatItem.put(\"tag\", \"div\");\n\t\t\tMap<String, String> atText = new HashMap<>();\n\t\t\tatText.put(\"tag\", \"lark_md\");\n\t\t\tatText.put(\"content\", \"<at id=all></at>\");\n\t\t\tatItem.put(\"text\", atText);\n\t\t\telements.add(atItem);\n\t\t}\n\t\tMap<String, Object> cardContent = new HashMap<>();\n\t\tcardContent.put(\"header\", header);\n\t\tcardContent.put(\"elements\", elements);\n\t\treturn this.toJsonString(cardContent);\n\t}\n\n\tprivate String toJsonString(Object o) {\n\t\ttry {\n\t\t\tJsonMapper jsonMapper = JsonMapper.builder().build();\n\t\t\treturn jsonMapper.writeValueAsString(o);\n\t\t}\n\t\tcatch (Exception ex) {\n\t\t\tlog.warn(\"Failed to serialize JSON object\", ex);\n\t\t}\n\t\treturn null;\n\t}\n\n\tpublic URI getWebhookUrl() {\n\t\treturn this.webhookUrl;\n\t}\n\n\tpublic void setWebhookUrl(URI webhookUrl) {\n\t\tthis.webhookUrl = webhookUrl;\n\t}\n\n\tpublic void setRestTemplate(RestTemplate restTemplate) {\n\t\tthis.restTemplate = restTemplate;\n\t}\n\n\tpublic boolean isAtAll() {\n\t\treturn atAll;\n\t}\n\n\tpublic void setAtAll(boolean atAll) {\n\t\tthis.atAll = atAll;\n\t}\n\n\tpublic String getSecret() {\n\t\treturn secret;\n\t}\n\n\tpublic void setSecret(String secret) {\n\t\tthis.secret = secret;\n\t}\n\n\tpublic MessageType getMessageType() {\n\t\treturn messageType;\n\t}\n\n\tpublic void setMessageType(MessageType messageType) {\n\t\tthis.messageType = messageType;\n\t}\n\n\tpublic Card getCard() {\n\t\treturn card;\n\t}\n\n\tpublic void setCard(Card card) {\n\t\tthis.card = card;\n\t}\n\n\tpublic enum MessageType {\n\n\t\ttext, interactive\n\n\t}\n\n\tpublic static class Card {\n\n\t\t/**\n\t\t * This is header title.\n\t\t */\n\t\tprivate String title = \"Codecentric's Spring Boot Admin notice\";\n\n\t\tprivate String themeColor = \"red\";\n\n\t\tpublic String getTitle() {\n\t\t\treturn title;\n\t\t}\n\n\t\tpublic void setTitle(String title) {\n\t\t\tthis.title = title;\n\t\t}\n\n\t\tpublic String getThemeColor() {\n\t\t\treturn themeColor;\n\t\t}\n\n\t\tpublic void setThemeColor(String themeColor) {\n\t\t\tthis.themeColor = themeColor;\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/HazelcastNotificationTrigger.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.util.concurrent.ConcurrentMap;\n\nimport org.reactivestreams.Publisher;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\npublic class HazelcastNotificationTrigger extends NotificationTrigger {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(HazelcastNotificationTrigger.class);\n\n\tprivate final ConcurrentMap<InstanceId, Long> sentNotifications;\n\n\tpublic HazelcastNotificationTrigger(Notifier notifier, Publisher<InstanceEvent> events,\n\t\t\tConcurrentMap<InstanceId, Long> sentNotifications) {\n\t\tsuper(notifier, events);\n\t\tthis.sentNotifications = sentNotifications;\n\t}\n\n\t@Override\n\tprotected Mono<Void> sendNotifications(InstanceEvent event) {\n\t\twhile (true) {\n\t\t\tLong lastSentEvent = this.sentNotifications.getOrDefault(event.getInstance(), -1L);\n\t\t\tif (lastSentEvent >= event.getVersion()) {\n\t\t\t\tlog.debug(\"Notifications already sent. Not triggering notifiers for {}\", event);\n\t\t\t\treturn Mono.empty();\n\t\t\t}\n\n\t\t\tif (lastSentEvent < 0) {\n\t\t\t\tif (this.sentNotifications.putIfAbsent(event.getInstance(), event.getVersion()) == null) {\n\t\t\t\t\tlog.debug(\"Triggering notifiers for {}\", event);\n\t\t\t\t\treturn super.sendNotifications(event);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (this.sentNotifications.replace(event.getInstance(), lastSentEvent, event.getVersion())) {\n\t\t\t\t\tlog.debug(\"Triggering notifiers for {}\", event);\n\t\t\t\t\treturn super.sendNotifications(event);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/HipchatNotifier.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.net.URI;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.jspecify.annotations.Nullable;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.MediaType;\nimport org.springframework.web.client.RestTemplate;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\nimport de.codecentric.boot.admin.server.notify.filter.AbstractContentNotifier;\n\n/**\n * Notifier submitting events to HipChat.\n *\n * @author Jamie Brown\n */\npublic class HipchatNotifier extends AbstractContentNotifier {\n\n\tprivate static final String DEFAULT_DESCRIPTION = \"<strong>#{name}</strong>/#{id} is <strong>#{status}</strong>\";\n\n\tprivate RestTemplate restTemplate;\n\n\t/**\n\t * Base URL for HipChat API (i.e. https://ACCOUNT_NAME.hipchat.com/v2\n\t */\n\t@Nullable private URI url;\n\n\t/**\n\t * API token that has access to notify in the room\n\t */\n\t@Nullable private String authToken;\n\n\t/**\n\t * Id of the room to notify\n\t */\n\t@Nullable private String roomId;\n\n\t/**\n\t * TRUE will cause OS notification, FALSE will only notify to room\n\t */\n\tprivate boolean notify = false;\n\n\tpublic HipchatNotifier(InstanceRepository repository, RestTemplate restTemplate) {\n\t\tsuper(repository);\n\t\tthis.restTemplate = restTemplate;\n\t}\n\n\t@Override\n\tprotected Mono<Void> doNotify(InstanceEvent event, Instance instance) {\n\t\treturn Mono.fromRunnable(\n\t\t\t\t() -> restTemplate.postForEntity(buildUrl(), createHipChatNotification(event, instance), Void.class));\n\t}\n\n\tprotected String buildUrl() {\n\t\tif (url == null) {\n\t\t\tthrow new IllegalStateException(\"'url' must not be null.\");\n\t\t}\n\t\treturn String.format(\"%s/room/%s/notification?auth_token=%s\", url, roomId, authToken);\n\t}\n\n\tprotected HttpEntity<Map<String, Object>> createHipChatNotification(InstanceEvent event, Instance instance) {\n\t\tMap<String, Object> body = new HashMap<>();\n\t\tbody.put(\"color\", getColor(event));\n\t\tbody.put(\"message\", createContent(event, instance));\n\t\tbody.put(\"notify\", getNotify());\n\t\tbody.put(\"message_format\", \"html\");\n\n\t\tHttpHeaders headers = new HttpHeaders();\n\t\theaders.setContentType(MediaType.APPLICATION_JSON);\n\t\treturn new HttpEntity<>(body, headers);\n\t}\n\n\tprotected boolean getNotify() {\n\t\treturn notify;\n\t}\n\n\t@Override\n\tprotected String getDefaultMessage() {\n\t\treturn DEFAULT_DESCRIPTION;\n\t}\n\n\tprotected String getColor(InstanceEvent event) {\n\t\tif (event instanceof InstanceStatusChangedEvent statusChangedEvent) {\n\t\t\treturn StatusInfo.STATUS_UP.equals(statusChangedEvent.getStatusInfo().getStatus()) ? \"green\" : \"red\";\n\t\t}\n\t\telse {\n\t\t\treturn \"gray\";\n\t\t}\n\t}\n\n\t@Nullable public URI getUrl() {\n\t\treturn url;\n\t}\n\n\tpublic void setUrl(@Nullable URI url) {\n\t\tthis.url = url;\n\t}\n\n\t@Nullable public String getAuthToken() {\n\t\treturn authToken;\n\t}\n\n\tpublic void setAuthToken(@Nullable String authToken) {\n\t\tthis.authToken = authToken;\n\t}\n\n\t@Nullable public String getRoomId() {\n\t\treturn roomId;\n\t}\n\n\tpublic void setRoomId(@Nullable String roomId) {\n\t\tthis.roomId = roomId;\n\t}\n\n\tpublic boolean isNotify() {\n\t\treturn notify;\n\t}\n\n\tpublic void setNotify(boolean notify) {\n\t\tthis.notify = notify;\n\t}\n\n\tpublic void setRestTemplate(RestTemplate restTemplate) {\n\t\tthis.restTemplate = restTemplate;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/LetsChatNotifier.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.net.URI;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Base64;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.jspecify.annotations.Nullable;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.http.MediaType;\nimport org.springframework.web.client.RestTemplate;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.notify.filter.AbstractContentNotifier;\n\n/**\n * Notifier submitting events to let´s Chat.\n *\n * @author Rico Pahlisch\n */\npublic class LetsChatNotifier extends AbstractContentNotifier {\n\n\tprivate static final String DEFAULT_MESSAGE = \"*#{name}* (#{id}) is *#{status}*\";\n\n\tprivate RestTemplate restTemplate;\n\n\t/**\n\t * Host URL for Let´s Chat\n\t */\n\t@Nullable private URI url;\n\n\t/**\n\t * Name of the room\n\t */\n\t@Nullable private String room;\n\n\t/**\n\t * Token for the Let´s chat API\n\t */\n\t@Nullable private String token;\n\n\t/**\n\t * username which sends notification\n\t */\n\tprivate String username = \"Spring Boot Admin\";\n\n\tpublic LetsChatNotifier(InstanceRepository repository, RestTemplate restTemplate) {\n\t\tsuper(repository);\n\t\tthis.restTemplate = restTemplate;\n\t}\n\n\t@Override\n\tprotected Mono<Void> doNotify(InstanceEvent event, Instance instance) {\n\t\tHttpHeaders headers = new HttpHeaders();\n\t\theaders.setContentType(MediaType.APPLICATION_JSON);\n\t\t// Let's Chat requires the token as basic username, the password can be an\n\t\t// arbitrary string.\n\t\tString auth = Base64.getEncoder()\n\t\t\t.encodeToString(String.format(\"%s:%s\", token, username).getBytes(StandardCharsets.UTF_8));\n\t\theaders.add(HttpHeaders.AUTHORIZATION, String.format(\"Basic %s\", auth));\n\t\treturn Mono.fromRunnable(() -> restTemplate.exchange(createUrl(), HttpMethod.POST,\n\t\t\t\tnew HttpEntity<>(createMessage(event, instance), headers), Void.class));\n\t}\n\n\tprivate URI createUrl() {\n\t\tif (url == null) {\n\t\t\tthrow new IllegalStateException(\"'url' must not be null.\");\n\t\t}\n\t\treturn URI.create(String.format(\"%s/rooms/%s/messages\", url, room));\n\t}\n\n\tprotected Object createMessage(InstanceEvent event, Instance instance) {\n\t\tMap<String, String> messageJson = new HashMap<>();\n\t\tmessageJson.put(\"text\", createContent(event, instance));\n\t\treturn messageJson;\n\t}\n\n\t@Override\n\tprotected String getDefaultMessage() {\n\t\treturn DEFAULT_MESSAGE;\n\t}\n\n\tpublic void setRestTemplate(RestTemplate restTemplate) {\n\t\tthis.restTemplate = restTemplate;\n\t}\n\n\t@Nullable public URI getUrl() {\n\t\treturn url;\n\t}\n\n\tpublic void setUrl(@Nullable URI url) {\n\t\tthis.url = url;\n\t}\n\n\tpublic String getUsername() {\n\t\treturn username;\n\t}\n\n\tpublic void setUsername(String username) {\n\t\tthis.username = username;\n\t}\n\n\t@Nullable public String getRoom() {\n\t\treturn room;\n\t}\n\n\tpublic void setRoom(@Nullable String room) {\n\t\tthis.room = room;\n\t}\n\n\t@Nullable public String getToken() {\n\t\treturn token;\n\t}\n\n\tpublic void setToken(@Nullable String token) {\n\t\tthis.token = token;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/LoggingNotifier.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\n\n/**\n * Notifier that just writes to a logger.\n *\n * @author Johannes Edmeier\n */\npublic class LoggingNotifier extends AbstractStatusChangeNotifier {\n\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(LoggingNotifier.class);\n\n\tpublic LoggingNotifier(InstanceRepository repository) {\n\t\tsuper(repository);\n\t}\n\n\t@Override\n\tprotected Mono<Void> doNotify(InstanceEvent event, Instance instance) {\n\t\treturn Mono.fromRunnable(() -> {\n\t\t\tif (event instanceof InstanceStatusChangedEvent statusChangedEvent) {\n\t\t\t\tLOGGER.info(\"Instance {} ({}) is {}\", instance.getRegistration().getName(), event.getInstance(),\n\t\t\t\t\t\tstatusChangedEvent.getStatusInfo().getStatus());\n\t\t\t}\n\t\t\telse {\n\t\t\t\tLOGGER.info(\"Instance {} ({}) {}\", instance.getRegistration().getName(), event.getInstance(),\n\t\t\t\t\t\tevent.getType());\n\t\t\t}\n\t\t});\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/MailNotifier.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport jakarta.mail.MessagingException;\nimport jakarta.mail.internet.MimeMessage;\nimport org.jspecify.annotations.Nullable;\nimport org.springframework.mail.javamail.JavaMailSender;\nimport org.springframework.mail.javamail.MimeMessageHelper;\nimport org.thymeleaf.TemplateEngine;\nimport org.thymeleaf.context.Context;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\n\nimport static java.util.Collections.singleton;\n\n/**\n * Notifier sending emails using Thymeleaf templates.\n *\n * @author Johannes Edmeier\n */\npublic class MailNotifier extends AbstractStatusChangeNotifier {\n\n\tprivate final JavaMailSender mailSender;\n\n\tprivate final TemplateEngine templateEngine;\n\n\t/**\n\t * recipients of the mail\n\t */\n\tprivate String[] to = { \"root@localhost\" };\n\n\t/**\n\t * cc-recipients of the mail\n\t */\n\tprivate String[] cc = {};\n\n\t/**\n\t * sender of the change\n\t */\n\tprivate String from = \"Spring Boot Admin <noreply@localhost>\";\n\n\t/**\n\t * Additional properties to be set for the template\n\t */\n\tprivate Map<String, Object> additionalProperties = new HashMap<>();\n\n\t/**\n\t * Base-URL used for hyperlinks in mail\n\t */\n\t@Nullable private String baseUrl;\n\n\t/**\n\t * Thymeleaf template for mail\n\t */\n\tprivate String template = \"META-INF/spring-boot-admin-server/mail/status-changed.html\";\n\n\tpublic MailNotifier(JavaMailSender mailSender, InstanceRepository repository, TemplateEngine templateEngine) {\n\t\tsuper(repository);\n\t\tthis.mailSender = mailSender;\n\t\tthis.templateEngine = templateEngine;\n\t}\n\n\t@Override\n\tprotected Mono<Void> doNotify(InstanceEvent event, Instance instance) {\n\t\treturn Mono.fromRunnable(() -> {\n\t\t\tContext ctx = new Context();\n\t\t\tctx.setVariables(additionalProperties);\n\t\t\tctx.setVariable(\"baseUrl\", this.baseUrl);\n\t\t\tctx.setVariable(\"event\", event);\n\t\t\tctx.setVariable(\"instance\", instance);\n\t\t\tctx.setVariable(\"lastStatus\", getLastStatus(event.getInstance()));\n\n\t\t\ttry {\n\t\t\t\tMimeMessage mimeMessage = mailSender.createMimeMessage();\n\t\t\t\tMimeMessageHelper message = new MimeMessageHelper(mimeMessage, StandardCharsets.UTF_8.name());\n\t\t\t\tmessage.setText(getBody(ctx).replaceAll(\"\\\\s+\\\\n\", \"\\n\"), true);\n\t\t\t\tmessage.setSubject(getSubject(ctx));\n\t\t\t\tmessage.setTo(this.to);\n\t\t\t\tmessage.setCc(this.cc);\n\t\t\t\tmessage.setFrom(this.from);\n\t\t\t\tmailSender.send(mimeMessage);\n\t\t\t}\n\t\t\tcatch (MessagingException ex) {\n\t\t\t\tthrow new RuntimeException(\"Error sending mail notification\", ex);\n\t\t\t}\n\t\t});\n\t}\n\n\tprotected String getBody(Context ctx) {\n\t\treturn templateEngine.process(this.template, ctx);\n\t}\n\n\tprotected String getSubject(Context ctx) {\n\t\treturn templateEngine.process(this.template, singleton(\"subject\"), ctx).trim();\n\t}\n\n\tpublic String[] getTo() {\n\t\treturn Arrays.copyOf(to, to.length);\n\t}\n\n\tpublic void setTo(String[] to) {\n\t\tthis.to = Arrays.copyOf(to, to.length);\n\t}\n\n\tpublic String[] getCc() {\n\t\treturn Arrays.copyOf(cc, cc.length);\n\t}\n\n\tpublic void setCc(String[] cc) {\n\t\tthis.cc = Arrays.copyOf(cc, cc.length);\n\t}\n\n\tpublic String getFrom() {\n\t\treturn from;\n\t}\n\n\tpublic void setFrom(String from) {\n\t\tthis.from = from;\n\t}\n\n\tpublic String getTemplate() {\n\t\treturn template;\n\t}\n\n\tpublic void setTemplate(String template) {\n\t\tthis.template = template;\n\t}\n\n\t@Nullable public String getBaseUrl() {\n\t\treturn baseUrl;\n\t}\n\n\tpublic void setBaseUrl(@Nullable String baseUrl) {\n\t\tthis.baseUrl = baseUrl;\n\t}\n\n\tpublic Map<String, Object> getAdditionalProperties() {\n\t\treturn additionalProperties;\n\t}\n\n\tpublic void setAdditionalProperties(Map<String, Object> additionalProperties) {\n\t\tthis.additionalProperties = additionalProperties;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/MattermostNotifier.java",
    "content": "/*\n * Copyright 2014-2025 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.net.URI;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.jspecify.annotations.Nullable;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.MediaType;\nimport org.springframework.web.client.RestTemplate;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\nimport de.codecentric.boot.admin.server.notify.filter.AbstractContentNotifier;\n\n/**\n * Notifier submitting events to Mattermost.\n *\n * @author Emir Boyaci\n */\npublic class MattermostNotifier extends AbstractContentNotifier {\n\n\tprivate static final String DEFAULT_MESSAGE = \"**#{name}** (#{id}) is **#{status}**\";\n\n\tprivate RestTemplate restTemplate;\n\n\t/**\n\t * API url for Mattermost (i.e. https://example.mattermost.com/api/v4/posts)\n\t */\n\t@Nullable private URI apiUrl;\n\n\t/**\n\t * Bot access token (i.e. dufc8q78hjgeccwsfhe37pcq1w)\n\t */\n\t@Nullable private String botAccessToken;\n\n\t/**\n\t * Optional channel name without # sign (i.e. h616jh436pysjpopp3259mhwxc)\n\t */\n\t@Nullable private String channelId;\n\n\tpublic MattermostNotifier(InstanceRepository repository, RestTemplate restTemplate) {\n\t\tsuper(repository);\n\t\tthis.restTemplate = restTemplate;\n\t}\n\n\t@Override\n\tprotected Mono<Void> doNotify(InstanceEvent event, Instance instance) {\n\t\tif (apiUrl == null) {\n\t\t\treturn Mono.error(new IllegalStateException(\"'url' must not be null.\"));\n\t\t}\n\t\treturn Mono.fromRunnable(() -> restTemplate.postForEntity(apiUrl, createMessage(event, instance), Void.class));\n\t}\n\n\tpublic void setRestTemplate(RestTemplate restTemplate) {\n\t\tthis.restTemplate = restTemplate;\n\t}\n\n\tprotected Object createMessage(InstanceEvent event, Instance instance) {\n\t\tMap<String, Object> messageJson = new HashMap<>();\n\t\tif (channelId != null) {\n\t\t\tmessageJson.put(\"channel_id\", channelId);\n\t\t}\n\n\t\tMap<String, Object> attachments = new HashMap<>();\n\t\tattachments.put(\"text\", createContent(event, instance));\n\t\tattachments.put(\"fallback\", createContent(event, instance));\n\t\tattachments.put(\"color\", getColor(event));\n\n\t\tMap<String, Object> props = new HashMap<>();\n\t\tprops.put(\"attachments\", Collections.singletonList(attachments));\n\n\t\tmessageJson.put(\"props\", props);\n\n\t\tHttpHeaders headers = new HttpHeaders();\n\t\theaders.setContentType(MediaType.APPLICATION_JSON);\n\t\theaders.setBearerAuth(botAccessToken);\n\t\treturn new HttpEntity<>(messageJson, headers);\n\t}\n\n\t@Override\n\tprotected String getDefaultMessage() {\n\t\treturn DEFAULT_MESSAGE;\n\t}\n\n\tprotected String getColor(InstanceEvent event) {\n\t\tif (event instanceof InstanceStatusChangedEvent statusChangedEvent) {\n\t\t\treturn StatusInfo.STATUS_UP.equals(statusChangedEvent.getStatusInfo().getStatus()) ? \"#2eb885\" : \"#a30100\";\n\t\t}\n\t\telse {\n\t\t\treturn \"#439FE0\";\n\t\t}\n\t}\n\n\t@Nullable public URI getApiUrl() {\n\t\treturn apiUrl;\n\t}\n\n\tpublic void setApiUrl(@Nullable URI apiUrl) {\n\t\tthis.apiUrl = apiUrl;\n\t}\n\n\t@Nullable public String getChannelId() {\n\t\treturn channelId;\n\t}\n\n\tpublic void setChannelId(@Nullable String channelId) {\n\t\tthis.channelId = channelId;\n\t}\n\n\t@Nullable public String getBotAccessToken() {\n\t\treturn botAccessToken;\n\t}\n\n\tpublic void setBotAccessToken(@Nullable String botAccessToken) {\n\t\tthis.botAccessToken = botAccessToken;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/MicrosoftTeamsNotifier.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.net.URI;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\n\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.jspecify.annotations.Nullable;\nimport org.springframework.expression.EvaluationContext;\nimport org.springframework.expression.Expression;\nimport org.springframework.expression.ParserContext;\nimport org.springframework.expression.spel.standard.SpelExpressionParser;\nimport org.springframework.expression.spel.support.DataBindingPropertyAccessor;\nimport org.springframework.expression.spel.support.MapAccessor;\nimport org.springframework.expression.spel.support.SimpleEvaluationContext;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.MediaType;\nimport org.springframework.web.client.RestTemplate;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceDeregisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\n\nimport static java.util.Collections.singletonList;\n\npublic class MicrosoftTeamsNotifier extends AbstractStatusChangeNotifier {\n\n\tprivate static final String STATUS_KEY = \"Status\";\n\n\tprivate static final String SERVICE_URL_KEY = \"Service URL\";\n\n\tprivate static final String HEALTH_URL_KEY = \"Health URL\";\n\n\tprivate static final String MANAGEMENT_URL_KEY = \"Management URL\";\n\n\tprivate static final String SOURCE_KEY = \"Source\";\n\n\tprivate static final String DEFAULT_THEME_COLOR_EXPRESSION = \"#{event.type == 'STATUS_CHANGED' ? (event.statusInfo.status=='UP' ? '6db33f' : 'b32d36') : '439fe0'}\";\n\n\tprivate static final String DEFAULT_DEREGISTER_ACTIVITY_SUBTITLE_EXPRESSION = \"#{instance.registration.name} with id #{instance.id} has de-registered from Spring Boot Admin\";\n\n\tprivate static final String DEFAULT_REGISTER_ACTIVITY_SUBTITLE_EXPRESSION = \"#{instance.registration.name} with id #{instance.id} has registered with Spring Boot Admin\";\n\n\tprivate static final String DEFAULT_STATUS_ACTIVITY_SUBTITLE_EXPRESSION = \"#{instance.registration.name} with id #{instance.id} changed status from #{lastStatus} to #{event.statusInfo.status}\";\n\n\tprivate final SpelExpressionParser parser = new SpelExpressionParser();\n\n\t@Setter\n\tprivate RestTemplate restTemplate;\n\n\t/**\n\t * Webhook url for Microsoft Teams Channel Webhook connector (i.e.\n\t * <a href=\"https://outlook.office.com/webhook/\">...</a>{webhook-id})\n\t */\n\t@Nullable private URI webhookUrl;\n\n\t/**\n\t * Theme Color is the color of the accent on the message that appears in Microsoft\n\t * Teams. Default is Spring Green\n\t */\n\tprivate Expression themeColor;\n\n\t/**\n\t * Message will be used as title of the Activity section of the Teams message when an\n\t * app de-registers.\n\t */\n\tprivate Expression deregisterActivitySubtitle;\n\n\t/**\n\t * Message will be used as title of the Activity section of the Teams message when an\n\t * app registers\n\t */\n\tprivate Expression registerActivitySubtitle;\n\n\t/**\n\t * Message will be used as title of the Activity section of the Teams message when an\n\t * app changes status\n\t */\n\tprivate Expression statusActivitySubtitle;\n\n\t/**\n\t * Title of the Teams message when an app de-registers\n\t */\n\t@Setter\n\t@Getter\n\tprivate String deRegisteredTitle = \"De-Registered\";\n\n\t/**\n\t * Title of the Teams message when an app registers\n\t */\n\t@Setter\n\t@Getter\n\tprivate String registeredTitle = \"Registered\";\n\n\t/**\n\t * Title of the Teams message when an app changes status\n\t */\n\t@Setter\n\t@Getter\n\tprivate String statusChangedTitle = \"Status Changed\";\n\n\t/**\n\t * Summary section of every Teams message originating from Spring Boot Admin\n\t */\n\t@Setter\n\t@Getter\n\tprivate String messageSummary = \"Spring Boot Admin Notification\";\n\n\tpublic MicrosoftTeamsNotifier(InstanceRepository repository, RestTemplate restTemplate) {\n\t\tsuper(repository);\n\t\tthis.restTemplate = restTemplate;\n\t\tthis.themeColor = parser.parseExpression(DEFAULT_THEME_COLOR_EXPRESSION, ParserContext.TEMPLATE_EXPRESSION);\n\t\tthis.deregisterActivitySubtitle = parser.parseExpression(DEFAULT_DEREGISTER_ACTIVITY_SUBTITLE_EXPRESSION,\n\t\t\t\tParserContext.TEMPLATE_EXPRESSION);\n\t\tthis.registerActivitySubtitle = parser.parseExpression(DEFAULT_REGISTER_ACTIVITY_SUBTITLE_EXPRESSION,\n\t\t\t\tParserContext.TEMPLATE_EXPRESSION);\n\t\tthis.statusActivitySubtitle = parser.parseExpression(DEFAULT_STATUS_ACTIVITY_SUBTITLE_EXPRESSION,\n\t\t\t\tParserContext.TEMPLATE_EXPRESSION);\n\t}\n\n\t@Override\n\tprotected Mono<Void> doNotify(InstanceEvent event, Instance instance) {\n\t\tMessage message;\n\t\tEvaluationContext context = createEvaluationContext(event, instance);\n\t\tif (event instanceof InstanceRegisteredEvent) {\n\t\t\tmessage = getRegisteredMessage(instance, context);\n\t\t}\n\t\telse if (event instanceof InstanceDeregisteredEvent) {\n\t\t\tmessage = getDeregisteredMessage(instance, context);\n\t\t}\n\t\telse if (event instanceof InstanceStatusChangedEvent) {\n\t\t\tmessage = getStatusChangedMessage(instance, context);\n\t\t}\n\t\telse {\n\t\t\treturn Mono.empty();\n\t\t}\n\n\t\tHttpHeaders headers = new HttpHeaders();\n\t\theaders.setContentType(MediaType.APPLICATION_JSON);\n\n\t\tif (webhookUrl == null) {\n\t\t\treturn Mono.error(new IllegalStateException(\"'webhookUrl' must not be null.\"));\n\t\t}\n\n\t\treturn Mono.fromRunnable(() -> this.restTemplate.postForEntity(webhookUrl,\n\t\t\t\tnew HttpEntity<Object>(message, headers), Void.class));\n\t}\n\n\t@Override\n\tprotected boolean shouldNotify(InstanceEvent event, Instance instance) {\n\t\treturn event instanceof InstanceRegisteredEvent || event instanceof InstanceDeregisteredEvent\n\t\t\t\t|| super.shouldNotify(event, instance);\n\t}\n\n\tprotected Message getDeregisteredMessage(Instance instance, EvaluationContext context) {\n\t\tString activitySubtitle = evaluateExpression(context, deregisterActivitySubtitle);\n\t\treturn createMessage(instance, deRegisteredTitle, activitySubtitle, context);\n\t}\n\n\tprotected Message getRegisteredMessage(Instance instance, EvaluationContext context) {\n\t\tString activitySubtitle = evaluateExpression(context, registerActivitySubtitle);\n\t\treturn createMessage(instance, registeredTitle, activitySubtitle, context);\n\t}\n\n\tprotected Message getStatusChangedMessage(Instance instance, EvaluationContext context) {\n\t\tString activitySubtitle = evaluateExpression(context, statusActivitySubtitle);\n\t\treturn createMessage(instance, statusChangedTitle, activitySubtitle, context);\n\t}\n\n\tprotected Message createMessage(Instance instance, String registeredTitle, String activitySubtitle,\n\t\t\tEvaluationContext context) {\n\t\tList<Fact> facts = new ArrayList<>();\n\t\tfacts.add(new Fact(STATUS_KEY, instance.getStatusInfo().getStatus()));\n\t\tfacts.add(new Fact(SERVICE_URL_KEY, instance.getRegistration().getServiceUrl()));\n\t\tfacts.add(new Fact(HEALTH_URL_KEY, instance.getRegistration().getHealthUrl()));\n\t\tfacts.add(new Fact(MANAGEMENT_URL_KEY, instance.getRegistration().getManagementUrl()));\n\t\tfacts.add(new Fact(SOURCE_KEY, instance.getRegistration().getSource()));\n\n\t\tSection section = Section.builder()\n\t\t\t.activityTitle(instance.getRegistration().getName())\n\t\t\t.activitySubtitle(activitySubtitle)\n\t\t\t.facts(facts)\n\t\t\t.build();\n\n\t\treturn Message.builder()\n\t\t\t.title(registeredTitle)\n\t\t\t.summary(messageSummary)\n\t\t\t.themeColor(evaluateExpression(context, themeColor))\n\t\t\t.sections(singletonList(section))\n\t\t\t.build();\n\t}\n\n\tprotected String evaluateExpression(EvaluationContext context, Expression expression) {\n\t\treturn Objects.requireNonNull(expression.getValue(context, String.class));\n\t}\n\n\tprotected EvaluationContext createEvaluationContext(InstanceEvent event, Instance instance) {\n\t\tMap<String, Object> root = new HashMap<>();\n\t\troot.put(\"event\", event);\n\t\troot.put(\"instance\", instance);\n\t\troot.put(\"lastStatus\", getLastStatus(event.getInstance()));\n\t\treturn SimpleEvaluationContext\n\t\t\t.forPropertyAccessors(DataBindingPropertyAccessor.forReadOnlyAccess(), new MapAccessor())\n\t\t\t.withRootObject(root)\n\t\t\t.build();\n\t}\n\n\t@Nullable public URI getWebhookUrl() {\n\t\treturn webhookUrl;\n\t}\n\n\tpublic void setWebhookUrl(@Nullable URI webhookUrl) {\n\t\tthis.webhookUrl = webhookUrl;\n\t}\n\n\tpublic String getThemeColor() {\n\t\treturn themeColor.getExpressionString();\n\t}\n\n\tpublic void setThemeColor(String themeColor) {\n\t\tthis.themeColor = parser.parseExpression(themeColor, ParserContext.TEMPLATE_EXPRESSION);\n\t}\n\n\tpublic String getDeregisterActivitySubtitle() {\n\t\treturn deregisterActivitySubtitle.getExpressionString();\n\t}\n\n\tpublic void setDeregisterActivitySubtitle(String deregisterActivitySubtitle) {\n\t\tthis.deregisterActivitySubtitle = parser.parseExpression(deregisterActivitySubtitle,\n\t\t\t\tParserContext.TEMPLATE_EXPRESSION);\n\t}\n\n\tpublic String getRegisterActivitySubtitle() {\n\t\treturn registerActivitySubtitle.getExpressionString();\n\t}\n\n\tpublic void setRegisterActivitySubtitle(String registerActivitySubtitle) {\n\t\tthis.registerActivitySubtitle = parser.parseExpression(registerActivitySubtitle,\n\t\t\t\tParserContext.TEMPLATE_EXPRESSION);\n\t}\n\n\tpublic String getStatusActivitySubtitle() {\n\t\treturn statusActivitySubtitle.getExpressionString();\n\t}\n\n\tpublic void setStatusActivitySubtitle(String statusActivitySubtitle) {\n\t\tthis.statusActivitySubtitle = parser.parseExpression(statusActivitySubtitle, ParserContext.TEMPLATE_EXPRESSION);\n\t}\n\n\t@Data\n\t@Builder\n\tpublic static class Message {\n\n\t\tprivate final String summary;\n\n\t\tprivate final String themeColor;\n\n\t\tprivate final String title;\n\n\t\t@Builder.Default\n\t\tprivate final List<Section> sections = new ArrayList<>();\n\n\t}\n\n\t@Data\n\t@Builder\n\tpublic static class Section {\n\n\t\tprivate final String activityTitle;\n\n\t\tprivate final String activitySubtitle;\n\n\t\t@Builder.Default\n\t\tprivate final List<Fact> facts = new ArrayList<>();\n\n\t}\n\n\tpublic record Fact(String name, @Nullable String value) {\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/NotificationTrigger.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport org.reactivestreams.Publisher;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.services.AbstractEventHandler;\n\npublic class NotificationTrigger extends AbstractEventHandler<InstanceEvent> {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(NotificationTrigger.class);\n\n\tprivate final Notifier notifier;\n\n\tpublic NotificationTrigger(Notifier notifier, Publisher<InstanceEvent> publisher) {\n\t\tsuper(publisher, InstanceEvent.class);\n\t\tthis.notifier = notifier;\n\t}\n\n\t@Override\n\tprotected Publisher<Void> handle(Flux<InstanceEvent> publisher) {\n\t\treturn publisher.flatMap(this::sendNotifications);\n\t}\n\n\tprotected Mono<Void> sendNotifications(InstanceEvent event) {\n\t\treturn this.notifier.notify(event)\n\t\t\t.doOnError((e) -> log.warn(\"Couldn't notify for event {} \", event, e))\n\t\t\t.onErrorResume((e) -> Mono.empty());\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/Notifier.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\n\n/**\n * Interface for components which emits notifications upon status changes in clients\n *\n * @author Johannes Edmeier\n */\npublic interface Notifier {\n\n\tMono<Void> notify(InstanceEvent event);\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/NotifierProxyProperties.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\n\n@lombok.Data\n@ConfigurationProperties(\"spring.boot.admin.notify.proxy\")\npublic class NotifierProxyProperties {\n\n\t/**\n\t * Proxy-Host for sending notifications\n\t */\n\tprivate String host;\n\n\t/**\n\t * Proxy-Port for sending notifications\n\t */\n\tprivate int port;\n\n\t/**\n\t * Proxy-User for sending notifications (if proxy requires authentication).\n\t */\n\tprivate String username;\n\n\t/**\n\t * Proxy-Password for sending notifications (if proxy requires authentication).\n\t */\n\tprivate String password;\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/OpsGenieNotifier.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.net.URI;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.jspecify.annotations.Nullable;\nimport org.springframework.expression.Expression;\nimport org.springframework.expression.ParserContext;\nimport org.springframework.expression.spel.standard.SpelExpressionParser;\nimport org.springframework.expression.spel.support.DataBindingPropertyAccessor;\nimport org.springframework.expression.spel.support.MapAccessor;\nimport org.springframework.expression.spel.support.SimpleEvaluationContext;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.http.MediaType;\nimport org.springframework.web.client.RestTemplate;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\n/**\n * Notifier submitting events to opsgenie.com.\n *\n * @author Fernando Sure\n */\npublic class OpsGenieNotifier extends AbstractStatusChangeNotifier {\n\n\tprivate static final URI DEFAULT_URI = URI.create(\"https://api.opsgenie.com/v2/alerts\");\n\n\tprivate static final String DEFAULT_MESSAGE = \"#{instance.registration.name}/#{instance.id} is #{instance.statusInfo.status}\";\n\n\tprivate final SpelExpressionParser parser = new SpelExpressionParser();\n\n\tprivate RestTemplate restTemplate;\n\n\t/**\n\t * BASE URL for OpsGenie API\n\t */\n\tprivate URI url = DEFAULT_URI;\n\n\t/**\n\t * Integration ApiKey\n\t */\n\t@Nullable private String apiKey;\n\n\t/**\n\t * Comma separated list of actions that can be executed.\n\t */\n\t@Nullable private String actions;\n\n\t/**\n\t * Field to specify source of alert. By default, it will be assigned to IP address of\n\t * incoming request\n\t */\n\t@Nullable private String source;\n\n\t/**\n\t * Comma separated list of labels attached to the alert\n\t */\n\t@Nullable private String tags;\n\n\t/**\n\t * The entity the alert is related to.\n\t */\n\t@Nullable private String entity;\n\n\t/**\n\t * Default owner of the execution. If user is not specified, the system becomes owner\n\t * of the execution.\n\t */\n\t@Nullable private String user;\n\n\t/**\n\t * Trigger description. SpEL template using event as root;\n\t */\n\tprivate Expression description;\n\n\tpublic OpsGenieNotifier(InstanceRepository repository, RestTemplate restTemplate) {\n\t\tsuper(repository);\n\t\tthis.restTemplate = restTemplate;\n\t\tthis.description = parser.parseExpression(DEFAULT_MESSAGE, ParserContext.TEMPLATE_EXPRESSION);\n\t}\n\n\t@Override\n\tprotected Mono<Void> doNotify(InstanceEvent event, Instance instance) {\n\t\treturn Mono.fromRunnable(() -> restTemplate.exchange(buildUrl(event, instance), HttpMethod.POST,\n\t\t\t\tcreateRequest(event, instance), Void.class));\n\t}\n\n\tprotected String buildUrl(InstanceEvent event, Instance instance) {\n\t\tif ((event instanceof InstanceStatusChangedEvent statusChangedEvent)\n\t\t\t\t&& (StatusInfo.STATUS_UP.equals(statusChangedEvent.getStatusInfo().getStatus()))) {\n\t\t\treturn String.format(\"%s/%s/close?identifierType=alias\", url, generateAlias(instance));\n\t\t}\n\t\treturn url.toString();\n\t}\n\n\tprotected HttpEntity<?> createRequest(InstanceEvent event, Instance instance) {\n\t\tMap<String, Object> body = new HashMap<>();\n\n\t\tif (user != null) {\n\t\t\tbody.put(\"user\", user);\n\t\t}\n\t\tif (source != null) {\n\t\t\tbody.put(\"source\", source);\n\t\t}\n\n\t\tif (event instanceof InstanceStatusChangedEvent statusChangedEvent\n\t\t\t\t&& !StatusInfo.STATUS_UP.equals(statusChangedEvent.getStatusInfo().getStatus())) {\n\n\t\t\tbody.put(\"message\", getMessage(event, instance));\n\t\t\tbody.put(\"alias\", generateAlias(instance));\n\t\t\tbody.put(\"description\", getDescription(event, instance));\n\t\t\tif (actions != null) {\n\t\t\t\tbody.put(\"actions\", actions);\n\t\t\t}\n\t\t\tif (tags != null) {\n\t\t\t\tbody.put(\"tags\", tags);\n\t\t\t}\n\t\t\tif (entity != null) {\n\t\t\t\tbody.put(\"entity\", entity);\n\t\t\t}\n\n\t\t\tMap<String, Object> details = new HashMap<>();\n\t\t\tdetails.put(\"type\", \"link\");\n\t\t\tdetails.put(\"href\", instance.getRegistration().getHealthUrl());\n\t\t\tdetails.put(\"text\", \"Instance health-endpoint\");\n\t\t\tbody.put(\"details\", details);\n\t\t}\n\n\t\tHttpHeaders headers = new HttpHeaders();\n\t\theaders.setContentType(MediaType.APPLICATION_JSON);\n\t\theaders.set(HttpHeaders.AUTHORIZATION, \"GenieKey \" + apiKey);\n\t\treturn new HttpEntity<>(body, headers);\n\t}\n\n\tprotected String generateAlias(Instance instance) {\n\t\treturn instance.getRegistration().getName() + \"_\" + instance.getId();\n\t}\n\n\t@Nullable protected String getMessage(InstanceEvent event, Instance instance) {\n\t\tMap<String, Object> root = new HashMap<>();\n\t\troot.put(\"event\", event);\n\t\troot.put(\"instance\", instance);\n\t\troot.put(\"lastStatus\", getLastStatus(event.getInstance()));\n\t\tSimpleEvaluationContext context = SimpleEvaluationContext\n\t\t\t.forPropertyAccessors(DataBindingPropertyAccessor.forReadOnlyAccess(), new MapAccessor())\n\t\t\t.withRootObject(root)\n\t\t\t.build();\n\t\treturn description.getValue(context, String.class);\n\t}\n\n\tprotected String getDescription(InstanceEvent event, Instance instance) {\n\t\treturn String.format(\"Instance %s (%s) went from %s to %s\", instance.getRegistration().getName(),\n\t\t\t\tinstance.getId(), getLastStatus(instance.getId()),\n\t\t\t\t((InstanceStatusChangedEvent) event).getStatusInfo().getStatus());\n\t}\n\n\t@Nullable public String getApiKey() {\n\t\treturn apiKey;\n\t}\n\n\tpublic void setApiKey(@Nullable String apiKey) {\n\t\tthis.apiKey = apiKey;\n\t}\n\n\tpublic void setDescription(String description) {\n\t\tthis.description = parser.parseExpression(description, ParserContext.TEMPLATE_EXPRESSION);\n\t}\n\n\tpublic String getMessage() {\n\t\treturn description.getExpressionString();\n\t}\n\n\tpublic void setRestTemplate(RestTemplate restTemplate) {\n\t\tthis.restTemplate = restTemplate;\n\t}\n\n\t@Nullable public String getActions() {\n\t\treturn actions;\n\t}\n\n\tpublic void setActions(@Nullable String actions) {\n\t\tthis.actions = actions;\n\t}\n\n\t@Nullable public String getSource() {\n\t\treturn source;\n\t}\n\n\tpublic void setSource(@Nullable String source) {\n\t\tthis.source = source;\n\t}\n\n\t@Nullable public String getTags() {\n\t\treturn tags;\n\t}\n\n\tpublic void setTags(@Nullable String tags) {\n\t\tthis.tags = tags;\n\t}\n\n\t@Nullable public String getEntity() {\n\t\treturn entity;\n\t}\n\n\tpublic void setEntity(@Nullable String entity) {\n\t\tthis.entity = entity;\n\t}\n\n\t@Nullable public String getUser() {\n\t\treturn user;\n\t}\n\n\tpublic void setUser(@Nullable String user) {\n\t\tthis.user = user;\n\t}\n\n\tpublic URI getUrl() {\n\t\treturn url;\n\t}\n\n\tpublic void setUrl(URI url) {\n\t\tthis.url = url;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/PagerdutyNotifier.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.net.URI;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport jakarta.annotation.Nullable;\nimport org.springframework.expression.Expression;\nimport org.springframework.expression.ParserContext;\nimport org.springframework.expression.spel.standard.SpelExpressionParser;\nimport org.springframework.expression.spel.support.DataBindingPropertyAccessor;\nimport org.springframework.expression.spel.support.MapAccessor;\nimport org.springframework.expression.spel.support.SimpleEvaluationContext;\nimport org.springframework.web.client.RestTemplate;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\n\nimport static java.util.Collections.singletonList;\n\n/**\n * Notifier submitting events to Pagerduty.\n *\n * @author Johannes Edmeier\n */\npublic class PagerdutyNotifier extends AbstractStatusChangeNotifier {\n\n\tpublic static final URI DEFAULT_URI = URI\n\t\t.create(\"https://events.pagerduty.com/generic/2010-04-15/create_event.json\");\n\n\tprivate static final String DEFAULT_DESCRIPTION = \"#{instance.registration.name}/#{instance.id} is #{instance.statusInfo.status}\";\n\n\tprivate final SpelExpressionParser parser = new SpelExpressionParser();\n\n\tprivate RestTemplate restTemplate;\n\n\t/**\n\t * URI for pagerduty-REST-API\n\t */\n\tprivate URI url = DEFAULT_URI;\n\n\t/**\n\t * Service-Key for pagerduty-REST-API\n\t */\n\t@Nullable\n\tprivate String serviceKey;\n\n\t/**\n\t * Client for pagerduty-REST-API\n\t */\n\t@Nullable\n\tprivate String client;\n\n\t/**\n\t * Client-url for pagerduty-REST-API\n\t */\n\t@Nullable\n\tprivate URI clientUrl;\n\n\t/**\n\t * Trigger description. SpEL template using event as root;\n\t */\n\tprivate Expression description;\n\n\tpublic PagerdutyNotifier(InstanceRepository repository, RestTemplate restTemplate) {\n\t\tsuper(repository);\n\t\tthis.restTemplate = restTemplate;\n\t\tthis.description = parser.parseExpression(DEFAULT_DESCRIPTION, ParserContext.TEMPLATE_EXPRESSION);\n\t}\n\n\t@Override\n\tprotected Mono<Void> doNotify(InstanceEvent event, Instance instance) {\n\t\treturn Mono\n\t\t\t.fromRunnable(() -> restTemplate.postForEntity(url, createPagerdutyEvent(event, instance), Void.class));\n\t}\n\n\tprotected Map<String, Object> createPagerdutyEvent(InstanceEvent event, Instance instance) {\n\t\tMap<String, Object> result = new HashMap<>();\n\t\tresult.put(\"service_key\", serviceKey);\n\t\tresult.put(\"incident_key\", instance.getRegistration().getName() + \"/\" + event.getInstance());\n\t\tresult.put(\"description\", getDescription(event, instance));\n\n\t\tMap<String, Object> details = getDetails(event);\n\t\tresult.put(\"details\", details);\n\n\t\tif (event instanceof InstanceStatusChangedEvent statusChangedEvent) {\n\t\t\tif (\"UP\".equals(statusChangedEvent.getStatusInfo().getStatus())) {\n\t\t\t\tresult.put(\"event_type\", \"resolve\");\n\t\t\t}\n\t\t\telse {\n\t\t\t\tresult.put(\"event_type\", \"trigger\");\n\t\t\t\tif (client != null) {\n\t\t\t\t\tresult.put(\"client\", client);\n\t\t\t\t}\n\t\t\t\tif (clientUrl != null) {\n\t\t\t\t\tresult.put(\"client_url\", clientUrl);\n\t\t\t\t}\n\n\t\t\t\tMap<String, Object> context = new HashMap<>();\n\t\t\t\tcontext.put(\"type\", \"link\");\n\t\t\t\tcontext.put(\"href\", instance.getRegistration().getHealthUrl());\n\t\t\t\tcontext.put(\"text\", \"Application health-endpoint\");\n\t\t\t\tresult.put(\"contexts\", singletonList(context));\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t@Nullable\n\tprotected String getDescription(InstanceEvent event, Instance instance) {\n\t\tMap<String, Object> root = new HashMap<>();\n\t\troot.put(\"event\", event);\n\t\troot.put(\"instance\", instance);\n\t\troot.put(\"lastStatus\", getLastStatus(event.getInstance()));\n\t\tSimpleEvaluationContext context = SimpleEvaluationContext\n\t\t\t.forPropertyAccessors(DataBindingPropertyAccessor.forReadOnlyAccess(), new MapAccessor())\n\t\t\t.withRootObject(root)\n\t\t\t.build();\n\t\treturn description.getValue(context, String.class);\n\t}\n\n\tprotected Map<String, Object> getDetails(InstanceEvent event) {\n\t\tMap<String, Object> details = new HashMap<>();\n\t\tif (event instanceof InstanceStatusChangedEvent statusChangedEvent) {\n\t\t\tdetails.put(\"from\", this.getLastStatus(event.getInstance()));\n\t\t\tdetails.put(\"to\", statusChangedEvent.getStatusInfo());\n\t\t}\n\t\treturn details;\n\t}\n\n\tpublic URI getUrl() {\n\t\treturn url;\n\t}\n\n\tpublic void setUrl(URI url) {\n\t\tthis.url = url;\n\t}\n\n\t@Nullable\n\tpublic String getClient() {\n\t\treturn client;\n\t}\n\n\tpublic void setClient(@Nullable String client) {\n\t\tthis.client = client;\n\t}\n\n\t@Nullable\n\tpublic URI getClientUrl() {\n\t\treturn clientUrl;\n\t}\n\n\tpublic void setClientUrl(@Nullable URI clientUrl) {\n\t\tthis.clientUrl = clientUrl;\n\t}\n\n\t@Nullable\n\tpublic String getServiceKey() {\n\t\treturn serviceKey;\n\t}\n\n\tpublic void setServiceKey(@Nullable String serviceKey) {\n\t\tthis.serviceKey = serviceKey;\n\t}\n\n\tpublic String getDescription() {\n\t\treturn description.getExpressionString();\n\t}\n\n\tpublic void setDescription(String description) {\n\t\tthis.description = parser.parseExpression(description, ParserContext.TEMPLATE_EXPRESSION);\n\t}\n\n\tpublic void setRestTemplate(RestTemplate restTemplate) {\n\t\tthis.restTemplate = restTemplate;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/RemindingNotifier.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.Arrays;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.logging.Level;\n\nimport org.jspecify.annotations.Nullable;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.util.Assert;\nimport reactor.core.Disposable;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.core.scheduler.Scheduler;\nimport reactor.core.scheduler.Schedulers;\nimport reactor.util.retry.Retry;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceDeregisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\n/**\n * Notifier that reminds certain statuses to send reminder notification using a delegate.\n *\n * @author Johannes Edmeier\n */\npublic class RemindingNotifier extends AbstractEventNotifier {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(RemindingNotifier.class);\n\n\tprivate final ConcurrentHashMap<InstanceId, Reminder> reminders = new ConcurrentHashMap<>();\n\n\tprivate final Notifier delegate;\n\n\tprivate Duration checkReminderInverval = Duration.ofSeconds(10);\n\n\tprivate Duration reminderPeriod = Duration.ofMinutes(10);\n\n\tprivate String[] reminderStatuses = { \"DOWN\", \"OFFLINE\" };\n\n\t@Nullable private Disposable subscription;\n\n\t@Nullable private Scheduler reminderScheduler;\n\n\tpublic RemindingNotifier(Notifier delegate, InstanceRepository repository) {\n\t\tsuper(repository);\n\t\tAssert.notNull(delegate, \"'delegate' must not be null!\");\n\t\tthis.delegate = delegate;\n\t}\n\n\t@Override\n\tpublic Mono<Void> doNotify(InstanceEvent event, Instance instance) {\n\t\treturn this.delegate.notify(event).doFinally((s) -> {\n\t\t\tif (shouldEndReminder(event)) {\n\t\t\t\tthis.reminders.remove(event.getInstance());\n\t\t\t}\n\t\t\telse if (shouldStartReminder(event)) {\n\t\t\t\tthis.reminders.putIfAbsent(event.getInstance(), new Reminder(event));\n\t\t\t}\n\t\t}).onErrorResume((e) -> Mono.empty());\n\t}\n\n\tpublic void start() {\n\t\tthis.reminderScheduler = Schedulers.newSingle(\"reminders\");\n\t\tthis.subscription = Flux.interval(this.checkReminderInverval, this.reminderScheduler)\n\t\t\t.log(log.getName(), Level.FINEST)\n\t\t\t.doOnSubscribe((s) -> log.debug(\"Started reminders\"))\n\t\t\t.flatMap((i) -> this.sendReminders())\n\t\t\t.retryWhen(Retry.indefinitely()\n\t\t\t\t.doBeforeRetry((s) -> log.warn(\"Unexpected error when sending reminders\", s.failure())))\n\t\t\t.subscribe();\n\t}\n\n\tpublic void stop() {\n\t\tif (this.subscription != null && !this.subscription.isDisposed()) {\n\t\t\tlog.debug(\"stopped reminders\");\n\t\t\tthis.subscription.dispose();\n\t\t\tthis.subscription = null;\n\t\t}\n\t\tif (this.reminderScheduler != null) {\n\t\t\tthis.reminderScheduler.dispose();\n\t\t\tthis.reminderScheduler = null;\n\t\t}\n\t}\n\n\tprotected Mono<Void> sendReminders() {\n\t\tInstant now = Instant.now();\n\n\t\treturn Flux.fromIterable(this.reminders.values())\n\t\t\t.filter((reminder) -> reminder.getLastNotification().plus(this.reminderPeriod).isBefore(now))\n\t\t\t.flatMap((reminder) -> this.delegate.notify(reminder.getEvent())\n\t\t\t\t.doOnSuccess((signal) -> reminder.setLastNotification(now)))\n\t\t\t.then();\n\t}\n\n\tprotected boolean shouldStartReminder(InstanceEvent event) {\n\t\tif (event instanceof InstanceStatusChangedEvent statusChangedEvent) {\n\t\t\treturn Arrays.binarySearch(this.reminderStatuses, statusChangedEvent.getStatusInfo().getStatus()) >= 0;\n\t\t}\n\t\treturn false;\n\t}\n\n\tprotected boolean shouldEndReminder(InstanceEvent event) {\n\t\tif (event instanceof InstanceDeregisteredEvent) {\n\t\t\treturn true;\n\t\t}\n\t\tif (event instanceof InstanceStatusChangedEvent statusChangedEvent) {\n\t\t\treturn Arrays.binarySearch(this.reminderStatuses, statusChangedEvent.getStatusInfo().getStatus()) < 0;\n\t\t}\n\t\treturn false;\n\t}\n\n\tpublic void setReminderPeriod(Duration reminderPeriod) {\n\t\tthis.reminderPeriod = reminderPeriod;\n\t}\n\n\tpublic void setReminderStatuses(String[] reminderStatuses) {\n\t\tString[] copy = Arrays.copyOf(reminderStatuses, reminderStatuses.length);\n\t\tArrays.sort(copy);\n\t\tthis.reminderStatuses = copy;\n\t}\n\n\tpublic void setCheckReminderInverval(Duration checkReminderInverval) {\n\t\tthis.checkReminderInverval = checkReminderInverval;\n\t}\n\n\tprotected static final class Reminder {\n\n\t\tprivate final InstanceEvent event;\n\n\t\tprivate Instant lastNotification;\n\n\t\tprivate Reminder(InstanceEvent event) {\n\t\t\tthis.event = event;\n\t\t\tthis.lastNotification = event.getTimestamp();\n\t\t}\n\n\t\tpublic Instant getLastNotification() {\n\t\t\treturn this.lastNotification;\n\t\t}\n\n\t\tpublic void setLastNotification(Instant lastNotification) {\n\t\t\tthis.lastNotification = lastNotification;\n\t\t}\n\n\t\tpublic InstanceEvent getEvent() {\n\t\t\treturn this.event;\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/RocketChatNotifier.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.net.URI;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.jspecify.annotations.Nullable;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.http.MediaType;\nimport org.springframework.web.client.RestTemplate;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.notify.filter.AbstractContentNotifier;\n\n/**\n * Notifier submitting events to RocketChat.\n *\n * @author Nicolas Badenne\n */\npublic class RocketChatNotifier extends AbstractContentNotifier {\n\n\tprivate static final String DEFAULT_MESSAGE = \"*#{name}* (#{id}) is *#{status}*\";\n\n\tprivate RestTemplate restTemplate;\n\n\t/**\n\t * Host URL for RocketChat server\n\t */\n\t@Nullable private String url;\n\n\t/**\n\t * Room Id to send message\n\t */\n\t@Nullable private String roomId;\n\n\t/**\n\t * Token for RocketChat API\n\t */\n\t@Nullable private String token;\n\n\t/**\n\t * User Id for RocketChat API\n\t */\n\tprivate String userId;\n\n\tpublic RocketChatNotifier(InstanceRepository repository, RestTemplate restTemplate) {\n\t\tsuper(repository);\n\t\tthis.restTemplate = restTemplate;\n\t}\n\n\t@Override\n\tprotected Mono<Void> doNotify(InstanceEvent event, Instance instance) {\n\t\tHttpHeaders headers = new HttpHeaders();\n\t\theaders.setContentType(MediaType.APPLICATION_JSON);\n\t\theaders.add(\"X-Auth-Token\", token);\n\t\theaders.add(\"X-User-Id\", userId);\n\t\treturn Mono.fromRunnable(() -> restTemplate.exchange(getUri(), HttpMethod.POST,\n\t\t\t\tnew HttpEntity<>(createMessage(event, instance), headers), Void.class));\n\t}\n\n\tprivate URI getUri() {\n\t\tif (url == null) {\n\t\t\tthrow new IllegalStateException(\"'url' must not be null.\");\n\t\t}\n\t\treturn URI.create(String.format(\"%s/api/v1/chat.sendMessage\", url));\n\t}\n\n\tprotected Object createMessage(InstanceEvent event, Instance instance) {\n\t\tMap<String, String> messageJsonData = new HashMap<>();\n\t\tmessageJsonData.put(\"rid\", roomId);\n\t\tmessageJsonData.put(\"msg\", createContent(event, instance));\n\t\tMap<String, Object> messageJson = new HashMap<>();\n\t\tmessageJson.put(\"message\", messageJsonData);\n\t\treturn messageJson;\n\t}\n\n\t@Override\n\tprotected Map<String, Object> buildContentModel(InstanceEvent event, Instance instance) {\n\t\tvar content = super.buildContentModel(event, instance);\n\t\tcontent.put(\"roomId\", roomId);\n\t\treturn content;\n\t}\n\n\t@Override\n\tprotected String getDefaultMessage() {\n\t\treturn DEFAULT_MESSAGE;\n\t}\n\n\tpublic void setRestTemplate(RestTemplate restTemplate) {\n\t\tthis.restTemplate = restTemplate;\n\t}\n\n\t@Nullable public String getUrl() {\n\t\treturn url;\n\t}\n\n\tpublic void setUrl(String url) {\n\t\tthis.url = url;\n\t}\n\n\t@Nullable public String getRoomId() {\n\t\treturn roomId;\n\t}\n\n\tpublic void setRoomId(String roomId) {\n\t\tthis.roomId = roomId;\n\t}\n\n\t@Nullable public String getToken() {\n\t\treturn token;\n\t}\n\n\tpublic void setToken(String token) {\n\t\tthis.token = token;\n\t}\n\n\t@Nullable public String getUserId() {\n\t\treturn userId;\n\t}\n\n\tpublic void setUserId(String userId) {\n\t\tthis.userId = userId;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/SlackNotifier.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.net.URI;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.jspecify.annotations.Nullable;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.MediaType;\nimport org.springframework.web.client.RestTemplate;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\nimport de.codecentric.boot.admin.server.notify.filter.AbstractContentNotifier;\n\n/**\n * Notifier submitting events to Slack.\n *\n * @author Artur Dobosiewicz\n */\npublic class SlackNotifier extends AbstractContentNotifier {\n\n\tprivate static final String DEFAULT_MESSAGE = \"*#{name}* (#{id}) is *#{status}*\";\n\n\tprivate RestTemplate restTemplate;\n\n\t/**\n\t * Webhook url for Slack API (i.e. https://hooks.slack.com/services/xxx)\n\t */\n\t@Nullable private URI webhookUrl;\n\n\t/**\n\t * Optional channel name without # sign (i.e. somechannel)\n\t */\n\t@Nullable private String channel;\n\n\t/**\n\t * Optional emoji icon without colons (i.e. my-emoji)\n\t */\n\t@Nullable private String icon;\n\n\t/**\n\t * Optional username which sends notification\n\t */\n\t@Nullable private String username = \"Spring Boot Admin\";\n\n\tpublic SlackNotifier(InstanceRepository repository, RestTemplate restTemplate) {\n\t\tsuper(repository);\n\t\tthis.restTemplate = restTemplate;\n\t}\n\n\t@Override\n\tprotected Mono<Void> doNotify(InstanceEvent event, Instance instance) {\n\t\tif (webhookUrl == null) {\n\t\t\treturn Mono.error(new IllegalStateException(\"'webhookUrl' must not be null.\"));\n\t\t}\n\t\treturn Mono\n\t\t\t.fromRunnable(() -> restTemplate.postForEntity(webhookUrl, createMessage(event, instance), Void.class));\n\t}\n\n\tpublic void setRestTemplate(RestTemplate restTemplate) {\n\t\tthis.restTemplate = restTemplate;\n\t}\n\n\tprotected Object createMessage(InstanceEvent event, Instance instance) {\n\t\tMap<String, Object> messageJson = new HashMap<>();\n\t\tmessageJson.put(\"username\", username);\n\t\tif (icon != null) {\n\t\t\tmessageJson.put(\"icon_emoji\", \":\" + icon + \":\");\n\t\t}\n\t\tif (channel != null) {\n\t\t\tmessageJson.put(\"channel\", channel);\n\t\t}\n\n\t\tMap<String, Object> attachments = new HashMap<>();\n\t\tattachments.put(\"text\", createContent(event, instance));\n\t\tattachments.put(\"color\", getColor(event));\n\t\tattachments.put(\"mrkdwn_in\", Collections.singletonList(\"text\"));\n\t\tmessageJson.put(\"attachments\", Collections.singletonList(attachments));\n\n\t\tHttpHeaders headers = new HttpHeaders();\n\t\theaders.setContentType(MediaType.APPLICATION_JSON);\n\t\treturn new HttpEntity<>(messageJson, headers);\n\t}\n\n\tprotected String getColor(InstanceEvent event) {\n\t\tif (event instanceof InstanceStatusChangedEvent statusChangedEvent) {\n\t\t\treturn StatusInfo.STATUS_UP.equals(statusChangedEvent.getStatusInfo().getStatus()) ? \"good\" : \"danger\";\n\t\t}\n\t\telse {\n\t\t\treturn \"#439FE0\";\n\t\t}\n\t}\n\n\t@Nullable public URI getWebhookUrl() {\n\t\treturn webhookUrl;\n\t}\n\n\tpublic void setWebhookUrl(@Nullable URI webhookUrl) {\n\t\tthis.webhookUrl = webhookUrl;\n\t}\n\n\t@Nullable public String getChannel() {\n\t\treturn channel;\n\t}\n\n\tpublic void setChannel(@Nullable String channel) {\n\t\tthis.channel = channel;\n\t}\n\n\t@Nullable public String getIcon() {\n\t\treturn icon;\n\t}\n\n\tpublic void setIcon(@Nullable String icon) {\n\t\tthis.icon = icon;\n\t}\n\n\t@Nullable public String getUsername() {\n\t\treturn username;\n\t}\n\n\tpublic void setUsername(@Nullable String username) {\n\t\tthis.username = username;\n\t}\n\n\t@Override\n\tprotected String getDefaultMessage() {\n\t\treturn DEFAULT_MESSAGE;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/TelegramNotifier.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.jspecify.annotations.Nullable;\nimport org.springframework.web.client.RestTemplate;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.notify.filter.AbstractContentNotifier;\n\n/**\n * Notifier submitting events to Telegram.\n */\npublic class TelegramNotifier extends AbstractContentNotifier {\n\n\tprivate static final String DEFAULT_MESSAGE = \"<strong>#{name}</strong>/#{id} is <strong>#{status}</strong>\";\n\n\tprivate RestTemplate restTemplate;\n\n\t/**\n\t * base url for telegram (i.e. https://api.telegram.org)\n\t */\n\tprivate String apiUrl = \"https://api.telegram.org\";\n\n\t/**\n\t * Unique identifier for the target chat or username of the target channel\n\t */\n\t@Nullable private String chatId;\n\n\t/**\n\t * The token identifying und authorizing your Telegram bot (e.g.\n\t * `123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11`)\n\t */\n\t@Nullable private String authToken;\n\n\t/**\n\t * Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width\n\t * text or inline URLs in your bot's message.\n\t */\n\tprivate String parseMode = \"HTML\";\n\n\t/**\n\t * If true users will receive a notification with no sound.\n\t */\n\tprivate boolean disableNotify = false;\n\n\tpublic TelegramNotifier(InstanceRepository repository, RestTemplate restTemplate) {\n\t\tsuper(repository);\n\t\tthis.restTemplate = restTemplate;\n\t}\n\n\t@Override\n\tprotected Mono<Void> doNotify(InstanceEvent event, Instance instance) {\n\t\treturn Mono\n\t\t\t.fromRunnable(() -> restTemplate.getForObject(buildUrl(), Void.class, createMessage(event, instance)));\n\t}\n\n\tprotected String buildUrl() {\n\t\treturn String.format(\"%s/bot%s/sendmessage?chat_id={chat_id}&text={text}&parse_mode={parse_mode}\"\n\t\t\t\t+ \"&disable_notification={disable_notification}\", this.apiUrl, this.authToken);\n\t}\n\n\tprivate Map<String, Object> createMessage(InstanceEvent event, Instance instance) {\n\t\tMap<String, Object> parameters = new HashMap<>();\n\t\tparameters.put(\"chat_id\", this.chatId);\n\t\tparameters.put(\"parse_mode\", this.parseMode);\n\t\tparameters.put(\"disable_notification\", this.disableNotify);\n\t\tparameters.put(\"text\", createContent(event, instance));\n\t\treturn parameters;\n\t}\n\n\t@Override\n\tprotected String getDefaultMessage() {\n\t\treturn DEFAULT_MESSAGE;\n\t}\n\n\tpublic void setRestTemplate(RestTemplate restTemplate) {\n\t\tthis.restTemplate = restTemplate;\n\t}\n\n\tpublic String getApiUrl() {\n\t\treturn apiUrl;\n\t}\n\n\tpublic void setApiUrl(String apiUrl) {\n\t\tthis.apiUrl = apiUrl;\n\t}\n\n\t@Nullable public String getChatId() {\n\t\treturn chatId;\n\t}\n\n\tpublic void setChatId(@Nullable String chatId) {\n\t\tthis.chatId = chatId;\n\t}\n\n\t@Nullable public String getAuthToken() {\n\t\treturn authToken;\n\t}\n\n\tpublic void setAuthToken(@Nullable String authToken) {\n\t\tthis.authToken = authToken;\n\t}\n\n\tpublic boolean isDisableNotify() {\n\t\treturn disableNotify;\n\t}\n\n\tpublic void setDisableNotify(boolean disableNotify) {\n\t\tthis.disableNotify = disableNotify;\n\t}\n\n\tpublic String getParseMode() {\n\t\treturn parseMode;\n\t}\n\n\tpublic void setParseMode(String parseMode) {\n\t\tthis.parseMode = parseMode;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/WebexNotifier.java",
    "content": "/*\n * Copyright 2014-2024 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.net.URI;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.jspecify.annotations.Nullable;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.MediaType;\nimport org.springframework.web.client.RestTemplate;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.notify.filter.AbstractContentNotifier;\n\n// The following class, `WebexNotifier`, is responsible for sending notifications through the Webex API\n// whenever events related to the state of instances within the Spring Boot Admin server occur.\n\n/**\n * `WebexNotifier` sends notifications via Webex API when instance events occur. It is\n * part of the spring-boot-admin-server which is used for monitoring and managing Spring\n * Boot applications.\n */\npublic class WebexNotifier extends AbstractContentNotifier {\n\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(WebexNotifier.class);\n\n\tprivate static final URI DEFAULT_URL = URI.create(\"https://webexapis.com/v1/messages\");\n\n\tprivate static final String DEFAULT_MESSAGE = \"<strong>#{name}</strong>/#{id} is <strong>#{status}</strong>\";\n\n\tprivate RestTemplate restTemplate;\n\n\t/**\n\t * base url for Webex API (i.e. https://webexapis.com/v1/messages)\n\t */\n\tprivate URI url = DEFAULT_URL;\n\n\t/**\n\t * Bearer authentication token for Webex API\n\t */\n\t@Nullable private String authToken;\n\n\t/**\n\t * Room identifier in Webex where the message will be sent\n\t */\n\t@Nullable private String roomId;\n\n\t/**\n\t * Creates a new WebexNotifier with the given repository and restTemplate.\n\t * @param repository the instance repository responsible for storing instances\n\t * @param restTemplate the restTemplate used to make HTTP requests\n\t */\n\tpublic WebexNotifier(InstanceRepository repository, RestTemplate restTemplate) {\n\t\tsuper(repository);\n\t\tthis.restTemplate = restTemplate;\n\t}\n\n\t/**\n\t * Sends a notification with the given event and instance.\n\t * @param event the instance event to notify\n\t * @param instance the instance associated with the event\n\t * @return a Mono representing the completion of the notification\n\t * @throws IllegalStateException if 'authToken' is null\n\t */\n\t@Override\n\tprotected Mono<Void> doNotify(InstanceEvent event, Instance instance) {\n\n\t\tif (authToken == null) {\n\t\t\treturn Mono.error(new IllegalStateException(\"'authToken' must not be null.\"));\n\t\t}\n\n\t\tHttpHeaders headers = new HttpHeaders();\n\t\theaders.setContentType(MediaType.APPLICATION_JSON);\n\t\theaders.setBearerAuth(authToken);\n\n\t\tLOGGER.debug(\"Event: {}\", event.getInstance());\n\n\t\treturn Mono.fromRunnable(() -> restTemplate.postForEntity(url,\n\t\t\t\tnew HttpEntity<>(createMessage(event, instance), headers), Void.class));\n\t}\n\n\t/**\n\t * Creates a message object containing the parameters required for sending a\n\t * notification.\n\t * @param event the instance event for which the message is being created\n\t * @param instance the instance associated with the event\n\t * @return a Map object containing the parameters for sending a notification\n\t */\n\tprotected Object createMessage(InstanceEvent event, Instance instance) {\n\t\tMap<String, Object> parameters = new HashMap<>();\n\t\tparameters.put(\"roomId\", this.roomId);\n\t\tparameters.put(\"markdown\", createContent(event, instance));\n\t\treturn parameters;\n\t}\n\n\t@Override\n\tprotected String getDefaultMessage() {\n\t\treturn DEFAULT_MESSAGE;\n\t}\n\n\tpublic void setRestTemplate(RestTemplate restTemplate) {\n\t\tthis.restTemplate = restTemplate;\n\t}\n\n\tpublic URI getUrl() {\n\t\treturn url;\n\t}\n\n\tpublic void setUrl(URI url) {\n\t\tthis.url = url;\n\t}\n\n\t@Nullable public String getAuthToken() {\n\t\treturn authToken;\n\t}\n\n\tpublic void setAuthToken(@Nullable String authToken) {\n\t\tthis.authToken = authToken;\n\t}\n\n\t@Nullable public String getRoomId() {\n\t\treturn roomId;\n\t}\n\n\tpublic void setRoomId(@Nullable String roomId) {\n\t\tthis.roomId = roomId;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/filter/AbstractContentNotifier.java",
    "content": "/*\n * Copyright 2014-2025 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify.filter;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.springframework.expression.Expression;\nimport org.springframework.expression.ParserContext;\nimport org.springframework.expression.spel.standard.SpelExpressionParser;\nimport org.springframework.expression.spel.support.DataBindingPropertyAccessor;\nimport org.springframework.expression.spel.support.MapAccessor;\nimport org.springframework.expression.spel.support.SimpleEvaluationContext;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.notify.AbstractStatusChangeNotifier;\n\n/**\n * Base class for notifiers that generate message content from templates using Spring\n * Expression Language (SpEL).\n * <p>\n * This class provides a framework for creating custom notifiers that format notification\n * messages using SpEL templates with access to instance event data. Subclasses must\n * implement two methods:\n * <ul>\n * <li>{@link #buildContentModel(InstanceEvent, Instance)} - Provide the data model for\n * template evaluation</li>\n * <li>{@link #getDefaultMessage()} - Define the default SpEL template string</li>\n * </ul>\n * <p>\n * <b>Usage Example:</b> <pre>{@code\n * public class EmailNotifier extends AbstractContentNotifier {\n *     public EmailNotifier(InstanceRepository repository) {\n *         super(repository);\n *     }\n *\n *     &#64;Override\n *     protected Map<String, Object> getContent(InstanceEvent event, Instance instance) {\n *         var content = super.getContent(event, instance);\n *         content.put(\"customContent\", \"Hello, World!\");\n *         return content;\n *     }\n *\n *\n\n&#64;Override\n *     protected String getDefaultMessage() {\n *         return \"#{name} is #{status} at #{url}\";\n *     }\n * }\n * }</pre>\n * <p>\n * The message template can be customized at runtime using {@link #setMessage(String)}.\n */\npublic abstract class AbstractContentNotifier extends AbstractStatusChangeNotifier {\n\n\tprivate Expression message;\n\n\tprivate final SpelExpressionParser parser = new SpelExpressionParser();\n\n\tpublic AbstractContentNotifier(InstanceRepository repository) {\n\t\tsuper(repository);\n\n\t\tthis.message = this.parser.parseExpression(getDefaultMessage(), ParserContext.TEMPLATE_EXPRESSION);\n\t}\n\n\tpublic String getMessage() {\n\t\treturn this.message.getExpressionString();\n\t}\n\n\tpublic void setMessage(String message) {\n\t\tthis.message = this.parser.parseExpression(message, ParserContext.TEMPLATE_EXPRESSION);\n\t}\n\n\t/**\n\t * Generates the notification message content by evaluating the SpEL template with\n\t * event and instance data.\n\t * <p>\n\t * This method combines the configured message template with the data provided by\n\t * {@link #buildContentModel(InstanceEvent, Instance)} to produce the final\n\t * notification text.\n\t * @param event the instance event that triggered the notification\n\t * @param instance the instance associated with the event\n\t * @return the evaluated message content as a string\n\t */\n\tprotected String createContent(InstanceEvent event, Instance instance) {\n\t\tSimpleEvaluationContext context = SimpleEvaluationContext\n\t\t\t.forPropertyAccessors(DataBindingPropertyAccessor.forReadOnlyAccess(), new MapAccessor())\n\t\t\t.withRootObject(buildContentModel(event, instance))\n\t\t\t.build();\n\t\treturn this.message.getValue(context, String.class);\n\t}\n\n\t/**\n\t * Provides the data model used for evaluating the message template.\n\t * <p>\n\t * The returned map contains key-value pairs that can be referenced in the SpEL\n\t * template using #{key} syntax. For example, if the map contains {\"name\": \"MyApp\",\n\t * \"status\": \"UP\"}, the template \"#{name} is #{status}\" would evaluate to \"MyApp is\n\t * UP\".\n\t * @param event the instance event containing event-specific data\n\t * @param instance the instance containing registration and status information\n\t * @return a map of template variables and their values\n\t */\n\tprotected Map<String, Object> buildContentModel(InstanceEvent event, Instance instance) {\n\t\tMap<String, Object> content = new HashMap<>();\n\t\tcontent.put(\"name\", instance.getRegistration().getName());\n\t\tcontent.put(\"id\", instance.getId().getValue());\n\t\tcontent.put(\"status\", (event instanceof InstanceStatusChangedEvent statusChangedEvent)\n\t\t\t\t? statusChangedEvent.getStatusInfo().getStatus() : \"UNKNOWN\");\n\t\tcontent.put(\"lastStatus\", getLastStatus(event.getInstance()));\n\n\t\treturn content;\n\t}\n\n\t/**\n\t * Defines the default SpEL template string used for message generation.\n\t * <p>\n\t * The template should use #{key} syntax to reference variables provided by\n\t * {@link #buildContentModel(InstanceEvent, Instance)}. This default can be overridden\n\t * at runtime using {@link #setMessage(String)}.\n\t * @return the default SpEL template string (e.g., \"#{name} is #{status}\")\n\t */\n\tprotected abstract String getDefaultMessage();\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/filter/AbstractNotificationFilter.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify.filter;\n\nimport java.util.concurrent.atomic.AtomicLong;\n\npublic abstract class AbstractNotificationFilter implements NotificationFilter {\n\n\tprivate static final AtomicLong instanceCounter = new AtomicLong(0L);\n\n\tprivate final String id;\n\n\tpublic AbstractNotificationFilter() {\n\t\tthis.id = \"F-\" + instanceCounter.getAndIncrement();\n\t}\n\n\t@Override\n\tpublic String getId() {\n\t\treturn id;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/filter/ApplicationNameNotificationFilter.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify.filter;\n\nimport java.time.Instant;\n\nimport org.jspecify.annotations.Nullable;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\n\npublic class ApplicationNameNotificationFilter extends ExpiringNotificationFilter {\n\n\tprivate final String applicationName;\n\n\tpublic ApplicationNameNotificationFilter(String applicationName, @Nullable Instant expiry) {\n\t\tsuper(expiry);\n\t\tthis.applicationName = applicationName;\n\t}\n\n\t@Override\n\tprotected boolean doFilter(InstanceEvent event, Instance instance) {\n\t\treturn applicationName.equals(instance.getRegistration().getName());\n\t}\n\n\tpublic String getApplicationName() {\n\t\treturn applicationName;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"NotificationFilter [applicationName=\" + applicationName + \", expiry=\" + getExpiry() + \"]\";\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/filter/ExpiringNotificationFilter.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify.filter;\n\nimport java.time.Instant;\n\nimport org.jspecify.annotations.Nullable;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\n\npublic abstract class ExpiringNotificationFilter extends AbstractNotificationFilter {\n\n\t@Nullable private final Instant expiry;\n\n\tpublic ExpiringNotificationFilter(@Nullable Instant expiry) {\n\t\tthis.expiry = expiry;\n\t}\n\n\tpublic boolean isExpired() {\n\t\treturn expiry != null && expiry.isBefore(Instant.now());\n\t}\n\n\t@Override\n\tpublic boolean filter(InstanceEvent event, Instance instance) {\n\t\treturn !isExpired() && doFilter(event, instance);\n\t}\n\n\tprotected abstract boolean doFilter(InstanceEvent event, Instance instance);\n\n\t@Nullable public Instant getExpiry() {\n\t\treturn expiry;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/filter/FilteringNotifier.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify.filter;\n\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\n\nimport org.jspecify.annotations.Nullable;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.util.Assert;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.notify.AbstractEventNotifier;\nimport de.codecentric.boot.admin.server.notify.Notifier;\n\n/**\n * Notifier that allows to filter certain events based on policies.\n *\n * @author Johannes Edmeier\n */\npublic class FilteringNotifier extends AbstractEventNotifier {\n\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(FilteringNotifier.class);\n\n\tprivate final ConcurrentMap<String, NotificationFilter> filters = new ConcurrentHashMap<>();\n\n\tprivate final Notifier delegate;\n\n\tprivate Instant lastCleanup = Instant.EPOCH;\n\n\tprivate Duration cleanupInterval = Duration.ofSeconds(10);\n\n\tpublic FilteringNotifier(Notifier delegate, InstanceRepository repository) {\n\t\tsuper(repository);\n\t\tAssert.notNull(delegate, \"'delegate' must not be null!\");\n\t\tthis.delegate = delegate;\n\t}\n\n\t@Override\n\tprotected boolean shouldNotify(InstanceEvent event, Instance instance) {\n\t\treturn !filter(event, instance);\n\t}\n\n\t@Override\n\tpublic Mono<Void> doNotify(InstanceEvent event, Instance instance) {\n\t\treturn delegate.notify(event);\n\t}\n\n\tprivate boolean filter(InstanceEvent event, Instance instance) {\n\t\tcleanUp();\n\t\tfor (Entry<String, NotificationFilter> entry : getNotificationFilters().entrySet()) {\n\t\t\tif (entry.getValue().filter(event, instance)) {\n\t\t\t\tLOGGER.debug(\"The event '{}' was suppressed by filter '{}'\", event, entry);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\tprivate void cleanUp() {\n\t\tInstant now = Instant.now();\n\t\tif (lastCleanup.plus(cleanupInterval).isAfter(now)) {\n\t\t\treturn;\n\t\t}\n\t\tlastCleanup = now;\n\t\tfor (Entry<String, NotificationFilter> entry : getNotificationFilters().entrySet()) {\n\t\t\tif (entry.getValue() instanceof ExpiringNotificationFilter filter && filter.isExpired()) {\n\t\t\t\tLOGGER.debug(\"Expired filter '{}' removed\", entry);\n\t\t\t\tfilters.remove(entry.getKey());\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic void addFilter(NotificationFilter filter) {\n\t\tLOGGER.debug(\"Added filter '{}'\", filter);\n\t\tfilters.put(filter.getId(), filter);\n\t}\n\n\t@Nullable public NotificationFilter removeFilter(String id) {\n\t\tLOGGER.debug(\"Removed filter with id '{}'\", id);\n\t\treturn filters.remove(id);\n\t}\n\n\tpublic Map<String, NotificationFilter> getNotificationFilters() {\n\t\treturn Collections.unmodifiableMap(new HashMap<>(filters));\n\t}\n\n\tpublic void setCleanupInterval(Duration cleanupInterval) {\n\t\tthis.cleanupInterval = cleanupInterval;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/filter/InstanceIdNotificationFilter.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify.filter;\n\nimport java.time.Instant;\n\nimport org.jspecify.annotations.Nullable;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\npublic class InstanceIdNotificationFilter extends ExpiringNotificationFilter {\n\n\tprivate final InstanceId instanceId;\n\n\tpublic InstanceIdNotificationFilter(InstanceId instanceId, @Nullable Instant expiry) {\n\t\tsuper(expiry);\n\t\tthis.instanceId = instanceId;\n\t}\n\n\t@Override\n\tprotected boolean doFilter(InstanceEvent event, Instance instance) {\n\t\treturn instanceId.equals(event.getInstance());\n\t}\n\n\tpublic InstanceId getInstanceId() {\n\t\treturn instanceId;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"NotificationFilter [instanceId=\" + instanceId + \", expiry=\" + getExpiry() + \"]\";\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/filter/NotificationFilter.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify.filter;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\n\npublic interface NotificationFilter {\n\n\tString getId();\n\n\tboolean filter(InstanceEvent event, Instance instance);\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/filter/package-info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Spring Boot Admin Server - filter package.\n\n@NullMarked\npackage de.codecentric.boot.admin.server.notify.filter;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/filter/web/NotificationFilterController.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify.filter.web;\n\nimport java.time.Instant;\nimport java.util.Collection;\n\nimport org.jspecify.annotations.Nullable;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.util.MimeTypeUtils;\nimport org.springframework.web.bind.annotation.DeleteMapping;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.ResponseBody;\n\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.notify.filter.ApplicationNameNotificationFilter;\nimport de.codecentric.boot.admin.server.notify.filter.FilteringNotifier;\nimport de.codecentric.boot.admin.server.notify.filter.InstanceIdNotificationFilter;\nimport de.codecentric.boot.admin.server.notify.filter.NotificationFilter;\nimport de.codecentric.boot.admin.server.web.AdminController;\n\nimport static org.springframework.util.StringUtils.hasText;\n\n/**\n * REST-Controller for managing notification filters\n *\n * @author Johannes Edmeier\n */\n@AdminController\n@ResponseBody\npublic class NotificationFilterController {\n\n\tprivate final FilteringNotifier filteringNotifier;\n\n\tpublic NotificationFilterController(FilteringNotifier filteringNotifier) {\n\t\tthis.filteringNotifier = filteringNotifier;\n\t}\n\n\t@GetMapping(path = \"/notifications/filters\", produces = MimeTypeUtils.APPLICATION_JSON_VALUE)\n\tpublic Collection<NotificationFilter> getFilters() {\n\t\treturn filteringNotifier.getNotificationFilters().values();\n\t}\n\n\t@PostMapping(path = \"/notifications/filters\", produces = MimeTypeUtils.APPLICATION_JSON_VALUE)\n\tpublic ResponseEntity<?> addFilter(@RequestParam(name = \"instanceId\", required = false) String instanceId,\n\t\t\t@RequestParam(name = \"applicationName\", required = false) String name,\n\t\t\t@RequestParam(name = \"ttl\", required = false) Long ttl) {\n\t\tif (hasText(instanceId) || hasText(name)) {\n\t\t\tNotificationFilter filter = createFilter(hasText(instanceId) ? InstanceId.of(instanceId) : null, name, ttl);\n\t\t\tfilteringNotifier.addFilter(filter);\n\t\t\treturn ResponseEntity.ok(filter);\n\t\t}\n\t\telse {\n\t\t\treturn ResponseEntity.badRequest().body(\"Either 'instanceId' or 'applicationName' must be set\");\n\t\t}\n\t}\n\n\t@DeleteMapping(path = \"/notifications/filters/{id}\")\n\tpublic ResponseEntity<Void> deleteFilter(@PathVariable(\"id\") String id) {\n\t\tNotificationFilter deleted = filteringNotifier.removeFilter(id);\n\t\tif (deleted != null) {\n\t\t\treturn ResponseEntity.ok().build();\n\t\t}\n\t\telse {\n\t\t\treturn ResponseEntity.notFound().build();\n\t\t}\n\t}\n\n\tprivate NotificationFilter createFilter(@Nullable InstanceId id, String name, @Nullable Long ttl) {\n\t\tInstant expiry = ((ttl != null) && (ttl >= 0)) ? Instant.now().plusMillis(ttl) : null;\n\t\tif (id != null) {\n\t\t\treturn new InstanceIdNotificationFilter(id, expiry);\n\t\t}\n\t\telse {\n\t\t\treturn new ApplicationNameNotificationFilter(name, expiry);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/filter/web/package-info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Spring Boot Admin Server - notifications filter package.\n\n@NullMarked\npackage de.codecentric.boot.admin.server.notify.filter.web;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/notify/package-info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Spring Boot Admin Server - notifier package.\n\n@NullMarked\npackage de.codecentric.boot.admin.server.notify;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/services/AbstractEventHandler.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services;\n\nimport java.util.logging.Level;\n\nimport org.jspecify.annotations.Nullable;\nimport org.reactivestreams.Publisher;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport reactor.core.Disposable;\nimport reactor.core.publisher.Flux;\nimport reactor.core.scheduler.Scheduler;\nimport reactor.core.scheduler.Schedulers;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\n\npublic abstract class AbstractEventHandler<T extends InstanceEvent> {\n\n\tprivate final Logger log = LoggerFactory.getLogger(this.getClass());\n\n\tprivate final Publisher<InstanceEvent> publisher;\n\n\tprivate final Class<T> eventType;\n\n\t@Nullable private Disposable subscription;\n\n\t@Nullable private Scheduler scheduler;\n\n\tprotected AbstractEventHandler(Publisher<InstanceEvent> publisher, Class<T> eventType) {\n\t\tthis.publisher = publisher;\n\t\tthis.eventType = eventType;\n\t}\n\n\tpublic void start() {\n\t\tthis.scheduler = this.createScheduler();\n\t\tthis.subscription = Flux.from(this.publisher)\n\t\t\t.subscribeOn(this.scheduler)\n\t\t\t.log(this.log.getName(), Level.FINEST)\n\t\t\t.doOnSubscribe((s) -> this.log.debug(\"Subscribed to {} events\", this.eventType))\n\t\t\t.ofType(this.eventType)\n\t\t\t.cast(this.eventType)\n\t\t\t.transform(this::handle)\n\t\t\t.onErrorContinue((throwable, o) -> this.log.warn(\"Unexpected error\", throwable))\n\t\t\t.subscribe();\n\t}\n\n\tprotected abstract Publisher<Void> handle(Flux<T> publisher);\n\n\tprotected Scheduler createScheduler() {\n\t\treturn Schedulers.newSingle(this.getClass().getSimpleName());\n\t}\n\n\tpublic void stop() {\n\t\tif (this.subscription != null) {\n\t\t\tthis.subscription.dispose();\n\t\t\tthis.subscription = null;\n\t\t}\n\t\tif (this.scheduler != null) {\n\t\t\tthis.scheduler.dispose();\n\t\t\tthis.scheduler = null;\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/services/ApiMediaTypeHandler.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services;\n\nimport java.util.stream.Stream;\n\nimport org.springframework.boot.actuate.endpoint.ApiVersion;\nimport org.springframework.http.MediaType;\n\npublic class ApiMediaTypeHandler {\n\n\tpublic boolean isApiMediaType(MediaType mediaType) {\n\t\treturn Stream.of(ApiVersion.values())\n\t\t\t.map(ApiVersion::getProducedMimeType)\n\t\t\t.anyMatch((mimeType) -> mimeType.isCompatibleWith(mediaType));\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/services/ApplicationRegistry.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services;\n\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\n\nimport org.jspecify.annotations.Nullable;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.util.function.Tuple2;\nimport reactor.util.function.Tuples;\n\nimport de.codecentric.boot.admin.server.domain.entities.Application;\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.values.BuildVersion;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\nimport de.codecentric.boot.admin.server.eventstore.InstanceEventPublisher;\n\nimport static de.codecentric.boot.admin.server.domain.values.StatusInfo.STATUS_UNKNOWN;\nimport static java.util.Comparator.naturalOrder;\nimport static java.util.stream.Collectors.toMap;\n\n/**\n * Registry for all applications that should be managed/administrated by the Spring Boot\n * Admin server. Backed by an InstanceRegistry for persistence and an\n * InstanceEventPublisher for events\n *\n * @author Dean de Bree\n */\npublic class ApplicationRegistry {\n\n\tprivate final InstanceRegistry instanceRegistry;\n\n\tprivate final InstanceEventPublisher instanceEventPublisher;\n\n\tpublic ApplicationRegistry(InstanceRegistry instanceRegistry, InstanceEventPublisher instanceEventPublisher) {\n\t\tthis.instanceRegistry = instanceRegistry;\n\t\tthis.instanceEventPublisher = instanceEventPublisher;\n\t}\n\n\t/**\n\t * Get a list of all registered applications.\n\t * @return flux of all the applications.\n\t */\n\tpublic Flux<Application> getApplications() {\n\t\treturn this.instanceRegistry.getInstances()\n\t\t\t.filter(Instance::isRegistered)\n\t\t\t.groupBy((instance) -> instance.getRegistration().getName())\n\t\t\t.flatMap((grouped) -> toApplication(grouped.key(), grouped), Integer.MAX_VALUE);\n\t}\n\n\t/**\n\t * Get a specific application instance.\n\t * @param name the name of the application to find.\n\t * @return a Mono with the application or an empty Mono if not found.\n\t */\n\tpublic Mono<Application> getApplication(String name) {\n\t\treturn this.toApplication(name, this.instanceRegistry.getInstances(name).filter(Instance::isRegistered))\n\t\t\t.filter((a) -> !a.getInstances().isEmpty());\n\t}\n\n\tpublic Flux<Application> getApplicationStream() {\n\t\treturn Flux.from(this.instanceEventPublisher)\n\t\t\t.flatMap((event) -> this.instanceRegistry.getInstance(event.getInstance()))\n\t\t\t.map(this::getApplicationForInstance)\n\t\t\t.flatMap((group) -> toApplication(group.getT1(), group.getT2()));\n\t}\n\n\tpublic Flux<InstanceId> deregister(String name) {\n\t\treturn this.instanceRegistry.getInstances(name)\n\t\t\t.flatMap((instance) -> this.instanceRegistry.deregister(instance.getId()));\n\t}\n\n\tprotected Tuple2<String, Flux<Instance>> getApplicationForInstance(Instance instance) {\n\t\tString name = instance.getRegistration().getName();\n\t\treturn Tuples.of(name, this.instanceRegistry.getInstances(name).filter(Instance::isRegistered));\n\t}\n\n\tprotected Mono<Application> toApplication(String name, Flux<Instance> instances) {\n\t\treturn instances.collectList().map((instanceList) -> {\n\t\t\tTuple2<String, Instant> status = getStatus(instanceList);\n\t\t\treturn Application.create(name)\n\t\t\t\t.instances(instanceList)\n\t\t\t\t.buildVersion(getBuildVersion(instanceList))\n\t\t\t\t.status(status.getT1())\n\t\t\t\t.statusTimestamp(status.getT2())\n\t\t\t\t.build();\n\t\t});\n\t}\n\n\t@Nullable protected BuildVersion getBuildVersion(List<Instance> instances) {\n\t\tList<BuildVersion> versions = instances.stream()\n\t\t\t.map(Instance::getBuildVersion)\n\t\t\t.filter(Objects::nonNull)\n\t\t\t.distinct()\n\t\t\t.sorted()\n\t\t\t.toList();\n\t\tif (versions.isEmpty()) {\n\t\t\treturn null;\n\t\t}\n\t\telse if (versions.size() == 1) {\n\t\t\treturn versions.get(0);\n\t\t}\n\t\telse {\n\t\t\treturn BuildVersion.valueOf(versions.get(0) + \" ... \" + versions.get(versions.size() - 1));\n\t\t}\n\t}\n\n\tprotected Tuple2<String, Instant> getStatus(List<Instance> instances) {\n\t\t// TODO: Correct is just a second readmodel for groups\n\t\tMap<String, Instant> statusWithTime = instances.stream()\n\t\t\t.collect(toMap((instance) -> instance.getStatusInfo().getStatus(), Instance::getStatusTimestamp,\n\t\t\t\t\tthis::getMax));\n\t\tif (statusWithTime.size() == 1) {\n\t\t\tMap.Entry<String, Instant> e = statusWithTime.entrySet().iterator().next();\n\t\t\treturn Tuples.of(e.getKey(), e.getValue());\n\t\t}\n\n\t\tif (statusWithTime.containsKey(StatusInfo.STATUS_UP)) {\n\t\t\tInstant oldestNonUp = statusWithTime.entrySet()\n\t\t\t\t.stream()\n\t\t\t\t.filter((e) -> !StatusInfo.STATUS_UP.equals(e.getKey()))\n\t\t\t\t.map(Map.Entry::getValue)\n\t\t\t\t.min(naturalOrder())\n\t\t\t\t.orElse(Instant.EPOCH);\n\t\t\tInstant latest = getMax(oldestNonUp, statusWithTime.getOrDefault(StatusInfo.STATUS_UP, Instant.EPOCH));\n\t\t\treturn Tuples.of(StatusInfo.STATUS_RESTRICTED, latest);\n\t\t}\n\n\t\treturn statusWithTime.entrySet()\n\t\t\t.stream()\n\t\t\t.min(Map.Entry.comparingByKey(StatusInfo.severity()))\n\t\t\t.map((e) -> Tuples.of(e.getKey(), e.getValue()))\n\t\t\t.orElse(Tuples.of(STATUS_UNKNOWN, Instant.EPOCH));\n\t}\n\n\tprotected Instant getMax(Instant t1, Instant t2) {\n\t\treturn (t1.compareTo(t2) >= 0) ? t1 : t2;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/services/CloudFoundryInstanceIdGenerator.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services;\n\nimport org.springframework.util.StringUtils;\n\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\n\n/**\n * Generates CF instance uniqueId \"applicationId:instanceId\" for CloudFoundry instance.\n * Uses a fallback InstanceIdGenerator when the metadata isn't present.\n *\n * @author Tetsushi Awano\n */\npublic class CloudFoundryInstanceIdGenerator implements InstanceIdGenerator {\n\n\tprivate final InstanceIdGenerator fallbackIdGenerator;\n\n\tpublic CloudFoundryInstanceIdGenerator(InstanceIdGenerator fallbackIdGenerator) {\n\t\tthis.fallbackIdGenerator = fallbackIdGenerator;\n\t}\n\n\t@Override\n\tpublic InstanceId generateId(Registration registration) {\n\t\tString applicationId = registration.getMetadata().get(\"applicationId\");\n\t\tString instanceId = registration.getMetadata().get(\"instanceId\");\n\n\t\tif (StringUtils.hasText(applicationId) && StringUtils.hasText(instanceId)) {\n\t\t\treturn InstanceId.of(String.format(\"%s:%s\", applicationId, instanceId));\n\t\t}\n\t\treturn fallbackIdGenerator.generateId(registration);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/services/EndpointDetectionTrigger.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services;\n\nimport org.reactivestreams.Publisher;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegistrationUpdatedEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\n\npublic class EndpointDetectionTrigger extends AbstractEventHandler<InstanceEvent> {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(EndpointDetectionTrigger.class);\n\n\tprivate final EndpointDetector endpointDetector;\n\n\tpublic EndpointDetectionTrigger(EndpointDetector endpointDetector, Publisher<InstanceEvent> publisher) {\n\t\tsuper(publisher, InstanceEvent.class);\n\t\tthis.endpointDetector = endpointDetector;\n\t}\n\n\t@Override\n\tprotected Publisher<Void> handle(Flux<InstanceEvent> publisher) {\n\t\treturn publisher\n\t\t\t.filter((event) -> event instanceof InstanceStatusChangedEvent\n\t\t\t\t\t|| event instanceof InstanceRegistrationUpdatedEvent)\n\t\t\t.flatMap(this::detectEndpoints);\n\t}\n\n\tprotected Mono<Void> detectEndpoints(InstanceEvent event) {\n\t\treturn this.endpointDetector.detectEndpoints(event.getInstance()).onErrorResume((e) -> {\n\t\t\tlog.warn(\"Unexpected error while detecting endpoints for {}\", event.getInstance(), e);\n\t\t\treturn Mono.empty();\n\t\t});\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/services/EndpointDetector.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.util.StringUtils;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.services.endpoints.EndpointDetectionStrategy;\n\n/**\n * @author Johannes Edmeier\n */\npublic class EndpointDetector {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(EndpointDetector.class);\n\n\tprivate final InstanceRepository repository;\n\n\tprivate final EndpointDetectionStrategy strategy;\n\n\tpublic EndpointDetector(InstanceRepository repository, EndpointDetectionStrategy strategy) {\n\t\tthis.repository = repository;\n\t\tthis.strategy = strategy;\n\t}\n\n\tpublic Mono<Void> detectEndpoints(InstanceId id) {\n\t\treturn repository.computeIfPresent(id, (key, instance) -> this.doDetectEndpoints(instance)).then();\n\t}\n\n\tprivate Mono<Instance> doDetectEndpoints(Instance instance) {\n\t\tif (!StringUtils.hasText(instance.getRegistration().getManagementUrl()) || instance.getStatusInfo().isOffline()\n\t\t\t\t|| instance.getStatusInfo().isUnknown()) {\n\t\t\treturn Mono.empty();\n\t\t}\n\t\tlog.debug(\"Detect endpoints for {}\", instance);\n\t\treturn strategy.detectEndpoints(instance).map(instance::withEndpoints);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/services/HashingInstanceUrlIdGenerator.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services;\n\nimport java.nio.charset.StandardCharsets;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\n\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\n\n/**\n * Generates an SHA-1 Hash based on the instance health url.\n */\npublic class HashingInstanceUrlIdGenerator implements InstanceIdGenerator {\n\n\tprivate static final char[] HEX_CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e',\n\t\t\t'f' };\n\n\t@Override\n\tpublic InstanceId generateId(Registration registration) {\n\t\ttry {\n\t\t\tMessageDigest digest = MessageDigest.getInstance(\"SHA-1\");\n\t\t\tbyte[] bytes = digest.digest(registration.getHealthUrl().getBytes(StandardCharsets.UTF_8));\n\t\t\treturn InstanceId.of(new String(encodeHex(bytes, 0, 12)));\n\t\t}\n\t\tcatch (NoSuchAlgorithmException ex) {\n\t\t\tthrow new IllegalStateException(ex);\n\t\t}\n\t}\n\n\tprivate char[] encodeHex(byte[] bytes, int offset, int length) {\n\t\tchar[] chars = new char[length];\n\t\tfor (int i = 0; i < length; i = i + 2) {\n\t\t\tbyte b = bytes[offset + (i / 2)];\n\t\t\tchars[i] = HEX_CHARS[(b >>> 0x4) & 0xf];\n\t\t\tchars[i + 1] = HEX_CHARS[b & 0xf];\n\t\t}\n\t\treturn chars;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/services/InfoUpdateTrigger.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services;\n\nimport java.time.Duration;\n\nimport org.reactivestreams.Publisher;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceEndpointsDetectedEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegistrationUpdatedEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\npublic class InfoUpdateTrigger extends AbstractEventHandler<InstanceEvent> {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(InfoUpdateTrigger.class);\n\n\tprivate final InfoUpdater infoUpdater;\n\n\tprivate final IntervalCheck intervalCheck;\n\n\tpublic InfoUpdateTrigger(InfoUpdater infoUpdater, Publisher<InstanceEvent> publisher, Duration updateInterval,\n\t\t\tDuration infoLifetime, Duration maxBackoff) {\n\t\tsuper(publisher, InstanceEvent.class);\n\t\tthis.infoUpdater = infoUpdater;\n\t\tthis.intervalCheck = new IntervalCheck(\"info\", this::updateInfo, updateInterval, infoLifetime, maxBackoff);\n\t}\n\n\t@Override\n\tprotected Publisher<Void> handle(Flux<InstanceEvent> publisher) {\n\t\treturn publisher\n\t\t\t.filter((event) -> event instanceof InstanceEndpointsDetectedEvent\n\t\t\t\t\t|| event instanceof InstanceStatusChangedEvent || event instanceof InstanceRegistrationUpdatedEvent)\n\t\t\t.flatMap((event) -> this.updateInfo(event.getInstance()));\n\t}\n\n\tprotected Mono<Void> updateInfo(InstanceId instanceId) {\n\t\treturn this.infoUpdater.updateInfo(instanceId).onErrorResume((e) -> {\n\t\t\tlog.warn(\"Unexpected error while updating info for {}\", instanceId, e);\n\t\t\treturn Mono.empty();\n\t\t}).doFinally((s) -> this.intervalCheck.markAsChecked(instanceId));\n\t}\n\n\t@Override\n\tpublic void start() {\n\t\tsuper.start();\n\t\tthis.intervalCheck.start();\n\t}\n\n\t@Override\n\tpublic void stop() {\n\t\tsuper.stop();\n\t\tthis.intervalCheck.stop();\n\t}\n\n\tpublic void setInterval(Duration updateInterval) {\n\t\tthis.intervalCheck.setInterval(updateInterval);\n\t}\n\n\tpublic void setLifetime(Duration infoLifetime) {\n\t\tthis.intervalCheck.setMinRetention(infoLifetime);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/services/InfoUpdater.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services;\n\nimport java.util.Map;\nimport java.util.logging.Level;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.core.ParameterizedTypeReference;\nimport org.springframework.http.MediaType;\nimport org.springframework.web.reactive.function.client.ClientResponse;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.values.Endpoint;\nimport de.codecentric.boot.admin.server.domain.values.Info;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\n\n/**\n * The StatusUpdater is responsible for updating the status of all or a single application\n * querying the healthUrl.\n *\n * @author Johannes Edmeier\n */\npublic class InfoUpdater {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(InfoUpdater.class);\n\n\tprivate static final ParameterizedTypeReference<Map<String, Object>> RESPONSE_TYPE = new ParameterizedTypeReference<>() {\n\t};\n\n\tprivate final InstanceRepository repository;\n\n\tprivate final InstanceWebClient instanceWebClient;\n\n\tprivate final ApiMediaTypeHandler apiMediaTypeHandler;\n\n\tpublic InfoUpdater(InstanceRepository repository, InstanceWebClient instanceWebClient,\n\t\t\tApiMediaTypeHandler apiMediaTypeHandler) {\n\t\tthis.repository = repository;\n\t\tthis.instanceWebClient = instanceWebClient;\n\t\tthis.apiMediaTypeHandler = apiMediaTypeHandler;\n\t}\n\n\tpublic Mono<Void> updateInfo(InstanceId id) {\n\t\treturn this.repository.computeIfPresent(id, (key, instance) -> this.doUpdateInfo(instance)).then();\n\t}\n\n\tprotected Mono<Instance> doUpdateInfo(Instance instance) {\n\t\tif (instance.getStatusInfo().isOffline() || instance.getStatusInfo().isUnknown()) {\n\t\t\treturn Mono.empty();\n\t\t}\n\t\tif (!instance.getEndpoints().isPresent(Endpoint.INFO)) {\n\t\t\treturn Mono.empty();\n\t\t}\n\n\t\tlog.debug(\"Update info for {}\", instance);\n\t\treturn this.instanceWebClient.instance(instance)\n\t\t\t.get()\n\t\t\t.uri(Endpoint.INFO)\n\t\t\t.exchangeToMono((response) -> convertInfo(instance, response))\n\t\t\t.log(log.getName(), Level.FINEST)\n\t\t\t.onErrorResume((ex) -> Mono.just(convertInfo(instance, ex)))\n\t\t\t.map(instance::withInfo);\n\t}\n\n\tprotected Mono<Info> convertInfo(Instance instance, ClientResponse response) {\n\t\tif (response.statusCode().is2xxSuccessful() && response.headers()\n\t\t\t.contentType()\n\t\t\t.filter((mt) -> mt.isCompatibleWith(MediaType.APPLICATION_JSON)\n\t\t\t\t\t|| this.apiMediaTypeHandler.isApiMediaType(mt))\n\t\t\t.isPresent()) {\n\t\t\treturn response.bodyToMono(RESPONSE_TYPE).map(Info::from).defaultIfEmpty(Info.empty());\n\t\t}\n\t\tlog.info(\"Couldn't retrieve info for {}: {}\", instance, response.statusCode());\n\t\treturn response.releaseBody().then(Mono.just(Info.empty()));\n\t}\n\n\tprotected Info convertInfo(Instance instance, Throwable ex) {\n\t\tlog.warn(\"Couldn't retrieve info for {}\", instance, ex);\n\t\treturn Info.empty();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/services/InstanceFilter.java",
    "content": "/*\n * Copyright 2014-2024 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\n\n/**\n * Interface that is applied to InstanceRegistry and returns Instances matching the\n * filter, only. Default implementation is to return all instances.\n *\n * @author dzahbarov\n * @see de.codecentric.boot.admin.server.config.AdminServerAutoConfiguration#instanceFilter()\n */\n@FunctionalInterface\npublic interface InstanceFilter {\n\n\tboolean filter(Instance instance);\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/services/InstanceIdGenerator.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services;\n\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\n\npublic interface InstanceIdGenerator {\n\n\t/**\n\t * Generate an id based on the given Instance\n\t * @param registration the registration the id is computed for.\n\t * @return the instance id\n\t */\n\tInstanceId generateId(Registration registration);\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/services/InstanceRegistry.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services;\n\nimport org.springframework.util.Assert;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\n\n/**\n * Registry for all application instances that should be managed/administrated by the\n * Spring Boot Admin server. Backed by an InstanceRepository for persistence, an\n * InstanceIdGenerator for id generation and InstanceFilter for instance filtering.\n */\npublic class InstanceRegistry {\n\n\tprivate final InstanceRepository repository;\n\n\tprivate final InstanceIdGenerator generator;\n\n\tprivate final InstanceFilter filter;\n\n\tpublic InstanceRegistry(InstanceRepository repository, InstanceIdGenerator generator, InstanceFilter filter) {\n\t\tthis.repository = repository;\n\t\tthis.generator = generator;\n\t\tthis.filter = filter;\n\t}\n\n\t/**\n\t * Register instance.\n\t * @param registration instance to be registered.\n\t * @return the id of the registered instance.\n\t */\n\tpublic Mono<InstanceId> register(Registration registration) {\n\t\tAssert.notNull(registration, \"'registration' must not be null\");\n\t\tInstanceId id = generator.generateId(registration);\n\t\tAssert.notNull(id, \"'id' must not be null\");\n\t\treturn repository.compute(id, (key, instance) -> {\n\t\t\tif (instance == null) {\n\t\t\t\tinstance = Instance.create(key);\n\t\t\t}\n\t\t\treturn Mono.just(instance.register(registration));\n\t\t}).map(Instance::getId);\n\t}\n\n\t/**\n\t * Get a list of all registered instances that satisfy the filter.\n\t * @return list of all instances satisfying the filter.\n\t */\n\tpublic Flux<Instance> getInstances() {\n\t\treturn repository.findAll().filter(filter::filter);\n\t}\n\n\t/**\n\t * Get a list of all registered application instances that satisfy the filter.\n\t * @param name the name to search for.\n\t * @return list of instances for the given application that satisfy the filter.\n\t */\n\tpublic Flux<Instance> getInstances(String name) {\n\t\treturn repository.findByName(name).filter(filter::filter);\n\t}\n\n\t/**\n\t * Get a specific instance\n\t * @param id the id\n\t * @return a Mono with the Instance.\n\t */\n\tpublic Mono<Instance> getInstance(InstanceId id) {\n\t\treturn repository.find(id).filter(filter::filter);\n\t}\n\n\t/**\n\t * Remove a specific instance from services\n\t * @param id the instances id to unregister\n\t * @return the id of the unregistered instance\n\t */\n\tpublic Mono<InstanceId> deregister(InstanceId id) {\n\t\treturn repository.computeIfPresent(id, (key, instance) -> Mono.just(instance.deregister()))\n\t\t\t.map(Instance::getId);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/services/IntervalCheck.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services;\n\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.logging.Level;\n\nimport lombok.Getter;\nimport lombok.NonNull;\nimport lombok.Setter;\nimport lombok.extern.slf4j.Slf4j;\nimport org.jspecify.annotations.Nullable;\nimport org.reactivestreams.Publisher;\nimport reactor.core.Disposable;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.core.scheduler.Scheduler;\nimport reactor.core.scheduler.Schedulers;\nimport reactor.util.retry.Retry;\n\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\n/**\n * Calls the checkFn for all instances in the given time, but not before the given\n * retention time has passed. The instances which will be checked have to be registered\n * via `markAsChecked`.\n *\n * @author Johannes Edmeier\n */\n@Slf4j\npublic class IntervalCheck {\n\n\tprivate final String name;\n\n\tprivate final Map<InstanceId, Instant> lastChecked = new ConcurrentHashMap<>();\n\n\tprivate final Function<InstanceId, Mono<Void>> checkFn;\n\n\t@Setter\n\tprivate Duration maxBackoff;\n\n\t@Getter\n\t@Setter\n\tprivate Duration interval;\n\n\t@Setter\n\tprivate Duration minRetention;\n\n\t@Nullable private Disposable subscription;\n\n\t@Nullable private Scheduler scheduler;\n\n\t@Setter\n\t@NonNull\n\tprivate Consumer<Throwable> retryConsumer;\n\n\tpublic IntervalCheck(String name, Function<InstanceId, Mono<Void>> checkFn, Duration interval,\n\t\t\tDuration minRetention, Duration maxBackoff) {\n\t\tthis.name = name;\n\t\tthis.retryConsumer = (Throwable throwable) -> log.warn(\"Unexpected error in {}-check\", this.name, throwable);\n\t\tthis.checkFn = checkFn;\n\t\tthis.interval = interval;\n\t\tthis.minRetention = minRetention;\n\t\tthis.maxBackoff = maxBackoff;\n\t}\n\n\tpublic void start() {\n\t\tthis.scheduler = Schedulers.newSingle(this.name + \"-check\");\n\t\tthis.subscription = Flux.interval(this.interval)\n\t\t\t// ensure the most recent interval tick is always processed, preventing\n\t\t\t// lost checks under overload.\n\t\t\t.onBackpressureLatest()\n\t\t\t.doOnSubscribe((s) -> log.debug(\"Scheduled {}-check every {}\", this.name, this.interval))\n\t\t\t.log(log.getName(), Level.FINEST) //\n\t\t\t.subscribeOn(this.scheduler) //\n\t\t\t// Allow concurrent check cycles if previous is slow\n\t\t\t.flatMap((i) -> this.checkAllInstances(), Math.max(1, Runtime.getRuntime().availableProcessors() / 2))\n\t\t\t.retryWhen(createRetrySpec())\n\t\t\t.subscribe(null, (Throwable error) -> log.error(\"Unexpected error in {}-check\", this.name, error));\n\t}\n\n\tprivate Retry createRetrySpec() {\n\t\treturn Retry.backoff(Long.MAX_VALUE, Duration.ofSeconds(1))\n\t\t\t.maxBackoff(maxBackoff)\n\t\t\t.doBeforeRetry((s) -> this.retryConsumer.accept(s.failure()));\n\t}\n\n\tpublic void markAsChecked(InstanceId instanceId) {\n\t\tthis.lastChecked.put(instanceId, Instant.now());\n\t}\n\n\tprotected Publisher<Void> checkAllInstances() {\n\t\tlog.debug(\"check {} for all instances\", this.name);\n\t\tInstant expiration = Instant.now().minus(this.minRetention);\n\t\treturn Flux.fromIterable(this.lastChecked.entrySet())\n\t\t\t.filter((entry) -> entry.getValue().isBefore(expiration))\n\t\t\t.map(Map.Entry::getKey)\n\t\t\t.flatMap(this.checkFn)\n\t\t\t.then();\n\t}\n\n\tpublic void stop() {\n\t\tif (this.subscription != null) {\n\t\t\tthis.subscription.dispose();\n\t\t\tthis.subscription = null;\n\t\t}\n\t\tif (this.scheduler != null) {\n\t\t\tthis.scheduler.dispose();\n\t\t\tthis.scheduler = null;\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/services/StatusUpdateTrigger.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services;\n\nimport java.time.Duration;\n\nimport org.reactivestreams.Publisher;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegistrationUpdatedEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\npublic class StatusUpdateTrigger extends AbstractEventHandler<InstanceEvent> {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(StatusUpdateTrigger.class);\n\n\tprivate final StatusUpdater statusUpdater;\n\n\tprivate final IntervalCheck intervalCheck;\n\n\tpublic StatusUpdateTrigger(StatusUpdater statusUpdater, Publisher<InstanceEvent> publisher, Duration updateInterval,\n\t\t\tDuration statusLifetime, Duration maxBackoff) {\n\t\tsuper(publisher, InstanceEvent.class);\n\t\tthis.statusUpdater = statusUpdater;\n\t\tthis.intervalCheck = new IntervalCheck(\"status\", this::updateStatus, updateInterval, statusLifetime,\n\t\t\t\tmaxBackoff);\n\t}\n\n\t@Override\n\tprotected Publisher<Void> handle(Flux<InstanceEvent> publisher) {\n\t\treturn publisher\n\t\t\t.filter((event) -> event instanceof InstanceRegisteredEvent\n\t\t\t\t\t|| event instanceof InstanceRegistrationUpdatedEvent)\n\t\t\t.flatMap((event) -> updateStatus(event.getInstance()));\n\t}\n\n\tprotected Mono<Void> updateStatus(InstanceId instanceId) {\n\t\treturn this.statusUpdater.timeout(this.intervalCheck.getInterval())\n\t\t\t.updateStatus(instanceId)\n\t\t\t.onErrorResume((e) -> {\n\t\t\t\tlog.warn(\"Unexpected error while updating status for {}\", instanceId, e);\n\t\t\t\treturn Mono.empty();\n\t\t\t})\n\t\t\t.doFinally((s) -> this.intervalCheck.markAsChecked(instanceId));\n\t}\n\n\t@Override\n\tpublic void start() {\n\t\tsuper.start();\n\t\tthis.intervalCheck.start();\n\t}\n\n\t@Override\n\tpublic void stop() {\n\t\tsuper.stop();\n\t\tthis.intervalCheck.stop();\n\t}\n\n\tpublic void setInterval(Duration updateInterval) {\n\t\tthis.intervalCheck.setInterval(updateInterval);\n\t}\n\n\tpublic void setLifetime(Duration statusLifetime) {\n\t\tthis.intervalCheck.setMinRetention(statusLifetime);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/services/StatusUpdater.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services;\n\nimport java.time.Duration;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.logging.Level;\n\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.core.ParameterizedTypeReference;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.HttpStatusCode;\nimport org.springframework.http.MediaType;\nimport org.springframework.web.reactive.function.client.ClientResponse;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.values.Endpoint;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\n\nimport static java.util.Collections.emptyMap;\n\n/**\n * The StatusUpdater is responsible for updating the status of all or a single application\n * querying the healthUrl.\n *\n * @author Johannes Edmeier\n */\n@Slf4j\n@RequiredArgsConstructor\npublic class StatusUpdater {\n\n\tprivate static final ParameterizedTypeReference<Map<String, Object>> RESPONSE_TYPE = new ParameterizedTypeReference<>() {\n\t};\n\n\tprivate final InstanceRepository repository;\n\n\tprivate final InstanceWebClient instanceWebClient;\n\n\tprivate final ApiMediaTypeHandler apiMediaTypeHandler;\n\n\tprivate Duration timeout = Duration.ofSeconds(10);\n\n\tpublic StatusUpdater timeout(Duration timeout) {\n\t\tthis.timeout = timeout;\n\t\treturn this;\n\t}\n\n\tpublic Mono<Void> updateStatus(InstanceId id) {\n\t\treturn this.repository.computeIfPresent(id, (key, instance) -> this.doUpdateStatus(instance)).then();\n\t}\n\n\tprotected Mono<Instance> doUpdateStatus(Instance instance) {\n\t\tif (!instance.isRegistered()) {\n\t\t\treturn Mono.empty();\n\t\t}\n\n\t\tlog.debug(\"Update status for {}\", instance);\n\t\treturn this.instanceWebClient.instance(instance)\n\t\t\t.get()\n\t\t\t.uri(Endpoint.HEALTH)\n\t\t\t.exchangeToMono(this::convertStatusInfo)\n\t\t\t.log(log.getName(), Level.FINEST)\n\t\t\t.timeout(getTimeoutWithMargin())\n\t\t\t.doOnError((ex) -> logError(instance, ex))\n\t\t\t.onErrorResume(this::handleError)\n\t\t\t.map(instance::withStatusInfo);\n\t}\n\n\t/*\n\t * return a timeout less than the given one to prevent backdrops in concurrent get\n\t * request. This prevents flakiness of health checks.\n\t */\n\tprivate Duration getTimeoutWithMargin() {\n\t\treturn this.timeout.minusSeconds(1).abs();\n\t}\n\n\tprotected Mono<StatusInfo> convertStatusInfo(ClientResponse response) {\n\t\tboolean hasCompatibleContentType = response.headers()\n\t\t\t.contentType()\n\t\t\t.filter((mt) -> mt.isCompatibleWith(MediaType.APPLICATION_JSON)\n\t\t\t\t\t|| this.apiMediaTypeHandler.isApiMediaType(mt))\n\t\t\t.isPresent();\n\n\t\tStatusInfo statusInfoFromStatus = this.getStatusInfoFromStatus(response.statusCode(), emptyMap());\n\t\tif (hasCompatibleContentType) {\n\t\t\treturn response.bodyToMono(RESPONSE_TYPE).map((body) -> {\n\t\t\t\tif (body.get(\"status\") instanceof String) {\n\t\t\t\t\treturn StatusInfo.from(body);\n\t\t\t\t}\n\t\t\t\treturn getStatusInfoFromStatus(response.statusCode(), body);\n\t\t\t}).defaultIfEmpty(statusInfoFromStatus);\n\t\t}\n\t\treturn response.releaseBody().then(Mono.just(statusInfoFromStatus));\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprotected StatusInfo getStatusInfoFromStatus(HttpStatusCode httpStatus, Map<String, ?> body) {\n\t\tif (httpStatus.is2xxSuccessful()) {\n\t\t\treturn StatusInfo.ofUp();\n\t\t}\n\t\tMap<String, Object> details = new LinkedHashMap<>();\n\t\tdetails.put(\"status\", httpStatus.value());\n\t\tdetails.put(\"error\", Objects.requireNonNull(HttpStatus.resolve(httpStatus.value())).getReasonPhrase());\n\t\tif (body.get(\"details\") instanceof Map) {\n\t\t\tdetails.putAll((Map<? extends String, ?>) body.get(\"details\"));\n\t\t}\n\t\telse {\n\t\t\tdetails.putAll(body);\n\t\t}\n\t\treturn StatusInfo.ofDown(details);\n\t}\n\n\tprotected Mono<StatusInfo> handleError(Throwable ex) {\n\t\tMap<String, Object> details = new HashMap<>();\n\t\tdetails.put(\"message\", ex.getMessage());\n\t\tdetails.put(\"exception\", ex.getClass().getName());\n\t\treturn Mono.just(StatusInfo.ofOffline(details));\n\t}\n\n\tprotected void logError(Instance instance, Throwable ex) {\n\t\tif (instance.getStatusInfo().isOffline()) {\n\t\t\tlog.debug(\"Couldn't retrieve status for {}\", instance, ex);\n\t\t}\n\t\telse {\n\t\t\tlog.info(\"Couldn't retrieve status for {}\", instance, ex);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/services/endpoints/ChainingStrategy.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services.endpoints;\n\nimport org.springframework.util.Assert;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.values.Endpoints;\n\npublic class ChainingStrategy implements EndpointDetectionStrategy {\n\n\tprivate final EndpointDetectionStrategy[] delegates;\n\n\tpublic ChainingStrategy(EndpointDetectionStrategy... delegates) {\n\t\tAssert.notNull(delegates, \"'delegates' must not be null.\");\n\t\tAssert.noNullElements(delegates, \"'delegates' must not contain null.\");\n\t\tthis.delegates = delegates;\n\t}\n\n\t@Override\n\tpublic Mono<Endpoints> detectEndpoints(Instance instance) {\n\t\tMono<Endpoints> result = Mono.empty();\n\t\tfor (EndpointDetectionStrategy delegate : delegates) {\n\t\t\tresult = result.switchIfEmpty(delegate.detectEndpoints(instance));\n\t\t}\n\t\treturn result.switchIfEmpty(Mono.just(Endpoints.empty()));\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/services/endpoints/EndpointDetectionStrategy.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services.endpoints;\n\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.values.Endpoints;\n\npublic interface EndpointDetectionStrategy {\n\n\tMono<Endpoints> detectEndpoints(Instance instance);\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/services/endpoints/ProbeEndpointsStrategy.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services.endpoints;\n\nimport java.net.URI;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Function;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.util.Assert;\nimport org.springframework.web.reactive.function.client.ClientResponse;\nimport org.springframework.web.util.UriComponentsBuilder;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.values.Endpoint;\nimport de.codecentric.boot.admin.server.domain.values.Endpoints;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\n\nimport static java.util.Comparator.comparingInt;\nimport static java.util.stream.Collectors.groupingBy;\n\npublic class ProbeEndpointsStrategy implements EndpointDetectionStrategy {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(ProbeEndpointsStrategy.class);\n\n\tprivate final List<EndpointDefinition> endpoints;\n\n\tprivate final InstanceWebClient instanceWebClient;\n\n\tpublic ProbeEndpointsStrategy(InstanceWebClient instanceWebClient, String[] endpoints) {\n\t\tAssert.notNull(endpoints, \"'endpoints' must not be null.\");\n\t\tAssert.noNullElements(endpoints, \"'endpoints' must not contain null.\");\n\t\tthis.endpoints = Arrays.stream(endpoints).map(EndpointDefinition::create).toList();\n\t\tthis.instanceWebClient = instanceWebClient;\n\t}\n\n\t@Override\n\tpublic Mono<Endpoints> detectEndpoints(Instance instance) {\n\t\tif (instance.getRegistration().getManagementUrl() == null) {\n\t\t\tlog.debug(\"Endpoint probe for instance {} omitted. No management-url registered.\", instance.getId());\n\t\t\treturn Mono.empty();\n\t\t}\n\n\t\treturn Flux.fromIterable(this.endpoints)\n\t\t\t.flatMap((endpoint) -> detectEndpoint(instance, endpoint))\n\t\t\t.collectList()\n\t\t\t.flatMap(this::convert);\n\t}\n\n\tprotected Mono<DetectedEndpoint> detectEndpoint(Instance instance, EndpointDefinition endpoint) {\n\t\tAssert.notNull(instance.getRegistration().getManagementUrl(), \"managementUrl must not be null\");\n\t\tURI uri = UriComponentsBuilder.fromUriString(instance.getRegistration().getManagementUrl())\n\t\t\t.path(\"/\")\n\t\t\t.path(endpoint.path())\n\t\t\t.build()\n\t\t\t.toUri();\n\t\treturn this.instanceWebClient.instance(instance)\n\t\t\t.options()\n\t\t\t.uri(uri)\n\t\t\t.exchangeToMono(this.convert(instance.getId(), endpoint, uri))\n\t\t\t.onErrorResume((e) -> {\n\t\t\t\tlog.warn(\"Endpoint probe for instance {} on endpoint '{}' failed: {}\", instance.getId(), uri,\n\t\t\t\t\t\te.getMessage());\n\t\t\t\tlog.debug(\"Endpoint probe for instance {} on endpoint '{}' failed.\", instance.getId(), uri, e);\n\t\t\t\treturn Mono.empty();\n\t\t\t});\n\t}\n\n\tprotected Function<ClientResponse, Mono<DetectedEndpoint>> convert(InstanceId instanceId,\n\t\t\tEndpointDefinition endpointDefinition, URI uri) {\n\t\treturn (response) -> {\n\t\t\tMono<DetectedEndpoint> endpoint = Mono.empty();\n\t\t\tif (response.statusCode().is2xxSuccessful()) {\n\t\t\t\tendpoint = Mono.just(DetectedEndpoint.of(endpointDefinition, uri.toString()));\n\t\t\t\tlog.debug(\"Endpoint probe for instance {} on endpoint '{}' successful.\", instanceId, uri);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tlog.debug(\"Endpoint probe for instance {} on endpoint '{}' failed with status {}.\", instanceId, uri,\n\t\t\t\t\t\tresponse.statusCode().value());\n\t\t\t}\n\t\t\treturn response.releaseBody().then(endpoint);\n\t\t};\n\t}\n\n\tprotected Mono<Endpoints> convert(List<DetectedEndpoint> endpoints) {\n\t\tif (endpoints.isEmpty()) {\n\t\t\treturn Mono.empty();\n\t\t}\n\n\t\tMap<String, List<DetectedEndpoint>> endpointsById = endpoints.stream()\n\t\t\t.collect(groupingBy((e) -> e.definition().id()));\n\t\tList<Endpoint> result = endpointsById.values().stream().map((endpointList) -> {\n\t\t\tendpointList.sort(comparingInt((e) -> this.endpoints.indexOf(e.definition())));\n\t\t\tif (endpointList.size() > 1) {\n\t\t\t\tlog.warn(\"Duplicate endpoints for id '{}' detected. Omitting: {}\",\n\t\t\t\t\t\tendpointList.get(0).definition().id(), endpointList.subList(1, endpointList.size()));\n\t\t\t}\n\t\t\treturn endpointList.get(0).endpoint();\n\t\t}).toList();\n\t\treturn Mono.just(Endpoints.of(result));\n\t}\n\n\tprotected record DetectedEndpoint(EndpointDefinition definition, Endpoint endpoint) {\n\n\t\tprivate static DetectedEndpoint of(EndpointDefinition endpointDefinition, String url) {\n\t\t\treturn new DetectedEndpoint(endpointDefinition, Endpoint.of(endpointDefinition.id(), url));\n\t\t}\n\n\t}\n\n\tprotected record EndpointDefinition(String id, String path) {\n\n\t\tprivate static EndpointDefinition create(String idWithPath) {\n\t\t\tint idxDelimiter = idWithPath.indexOf(':');\n\t\t\tif (idxDelimiter < 0) {\n\t\t\t\treturn new EndpointDefinition(idWithPath, idWithPath);\n\t\t\t}\n\t\t\telse {\n\t\t\t\treturn new EndpointDefinition(idWithPath.substring(0, idxDelimiter),\n\t\t\t\t\t\tidWithPath.substring(idxDelimiter + 1));\n\t\t\t}\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/services/endpoints/QueryIndexEndpointStrategy.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services.endpoints;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.function.Function;\n\nimport com.fasterxml.jackson.annotation.JsonCreator;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport lombok.Data;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.web.reactive.function.client.ClientResponse;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.values.Endpoint;\nimport de.codecentric.boot.admin.server.domain.values.Endpoints;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.services.ApiMediaTypeHandler;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\n\npublic class QueryIndexEndpointStrategy implements EndpointDetectionStrategy {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(QueryIndexEndpointStrategy.class);\n\n\tprivate final InstanceWebClient instanceWebClient;\n\n\tprivate final ApiMediaTypeHandler apiMediaTypeHandler;\n\n\tpublic QueryIndexEndpointStrategy(InstanceWebClient instanceWebClient, ApiMediaTypeHandler apiMediaTypeHandler) {\n\t\tthis.instanceWebClient = instanceWebClient;\n\t\tthis.apiMediaTypeHandler = apiMediaTypeHandler;\n\t}\n\n\t@Override\n\tpublic Mono<Endpoints> detectEndpoints(Instance instance) {\n\t\tRegistration registration = instance.getRegistration();\n\t\tString managementUrl = registration.getManagementUrl();\n\t\tif (managementUrl == null || Objects.equals(registration.getServiceUrl(), managementUrl)) {\n\t\t\tlog.debug(\"Querying actuator-index for instance {} omitted.\", instance.getId());\n\t\t\treturn Mono.empty();\n\t\t}\n\n\t\treturn this.instanceWebClient.instance(instance)\n\t\t\t.get()\n\t\t\t.uri(managementUrl)\n\t\t\t.exchangeToMono(this.convert(instance, managementUrl))\n\t\t\t.onErrorResume((e) -> {\n\t\t\t\tlog.warn(\"Querying actuator-index for instance {} on '{}' failed: {}\", instance.getId(), managementUrl,\n\t\t\t\t\t\te.getMessage());\n\t\t\t\tlog.debug(\"Querying actuator-index for instance {} on '{}' failed.\", instance.getId(), managementUrl,\n\t\t\t\t\t\te);\n\t\t\t\treturn Mono.empty();\n\t\t\t});\n\t}\n\n\tprotected Function<ClientResponse, Mono<Endpoints>> convert(Instance instance, String managementUrl) {\n\t\treturn (response) -> {\n\t\t\tif (!response.statusCode().is2xxSuccessful()) {\n\t\t\t\tlog.debug(\"Querying actuator-index for instance {} on '{}' failed with status {}.\", instance.getId(),\n\t\t\t\t\t\tmanagementUrl, response.statusCode().value());\n\t\t\t\treturn response.releaseBody().then(Mono.empty());\n\t\t\t}\n\n\t\t\tif (response.headers().contentType().filter(this.apiMediaTypeHandler::isApiMediaType).isEmpty()) {\n\t\t\t\tlog.debug(\"Querying actuator-index for instance {} on '{}' failed with incompatible Content-Type '{}'.\",\n\t\t\t\t\t\tinstance.getId(), managementUrl,\n\t\t\t\t\t\tresponse.headers().contentType().map(Objects::toString).orElse(\"(missing)\"));\n\t\t\t\treturn response.releaseBody().then(Mono.empty());\n\t\t\t}\n\n\t\t\tlog.debug(\"Querying actuator-index for instance {} on '{}' successful.\", instance.getId(), managementUrl);\n\t\t\treturn response.bodyToMono(Response.class)\n\t\t\t\t.flatMap(this::convertResponse)\n\t\t\t\t.map(this.alignWithManagementUrl(instance.getId(), managementUrl));\n\t\t};\n\t}\n\n\tprotected Function<Endpoints, Endpoints> alignWithManagementUrl(InstanceId instanceId, String managementUrl) {\n\t\treturn (endpoints) -> {\n\t\t\tif (!managementUrl.startsWith(\"https:\")) {\n\t\t\t\treturn endpoints;\n\t\t\t}\n\t\t\tif (endpoints.stream().noneMatch((e) -> e.getUrl().startsWith(\"http:\"))) {\n\t\t\t\treturn endpoints;\n\t\t\t}\n\t\t\tlog.warn(\n\t\t\t\t\t\"Endpoints for instance {} queried from {} are falsely using http. Rewritten to https. Consider configuring this instance to use 'server.forward-headers-strategy=native'.\",\n\t\t\t\t\tinstanceId, managementUrl);\n\n\t\t\treturn Endpoints.of(endpoints.stream()\n\t\t\t\t.map((e) -> Endpoint.of(e.getId(), e.getUrl().replaceFirst(\"http:\", \"https:\")))\n\t\t\t\t.toList());\n\t\t};\n\t}\n\n\tprotected Mono<Endpoints> convertResponse(Response response) {\n\t\tList<Endpoint> endpoints = response.getLinks()\n\t\t\t.entrySet()\n\t\t\t.stream()\n\t\t\t.filter((e) -> !e.getKey().equals(\"self\") && !e.getValue().isTemplated())\n\t\t\t.map((e) -> Endpoint.of(e.getKey(), e.getValue().getHref()))\n\t\t\t.toList();\n\t\treturn endpoints.isEmpty() ? Mono.empty() : Mono.just(Endpoints.of(endpoints));\n\t}\n\n\t@Data\n\tprotected static class Response {\n\n\t\t@JsonProperty(\"_links\")\n\t\tprivate Map<String, EndpointRef> links = new HashMap<>();\n\n\t\t@Data\n\t\tprotected static class EndpointRef {\n\n\t\t\tprivate final String href;\n\n\t\t\tprivate final boolean templated;\n\n\t\t\t@JsonCreator\n\t\t\tEndpointRef(@JsonProperty(\"href\") String href, @JsonProperty(\"templated\") boolean templated) {\n\t\t\t\tthis.href = href;\n\t\t\t\tthis.templated = templated;\n\t\t\t}\n\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/services/endpoints/package-info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Spring Boot Admin Server - endpoints package.\n\n@NullMarked\npackage de.codecentric.boot.admin.server.services.endpoints;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/services/package-info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Spring Boot Admin Server - services package.\n\n@NullMarked\npackage de.codecentric.boot.admin.server.services;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/AdminServerModule.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport tools.jackson.databind.module.SimpleModule;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceDeregisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEndpointsDetectedEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceInfoChangedEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegistrationUpdatedEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.BuildVersion;\nimport de.codecentric.boot.admin.server.domain.values.Endpoint;\nimport de.codecentric.boot.admin.server.domain.values.Endpoints;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\nimport de.codecentric.boot.admin.server.domain.values.Tags;\n\n/**\n * Jackson module for Spring Boot Admin Server. <br>\n * To use this module, add it to your JsonMapper builder: <pre>\n *     JsonMapper.Builder builder = JsonMapper.builder();\n *     builder.addModule(new AdminServerModule(...));\n *     return builder.build();\n * </pre>\n *\n * @author Stefan Rempfer\n */\npublic class AdminServerModule extends SimpleModule {\n\n\t/**\n\t * Construct the module with a pattern for registration metadata keys. The values of\n\t * the matched metadata keys will be sanitized before serializing to json.\n\t * @param metadataKeyPatterns pattern for metadata keys which should be sanitized\n\t */\n\tpublic AdminServerModule(String[] metadataKeyPatterns) {\n\t\tsuper(AdminServerModule.class.getName());\n\n\t\taddDeserializer(Registration.class, new RegistrationDeserializer());\n\t\tsetSerializerModifier(new RegistrationBeanSerializerModifier(new SanitizingMapSerializer(metadataKeyPatterns)));\n\n\t\tsetMixInAnnotation(InstanceDeregisteredEvent.class, InstanceDeregisteredEventMixin.class);\n\t\tsetMixInAnnotation(InstanceEndpointsDetectedEvent.class, InstanceEndpointsDetectedEventMixin.class);\n\t\tsetMixInAnnotation(InstanceEvent.class, InstanceEventMixin.class);\n\t\tsetMixInAnnotation(InstanceInfoChangedEvent.class, InstanceInfoChangedEventMixin.class);\n\t\tsetMixInAnnotation(InstanceRegisteredEvent.class, InstanceRegisteredEventMixin.class);\n\t\tsetMixInAnnotation(InstanceRegistrationUpdatedEvent.class, InstanceRegistrationUpdatedEventMixin.class);\n\t\tsetMixInAnnotation(InstanceStatusChangedEvent.class, InstanceStatusChangedEventMixin.class);\n\n\t\tsetMixInAnnotation(BuildVersion.class, BuildVersionMixin.class);\n\t\tsetMixInAnnotation(Endpoint.class, EndpointMixin.class);\n\t\tsetMixInAnnotation(Endpoints.class, EndpointsMixin.class);\n\t\tsetMixInAnnotation(InstanceId.class, InstanceIdMixin.class);\n\t\tsetMixInAnnotation(StatusInfo.class, StatusInfoMixin.class);\n\t\tsetMixInAnnotation(Tags.class, TagsMixin.class);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/BuildVersionMixin.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport com.fasterxml.jackson.annotation.JsonCreator;\nimport com.fasterxml.jackson.annotation.JsonValue;\n\nimport de.codecentric.boot.admin.server.domain.values.BuildVersion;\n\n/**\n * Jackson Mixin class helps in serialize/deserialize {@link BuildVersion}.\n *\n * @author Stefan Rempfer\n */\npublic abstract class BuildVersionMixin {\n\n\t@JsonCreator\n\tpublic static BuildVersion valueOf(String s) {\n\t\treturn BuildVersion.valueOf(s);\n\t}\n\n\t@JsonValue\n\tpublic abstract String getValue();\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/EndpointMixin.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport com.fasterxml.jackson.annotation.JsonCreator;\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport de.codecentric.boot.admin.server.domain.values.Endpoint;\n\n/**\n * Jackson Mixin class helps in serialize/deserialize {@link Endpoint}.\n *\n * @author Stefan Rempfer\n */\npublic abstract class EndpointMixin {\n\n\t@JsonCreator\n\tpublic static Endpoint of(@JsonProperty(\"id\") String id, @JsonProperty(\"url\") String url) {\n\t\treturn Endpoint.of(id, url);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/EndpointsMixin.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport java.util.Collection;\n\nimport com.fasterxml.jackson.annotation.JsonCreator;\nimport org.jspecify.annotations.Nullable;\n\nimport de.codecentric.boot.admin.server.domain.values.Endpoint;\nimport de.codecentric.boot.admin.server.domain.values.Endpoints;\n\n/**\n * Jackson Mixin class helps in serialize/deserialize {@link Endpoints}.\n *\n * @author Stefan Rempfer\n */\npublic abstract class EndpointsMixin {\n\n\t@JsonCreator\n\tpublic static Endpoints of(@Nullable Collection<Endpoint> endpoints) {\n\t\treturn Endpoints.of(endpoints);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/InfoMixin.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\nimport com.fasterxml.jackson.annotation.JsonAnyGetter;\nimport com.fasterxml.jackson.annotation.JsonAnySetter;\nimport tools.jackson.databind.annotation.JsonDeserialize;\n\nimport de.codecentric.boot.admin.server.domain.values.Info;\n\n/**\n * Jackson Mixin class helps in serialize/deserialize {@link Info}.\n *\n * @author Stefan Rempfer\n */\n@JsonDeserialize(builder = InfoMixin.Builder.class)\npublic abstract class InfoMixin {\n\n\t@JsonAnyGetter\n\tpublic abstract Map<String, Object> getValues();\n\n\tpublic static class Builder {\n\n\t\tprivate final Map<String, Object> values = new LinkedHashMap<>();\n\n\t\t@JsonAnySetter\n\t\tpublic Builder set(String key, Object value) {\n\t\t\tthis.values.put(key, value);\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic Info build() {\n\t\t\treturn Info.from(this.values);\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/InstanceDeregisteredEventMixin.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport java.time.Instant;\n\nimport com.fasterxml.jackson.annotation.JsonCreator;\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceDeregisteredEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\n/**\n * Jackson Mixin class helps in serialize/deserialize {@link InstanceDeregisteredEvent}.\n *\n * @author Stefan Rempfer\n */\npublic abstract class InstanceDeregisteredEventMixin {\n\n\t@JsonCreator\n\tpublic InstanceDeregisteredEventMixin(@JsonProperty(\"instance\") InstanceId instance,\n\t\t\t@JsonProperty(\"version\") long version, @JsonProperty(\"timestamp\") Instant timestamp) {\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/InstanceEndpointsDetectedEventMixin.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport java.time.Instant;\n\nimport com.fasterxml.jackson.annotation.JsonCreator;\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceEndpointsDetectedEvent;\nimport de.codecentric.boot.admin.server.domain.values.Endpoints;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\n/**\n * Jackson Mixin class helps in serialize/deserialize\n * {@link InstanceEndpointsDetectedEvent}.\n *\n * @author Stefan Rempfer\n */\npublic abstract class InstanceEndpointsDetectedEventMixin {\n\n\t@JsonCreator\n\tpublic InstanceEndpointsDetectedEventMixin(@JsonProperty(\"instance\") InstanceId instance,\n\t\t\t@JsonProperty(\"version\") long version, @JsonProperty(\"timestamp\") Instant timestamp,\n\t\t\t@JsonProperty(\"endpoints\") Endpoints endpoints) {\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/InstanceEventMixin.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport com.fasterxml.jackson.annotation.JsonSubTypes;\nimport com.fasterxml.jackson.annotation.JsonTypeInfo;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceDeregisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEndpointsDetectedEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceInfoChangedEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegistrationUpdatedEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\n\n/**\n * Jackson Mixin class helps in serialize/deserialize {@link InstanceEvent}s.\n *\n * @author Stefan Rempfer\n */\n@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = \"type\")\n@JsonSubTypes({\n\t\t@JsonSubTypes.Type(value = InstanceEndpointsDetectedEvent.class, name = InstanceEndpointsDetectedEvent.TYPE),\n\t\t@JsonSubTypes.Type(value = InstanceRegistrationUpdatedEvent.class,\n\t\t\t\tname = InstanceRegistrationUpdatedEvent.TYPE),\n\t\t@JsonSubTypes.Type(value = InstanceInfoChangedEvent.class, name = InstanceInfoChangedEvent.TYPE),\n\t\t@JsonSubTypes.Type(value = InstanceDeregisteredEvent.class, name = InstanceDeregisteredEvent.TYPE),\n\t\t@JsonSubTypes.Type(value = InstanceRegisteredEvent.class, name = InstanceRegisteredEvent.TYPE),\n\t\t@JsonSubTypes.Type(value = InstanceStatusChangedEvent.class, name = InstanceStatusChangedEvent.TYPE) })\npublic abstract class InstanceEventMixin {\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/InstanceIdMixin.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport com.fasterxml.jackson.annotation.JsonCreator;\nimport com.fasterxml.jackson.annotation.JsonValue;\n\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\n/**\n * Jackson Mixin class helps in serialize/deserialize {@link InstanceId}.\n *\n * @author Stefan Rempfer\n */\npublic abstract class InstanceIdMixin {\n\n\t@JsonCreator\n\tpublic static InstanceId of(String value) {\n\t\treturn InstanceId.of(value);\n\t}\n\n\t@JsonValue\n\tpublic abstract String getValue();\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/InstanceInfoChangedEventMixin.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport java.time.Instant;\n\nimport com.fasterxml.jackson.annotation.JsonCreator;\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceInfoChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.Info;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\n/**\n * Jackson Mixin class helps in serialize/deserialize {@link InstanceInfoChangedEvent}.\n *\n * @author Stefan Rempfer\n */\npublic abstract class InstanceInfoChangedEventMixin {\n\n\t@JsonCreator\n\tpublic InstanceInfoChangedEventMixin(@JsonProperty(\"instance\") InstanceId instance,\n\t\t\t@JsonProperty(\"version\") long version, @JsonProperty(\"timestamp\") Instant timestamp,\n\t\t\t@JsonProperty(\"info\") Info info) {\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/InstanceRegisteredEventMixin.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport java.time.Instant;\n\nimport com.fasterxml.jackson.annotation.JsonCreator;\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegisteredEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\n\n/**\n * Jackson Mixin class helps in serialize/deserialize {@link InstanceRegisteredEvent}.\n *\n * @author Stefan Rempfer\n */\npublic abstract class InstanceRegisteredEventMixin {\n\n\t@JsonCreator\n\tpublic InstanceRegisteredEventMixin(@JsonProperty(\"instance\") InstanceId instance,\n\t\t\t@JsonProperty(\"version\") long version, @JsonProperty(\"timestamp\") Instant timestamp,\n\t\t\t@JsonProperty(\"registration\") Registration registration) {\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/InstanceRegistrationUpdatedEventMixin.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport java.time.Instant;\n\nimport com.fasterxml.jackson.annotation.JsonCreator;\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegistrationUpdatedEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\n\n/**\n * Jackson Mixin class helps in serialize/deserialize\n * {@link InstanceRegistrationUpdatedEvent}.\n *\n * @author Stefan Rempfer\n */\npublic abstract class InstanceRegistrationUpdatedEventMixin {\n\n\t@JsonCreator\n\tpublic InstanceRegistrationUpdatedEventMixin(@JsonProperty(\"instance\") InstanceId instance,\n\t\t\t@JsonProperty(\"version\") long version, @JsonProperty(\"timestamp\") Instant timestamp,\n\t\t\t@JsonProperty(\"registration\") Registration registration) {\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/InstanceStatusChangedEventMixin.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport java.time.Instant;\n\nimport com.fasterxml.jackson.annotation.JsonCreator;\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\n/**\n * Jackson Mixin class helps in serialize/deserialize {@link InstanceStatusChangedEvent}.\n *\n * @author Stefan Rempfer\n */\npublic abstract class InstanceStatusChangedEventMixin {\n\n\t@JsonCreator\n\tpublic InstanceStatusChangedEventMixin(@JsonProperty(\"instance\") InstanceId instance,\n\t\t\t@JsonProperty(\"version\") long version, @JsonProperty(\"timestamp\") Instant timestamp,\n\t\t\t@JsonProperty(\"statusInfo\") StatusInfo statusInfo) {\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/RegistrationBeanSerializerModifier.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport java.util.List;\n\nimport tools.jackson.databind.BeanDescription;\nimport tools.jackson.databind.SerializationConfig;\nimport tools.jackson.databind.ValueSerializer;\nimport tools.jackson.databind.ser.BeanPropertyWriter;\nimport tools.jackson.databind.ser.ValueSerializerModifier;\n\nimport de.codecentric.boot.admin.server.domain.values.Registration;\n\npublic class RegistrationBeanSerializerModifier extends ValueSerializerModifier {\n\n\tprivate final ValueSerializer<Object> metadataSerializer;\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic RegistrationBeanSerializerModifier(SanitizingMapSerializer metadataSerializer) {\n\t\tthis.metadataSerializer = (ValueSerializer<Object>) (ValueSerializer) metadataSerializer;\n\t}\n\n\t@Override\n\tpublic List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription.Supplier beanDesc,\n\t\t\tList<BeanPropertyWriter> beanProperties) {\n\t\tif (!Registration.class.isAssignableFrom(beanDesc.getBeanClass())) {\n\t\t\treturn beanProperties;\n\t\t}\n\n\t\tbeanProperties.stream()\n\t\t\t.filter((beanProperty) -> \"metadata\".equals(beanProperty.getName()))\n\t\t\t.forEach((beanProperty) -> beanProperty.assignSerializer(metadataSerializer));\n\t\treturn beanProperties;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/RegistrationDeserializer.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport tools.jackson.core.JsonParser;\nimport tools.jackson.databind.DeserializationContext;\nimport tools.jackson.databind.JsonNode;\nimport tools.jackson.databind.deser.std.StdDeserializer;\n\nimport de.codecentric.boot.admin.server.domain.values.Registration;\n\npublic class RegistrationDeserializer extends StdDeserializer<Registration> {\n\n\tpublic RegistrationDeserializer() {\n\t\tsuper(Registration.class);\n\t}\n\n\t@Override\n\tpublic Registration deserialize(JsonParser p, DeserializationContext ctxt) {\n\t\tJsonNode node = p.readValueAsTree();\n\t\tRegistration.Builder builder = Registration.builder();\n\n\t\tbuilder.name(firstNonNullAsString(node, \"name\"));\n\n\t\tif (node.hasNonNull(\"url\")) {\n\t\t\tString url = firstNonNullAsString(node, \"url\");\n\t\t\tbuilder.healthUrl(url.replaceFirst(\"/+$\", \"\") + \"/health\").managementUrl(url);\n\t\t}\n\t\telse {\n\t\t\tbuilder.healthUrl(firstNonNullAsString(node, \"healthUrl\", \"health_url\"));\n\t\t\tbuilder.managementUrl(firstNonNullAsString(node, \"managementUrl\", \"management_url\"));\n\t\t\tbuilder.serviceUrl(firstNonNullAsString(node, \"serviceUrl\", \"service_url\"));\n\t\t}\n\n\t\tif (node.has(\"metadata\")) {\n\t\t\tnode.get(\"metadata\")\n\t\t\t\t.properties()\n\t\t\t\t.forEach((entry) -> builder.metadata(entry.getKey(), entry.getValue().asString()));\n\t\t}\n\n\t\tbuilder.source(firstNonNullAsString(node, \"source\"));\n\n\t\treturn builder.build();\n\t}\n\n\tprivate String firstNonNullAsString(JsonNode node, String... fieldNames) {\n\t\tfor (String fieldName : fieldNames) {\n\t\t\tif (node.hasNonNull(fieldName)) {\n\t\t\t\treturn node.get(fieldName).asString();\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/SanitizingMapSerializer.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.util.regex.Pattern;\n\nimport org.jspecify.annotations.Nullable;\nimport tools.jackson.core.JsonGenerator;\nimport tools.jackson.databind.SerializationContext;\nimport tools.jackson.databind.ser.std.StdSerializer;\n\npublic class SanitizingMapSerializer extends StdSerializer<Map<String, String>> {\n\n\tprivate final Pattern[] keysToSanitize;\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic SanitizingMapSerializer(String[] patterns) {\n\t\tsuper((Class<Map<String, String>>) (Class<?>) Map.class);\n\t\tthis.keysToSanitize = createPatterns(patterns);\n\t}\n\n\tprivate static Pattern[] createPatterns(String... keys) {\n\t\treturn Arrays.stream(keys).map((key) -> Pattern.compile(key, Pattern.CASE_INSENSITIVE)).toArray(Pattern[]::new);\n\t}\n\n\t@Override\n\tpublic void serialize(Map<String, String> value, JsonGenerator gen, SerializationContext provider) {\n\t\tgen.writeStartObject();\n\t\tfor (Map.Entry<String, String> entry : value.entrySet()) {\n\t\t\tgen.writeStringProperty(entry.getKey(), sanitize(entry.getKey(), entry.getValue()));\n\t\t}\n\t\tgen.writeEndObject();\n\t}\n\n\t@Nullable private String sanitize(String key, @Nullable String value) {\n\t\tif (value == null) {\n\t\t\treturn null;\n\t\t}\n\n\t\tboolean matchesAnyPattern = Arrays.stream(this.keysToSanitize)\n\t\t\t.anyMatch((pattern) -> pattern.matcher(key).matches());\n\t\treturn matchesAnyPattern ? \"******\" : value;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/StatusInfoMixin.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport java.util.Map;\n\nimport com.fasterxml.jackson.annotation.JsonCreator;\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport org.jspecify.annotations.Nullable;\n\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\n/**\n * Jackson Mixin class helps in serialize/deserialize {@link StatusInfo}.\n *\n * @author Stefan Rempfer\n */\npublic abstract class StatusInfoMixin {\n\n\t@JsonCreator\n\tpublic static StatusInfo valueOf(@JsonProperty(\"status\") String statusCode,\n\t\t\t@JsonProperty(\"details\") @Nullable Map<String, ?> details) {\n\t\treturn StatusInfo.valueOf(statusCode, details);\n\t}\n\n\t@JsonIgnore\n\tpublic abstract boolean isUp();\n\n\t@JsonIgnore\n\tpublic abstract boolean isOffline();\n\n\t@JsonIgnore\n\tpublic abstract boolean isDown();\n\n\t@JsonIgnore\n\tpublic abstract boolean isUnknown();\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/TagsMixin.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport java.util.Map;\n\nimport com.fasterxml.jackson.annotation.JsonAnyGetter;\nimport com.fasterxml.jackson.annotation.JsonCreator;\n\nimport de.codecentric.boot.admin.server.domain.values.Tags;\n\n/**\n * Jackson Mixin class helps in serialize/deserialize {@link Tags}.\n *\n * @author Stefan Rempfer\n */\npublic abstract class TagsMixin {\n\n\t@JsonCreator\n\tpublic static Tags from(Map<String, ?> map) {\n\t\treturn Tags.from(map);\n\t}\n\n\t@JsonAnyGetter\n\tpublic abstract Map<String, String> getValues();\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/package-info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Spring Boot Admin Server - jackson utils package.\n\n@NullMarked\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/AdminController.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Indicates that the annotated class is a mvc controller used within spring boot admin.\n *\n * @author Johannes Edmeier\n */\n@Target({ ElementType.TYPE })\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface AdminController {\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/ApplicationsController.java",
    "content": "/*\n * Copyright 2014-2024 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web;\n\nimport java.time.Duration;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.context.ApplicationEventPublisher;\nimport org.springframework.http.MediaType;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.http.codec.ServerSentEvent;\nimport org.springframework.web.bind.annotation.DeleteMapping;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Application;\nimport de.codecentric.boot.admin.server.services.ApplicationRegistry;\nimport de.codecentric.boot.admin.server.web.client.RefreshInstancesEvent;\n\n/**\n * REST controller for controlling registration of managed instances.\n */\n@AdminController\n@ResponseBody\npublic class ApplicationsController {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(ApplicationsController.class);\n\n\tprivate static final ServerSentEvent<?> PING = ServerSentEvent.builder().comment(\"ping\").build();\n\n\tprivate static final Flux<ServerSentEvent<?>> PING_FLUX = Flux.interval(Duration.ZERO, Duration.ofSeconds(10L))\n\t\t.map((tick) -> PING);\n\n\tprivate final ApplicationRegistry registry;\n\n\tprivate final ApplicationEventPublisher publisher;\n\n\tpublic ApplicationsController(ApplicationRegistry registry, ApplicationEventPublisher publisher) {\n\t\tthis.registry = registry;\n\t\tthis.publisher = publisher;\n\t}\n\n\t@GetMapping(path = \"/applications\", produces = MediaType.APPLICATION_JSON_VALUE)\n\tpublic Flux<Application> applications() {\n\t\treturn registry.getApplications();\n\t}\n\n\t@PostMapping(path = \"/applications\", produces = MediaType.APPLICATION_JSON_VALUE)\n\tpublic void refreshApplications() {\n\t\tpublisher.publishEvent(new RefreshInstancesEvent(this));\n\t}\n\n\t@GetMapping(path = \"/applications/{name}\", produces = MediaType.APPLICATION_JSON_VALUE)\n\tpublic Mono<ResponseEntity<Application>> application(@PathVariable(\"name\") String name) {\n\t\treturn registry.getApplication(name).map(ResponseEntity::ok).defaultIfEmpty(ResponseEntity.notFound().build());\n\t}\n\n\t@GetMapping(path = \"/applications\", produces = MediaType.TEXT_EVENT_STREAM_VALUE)\n\tpublic Flux<ServerSentEvent<Application>> applicationsStream() {\n\t\treturn registry.getApplicationStream()\n\t\t\t.map((application) -> ServerSentEvent.builder(application).build())\n\t\t\t.mergeWith(ping());\n\t}\n\n\t@DeleteMapping(path = \"/applications/{name}\")\n\tpublic Mono<ResponseEntity<Void>> unregister(@PathVariable(\"name\") String name) {\n\t\tlog.debug(\"Unregister application with name '{}'\", name);\n\t\treturn registry.deregister(name)\n\t\t\t.collectList()\n\t\t\t.map((deregistered) -> !deregistered.isEmpty() ? ResponseEntity.noContent().build()\n\t\t\t\t\t: ResponseEntity.notFound().build());\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprivate static <T> Flux<ServerSentEvent<T>> ping() {\n\t\treturn (Flux<ServerSentEvent<T>>) (Flux) PING_FLUX;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/HttpHeaderFilter.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web;\n\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport org.springframework.http.HttpHeaders;\n\nimport static java.util.stream.Collectors.toMap;\n\n/**\n * Returns a new HttpHeaders from the given one but omits the hop-by-hop headers and\n * specified headers.\n *\n * @author Johannes Edmeier\n */\npublic class HttpHeaderFilter {\n\n\tprivate static final String[] HOP_BY_HOP_HEADERS = new String[] { \"Host\", \"Connection\", \"Keep-Alive\",\n\t\t\t\"Proxy-Authenticate\", \"Proxy-Authorization\", \"TE\", \"Trailer\", \"Transfer-Encoding\", \"Upgrade\",\n\t\t\t\"X-Application-Context\" };\n\n\tprivate final Set<String> ignoredHeaders;\n\n\tpublic HttpHeaderFilter(Set<String> ignoredHeaders) {\n\t\tthis.ignoredHeaders = Stream.concat(ignoredHeaders.stream(), Arrays.stream(HOP_BY_HOP_HEADERS))\n\t\t\t.map(String::toLowerCase)\n\t\t\t.collect(Collectors.toSet());\n\t}\n\n\tpublic HttpHeaders filterHeaders(HttpHeaders headers) {\n\t\tHttpHeaders filtered = new HttpHeaders();\n\t\tfiltered.putAll(headers.headerSet()\n\t\t\t.stream()\n\t\t\t.filter((e) -> this.includeHeader(e.getKey()))\n\t\t\t.collect(toMap(Map.Entry::getKey, Map.Entry::getValue)));\n\t\treturn filtered;\n\t}\n\n\tprivate boolean includeHeader(String header) {\n\t\treturn !this.ignoredHeaders.contains(header.toLowerCase());\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/InstanceWebProxy.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.util.List;\nimport java.util.concurrent.TimeoutException;\nimport java.util.function.Function;\n\nimport com.fasterxml.jackson.annotation.JsonInclude;\nimport io.netty.handler.timeout.ReadTimeoutException;\nimport org.jspecify.annotations.Nullable;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.client.reactive.ClientHttpRequest;\nimport org.springframework.web.reactive.function.BodyInserter;\nimport org.springframework.web.reactive.function.client.ClientResponse;\nimport org.springframework.web.reactive.function.client.ExchangeStrategies;\nimport org.springframework.web.reactive.function.client.WebClient;\nimport org.springframework.web.reactive.function.client.WebClientRequestException;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\nimport de.codecentric.boot.admin.server.web.client.exception.ResolveEndpointException;\n\nimport static org.springframework.http.HttpMethod.PATCH;\nimport static org.springframework.http.HttpMethod.POST;\nimport static org.springframework.http.HttpMethod.PUT;\n\n/**\n * Forwards a request to a single instances endpoint and will respond with: - 502 (Bad\n * Gateway) when any error occurs during the request - 503 (Service unavailable) when the\n * instance is not found - 504 (Gateway timeout) when the request exceeds the timeout\n *\n * @author Johannes Edmeier\n */\npublic class InstanceWebProxy {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(InstanceWebProxy.class);\n\n\tprivate static final Instance NULL_INSTANCE = Instance.create(InstanceId.of(\"null\"));\n\n\tprivate final InstanceWebClient instanceWebClient;\n\n\tprivate final ExchangeStrategies strategies = ExchangeStrategies.withDefaults();\n\n\tpublic InstanceWebProxy(InstanceWebClient instanceWebClient) {\n\t\tthis.instanceWebClient = instanceWebClient;\n\t}\n\n\tpublic <V> Mono<V> forward(Mono<Instance> instanceMono, ForwardRequest forwardRequest,\n\t\t\tFunction<ClientResponse, Mono<V>> responseHandler) {\n\t\treturn instanceMono.defaultIfEmpty(NULL_INSTANCE).flatMap((instance) -> {\n\t\t\tif (!instance.equals(NULL_INSTANCE)) {\n\t\t\t\treturn this.forward(instance, forwardRequest, responseHandler);\n\t\t\t}\n\t\t\telse {\n\t\t\t\treturn Mono.defer(() -> responseHandler\n\t\t\t\t\t.apply(ClientResponse.create(HttpStatus.SERVICE_UNAVAILABLE, this.strategies).build()));\n\t\t\t}\n\t\t});\n\t}\n\n\tpublic Flux<InstanceResponse> forward(Flux<Instance> instances, ForwardRequest forwardRequest) {\n\t\treturn instances.flatMap((instance) -> this.forward(instance, forwardRequest, (clientResponse) -> {\n\t\t\tInstanceResponse.Builder response = InstanceResponse.builder()\n\t\t\t\t.instanceId(instance.getId())\n\t\t\t\t.status(clientResponse.statusCode().value())\n\t\t\t\t.contentType(String.join(\", \", clientResponse.headers().header(HttpHeaders.CONTENT_TYPE)));\n\t\t\treturn clientResponse.bodyToMono(String.class)\n\t\t\t\t.map(response::body)\n\t\t\t\t.defaultIfEmpty(response)\n\t\t\t\t.map(InstanceResponse.Builder::build);\n\t\t}));\n\t}\n\n\tprivate <V> Mono<V> forward(Instance instance, ForwardRequest forwardRequest,\n\t\t\tFunction<ClientResponse, Mono<V>> responseHandler) {\n\t\tlog.trace(\"Proxy-Request for instance {} with URL '{}'\", instance.getId(), forwardRequest.getUri());\n\t\tWebClient.RequestBodySpec bodySpec = this.instanceWebClient.instance(instance)\n\t\t\t.method(forwardRequest.getMethod())\n\t\t\t.uri(forwardRequest.getUri())\n\t\t\t.headers((h) -> h.addAll(forwardRequest.getHeaders()));\n\n\t\tWebClient.RequestHeadersSpec<?> headersSpec = bodySpec;\n\t\tif (requiresBody(forwardRequest.getMethod())) {\n\t\t\theadersSpec = bodySpec.body(forwardRequest.getBody());\n\t\t}\n\n\t\treturn headersSpec.exchangeToMono(responseHandler).onErrorResume(ResolveEndpointException.class, (ex) -> {\n\t\t\tlog.trace(\"No Endpoint found for Proxy-Request for instance {} with URL '{}'\", instance.getId(),\n\t\t\t\t\tforwardRequest.getUri());\n\t\t\treturn responseHandler.apply(ClientResponse.create(HttpStatus.NOT_FOUND, this.strategies).build());\n\t\t}).onErrorResume((ex) -> {\n\t\t\tThrowable cause = ex;\n\t\t\tif (ex instanceof WebClientRequestException) {\n\t\t\t\tcause = ex.getCause();\n\t\t\t}\n\t\t\tif (cause instanceof ReadTimeoutException || cause instanceof TimeoutException) {\n\t\t\t\tlog.trace(\"Timeout for Proxy-Request for instance {} with URL '{}'\", instance.getId(),\n\t\t\t\t\t\tforwardRequest.getUri());\n\t\t\t\treturn responseHandler\n\t\t\t\t\t.apply(ClientResponse.create(HttpStatus.GATEWAY_TIMEOUT, this.strategies).build());\n\t\t\t}\n\t\t\tif (cause instanceof IOException) {\n\t\t\t\tlog.trace(\"Proxy-Request for instance {} with URL '{}' errored\", instance.getId(),\n\t\t\t\t\t\tforwardRequest.getUri(), cause);\n\t\t\t\treturn responseHandler.apply(ClientResponse.create(HttpStatus.BAD_GATEWAY, this.strategies).build());\n\t\t\t}\n\t\t\treturn Mono.error(ex);\n\t\t});\n\t}\n\n\tprivate boolean requiresBody(HttpMethod method) {\n\t\treturn List.of(PUT, POST, PATCH).contains(method);\n\t}\n\n\t@lombok.Data\n\t@lombok.Builder(builderClassName = \"Builder\")\n\tpublic static class InstanceResponse {\n\n\t\tprivate final InstanceId instanceId;\n\n\t\tprivate final int status;\n\n\t\t@Nullable\n\t\t@JsonInclude(JsonInclude.Include.NON_EMPTY)\n\t\tprivate final String body;\n\n\t\t@Nullable\n\t\t@JsonInclude(JsonInclude.Include.NON_EMPTY)\n\t\tprivate final String contentType;\n\n\t}\n\n\t@lombok.Data\n\t@lombok.Builder(builderClassName = \"Builder\")\n\tpublic static class ForwardRequest {\n\n\t\tprivate final URI uri;\n\n\t\tprivate final HttpMethod method;\n\n\t\tprivate final HttpHeaders headers;\n\n\t\tprivate final BodyInserter<?, ? super ClientHttpRequest> body;\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/InstancesController.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web;\n\nimport java.net.URI;\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.http.MediaType;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.http.codec.ServerSentEvent;\nimport org.springframework.web.bind.annotation.DeleteMapping;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.util.UriComponentsBuilder;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.eventstore.InstanceEventStore;\nimport de.codecentric.boot.admin.server.services.InstanceRegistry;\n\n/**\n * REST controller for controlling registration of managed instances.\n */\n@AdminController\n@ResponseBody\npublic class InstancesController {\n\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(InstancesController.class);\n\n\tprivate static final ServerSentEvent<?> PING = ServerSentEvent.builder().comment(\"ping\").build();\n\n\tprivate static final Flux<ServerSentEvent<?>> PING_FLUX = Flux.interval(Duration.ZERO, Duration.ofSeconds(10L))\n\t\t.map((tick) -> PING);\n\n\tprivate final InstanceRegistry registry;\n\n\tprivate final InstanceEventStore eventStore;\n\n\tpublic InstancesController(InstanceRegistry registry, InstanceEventStore eventStore) {\n\t\tthis.registry = registry;\n\t\tthis.eventStore = eventStore;\n\t}\n\n\t/**\n\t * Register an instance.\n\t * @param registration registration info\n\t * @param builder the UriComponentsBuilder\n\t * @return the registered instance id;\n\t */\n\t@PostMapping(path = \"/instances\", consumes = MediaType.APPLICATION_JSON_VALUE)\n\tpublic Mono<ResponseEntity<Map<String, InstanceId>>> register(@RequestBody Registration registration,\n\t\t\tUriComponentsBuilder builder) {\n\t\tRegistration withSource = Registration.copyOf(registration).source(\"http-api\").build();\n\t\tLOGGER.debug(\"Register instance {}\", withSource);\n\t\treturn registry.register(withSource).map((id) -> {\n\t\t\tURI location = builder.replacePath(\"/instances/{id}\").buildAndExpand(id).toUri();\n\t\t\treturn ResponseEntity.created(location).body(Collections.singletonMap(\"id\", id));\n\t\t});\n\t}\n\n\t/**\n\t * List all registered instances with name\n\t * @param name the name to search for\n\t * @return application list\n\t */\n\t@GetMapping(path = \"/instances\", produces = MediaType.APPLICATION_JSON_VALUE, params = \"name\")\n\tpublic Flux<Instance> instances(@RequestParam(\"name\") String name) {\n\t\treturn registry.getInstances(name).filter(Instance::isRegistered);\n\t}\n\n\t/**\n\t * List all registered instances with name\n\t * @return application list\n\t */\n\t@GetMapping(path = \"/instances\", produces = MediaType.APPLICATION_JSON_VALUE)\n\tpublic Flux<Instance> instances() {\n\t\tLOGGER.debug(\"Deliver all registered instances\");\n\t\treturn registry.getInstances().filter(Instance::isRegistered);\n\t}\n\n\t/**\n\t * Get a single instance.\n\t * @param id the application identifier.\n\t * @return the registered application.\n\t */\n\t@GetMapping(path = \"/instances/{id}\", produces = MediaType.APPLICATION_JSON_VALUE)\n\tpublic Mono<ResponseEntity<Instance>> instance(@PathVariable String id) {\n\t\tLOGGER.debug(\"Deliver registered instance with ID '{}'\", id);\n\t\treturn registry.getInstance(InstanceId.of(id))\n\t\t\t.filter(Instance::isRegistered)\n\t\t\t.map(ResponseEntity::ok)\n\t\t\t.defaultIfEmpty(ResponseEntity.notFound().build());\n\t}\n\n\t/**\n\t * Unregister an instance\n\t * @param id the instance id.\n\t * @return response indicating the success\n\t */\n\t@DeleteMapping(path = \"/instances/{id}\")\n\tpublic Mono<ResponseEntity<Void>> unregister(@PathVariable String id) {\n\t\tLOGGER.debug(\"Unregister instance with ID '{}'\", id);\n\t\treturn registry.deregister(InstanceId.of(id))\n\t\t\t.map((v) -> ResponseEntity.noContent().<Void>build())\n\t\t\t.defaultIfEmpty(ResponseEntity.notFound().build());\n\t}\n\n\t/**\n\t * Retrieve all instance events as a JSON array. Returns all events for all registered\n\t * instances. Useful for reconstructing application state or initializing the UI.\n\t * @return flux of {@link InstanceEvent} objects\n\t */\n\t@GetMapping(path = \"/instances/events\", produces = MediaType.APPLICATION_JSON_VALUE)\n\tpublic Flux<InstanceEvent> events() {\n\t\treturn eventStore.findAll();\n\t}\n\n\t/**\n\t * Stream all instance events as Server-Sent Events (SSE). Returns a continuous stream\n\t * of instance events for real-time monitoring and UI updates.\n\t * @return flux of {@link ServerSentEvent} containing {@link InstanceEvent}\n\t */\n\t@GetMapping(path = \"/instances/events\", produces = MediaType.TEXT_EVENT_STREAM_VALUE)\n\tpublic Flux<ServerSentEvent<InstanceEvent>> eventStream() {\n\t\treturn Flux.from(eventStore).map((event) -> ServerSentEvent.builder(event).build()).mergeWith(ping());\n\t}\n\n\t/**\n\t * Stream events for a specific instance as Server-Sent Events (SSE). Streams events\n\t * for the instance identified by its ID. Each event is delivered as an SSE message.\n\t * @param id the instance ID\n\t * @return flux of {@link ServerSentEvent} containing {@link Instance}\n\t */\n\t@GetMapping(path = \"/instances/{id}\", produces = MediaType.TEXT_EVENT_STREAM_VALUE)\n\tpublic Flux<ServerSentEvent<Instance>> instanceStream(@PathVariable String id) {\n\t\treturn Flux.from(eventStore)\n\t\t\t.filter((event) -> event.getInstance().equals(InstanceId.of(id)))\n\t\t\t.flatMap((event) -> registry.getInstance(event.getInstance()))\n\t\t\t.map((event) -> ServerSentEvent.builder(event).build())\n\t\t\t.mergeWith(ping());\n\t}\n\n\t/**\n\t * Returns a periodic Server-Sent Event (SSE) comment-only ping every 10 seconds.\n\t * <p>\n\t * This method is used to keep SSE connections alive for all event stream endpoints in\n\t * Spring Boot Admin. The ping event is sent as a comment (\": ping\") and does not\n\t * contain any data payload. <br>\n\t * <b>Why?</b> Many proxies, firewalls, and browsers may close idle HTTP connections.\n\t * The ping event provides regular activity on the stream, ensuring the connection\n\t * remains open even when no instance events are emitted. <br>\n\t * <b>Technical details:</b>\n\t * <ul>\n\t * <li>Interval: 10 seconds</li>\n\t * <li>Format: SSE comment-only event</li>\n\t * <li>Applies to: All event stream endpoints (e.g., /instances/events,\n\t * /instances/{id} with Accept: text/event-stream)</li>\n\t * </ul>\n\t * </p>\n\t * @param <T> the type of event data (unused for ping)\n\t * @return flux of ServerSentEvent representing periodic ping comments\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tprivate static <T> Flux<ServerSentEvent<T>> ping() {\n\t\treturn (Flux<ServerSentEvent<T>>) (Flux) PING_FLUX;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/PathUtils.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web;\n\nimport org.springframework.util.StringUtils;\n\npublic final class PathUtils {\n\n\tprivate PathUtils() {\n\t}\n\n\tpublic static String normalizePath(String path) {\n\t\tif (!StringUtils.hasText(path)) {\n\t\t\treturn path;\n\t\t}\n\t\tString normalizedPath = path;\n\t\tif (!normalizedPath.startsWith(\"/\")) {\n\t\t\tnormalizedPath = \"/\" + normalizedPath;\n\t\t}\n\t\tif (normalizedPath.endsWith(\"/\")) {\n\t\t\tnormalizedPath = normalizedPath.substring(0, normalizedPath.length() - 1);\n\t\t}\n\t\tif (normalizedPath.startsWith(\"//\")) {\n\t\t\tnormalizedPath = normalizedPath.substring(1);\n\t\t}\n\t\treturn normalizedPath;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/client/BasicAuthHttpHeaderProvider.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.Base64;\nimport java.util.Collections;\nimport java.util.Map;\n\nimport org.jspecify.annotations.Nullable;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.util.StringUtils;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\n\n/**\n * Provides Basic Auth headers for the {@link Instance} using the metadata for \"user.name\"\n * and \"user.password\".\n *\n * Other allowed key names: - \"user-name\" / \"user-password\" - \"username\" / \"userpassword\"\n *\n * @author Johannes Edmeier\n */\npublic class BasicAuthHttpHeaderProvider implements HttpHeadersProvider {\n\n\tprivate static final String[] USERNAME_KEYS = { \"user.name\", \"user-name\", \"username\" };\n\n\tprivate static final String[] PASSWORD_KEYS = { \"user.password\", \"user-password\", \"userpassword\" };\n\n\t@Nullable private final String defaultUserName;\n\n\t@Nullable private final String defaultPassword;\n\n\tprivate final Map<String, InstanceCredentials> serviceMap;\n\n\tpublic BasicAuthHttpHeaderProvider(@Nullable String defaultUserName, @Nullable String defaultPassword,\n\t\t\tMap<String, InstanceCredentials> serviceMap) {\n\t\tthis.defaultUserName = defaultUserName;\n\t\tthis.defaultPassword = defaultPassword;\n\t\tthis.serviceMap = serviceMap;\n\t}\n\n\tpublic BasicAuthHttpHeaderProvider() {\n\t\tthis(null, null, Collections.emptyMap());\n\t}\n\n\tprivate static @Nullable String getMetadataValue(Instance instance, String[] keys) {\n\t\tMap<String, String> metadata = instance.getRegistration().getMetadata();\n\t\tfor (String key : keys) {\n\t\t\tString value = metadata.get(key);\n\t\t\tif (value != null) {\n\t\t\t\treturn value;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tprivate static String base64Encode(byte[] src) {\n\t\tif (src.length == 0) {\n\t\t\treturn \"\";\n\t\t}\n\t\tbyte[] dest = Base64.getEncoder().encode(src);\n\t\treturn new String(dest, StandardCharsets.UTF_8);\n\t}\n\n\t@Override\n\tpublic HttpHeaders getHeaders(Instance instance) {\n\t\tString username = getMetadataValue(instance, USERNAME_KEYS);\n\t\tString password = getMetadataValue(instance, PASSWORD_KEYS);\n\n\t\tif (!(StringUtils.hasText(username) && StringUtils.hasText(password))) {\n\t\t\tString registeredName = instance.getRegistration().getName();\n\t\t\tInstanceCredentials credentials = this.serviceMap.get(registeredName);\n\t\t\tif (credentials != null) {\n\t\t\t\tusername = credentials.getUserName();\n\t\t\t\tpassword = credentials.getUserPassword();\n\t\t\t}\n\t\t\telse {\n\t\t\t\tusername = this.defaultUserName;\n\t\t\t\tpassword = this.defaultPassword;\n\t\t\t}\n\t\t}\n\n\t\tHttpHeaders headers = new HttpHeaders();\n\t\tif (StringUtils.hasText(username) && StringUtils.hasText(password)) {\n\t\t\theaders.set(HttpHeaders.AUTHORIZATION, encode(username, password));\n\t\t}\n\t\treturn headers;\n\t}\n\n\tprotected String encode(String username, String password) {\n\t\tString token = base64Encode((username + \":\" + password).getBytes(StandardCharsets.UTF_8));\n\t\treturn \"Basic \" + token;\n\t}\n\n\t@lombok.Data\n\t@lombok.NoArgsConstructor\n\t@lombok.AllArgsConstructor\n\tpublic static class InstanceCredentials {\n\n\t\t/**\n\t\t * user name for this instance\n\t\t */\n\t\t@lombok.NonNull\n\t\tprivate String userName;\n\n\t\t/**\n\t\t * user password for this instance\n\t\t */\n\t\t@lombok.NonNull\n\t\tprivate String userPassword;\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/client/CloudFoundryHttpHeaderProvider.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client;\n\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.util.StringUtils;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\n\n/**\n * Provides CloudFoundry related X-CF-APP-INSTANCE header for the {@link Instance} using\n * the metadata for \"applicationId\" and \"instanceId\".\n *\n * @author Tetsushi Awano\n */\npublic class CloudFoundryHttpHeaderProvider implements HttpHeadersProvider {\n\n\t@Override\n\tpublic HttpHeaders getHeaders(Instance instance) {\n\t\tString applicationId = instance.getRegistration().getMetadata().get(\"applicationId\");\n\t\tString instanceId = instance.getRegistration().getMetadata().get(\"instanceId\");\n\n\t\tif (StringUtils.hasText(applicationId) && StringUtils.hasText(instanceId)) {\n\t\t\tHttpHeaders headers = new HttpHeaders();\n\t\t\theaders.set(\"X-CF-APP-INSTANCE\", applicationId + \":\" + instanceId);\n\t\t\treturn headers;\n\t\t}\n\n\t\treturn HttpHeaders.EMPTY;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/client/CompositeHttpHeadersProvider.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client;\n\nimport java.util.Collection;\n\nimport org.springframework.http.HttpHeaders;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\n\npublic class CompositeHttpHeadersProvider implements HttpHeadersProvider {\n\n\tprivate final Collection<HttpHeadersProvider> delegates;\n\n\tpublic CompositeHttpHeadersProvider(Collection<HttpHeadersProvider> delegates) {\n\t\tthis.delegates = delegates;\n\t}\n\n\t@Override\n\tpublic HttpHeaders getHeaders(Instance instance) {\n\t\tHttpHeaders headers = new HttpHeaders();\n\t\tdelegates.forEach((delegate) -> headers.addAll(delegate.getHeaders(instance)));\n\t\treturn headers;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/client/HttpHeadersProvider.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client;\n\nimport org.springframework.http.HttpHeaders;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\n\n/**\n * Is responsible to provide the {@link HttpHeaders} used to interact with the given\n * {@link Instance}.\n *\n * @author Johannes Edmeier\n */\npublic interface HttpHeadersProvider {\n\n\tHttpHeaders getHeaders(Instance instance);\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/client/InstanceExchangeFilterFunction.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client;\n\nimport org.springframework.web.reactive.function.client.ClientRequest;\nimport org.springframework.web.reactive.function.client.ClientResponse;\nimport org.springframework.web.reactive.function.client.ExchangeFunction;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\n\n/**\n * Represents a function that filters an{@linkplain ExchangeFunction exchange function}\n * issued on a registered instance.\n *\n * @author Johannes Edmeier\n */\n@FunctionalInterface\npublic interface InstanceExchangeFilterFunction {\n\n\tMono<ClientResponse> filter(Instance instance, ClientRequest request, ExchangeFunction next);\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/client/InstanceExchangeFilterFunctions.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client;\n\nimport java.net.URI;\nimport java.time.Duration;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.stream.Stream;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.boot.actuate.endpoint.ApiVersion;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.http.MediaType;\nimport org.springframework.util.CollectionUtils;\nimport org.springframework.util.MultiValueMap;\nimport org.springframework.web.reactive.function.client.ClientRequest;\nimport org.springframework.web.reactive.function.client.ClientResponse;\nimport org.springframework.web.util.UriComponents;\nimport org.springframework.web.util.UriComponentsBuilder;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.values.Endpoint;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.web.client.cookies.PerInstanceCookieStore;\nimport de.codecentric.boot.admin.server.web.client.exception.ResolveEndpointException;\nimport de.codecentric.boot.admin.server.web.client.reactive.ReactiveHttpHeadersProvider;\n\nimport static java.util.Arrays.asList;\nimport static java.util.Collections.singletonList;\n\npublic final class InstanceExchangeFilterFunctions {\n\n\tpublic static final String ATTRIBUTE_ENDPOINT = \"endpointId\";\n\n\tprivate static final Logger log = LoggerFactory.getLogger(InstanceExchangeFilterFunctions.class);\n\n\tprivate static final List<MediaType> DEFAULT_LOGFILE_ACCEPT_MEDIA_TYPES = singletonList(MediaType.TEXT_PLAIN);\n\n\tstatic final MediaType V1_ACTUATOR_JSON = MediaType.valueOf(\"application/vnd.spring-boot.actuator.v1+json\");\n\n\tprivate static final List<MediaType> DEFAULT_ACCEPT_MEDIA_TYPES = asList(\n\t\t\tnew MediaType(ApiVersion.V3.getProducedMimeType()), new MediaType(ApiVersion.V2.getProducedMimeType()),\n\t\t\tV1_ACTUATOR_JSON, MediaType.APPLICATION_JSON);\n\n\tprivate InstanceExchangeFilterFunctions() {\n\t}\n\n\tpublic static InstanceExchangeFilterFunction addHeaders(HttpHeadersProvider httpHeadersProvider) {\n\t\treturn (instance, request, next) -> {\n\t\t\trequest = ClientRequest.from(request)\n\t\t\t\t.headers((headers) -> headers.addAll(httpHeadersProvider.getHeaders(instance)))\n\t\t\t\t.build();\n\t\t\treturn next.exchange(request);\n\t\t};\n\t}\n\n\tpublic static InstanceExchangeFilterFunction addHeadersReactive(ReactiveHttpHeadersProvider httpHeadersProvider) {\n\t\treturn (instance, request, next) -> httpHeadersProvider.getHeaders(instance).flatMap((httpHeaders) -> {\n\t\t\tClientRequest requestWithAdditionalHeaders = ClientRequest.from(request)\n\t\t\t\t.headers((headers) -> headers.addAll(httpHeaders))\n\t\t\t\t.build();\n\n\t\t\treturn next.exchange(requestWithAdditionalHeaders);\n\t\t}).switchIfEmpty(Mono.defer(() -> next.exchange(request)));\n\t}\n\n\tpublic static InstanceExchangeFilterFunction rewriteEndpointUrl() {\n\t\treturn (instance, request, next) -> {\n\t\t\tif (request.url().isAbsolute()) {\n\t\t\t\tlog.trace(\"Absolute URL '{}' for instance {} not rewritten\", request.url(), instance.getId());\n\t\t\t\tif (request.url().toString().equals(instance.getRegistration().getManagementUrl())) {\n\t\t\t\t\trequest = ClientRequest.from(request)\n\t\t\t\t\t\t.attribute(ATTRIBUTE_ENDPOINT, Endpoint.ACTUATOR_INDEX)\n\t\t\t\t\t\t.build();\n\t\t\t\t}\n\t\t\t\treturn next.exchange(request);\n\t\t\t}\n\n\t\t\tUriComponents requestUrl = UriComponentsBuilder.fromUri(request.url()).build();\n\t\t\tif (requestUrl.getPathSegments().isEmpty()) {\n\t\t\t\treturn Mono.error(new ResolveEndpointException(\"No endpoint specified\"));\n\t\t\t}\n\n\t\t\tString endpointId = requestUrl.getPathSegments().get(0);\n\t\t\tOptional<Endpoint> endpoint = instance.getEndpoints().get(endpointId);\n\n\t\t\tif (endpoint.isEmpty()) {\n\t\t\t\treturn Mono.error(new ResolveEndpointException(\"Endpoint '\" + endpointId + \"' not found\"));\n\t\t\t}\n\n\t\t\tURI rewrittenUrl = rewriteUrl(requestUrl, endpoint.get().getUrl());\n\t\t\tlog.trace(\"URL '{}' for Endpoint {} of instance {} rewritten to {}\", requestUrl, endpoint.get().getId(),\n\t\t\t\t\tinstance.getId(), rewrittenUrl);\n\t\t\trequest = ClientRequest.from(request)\n\t\t\t\t.attribute(ATTRIBUTE_ENDPOINT, endpoint.get().getId())\n\t\t\t\t.url(rewrittenUrl)\n\t\t\t\t.build();\n\t\t\treturn next.exchange(request);\n\t\t};\n\t}\n\n\tprivate static URI rewriteUrl(UriComponents oldUrl, String targetUrl) {\n\t\tString[] newPathSegments = oldUrl.getPathSegments()\n\t\t\t.subList(1, oldUrl.getPathSegments().size())\n\t\t\t.toArray(new String[] {});\n\t\treturn UriComponentsBuilder.fromUriString(targetUrl)\n\t\t\t.pathSegment(newPathSegments)\n\t\t\t.query(oldUrl.getQuery())\n\t\t\t.build(true)\n\t\t\t.toUri();\n\t}\n\n\tpublic static InstanceExchangeFilterFunction convertLegacyEndpoints(List<LegacyEndpointConverter> converters) {\n\t\treturn (instance, request, next) -> {\n\t\t\tMono<ClientResponse> clientResponse = next.exchange(request);\n\n\t\t\tOptional<Object> endpoint = request.attribute(ATTRIBUTE_ENDPOINT);\n\t\t\tif (endpoint.isEmpty()) {\n\t\t\t\treturn clientResponse;\n\t\t\t}\n\n\t\t\tfor (LegacyEndpointConverter converter : converters) {\n\t\t\t\tif (converter.canConvert(endpoint.get())) {\n\t\t\t\t\treturn clientResponse.map((response) -> {\n\t\t\t\t\t\tif (isLegacyResponse(response)) {\n\t\t\t\t\t\t\treturn convertLegacyResponse(converter, response);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn response;\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn clientResponse;\n\t\t};\n\t}\n\n\tprivate static Boolean isLegacyResponse(ClientResponse response) {\n\t\treturn response.headers()\n\t\t\t.contentType()\n\t\t\t.filter((t) -> V1_ACTUATOR_JSON.isCompatibleWith(t) || MediaType.APPLICATION_JSON.isCompatibleWith(t))\n\t\t\t.isPresent();\n\t}\n\n\tprivate static ClientResponse convertLegacyResponse(LegacyEndpointConverter converter, ClientResponse response) {\n\t\treturn response.mutate().headers((headers) -> {\n\t\t\theaders.setContentType(MediaType.asMediaType(ApiVersion.LATEST.getProducedMimeType()));\n\t\t\theaders.remove(HttpHeaders.CONTENT_LENGTH);\n\t\t}).body(converter::convert).build();\n\t}\n\n\tpublic static InstanceExchangeFilterFunction setDefaultAcceptHeader() {\n\t\treturn (instance, request, next) -> {\n\t\t\tif (request.headers().getAccept().isEmpty()) {\n\t\t\t\tBoolean isRequestForLogfile = request.attribute(ATTRIBUTE_ENDPOINT)\n\t\t\t\t\t.map(Endpoint.LOGFILE::equals)\n\t\t\t\t\t.orElse(false);\n\t\t\t\tList<MediaType> acceptedHeaders = isRequestForLogfile ? DEFAULT_LOGFILE_ACCEPT_MEDIA_TYPES\n\t\t\t\t\t\t: DEFAULT_ACCEPT_MEDIA_TYPES;\n\t\t\t\trequest = ClientRequest.from(request).headers((headers) -> headers.setAccept(acceptedHeaders)).build();\n\t\t\t}\n\t\t\treturn next.exchange(request);\n\t\t};\n\t}\n\n\tpublic static InstanceExchangeFilterFunction retry(int defaultRetries, Map<String, Integer> retriesPerEndpoint) {\n\t\treturn (instance, request, next) -> {\n\t\t\tint retries = 0;\n\t\t\tif (!request.method().equals(HttpMethod.DELETE) && !request.method().equals(HttpMethod.PATCH)\n\t\t\t\t\t&& !request.method().equals(HttpMethod.POST) && !request.method().equals(HttpMethod.PUT)) {\n\t\t\t\tretries = request.attribute(ATTRIBUTE_ENDPOINT).map(retriesPerEndpoint::get).orElse(defaultRetries);\n\t\t\t}\n\t\t\treturn next.exchange(request).retry(retries);\n\t\t};\n\t}\n\n\tpublic static InstanceExchangeFilterFunction timeout(Duration defaultTimeout,\n\t\t\tMap<String, Duration> timeoutPerEndpoint) {\n\t\treturn (instance, request, next) -> {\n\t\t\tDuration timeout = request.attribute(ATTRIBUTE_ENDPOINT)\n\t\t\t\t.map(timeoutPerEndpoint::get)\n\t\t\t\t.orElse(defaultTimeout);\n\t\t\treturn next.exchange(request).timeout(timeout);\n\t\t};\n\t}\n\n\t// Accept header is broken on /logfile. We need to add \"*/*\" for old clients\n\t// see https://github.com/spring-projects/spring-boot/issues/16188\n\tpublic static InstanceExchangeFilterFunction logfileAcceptWorkaround() {\n\t\treturn (instance, request, next) -> {\n\t\t\tif (request.attribute(ATTRIBUTE_ENDPOINT).map(Endpoint.LOGFILE::equals).orElse(false)) {\n\t\t\t\tList<MediaType> newAcceptHeaders = Stream\n\t\t\t\t\t.concat(request.headers().getAccept().stream(), Stream.of(MediaType.ALL))\n\t\t\t\t\t.toList();\n\t\t\t\trequest = ClientRequest.from(request).headers((h) -> h.setAccept(newAcceptHeaders)).build();\n\t\t\t}\n\t\t\treturn next.exchange(request);\n\t\t};\n\t}\n\n\t/**\n\t * Creates the {@link InstanceExchangeFilterFunction} that could handle cookies during\n\t * requests and responses to/from applications.\n\t * @param store the cookie store to use\n\t * @return the new filter function\n\t */\n\tpublic static InstanceExchangeFilterFunction handleCookies(final PerInstanceCookieStore store) {\n\t\treturn (instance, request, next) -> {\n\t\t\t// we need an absolute URL to be able to deal with cookies\n\t\t\tif (request.url().isAbsolute()) {\n\t\t\t\treturn next.exchange(enrichRequestWithStoredCookies(instance.getId(), request, store))\n\t\t\t\t\t.map((response) -> storeCookiesFromResponse(instance.getId(), request, response, store));\n\t\t\t}\n\n\t\t\treturn next.exchange(request);\n\t\t};\n\t}\n\n\tprivate static ClientRequest enrichRequestWithStoredCookies(final InstanceId instId, final ClientRequest request,\n\t\t\tfinal PerInstanceCookieStore store) {\n\t\tfinal MultiValueMap<String, String> storedCookies = store.get(instId, request.url(),\n\t\t\t\trequest.headers().asMultiValueMap());\n\t\tif (CollectionUtils.isEmpty(storedCookies)) {\n\t\t\tlog.trace(\"No cookies found for request [url={}]\", request.url());\n\t\t\treturn request;\n\t\t}\n\n\t\tlog.trace(\"Cookies found for request [url={}]\", request.url());\n\t\treturn ClientRequest.from(request).cookies((cm) -> cm.addAll(storedCookies)).build();\n\t}\n\n\tprivate static ClientResponse storeCookiesFromResponse(final InstanceId instId, final ClientRequest request,\n\t\t\tfinal ClientResponse response, final PerInstanceCookieStore store) {\n\t\tfinal HttpHeaders headers = response.headers().asHttpHeaders();\n\t\tlog.trace(\"Searching for cookies in header values of response [url={},headerValues={}]\", request.url(),\n\t\t\t\theaders);\n\n\t\tstore.put(instId, request.url(), headers.asMultiValueMap());\n\n\t\treturn response;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/client/InstanceWebClient.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.function.Consumer;\n\nimport org.springframework.web.reactive.function.client.ClientRequest;\nimport org.springframework.web.reactive.function.client.ExchangeFilterFunction;\nimport org.springframework.web.reactive.function.client.WebClient;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.web.client.exception.ResolveInstanceException;\n\npublic class InstanceWebClient {\n\n\tpublic static final String ATTRIBUTE_INSTANCE = \"instance\";\n\n\tprivate final WebClient webClient;\n\n\tprotected InstanceWebClient(WebClient webClient) {\n\t\tthis.webClient = webClient;\n\t}\n\n\tpublic WebClient instance(Mono<Instance> instance) {\n\t\treturn this.webClient.mutate().filters((filters) -> filters.add(0, setInstance(instance))).build();\n\t}\n\n\tpublic WebClient instance(Instance instance) {\n\t\treturn this.instance(Mono.justOrEmpty(instance));\n\t}\n\n\tpublic static InstanceWebClient.Builder builder() {\n\t\treturn new InstanceWebClient.Builder();\n\t}\n\n\tpublic static InstanceWebClient.Builder builder(WebClient.Builder webClient) {\n\t\treturn new InstanceWebClient.Builder(webClient);\n\t}\n\n\tprivate static ExchangeFilterFunction setInstance(Mono<Instance> instance) {\n\t\treturn (request, next) -> instance\n\t\t\t.map((i) -> ClientRequest.from(request).attribute(ATTRIBUTE_INSTANCE, i).build())\n\t\t\t.switchIfEmpty(Mono.error(() -> new ResolveInstanceException(\"Could not resolve Instance\")))\n\t\t\t.flatMap(next::exchange);\n\t}\n\n\tprivate static ExchangeFilterFunction toExchangeFilterFunction(InstanceExchangeFilterFunction filter) {\n\t\treturn (request, next) -> request.attribute(ATTRIBUTE_INSTANCE)\n\t\t\t.filter(Instance.class::isInstance)\n\t\t\t.map(Instance.class::cast)\n\t\t\t.map((instance) -> filter.filter(instance, request, next))\n\t\t\t.orElseGet(() -> next.exchange(request));\n\t}\n\n\tpublic static class Builder {\n\n\t\tprivate List<InstanceExchangeFilterFunction> filters = new ArrayList<>();\n\n\t\tprivate WebClient.Builder webClientBuilder;\n\n\t\tpublic Builder() {\n\t\t\tthis(WebClient.builder());\n\t\t}\n\n\t\tpublic Builder(WebClient.Builder webClientBuilder) {\n\t\t\tthis.webClientBuilder = webClientBuilder;\n\t\t}\n\n\t\tprotected Builder(Builder other) {\n\t\t\tthis.filters = new ArrayList<>(other.filters);\n\t\t\tthis.webClientBuilder = other.webClientBuilder.clone();\n\t\t}\n\n\t\tpublic Builder filter(InstanceExchangeFilterFunction filter) {\n\t\t\tthis.filters.add(filter);\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic Builder filters(Consumer<List<InstanceExchangeFilterFunction>> filtersConsumer) {\n\t\t\tfiltersConsumer.accept(this.filters);\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic Builder webClient(WebClient.Builder builder) {\n\t\t\tthis.webClientBuilder = builder;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic Builder clone() {\n\t\t\treturn new Builder(this);\n\t\t}\n\n\t\tpublic InstanceWebClient build() {\n\t\t\tthis.filters.stream()\n\t\t\t\t.map(InstanceWebClient::toExchangeFilterFunction)\n\t\t\t\t.forEach(this.webClientBuilder::filter);\n\t\t\treturn new InstanceWebClient(this.webClientBuilder.build());\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/client/InstanceWebClientCustomizer.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client;\n\n/**\n * Callback interface that can be used to customize a {@link InstanceWebClient.Builder\n * InstanceWebClient.Builder}\n *\n * @author Johannes Edmeier\n */\n@FunctionalInterface\npublic interface InstanceWebClientCustomizer {\n\n\tvoid customize(InstanceWebClient.Builder instanceWebClientBuilder);\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/client/LegacyEndpointConverter.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client;\n\nimport java.util.function.Function;\n\nimport org.springframework.core.io.buffer.DataBuffer;\nimport reactor.core.publisher.Flux;\n\n/**\n * @author Johannes Edmeier\n */\npublic class LegacyEndpointConverter {\n\n\tprivate final String endpointId;\n\n\tprivate final Function<Flux<DataBuffer>, Flux<DataBuffer>> converterFn;\n\n\tprotected LegacyEndpointConverter(String endpointId, Function<Flux<DataBuffer>, Flux<DataBuffer>> converterFn) {\n\t\tthis.endpointId = endpointId;\n\t\tthis.converterFn = converterFn;\n\t}\n\n\tpublic boolean canConvert(Object endpointId) {\n\t\treturn this.endpointId.equals(endpointId);\n\t}\n\n\tpublic Flux<DataBuffer> convert(Flux<DataBuffer> body) {\n\t\treturn converterFn.apply(body);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/client/LegacyEndpointConverters.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client;\n\nimport java.time.DateTimeException;\nimport java.time.Instant;\nimport java.time.OffsetDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Date;\nimport java.util.LinkedHashMap;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\nimport org.jspecify.annotations.Nullable;\nimport org.springframework.core.ParameterizedTypeReference;\nimport org.springframework.core.ResolvableType;\nimport org.springframework.core.io.buffer.DataBuffer;\nimport org.springframework.core.io.buffer.DefaultDataBufferFactory;\nimport org.springframework.http.codec.json.JacksonJsonDecoder;\nimport org.springframework.http.codec.json.JacksonJsonEncoder;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport tools.jackson.databind.DeserializationFeature;\nimport tools.jackson.databind.cfg.DateTimeFeature;\nimport tools.jackson.databind.json.JsonMapper;\n\nimport de.codecentric.boot.admin.server.domain.values.Endpoint;\n\nimport static java.util.Arrays.asList;\nimport static java.util.Collections.emptyList;\nimport static java.util.Collections.emptySet;\nimport static java.util.Collections.singletonList;\nimport static java.util.Collections.singletonMap;\nimport static java.util.function.Function.identity;\nimport static java.util.stream.Collectors.joining;\nimport static java.util.stream.Collectors.toMap;\n\npublic final class LegacyEndpointConverters {\n\n\tprivate static final ParameterizedTypeReference<Map<String, Object>> RESPONSE_TYPE_MAP = new ParameterizedTypeReference<>() {\n\t};\n\n\tprivate static final ParameterizedTypeReference<List<Object>> RESPONSE_TYPE_LIST = new ParameterizedTypeReference<>() {\n\t};\n\n\tprivate static final ParameterizedTypeReference<List<Map<String, Object>>> RESPONSE_TYPE_LIST_MAP = new ParameterizedTypeReference<>() {\n\t};\n\n\tprivate static final JacksonJsonDecoder DECODER;\n\n\tprivate static final JacksonJsonEncoder ENCODER;\n\n\tprivate static final DateTimeFormatter TIMESTAMP_PATTERN = DateTimeFormatter\n\t\t.ofPattern(\"yyyy-MM-dd'T'HH:mm:ss.SSSZ\");\n\n\tstatic {\n\t\tvar om = JsonMapper.builder()\n\t\t\t.disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)\n\t\t\t.disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS);\n\n\t\tDECODER = new JacksonJsonDecoder(om);\n\t\tENCODER = new JacksonJsonEncoder(om);\n\t}\n\n\tprivate LegacyEndpointConverters() {\n\t}\n\n\tpublic static LegacyEndpointConverter health() {\n\t\treturn new LegacyEndpointConverter(Endpoint.HEALTH,\n\t\t\t\tconvertUsing(RESPONSE_TYPE_MAP, RESPONSE_TYPE_MAP, LegacyEndpointConverters::convertHealth));\n\t}\n\n\tpublic static LegacyEndpointConverter env() {\n\t\treturn new LegacyEndpointConverter(Endpoint.ENV,\n\t\t\t\tconvertUsing(RESPONSE_TYPE_MAP, RESPONSE_TYPE_MAP, LegacyEndpointConverters::convertEnv));\n\t}\n\n\tpublic static LegacyEndpointConverter httptrace() {\n\t\treturn new LegacyEndpointConverter(Endpoint.HTTPTRACE,\n\t\t\t\tconvertUsing(RESPONSE_TYPE_LIST_MAP, RESPONSE_TYPE_MAP, LegacyEndpointConverters::convertHttptrace));\n\t}\n\n\tpublic static LegacyEndpointConverter threaddump() {\n\t\treturn new LegacyEndpointConverter(Endpoint.THREADDUMP,\n\t\t\t\tconvertUsing(RESPONSE_TYPE_LIST, RESPONSE_TYPE_MAP, LegacyEndpointConverters::convertThreaddump));\n\t}\n\n\tpublic static LegacyEndpointConverter liquibase() {\n\t\treturn new LegacyEndpointConverter(Endpoint.LIQUIBASE,\n\t\t\t\tconvertUsing(RESPONSE_TYPE_LIST_MAP, RESPONSE_TYPE_MAP, LegacyEndpointConverters::convertLiquibase));\n\t}\n\n\tpublic static LegacyEndpointConverter flyway() {\n\t\treturn new LegacyEndpointConverter(Endpoint.FLYWAY,\n\t\t\t\tconvertUsing(RESPONSE_TYPE_LIST_MAP, RESPONSE_TYPE_MAP, LegacyEndpointConverters::convertFlyway));\n\t}\n\n\tpublic static LegacyEndpointConverter info() {\n\t\treturn new LegacyEndpointConverter(Endpoint.INFO, (flux) -> flux);\n\t}\n\n\tpublic static LegacyEndpointConverter beans() {\n\t\treturn new LegacyEndpointConverter(Endpoint.BEANS,\n\t\t\t\tconvertUsing(RESPONSE_TYPE_LIST_MAP, RESPONSE_TYPE_MAP, LegacyEndpointConverters::convertBeans));\n\t}\n\n\tpublic static LegacyEndpointConverter configprops() {\n\t\treturn new LegacyEndpointConverter(Endpoint.CONFIGPROPS,\n\t\t\t\tconvertUsing(RESPONSE_TYPE_MAP, RESPONSE_TYPE_MAP, LegacyEndpointConverters::convertConfigprops));\n\t}\n\n\tpublic static LegacyEndpointConverter mappings() {\n\t\treturn new LegacyEndpointConverter(Endpoint.MAPPINGS,\n\t\t\t\tconvertUsing(RESPONSE_TYPE_MAP, RESPONSE_TYPE_MAP, LegacyEndpointConverters::convertMappings));\n\t}\n\n\tpublic static LegacyEndpointConverter startup() {\n\t\treturn new LegacyEndpointConverter(Endpoint.STARTUP, (flux) -> flux);\n\t}\n\n\tstatic Map<String, Object> convertMappingHandlerMethod(String methodDeclaration) {\n\t\t// In order to keep parsing logic sane, parameterized types are dropped early\n\t\t// and then we split the method declaration parts on space\n\t\t// Example:\n\t\t// public java.lang.Object bar.Handler.handle(java.util.List<java.lang.String>)\n\t\t// -> \"public\",\"java.lang.Object\",\"bar.Handler.handle(java.util.List)\"\n\t\tString[] declarationParts = methodDeclaration\n\t\t\t// remove parameterized types using the regex below\n\t\t\t.replaceAll(\"<[a-zA-Z1-9$_\\\\.,<> ]*>\", \"\")\n\t\t\t.replace(\" synchronized \", \" \")\n\t\t\t// and split on single space to get the decl parts\n\t\t\t.split(\" \");\n\n\t\t// method -> \"bar.Handler.handle(java.util.List)\"\n\t\tString method = declarationParts[2];\n\n\t\t// methodRef -> \"bar.Handler.handle\"\n\t\tString methodRef = method.substring(0, method.indexOf('('));\n\n\t\t// className -> \"bar.Handler\"\n\t\tString className = methodRef.substring(0, methodRef.lastIndexOf('.'));\n\n\t\t// methodName -> \"handle\"\n\t\tString methodName = methodRef.substring(methodRef.lastIndexOf('.') + 1);\n\n\t\t// In order to simulate descriptors' output we need to read the return type and\n\t\t// the arguments of the handler method. Parameterized types were stripped early\n\t\t// in the parsing process to maintain parsing simplicity.\n\t\t// We also assume object types (see L) which is highly likely for spring-web\n\t\t// @RequestMapping methods but there will be false positives (e.g. on void).\n\t\t//\n\t\t// returnType -> Ljava/lang/Object;\n\n\t\tString returnType = declarationParts[1].replaceFirst(\"^\", \"L\").replace(\".\", \"/\").concat(\";\");\n\n\t\t// In order to simulate descriptors' output we need to read the return type and\n\t\t// the arguments of the handler method. Parameterized types were stripped early\n\t\t// in the parsing process to maintain parsing simplicity.\n\t\t// We also assume object types (see L) which is highly likely for spring-web\n\t\t// @RequestMapping methods but there will be false positives.\n\t\t//\n\t\t// methodArgs -> (Ljava/util/List;)\n\t\tString methodArgs = Arrays.stream(method\n\t\t\t// get what's included in parentheses\n\t\t\t.substring(method.indexOf('('), method.length() - 1)\n\t\t\t// now remove the parenthesis\n\t\t\t.replaceAll(\"[()]\", \"\")\n\t\t\t// and split on comma\n\t\t\t.split(\",\")\n\t\t// then for each argument\n\t\t)\n\t\t\t.map((arg) -> arg\n\t\t\t\t// prepend L char - indicated ObjectType\n\t\t\t\t.replaceFirst(\"^\", \"L\")\n\t\t\t\t// replace dots with slashes\n\t\t\t\t.replace(\".\", \"/\")\n\t\t\t\t// and append ;\n\t\t\t\t.concat(\";\")\n\t\t\t// then join back to simulate MethodDescriptors output\n\t\t\t)\n\t\t\t.collect(joining(\"\", \"(\", \")\"));\n\n\t\tMap<String, Object> handlerMethod = new LinkedHashMap<>();\n\t\thandlerMethod.put(\"className\", className);\n\t\thandlerMethod.put(\"descriptor\", methodArgs + returnType);\n\t\thandlerMethod.put(\"name\", methodName);\n\t\treturn handlerMethod;\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprivate static <S, T> Function<Flux<DataBuffer>, Flux<DataBuffer>> convertUsing(\n\t\t\tParameterizedTypeReference<S> sourceType, ParameterizedTypeReference<T> targetType,\n\t\t\tFunction<S, T> converterFn) {\n\t\treturn (input) -> DECODER.decodeToMono(input, ResolvableType.forType(sourceType), null, null)\n\t\t\t.map((body) -> converterFn.apply((S) body))\n\t\t\t.flatMapMany((output) -> ENCODER.encode(Mono.just(output), new DefaultDataBufferFactory(),\n\t\t\t\t\tResolvableType.forType(targetType), null, null));\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprivate static Map<String, Object> convertHealth(Map<String, Object> body) {\n\t\tMap<String, Object> converted = new LinkedHashMap<>();\n\t\tMap<String, Object> details = new LinkedHashMap<>();\n\n\t\tbody.forEach((key, value) -> {\n\t\t\tif (\"status\".equals(key)) {\n\t\t\t\tconverted.put(key, value);\n\t\t\t}\n\t\t\telse if (value instanceof Map) {\n\t\t\t\tdetails.put(key, convertHealth((Map<String, Object>) value));\n\t\t\t}\n\t\t\telse {\n\t\t\t\tdetails.put(key, value);\n\t\t\t}\n\t\t});\n\t\tif (!details.isEmpty()) {\n\t\t\tconverted.put(\"details\", details);\n\t\t}\n\n\t\treturn converted;\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprivate static Map<String, Object> convertEnv(Map<String, Object> body) {\n\t\tMap<String, Object> converted = new LinkedHashMap<>();\n\t\tList<Map<String, Object>> propertySources = new ArrayList<>(body.size());\n\n\t\tbody.forEach((key, value) -> {\n\t\t\tif (\"profiles\".equals(key)) {\n\t\t\t\tconverted.put(\"activeProfiles\", value);\n\t\t\t}\n\t\t\telse if (value instanceof Map) {\n\t\t\t\tMap<String, Object> values = (Map<String, Object>) value;\n\n\t\t\t\tMap<String, Object> properties = new LinkedHashMap<>();\n\t\t\t\tvalues.forEach((propKey, propValue) -> properties.put(propKey, singletonMap(\"value\", propValue)));\n\n\t\t\t\tMap<String, Object> propertySource = new LinkedHashMap<>();\n\t\t\t\tpropertySource.put(\"name\", key);\n\t\t\t\tpropertySource.put(\"properties\", properties);\n\t\t\t\tpropertySources.add(propertySource);\n\t\t\t}\n\t\t});\n\t\tconverted.put(\"propertySources\", propertySources);\n\n\t\treturn converted;\n\t}\n\n\tprivate static Map<String, Object> convertHttptrace(List<Map<String, Object>> traces) {\n\t\treturn singletonMap(\"traces\", traces.stream().map(LegacyEndpointConverters::convertHttptrace).toList());\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprivate static Map<String, Object> convertHttptrace(Map<String, Object> in) {\n\t\tMap<String, Object> out = new LinkedHashMap<>();\n\t\tout.put(\"timestamp\", getInstant(in.get(\"timestamp\")));\n\t\tMap<String, Object> in_info = (Map<String, Object>) in.get(\"info\");\n\t\tif (in_info != null) {\n\t\t\tMap<String, Object> request = new LinkedHashMap<>();\n\t\t\trequest.put(\"method\", in_info.get(\"method\"));\n\t\t\trequest.put(\"uri\", in_info.get(\"path\"));\n\t\t\tout.put(\"request\", request);\n\n\t\t\tMap<String, Object> response = new LinkedHashMap<>();\n\n\t\t\tMap<String, Object> in_headers = (Map<String, Object>) in_info.get(\"headers\");\n\t\t\tif (in_headers != null) {\n\t\t\t\tMap<String, Object> in_request_headers = (Map<String, Object>) in_headers.get(\"request\");\n\t\t\t\tif (in_request_headers != null) {\n\t\t\t\t\tMap<String, Object> requestHeaders = new LinkedHashMap<>();\n\t\t\t\t\tin_request_headers.forEach((k, v) -> requestHeaders.put(k, singletonList(v)));\n\t\t\t\t\trequest.put(\"headers\", requestHeaders);\n\t\t\t\t}\n\n\t\t\t\tMap<String, Object> in_response_headers = (Map<String, Object>) in_headers.get(\"response\");\n\t\t\t\tif (in_response_headers != null) {\n\t\t\t\t\tif (in_response_headers.get(\"status\") instanceof String status) {\n\t\t\t\t\t\tresponse.put(\"status\", Long.valueOf(status));\n\t\t\t\t\t}\n\n\t\t\t\t\tMap<String, Object> responseHeaders = new LinkedHashMap<>();\n\t\t\t\t\tin_response_headers.forEach((k, v) -> responseHeaders.put(k, singletonList(v)));\n\t\t\t\t\tresponseHeaders.remove(\"status\");\n\t\t\t\t\tresponse.put(\"headers\", responseHeaders);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tout.put(\"response\", response);\n\t\t\tif (in_info.get(\"timeTaken\") instanceof String timeTaken) {\n\t\t\t\tout.put(\"timeTaken\", Long.valueOf(timeTaken));\n\t\t\t}\n\t\t}\n\t\treturn out;\n\t}\n\n\tprivate static Map<String, Object> convertThreaddump(List<Object> threads) {\n\t\treturn singletonMap(\"threads\", threads);\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprivate static Map<String, Object> convertLiquibase(List<Map<String, Object>> reports) {\n\t\tMap<String, Object> liquibaseBeans = reports.stream()\n\t\t\t.collect(toMap((r) -> (String) r.get(\"name\"), (r) -> singletonMap(\"changeSets\", LegacyEndpointConverters\n\t\t\t\t.convertLiquibaseChangesets((List<Map<String, Object>>) r.get(\"changeLogs\")))));\n\n\t\treturn singletonMap(\"contexts\", singletonMap(\"application\", singletonMap(\"liquibaseBeans\", liquibaseBeans)));\n\t}\n\n\tprivate static List<Map<String, Object>> convertLiquibaseChangesets(List<Map<String, Object>> changeSets) {\n\t\treturn changeSets.stream().map((changeset) -> {\n\t\t\tMap<String, Object> converted = new LinkedHashMap<>();\n\t\t\tconverted.put(\"id\", changeset.get(\"ID\"));\n\t\t\tconverted.put(\"author\", changeset.get(\"AUTHOR\"));\n\t\t\tconverted.put(\"changeLog\", changeset.get(\"FILENAME\"));\n\t\t\tif (changeset.get(\"DATEEXECUTED\") instanceof Long dateExecuted) {\n\t\t\t\tconverted.put(\"dateExecuted\", new Date(dateExecuted));\n\t\t\t}\n\t\t\tconverted.put(\"orderExecuted\", changeset.get(\"ORDEREXECUTED\"));\n\t\t\tconverted.put(\"execType\", changeset.get(\"EXECTYPE\"));\n\t\t\tconverted.put(\"checksum\", changeset.get(\"MD5SUM\"));\n\t\t\tconverted.put(\"description\", changeset.get(\"DESCRIPTION\"));\n\t\t\tconverted.put(\"comments\", changeset.get(\"COMMENTS\"));\n\t\t\tconverted.put(\"tag\", changeset.get(\"TAG\"));\n\t\t\tconverted.put(\"contexts\", (changeset.get(\"CONTEXTS\") instanceof String contexts)\n\t\t\t\t\t? new LinkedHashSet<>(asList((contexts).split(\",\\\\s*\"))) : emptySet());\n\t\t\tconverted.put(\"labels\", (changeset.get(\"LABELS\") instanceof String labels)\n\t\t\t\t\t? new LinkedHashSet<>(asList((labels).split(\",\\\\s*\"))) : emptySet());\n\t\t\tconverted.put(\"deploymentId\", changeset.get(\"DEPLOYMENT_ID\"));\n\t\t\treturn converted;\n\t\t}).toList();\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprivate static Map<String, Object> convertFlyway(List<Map<String, Object>> reports) {\n\t\tMap<String, Object> flywayBeans = reports.stream()\n\t\t\t.collect(toMap((r) -> (String) r.get(\"name\"), (r) -> singletonMap(\"migrations\", LegacyEndpointConverters\n\t\t\t\t.convertFlywayMigrations((List<Map<String, Object>>) r.get(\"migrations\")))));\n\t\treturn singletonMap(\"contexts\", singletonMap(\"application\", singletonMap(\"flywayBeans\", flywayBeans)));\n\t}\n\n\tprivate static List<Map<String, Object>> convertFlywayMigrations(List<Map<String, Object>> migrations) {\n\t\treturn migrations.stream().map((migration) -> {\n\t\t\tMap<String, Object> converted = new LinkedHashMap<>(migration);\n\t\t\tif (migration.get(\"installedOn\") instanceof Long installedOn) {\n\t\t\t\tconverted.put(\"installedOn\", new Date(installedOn));\n\t\t\t}\n\t\t\treturn converted;\n\t\t}).toList();\n\t}\n\n\tprivate static Map<String, Object> convertBeans(List<Map<String, Object>> contextBeans) {\n\t\tMap<String, Object> convertedContexts = contextBeans.stream().map((context) -> {\n\t\t\tString contextName = (String) context.get(\"context\");\n\t\t\tString parentId = (String) context.get(\"parent\");\n\n\t\t\t// SB 1.x /beans child application context has\n\t\t\t// itself as parent as well. In order to avoid contexts\n\t\t\t// with same name we simply append .child in that case.\n\t\t\tif (contextName.equals(parentId)) {\n\t\t\t\tcontextName = contextName + \".child\";\n\t\t\t}\n\n\t\t\tList<Map<String, Object>> legacyBeans = (List<Map<String, Object>>) context.get(\"beans\");\n\t\t\tMap<String, Object> convertedBeans = legacyBeans.stream()\n\t\t\t\t.collect(toMap((bean) -> (String) bean.get(\"bean\"), identity()));\n\n\t\t\tMap<String, Object> convertedContext = new LinkedHashMap<>();\n\t\t\tconvertedContext.put(\"contextName\", contextName);\n\t\t\tconvertedContext.put(\"parentId\", parentId);\n\t\t\tconvertedContext.put(\"beans\", convertedBeans);\n\t\t\treturn convertedContext;\n\t\t}).collect(toMap((context) -> (String) context.get(\"contextName\"), identity()));\n\n\t\treturn singletonMap(\"contexts\", convertedContexts);\n\t}\n\n\tprivate static Map<String, Object> convertConfigprops(Map<String, Object> configProps) {\n\t\tMap<String, Object> contexts = new LinkedHashMap<>();\n\n\t\t// SB 1.x /configprops contains a parent entry which\n\t\t// contains the configprops of the parent context.\n\t\t// We put this on a parentContext entry in the\n\t\t// converted response.\n\t\tObject parentConfigProps = configProps.get(\"parent\");\n\t\tif (parentConfigProps != null) {\n\t\t\tconfigProps.remove(\"parent\");\n\t\t\tcontexts.put(\"parentContext\", singletonMap(\"beans\", parentConfigProps));\n\t\t}\n\n\t\tcontexts.put(\"application\", singletonMap(\"beans\", configProps));\n\t\treturn singletonMap(\"contexts\", contexts);\n\t}\n\n\tprivate static Map<String, Object> convertMappings(Map<String, Object> mappings) {\n\t\tList<Map<String, Object>> convertedMappings = mappings.entrySet().stream().map((entry) -> {\n\t\t\tMap<String, Object> convertedMapping = new LinkedHashMap<>();\n\n\t\t\tMap<String, Object> convertedMappingDetails = new LinkedHashMap<>();\n\t\t\tconvertedMapping.put(\"details\", convertedMappingDetails);\n\n\t\t\tString predicate = entry.getKey();\n\t\t\tconvertedMapping.put(\"predicate\", predicate);\n\n\t\t\tString method = (String) ((Map<String, Object>) entry.getValue()).get(\"method\");\n\t\t\tif (method != null) {\n\t\t\t\tconvertedMapping.put(\"handler\", method);\n\t\t\t\tconvertedMappingDetails.put(\"handlerMethod\", convertMappingHandlerMethod(method));\n\t\t\t}\n\n\t\t\tconvertedMappingDetails.put(\"requestMappingConditions\", convertMappingConditions(predicate));\n\n\t\t\treturn convertedMapping;\n\t\t}).toList();\n\n\t\treturn singletonMap(\"contexts\", //\n\t\t\t\tsingletonMap(\"application\", //\n\t\t\t\t\t\tsingletonMap(\"mappings\", //\n\t\t\t\t\t\t\t\tsingletonMap(\"dispatcherServlets\", //\n\t\t\t\t\t\t\t\t\t\tsingletonMap(\"dispatcherServlet\", convertedMappings)))));\n\t}\n\n\tprivate static Map<String, Object> convertMappingConditions(String predicate) {\n\t\t// Before further processing we need to remove the following occurrences\n\t\t// {[, ]}, [, ] and split on comma to get condition pairs from the\n\t\t// predicate string.\n\t\t// Example:\n\t\t// {[/scratch/{ticketId}/selectPrize/{prizeId}],methods=[POST]}\n\t\t// -> \"/scratch/{ticketId}/selectPrize/{prizeId}\",\"methods=POST\"\n\t\tString[] conditionPairs = predicate\n\t\t\t// remove all {[ and ]} pairs\n\t\t\t.replaceAll(\"\\\\{\\\\[|\\\\]\\\\}\", \"\")\n\t\t\t// remove all single brackets [ and ]\n\t\t\t.replaceAll(\"[\\\\[\\\\]]\", \"\")\n\t\t\t// split on comma\n\t\t\t.split(\",\");\n\n\t\tMap<String, Object> conditionsMap = new LinkedHashMap<>();\n\t\tconditionsMap.put(\"consumes\", emptyList());\n\t\tconditionsMap.put(\"headers\", emptyList());\n\t\tconditionsMap.put(\"methods\", emptyList());\n\t\tconditionsMap.put(\"params\", emptyList());\n\t\tconditionsMap.put(\"patterns\", emptyList());\n\t\tconditionsMap.put(\"produces\", emptyList());\n\n\t\tArrays.stream(conditionPairs).forEach((condition) -> {\n\t\t\tString[] conditionParts = condition.split(\"=\");\n\n\t\t\t// URI path patterns part of the details doesn't follow the detail=value\n\t\t\t// semantics it's just the value so splits to a single item array\n\t\t\tboolean isPattern = conditionParts.length == 2;\n\t\t\tString conditionKey = isPattern ? conditionParts[0] : \"patterns\";\n\t\t\tString conditionValueStr = isPattern ? conditionParts[1] : conditionParts[0];\n\n\t\t\t// All detail values in SB1.x are in form of 'str1 || str2' so we split\n\t\t\t// them on ' || '\n\t\t\tString[] split = conditionValueStr.split(\" \\\\|\\\\| \");\n\t\t\tList<Object> conditionValue = Arrays.asList(split);\n\n\t\t\t// Based on conditionKey we may need to apply some transformations,\n\t\t\t// mostly wrapping, of the input values\n\t\t\tswitch (conditionKey) {\n\t\t\t\tcase \"consumes\":\n\t\t\t\tcase \"produces\":\n\t\t\t\t\tconditionValue = conditionValue.stream()\n\t\t\t\t\t\t.map((v) -> singletonMap(\"mediaType\", v))\n\t\t\t\t\t\t.collect(Collectors.toList());\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"headers\":\n\t\t\t\tcase \"params\":\n\t\t\t\tcase \"method\":\n\t\t\t\tcase \"patterns\":\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tconditionsMap.put(conditionKey, conditionValue);\n\t\t});\n\n\t\treturn conditionsMap;\n\t}\n\n\t@Nullable private static Instant getInstant(Object o) {\n\t\ttry {\n\t\t\tif (o instanceof String stringObj) {\n\t\t\t\treturn OffsetDateTime.parse(stringObj, TIMESTAMP_PATTERN).toInstant();\n\t\t\t}\n\t\t\telse if (o instanceof Long longObj) {\n\t\t\t\treturn Instant.ofEpochMilli(longObj);\n\t\t\t}\n\t\t}\n\t\tcatch (DateTimeException | ClassCastException ex) {\n\t\t\treturn null;\n\t\t}\n\t\treturn null;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/client/RefreshInstancesEvent.java",
    "content": "/*\n * Copyright 2014-2024 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client;\n\nimport org.springframework.context.ApplicationEvent;\n\npublic class RefreshInstancesEvent extends ApplicationEvent {\n\n\tpublic RefreshInstancesEvent(Object source) {\n\t\tsuper(source);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/client/cookies/CookieStoreCleanupTrigger.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client.cookies;\n\nimport org.reactivestreams.Publisher;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceDeregisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.services.AbstractEventHandler;\n\n/**\n * Triggers cleanup of {@link de.codecentric.boot.admin.server.domain.entities.Instance}\n * specific data in {@link PerInstanceCookieStore} on receiving an\n * {@link InstanceDeregisteredEvent}.\n */\npublic class CookieStoreCleanupTrigger extends AbstractEventHandler<InstanceDeregisteredEvent> {\n\n\tprivate final PerInstanceCookieStore cookieStore;\n\n\t/**\n\t * Creates a trigger to cleanup the cookie store on deregistering of an\n\t * {@link de.codecentric.boot.admin.server.domain.entities.Instance}.\n\t * @param publisher publisher of {@link InstanceEvent}s events\n\t * @param cookieStore the store to inform about deregistration of an\n\t * {@link de.codecentric.boot.admin.server.domain.entities.Instance}\n\t */\n\tpublic CookieStoreCleanupTrigger(final Publisher<InstanceEvent> publisher,\n\t\t\tfinal PerInstanceCookieStore cookieStore) {\n\t\tsuper(publisher, InstanceDeregisteredEvent.class);\n\n\t\tthis.cookieStore = cookieStore;\n\t}\n\n\t@Override\n\tprotected Publisher<Void> handle(final Flux<InstanceDeregisteredEvent> publisher) {\n\t\treturn publisher.flatMap((event) -> {\n\t\t\tcleanupCookieStore(event);\n\t\t\treturn Mono.empty();\n\t\t});\n\t}\n\n\tprivate void cleanupCookieStore(final InstanceDeregisteredEvent event) {\n\t\tcookieStore.cleanupInstance(event.getInstance());\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/client/cookies/JdkPerInstanceCookieStore.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client.cookies;\n\nimport java.io.IOException;\nimport java.net.CookieHandler;\nimport java.net.CookieManager;\nimport java.net.CookiePolicy;\nimport java.net.CookieStore;\nimport java.net.URI;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.concurrent.ConcurrentHashMap;\n\nimport org.springframework.util.Assert;\nimport org.springframework.util.LinkedMultiValueMap;\nimport org.springframework.util.MultiValueMap;\nimport org.springframework.util.MultiValueMapAdapter;\n\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.web.client.exception.InstanceWebClientException;\n\n/**\n * A {@link PerInstanceCookieStore} that is using per\n * {@link de.codecentric.boot.admin.server.domain.entities.Instance} a {@link CookieStore}\n * from JDK as back end store.\n *\n * As <code>Cookie2</code> cookies are\n * <a href= \"https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie2\">not\n * recommended any more</a> only\n * <a href= \"https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie\">Cookie</a>\n * cookies are supported.\n */\npublic class JdkPerInstanceCookieStore implements PerInstanceCookieStore {\n\n\tprivate static final String REQ_COOKIE_HEADER_KEY = \"Cookie\";\n\n\t/**\n\t * Holds a cookie store per\n\t * {@link de.codecentric.boot.admin.server.domain.entities.Instance}.\n\t */\n\tprivate final Map<InstanceId, CookieHandler> cookieHandlerRegistry = new ConcurrentHashMap<>();\n\n\tprivate final CookiePolicy cookiePolicy;\n\n\t/**\n\t * Creates a new {@link JdkPerInstanceCookieStore}.\n\t *\n\t * Same as\n\t *\n\t * <pre>\n\t * new JdkPerInstanceCookieStore(CookiePolicy.ACCEPT_ORIGINAL_SERVER);\n\t * </pre>\n\t */\n\tpublic JdkPerInstanceCookieStore() {\n\t\tthis(CookiePolicy.ACCEPT_ORIGINAL_SERVER);\n\t}\n\n\t/**\n\t * Creates a new {@link JdkPerInstanceCookieStore} using the given\n\t * {@link CookiePolicy}.\n\t * @param cookiePolicy policy used by created {@link CookieStore}s\n\t */\n\tpublic JdkPerInstanceCookieStore(final CookiePolicy cookiePolicy) {\n\t\tAssert.notNull(cookiePolicy, \"'cookiePolicy' must not be null\");\n\t\tthis.cookiePolicy = cookiePolicy;\n\t}\n\n\t@Override\n\tpublic MultiValueMap<String, String> get(final InstanceId instanceId, final URI requestUri,\n\t\t\tfinal MultiValueMap<String, String> requestHeaders) {\n\t\ttry {\n\t\t\tfinal List<String> rawCookies = getCookieHandler(instanceId).get(requestUri, requestHeaders)\n\t\t\t\t.get(REQ_COOKIE_HEADER_KEY);\n\n\t\t\t// split each rawCookie at first '=' into name/cookieValue and\n\t\t\t// return as MultiValueMap\n\t\t\treturn Optional.ofNullable(rawCookies)\n\t\t\t\t.map((rcList) -> rcList.stream()\n\t\t\t\t\t.map((rc) -> rc.split(\"=\", 2))\n\t\t\t\t\t.collect(LinkedMultiValueMap<String, String>::new, (map, nv) -> map.add(nv[0], nv[1]),\n\t\t\t\t\t\t\tMultiValueMapAdapter::addAll))\n\t\t\t\t.orElseGet(LinkedMultiValueMap<String, String>::new);\n\t\t}\n\t\tcatch (IOException ioe) {\n\t\t\tthrow new InstanceWebClientException(\"Could not get cookies from store.\", ioe);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void put(final InstanceId instanceId, final URI requestUrl, final MultiValueMap<String, String> headers) {\n\t\ttry {\n\t\t\tgetCookieHandler(instanceId).put(requestUrl, headers);\n\t\t}\n\t\tcatch (IOException ioe) {\n\t\t\tthrow new InstanceWebClientException(\"Could not set cookies to store.\", ioe);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void cleanupInstance(final InstanceId instanceId) {\n\t\tcookieHandlerRegistry.computeIfPresent(instanceId, (id, ch) -> null);\n\t}\n\n\t/**\n\t * Returns the stored {@link CookieHandler} for the identified\n\t * {@link de.codecentric.boot.admin.server.domain.entities.Instance} or creates a new\n\t * one, stores and returns it.\n\t * @param instanceId identifies the\n\t * {@link de.codecentric.boot.admin.server.domain.entities.Instance}\n\t * @return {@link CookieHandler} responsible for the given <code>instanceId</code>\n\t */\n\tprotected CookieHandler getCookieHandler(final InstanceId instanceId) {\n\t\treturn cookieHandlerRegistry.computeIfAbsent(instanceId, this::createCookieHandler);\n\t}\n\n\tprotected CookieHandler createCookieHandler(final InstanceId instanceId) {\n\t\treturn new CookieManager(null, cookiePolicy);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/client/cookies/PerInstanceCookieStore.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client.cookies;\n\nimport java.net.URI;\n\nimport org.springframework.util.MultiValueMap;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\n/**\n * A cookie store that stores cookies per {@link Instance}.\n */\npublic interface PerInstanceCookieStore {\n\n\t/**\n\t * Gets all the applicable cookies (cookie name =&gt; string representation of cookie)\n\t * for the given <code>instanceId</code> and the specified uri in the request header.\n\t *\n\t * The URI passed as an argument specifies the intended use for the cookies.\n\t * @param instanceId identifies the web client instance\n\t * @param requestUri a URI representing the intended use for the cookies\n\t * @param requestHeaders a Map from request header field names to lists of field\n\t * values representing the current request\n\t * @return an immutable map from cookie names to text representations of cookies to be\n\t * included into a request header\n\t */\n\tMultiValueMap<String, String> get(InstanceId instanceId, URI requestUri,\n\t\t\tMultiValueMap<String, String> requestHeaders);\n\n\t/**\n\t * Stores all the applicable cookies (examples are response header fields that are\n\t * named <code>Set-Cookie</code>) present in the response headers.\n\t * @param instanceId identifies the web client instance\n\t * @param requestUri an URI where the cookies come from\n\t * @param responseHeaders a map from field names to lists of field values representing\n\t * the response header fields\n\t */\n\tvoid put(InstanceId instanceId, URI requestUri, MultiValueMap<String, String> responseHeaders);\n\n\t/**\n\t * Informs the store that the cookies of the given <code>instanceId</code> could be\n\t * removed.\n\t * @param instanceId identifies the {@link Instance}\n\t */\n\tvoid cleanupInstance(InstanceId instanceId);\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/client/cookies/package-info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n@NullMarked\npackage de.codecentric.boot.admin.server.web.client.cookies;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/client/exception/InstanceWebClientException.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client.exception;\n\npublic class InstanceWebClientException extends RuntimeException {\n\n\tpublic InstanceWebClientException(String message) {\n\t\tsuper(message);\n\t}\n\n\tpublic InstanceWebClientException(String message, Throwable cause) {\n\t\tsuper(message, cause);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/client/exception/ResolveEndpointException.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client.exception;\n\npublic class ResolveEndpointException extends InstanceWebClientException {\n\n\tpublic ResolveEndpointException(String message) {\n\t\tsuper(message);\n\t}\n\n\tpublic ResolveEndpointException(String message, Throwable cause) {\n\t\tsuper(message, cause);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/client/exception/ResolveInstanceException.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client.exception;\n\npublic class ResolveInstanceException extends InstanceWebClientException {\n\n\tpublic ResolveInstanceException(String message) {\n\t\tsuper(message);\n\t}\n\n\tpublic ResolveInstanceException(String message, Throwable cause) {\n\t\tsuper(message, cause);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/client/exception/package-info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Spring Boot Admin Server - client exceptions package.\n\n@NullMarked\npackage de.codecentric.boot.admin.server.web.client.exception;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/client/package-info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Spring Boot Admin Server - instance client package.\n\n@NullMarked\npackage de.codecentric.boot.admin.server.web.client;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/client/reactive/CompositeReactiveHttpHeadersProvider.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client.reactive;\n\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.List;\n\nimport org.springframework.http.HttpHeaders;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\n\npublic class CompositeReactiveHttpHeadersProvider implements ReactiveHttpHeadersProvider {\n\n\tprivate final Collection<ReactiveHttpHeadersProvider> delegates;\n\n\tpublic CompositeReactiveHttpHeadersProvider(Collection<ReactiveHttpHeadersProvider> delegates) {\n\t\tthis.delegates = delegates;\n\t}\n\n\t@Override\n\tpublic Mono<HttpHeaders> getHeaders(Instance instance) {\n\t\tList<Mono<HttpHeaders>> headers = delegates.stream()\n\t\t\t.map((reactiveHttpHeadersProvider) -> reactiveHttpHeadersProvider.getHeaders(instance))\n\t\t\t.toList();\n\n\t\treturn Mono.zip(headers, this::mergeMonosToHeaders);\n\t}\n\n\tprivate HttpHeaders mergeMonosToHeaders(Object[] e) {\n\t\treturn Arrays.stream(e).map(HttpHeaders.class::cast).reduce(new HttpHeaders(), (h1, h2) -> {\n\t\t\th1.addAll(h2);\n\t\t\treturn h1;\n\t\t});\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/client/reactive/ReactiveHttpHeadersProvider.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client.reactive;\n\nimport org.springframework.http.HttpHeaders;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\n\n/**\n * Is responsible to provide the {@link HttpHeaders} used to interact with the given\n * {@link Instance}.\n */\npublic interface ReactiveHttpHeadersProvider {\n\n\tMono<HttpHeaders> getHeaders(Instance instance);\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/package-info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Spring Boot Admin Server - web package.\n\n@NullMarked\npackage de.codecentric.boot.admin.server.web;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/reactive/AdminControllerHandlerMapping.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.reactive;\n\nimport java.lang.reflect.Method;\n\nimport org.springframework.core.annotation.AnnotatedElementUtils;\nimport org.springframework.util.StringUtils;\nimport org.springframework.web.reactive.result.condition.PatternsRequestCondition;\nimport org.springframework.web.reactive.result.method.RequestMappingInfo;\nimport org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;\n\nimport de.codecentric.boot.admin.server.web.AdminController;\nimport de.codecentric.boot.admin.server.web.PathUtils;\n\npublic class AdminControllerHandlerMapping extends RequestMappingHandlerMapping {\n\n\tprivate final String adminContextPath;\n\n\tpublic AdminControllerHandlerMapping(String adminContextPath) {\n\t\tthis.adminContextPath = adminContextPath;\n\t}\n\n\t@Override\n\tprotected boolean isHandler(Class<?> beanType) {\n\t\treturn AnnotatedElementUtils.hasAnnotation(beanType, AdminController.class);\n\t}\n\n\t@Override\n\tprotected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {\n\t\tsuper.registerHandlerMethod(handler, method, withPrefix(mapping));\n\t}\n\n\tprivate RequestMappingInfo withPrefix(RequestMappingInfo mapping) {\n\t\tif (!StringUtils.hasText(adminContextPath)) {\n\t\t\treturn mapping;\n\t\t}\n\t\treturn mapping.mutate().paths(withNewPatterns(mapping.getPatternsCondition())).build();\n\t}\n\n\tprivate String[] withNewPatterns(PatternsRequestCondition patternsRequestCondition) {\n\t\treturn patternsRequestCondition.getPatterns()\n\t\t\t.stream()\n\t\t\t.map((pattern) -> PathUtils.normalizePath(adminContextPath + pattern))\n\t\t\t.toArray(String[]::new);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/reactive/InstancesProxyController.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.reactive;\n\nimport java.net.URI;\nimport java.util.Set;\n\nimport org.springframework.core.io.buffer.DataBuffer;\nimport org.springframework.core.io.buffer.DataBufferFactory;\nimport org.springframework.core.io.buffer.DataBufferUtils;\nimport org.springframework.core.io.buffer.DefaultDataBufferFactory;\nimport org.springframework.http.server.reactive.ServerHttpRequest;\nimport org.springframework.http.server.reactive.ServerHttpResponse;\nimport org.springframework.util.AntPathMatcher;\nimport org.springframework.util.PathMatcher;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.reactive.function.BodyExtractors;\nimport org.springframework.web.reactive.function.BodyInserters;\nimport org.springframework.web.util.UriComponentsBuilder;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.services.InstanceRegistry;\nimport de.codecentric.boot.admin.server.web.AdminController;\nimport de.codecentric.boot.admin.server.web.HttpHeaderFilter;\nimport de.codecentric.boot.admin.server.web.InstanceWebProxy;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\n\n/**\n * Http Handler for proxied requests\n */\n@AdminController\npublic class InstancesProxyController {\n\n\tprivate static final String INSTANCE_MAPPED_PATH = \"/instances/{instanceId}/actuator/**\";\n\n\tprivate static final String APPLICATION_MAPPED_PATH = \"/applications/{applicationName}/actuator/**\";\n\n\tprivate final PathMatcher pathMatcher = new AntPathMatcher();\n\n\tprivate final DataBufferFactory bufferFactory = new DefaultDataBufferFactory();\n\n\tprivate final InstanceRegistry registry;\n\n\tprivate final InstanceWebProxy instanceWebProxy;\n\n\tprivate final String adminContextPath;\n\n\tprivate final HttpHeaderFilter httpHeadersFilter;\n\n\tpublic InstancesProxyController(String adminContextPath, Set<String> ignoredHeaders, InstanceRegistry registry,\n\t\t\tInstanceWebClient instanceWebClient) {\n\t\tthis.adminContextPath = adminContextPath;\n\t\tthis.registry = registry;\n\t\tthis.httpHeadersFilter = new HttpHeaderFilter(ignoredHeaders);\n\t\tthis.instanceWebProxy = new InstanceWebProxy(instanceWebClient);\n\t}\n\n\t@RequestMapping(path = INSTANCE_MAPPED_PATH, method = { RequestMethod.GET, RequestMethod.HEAD, RequestMethod.POST,\n\t\t\tRequestMethod.PUT, RequestMethod.PATCH, RequestMethod.DELETE, RequestMethod.OPTIONS })\n\tpublic Mono<Void> endpointProxy(@PathVariable(\"instanceId\") String instanceId, ServerHttpRequest request,\n\t\t\tServerHttpResponse response) {\n\t\tInstanceWebProxy.ForwardRequest fwdRequest = createForwardRequest(request, request.getBody(),\n\t\t\t\tthis.adminContextPath + INSTANCE_MAPPED_PATH);\n\n\t\treturn this.instanceWebProxy.forward(this.registry.getInstance(InstanceId.of(instanceId)), fwdRequest,\n\t\t\t\t(clientResponse) -> {\n\t\t\t\t\tresponse.setStatusCode(clientResponse.statusCode());\n\t\t\t\t\tresponse.getHeaders()\n\t\t\t\t\t\t.addAll(this.httpHeadersFilter.filterHeaders(clientResponse.headers().asHttpHeaders()));\n\t\t\t\t\treturn response.writeAndFlushWith(clientResponse.body(BodyExtractors.toDataBuffers()).window(1));\n\t\t\t\t});\n\t}\n\n\t@ResponseBody\n\t@RequestMapping(path = APPLICATION_MAPPED_PATH, method = { RequestMethod.GET, RequestMethod.HEAD,\n\t\t\tRequestMethod.POST, RequestMethod.PUT, RequestMethod.PATCH, RequestMethod.DELETE, RequestMethod.OPTIONS })\n\tpublic Flux<InstanceWebProxy.InstanceResponse> endpointProxy(\n\t\t\t@PathVariable(\"applicationName\") String applicationName, ServerHttpRequest request) {\n\n\t\tFlux<DataBuffer> cachedBody = request.getBody().map((b) -> {\n\t\t\tDataBuffer dataBuffer = this.bufferFactory.allocateBuffer(b.readableByteCount());\n\t\t\ttry (var iterator = b.readableByteBuffers()) {\n\t\t\t\titerator.forEachRemaining(dataBuffer::write);\n\t\t\t}\n\t\t\tDataBufferUtils.release(b);\n\t\t\treturn dataBuffer;\n\t\t}).cache();\n\n\t\tInstanceWebProxy.ForwardRequest fwdRequest = createForwardRequest(request, cachedBody,\n\t\t\t\tthis.adminContextPath + APPLICATION_MAPPED_PATH);\n\n\t\treturn this.instanceWebProxy.forward(this.registry.getInstances(applicationName), fwdRequest);\n\t}\n\n\tprivate InstanceWebProxy.ForwardRequest createForwardRequest(ServerHttpRequest request, Flux<DataBuffer> cachedBody,\n\t\t\tString pathPattern) {\n\t\tString localPath = this.getLocalPath(pathPattern, request);\n\t\tURI uri = UriComponentsBuilder.fromPath(localPath).query(request.getURI().getRawQuery()).build(true).toUri();\n\t\treturn InstanceWebProxy.ForwardRequest.builder()\n\t\t\t.uri(uri)\n\t\t\t.method(request.getMethod())\n\t\t\t.headers(this.httpHeadersFilter.filterHeaders(request.getHeaders()))\n\t\t\t.body(BodyInserters.fromDataBuffers(cachedBody))\n\t\t\t.build();\n\t}\n\n\tprivate String getLocalPath(String pathPattern, ServerHttpRequest request) {\n\t\tString pathWithinApplication = request.getPath().pathWithinApplication().value();\n\t\treturn this.pathMatcher.extractPathWithinPattern(pathPattern, pathWithinApplication);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/reactive/package-info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Spring Boot Admin Server - react native package.\n\n@NullMarked\npackage de.codecentric.boot.admin.server.web.reactive;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/servlet/AdminControllerHandlerMapping.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.servlet;\n\nimport java.lang.reflect.Method;\nimport java.util.Set;\n\nimport org.springframework.core.annotation.AnnotatedElementUtils;\nimport org.springframework.util.StringUtils;\nimport org.springframework.web.servlet.mvc.method.RequestMappingInfo;\nimport org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;\nimport org.springframework.web.util.pattern.PathPattern;\n\nimport de.codecentric.boot.admin.server.web.AdminController;\nimport de.codecentric.boot.admin.server.web.PathUtils;\n\npublic class AdminControllerHandlerMapping extends RequestMappingHandlerMapping {\n\n\tprivate final String adminContextPath;\n\n\tpublic AdminControllerHandlerMapping(String adminContextPath) {\n\t\tthis.adminContextPath = adminContextPath;\n\t}\n\n\t@Override\n\tprotected boolean isHandler(Class<?> beanType) {\n\t\treturn AnnotatedElementUtils.hasAnnotation(beanType, AdminController.class);\n\t}\n\n\t@Override\n\tprotected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {\n\t\tsuper.registerHandlerMethod(handler, method, withPrefix(mapping));\n\t}\n\n\tprivate RequestMappingInfo withPrefix(RequestMappingInfo mapping) {\n\t\tif (!StringUtils.hasText(this.adminContextPath)) {\n\t\t\treturn mapping;\n\t\t}\n\n\t\tRequestMappingInfo.Builder mutate = mapping.mutate();\n\n\t\treturn mutate.paths(withNewPatterns(mapping.getPathPatternsCondition().getPatterns())).build();\n\t}\n\n\tprivate String[] withNewPatterns(Set<PathPattern> patterns) {\n\t\treturn patterns.stream()\n\t\t\t.map((pattern) -> PathUtils.normalizePath(this.adminContextPath + pattern.getPatternString()))\n\t\t\t.toArray(String[]::new);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/servlet/InstancesProxyController.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.servlet;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.net.URI;\nimport java.util.Set;\n\nimport jakarta.servlet.AsyncContext;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.springframework.core.io.buffer.DataBuffer;\nimport org.springframework.core.io.buffer.DataBufferFactory;\nimport org.springframework.core.io.buffer.DataBufferUtils;\nimport org.springframework.core.io.buffer.DefaultDataBufferFactory;\nimport org.springframework.http.server.ServerHttpResponse;\nimport org.springframework.http.server.ServletServerHttpRequest;\nimport org.springframework.http.server.ServletServerHttpResponse;\nimport org.springframework.util.AntPathMatcher;\nimport org.springframework.util.PathMatcher;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.reactive.function.BodyExtractors;\nimport org.springframework.web.reactive.function.BodyInserters;\nimport org.springframework.web.servlet.HandlerMapping;\nimport org.springframework.web.util.UriComponentsBuilder;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.services.InstanceRegistry;\nimport de.codecentric.boot.admin.server.web.AdminController;\nimport de.codecentric.boot.admin.server.web.HttpHeaderFilter;\nimport de.codecentric.boot.admin.server.web.InstanceWebProxy;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\n\n/**\n * Http Handler for proxied requests\n */\n@AdminController\npublic class InstancesProxyController {\n\n\tprivate static final String INSTANCE_MAPPED_PATH = \"/instances/{instanceId}/actuator/**\";\n\n\tprivate static final String APPLICATION_MAPPED_PATH = \"/applications/{applicationName}/actuator/**\";\n\n\tprivate final DataBufferFactory bufferFactory = new DefaultDataBufferFactory();\n\n\tprivate final PathMatcher pathMatcher = new AntPathMatcher();\n\n\tprivate final InstanceWebProxy instanceWebProxy;\n\n\tprivate final HttpHeaderFilter httpHeadersFilter;\n\n\tprivate final InstanceRegistry registry;\n\n\tprivate final String adminContextPath;\n\n\tpublic InstancesProxyController(String adminContextPath, Set<String> ignoredHeaders, InstanceRegistry registry,\n\t\t\tInstanceWebClient instanceWebClient) {\n\t\tthis.adminContextPath = adminContextPath;\n\t\tthis.registry = registry;\n\t\tthis.httpHeadersFilter = new HttpHeaderFilter(ignoredHeaders);\n\t\tthis.instanceWebProxy = new InstanceWebProxy(instanceWebClient);\n\t}\n\n\t@ResponseBody\n\t@RequestMapping(path = INSTANCE_MAPPED_PATH, method = { RequestMethod.GET, RequestMethod.HEAD, RequestMethod.POST,\n\t\t\tRequestMethod.PUT, RequestMethod.PATCH, RequestMethod.DELETE, RequestMethod.OPTIONS })\n\tpublic void instanceProxy(@PathVariable(\"instanceId\") String instanceId, HttpServletRequest servletRequest) {\n\t\t// start async because we will commit from different thread.\n\t\t// otherwise incorrect thread local objects (session and security context) will be\n\t\t// stored.\n\t\t// check for example\n\t\t// org.springframework.security.web.context.HttpSessionSecurityContextRepository.SaveToSessionRequestWrapper.startAsync()\n\t\tAsyncContext asyncContext = servletRequest.startAsync();\n\t\tasyncContext.setTimeout(-1); // no timeout because instanceWebProxy will handle it\n\t\t// for us\n\t\ttry {\n\t\t\tServletServerHttpRequest request = new ServletServerHttpRequest(\n\t\t\t\t\t(HttpServletRequest) asyncContext.getRequest());\n\t\t\tFlux<DataBuffer> requestBody = DataBufferUtils.readInputStream(request::getBody, this.bufferFactory, 4096);\n\t\t\tInstanceWebProxy.ForwardRequest fwdRequest = createForwardRequest(request, requestBody,\n\t\t\t\t\tthis.adminContextPath + INSTANCE_MAPPED_PATH);\n\n\t\t\tthis.instanceWebProxy\n\t\t\t\t.forward(this.registry.getInstance(InstanceId.of(instanceId)), fwdRequest, (clientResponse) -> {\n\t\t\t\t\tServerHttpResponse response = new ServletServerHttpResponse(\n\t\t\t\t\t\t\t(HttpServletResponse) asyncContext.getResponse());\n\t\t\t\t\tresponse.setStatusCode(clientResponse.statusCode());\n\t\t\t\t\tresponse.getHeaders()\n\t\t\t\t\t\t.addAll(this.httpHeadersFilter.filterHeaders(clientResponse.headers().asHttpHeaders()));\n\t\t\t\t\ttry {\n\t\t\t\t\t\tOutputStream responseBody = response.getBody();\n\t\t\t\t\t\tresponse.flush();\n\t\t\t\t\t\treturn clientResponse.body(BodyExtractors.toDataBuffers())\n\t\t\t\t\t\t\t.window(1)\n\t\t\t\t\t\t\t.concatMap((body) -> writeAndFlush(body, responseBody))\n\t\t\t\t\t\t\t.then();\n\t\t\t\t\t}\n\t\t\t\t\tcatch (IOException ex) {\n\t\t\t\t\t\treturn Mono.error(ex);\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t// We need to explicitly block so the headers are received and written\n\t\t\t\t// before any async dispatch otherwise the FrameworkServlet will add\n\t\t\t\t// wrong\n\t\t\t\t// Allow header for OPTIONS request\n\t\t\t\t.block();\n\t\t}\n\t\tfinally {\n\t\t\tasyncContext.complete();\n\t\t}\n\t}\n\n\t@ResponseBody\n\t@RequestMapping(path = APPLICATION_MAPPED_PATH, method = { RequestMethod.GET, RequestMethod.HEAD,\n\t\t\tRequestMethod.POST, RequestMethod.PUT, RequestMethod.PATCH, RequestMethod.DELETE, RequestMethod.OPTIONS })\n\tpublic Flux<InstanceWebProxy.InstanceResponse> endpointProxy(\n\t\t\t@PathVariable(\"applicationName\") String applicationName, HttpServletRequest servletRequest) {\n\n\t\tServletServerHttpRequest request = new ServletServerHttpRequest(servletRequest);\n\t\tFlux<DataBuffer> cachedBody = DataBufferUtils.readInputStream(request::getBody, this.bufferFactory, 4096)\n\t\t\t.cache();\n\n\t\tInstanceWebProxy.ForwardRequest fwdRequest = createForwardRequest(request, cachedBody,\n\t\t\t\tthis.adminContextPath + APPLICATION_MAPPED_PATH);\n\t\treturn this.instanceWebProxy.forward(this.registry.getInstances(applicationName), fwdRequest);\n\t}\n\n\tprivate InstanceWebProxy.ForwardRequest createForwardRequest(ServletServerHttpRequest request,\n\t\t\tFlux<DataBuffer> body, String pathPattern) {\n\t\tString endpointLocalPath = this.getLocalPath(pathPattern, request);\n\t\tURI uri = UriComponentsBuilder.fromPath(endpointLocalPath)\n\t\t\t.query(request.getURI().getRawQuery())\n\t\t\t.build(true)\n\t\t\t.toUri();\n\n\t\treturn InstanceWebProxy.ForwardRequest.builder()\n\t\t\t.uri(uri)\n\t\t\t.method(request.getMethod())\n\t\t\t.headers(this.httpHeadersFilter.filterHeaders(request.getHeaders()))\n\t\t\t.body(BodyInserters.fromDataBuffers(body))\n\t\t\t.build();\n\t}\n\n\tprivate String getLocalPath(String pathPattern, ServletServerHttpRequest request) {\n\t\tString pathWithinApplication = request.getServletRequest()\n\t\t\t.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)\n\t\t\t.toString();\n\t\treturn this.pathMatcher.extractPathWithinPattern(pathPattern, pathWithinApplication);\n\t}\n\n\tprivate Mono<Void> writeAndFlush(Flux<DataBuffer> body, OutputStream responseBody) {\n\t\treturn DataBufferUtils.write(body, responseBody).map(DataBufferUtils::release).then(Mono.create((sink) -> {\n\t\t\ttry {\n\t\t\t\tresponseBody.flush();\n\t\t\t\tsink.success();\n\t\t\t}\n\t\t\tcatch (IOException ex) {\n\t\t\t\tsink.error(ex);\n\t\t\t}\n\t\t}));\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/servlet/package-info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Spring Boot Admin Server - servlet package.\n\n@NullMarked\npackage de.codecentric.boot.admin.server.web.servlet;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json",
    "content": "{\n  \"groups\": [\n  ],\n  \"properties\": [\n    {\n      \"name\": \"spring.boot.admin.hazelcast.enabled\",\n      \"type\": \"java.lang.Boolean\",\n      \"description\": \"Enable Hazelcast support.\",\n      \"defaultValue\": \"true\"\n    },\n    {\n      \"name\": \"spring.boot.admin.hazelcast.event-store\",\n      \"type\": \"java.lang.String\",\n      \"description\": \"Name of backing Hazelcast-Map for storing the instance events.\",\n      \"defaultValue\": \"spring-boot-admin-application-store\"\n    },\n    {\n      \"name\": \"spring.boot.admin.hazelcast.sent-notifications\",\n      \"type\": \"java.lang.String\",\n      \"description\": \"Name of backing Hazelcast-Map for storing the sent notifications.\",\n      \"defaultValue\": \"spring-boot-admin-sent-notifications\"\n    },\n    {\n      \"name\": \"spring.boot.admin.monitor.period\",\n      \"type\": \"java.lang.Long\",\n      \"deprecation\": {\n        \"level\": \"warning\",\n        \"replacement\": \"spring.boot.admin.monitor.status-interval\"\n      }\n    },\n    {\n      \"name\": \"spring.boot.admin.monitor.read-timeout\",\n      \"type\": \"java.lang.Long\",\n      \"deprecation\": {\n        \"replacement\": \"spring.boot.admin.monitor.default-timeout\",\n        \"level\": \"error\"\n      }\n    },\n    {\n      \"name\": \"spring.boot.admin.monitor.connect-timeout\",\n      \"type\": \"java.lang.Long\",\n      \"deprecation\": {\n        \"replacement\": \"spring.boot.admin.monitor.default-timeout\",\n        \"level\": \"error\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "de.codecentric.boot.admin.server.config.AdminServerAutoConfiguration\nde.codecentric.boot.admin.server.config.AdminServerNotifierAutoConfiguration\nde.codecentric.boot.admin.server.config.AdminServerHazelcastAutoConfiguration\nde.codecentric.boot.admin.server.config.AdminServerCloudFoundryAutoConfiguration\n"
  },
  {
    "path": "spring-boot-admin-server/src/main/resources/META-INF/spring-boot-admin-server/mail/status-changed.html",
    "content": "<!DOCTYPE html>\n<!--\n  ~ Copyright 2014-2018 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<html xmlns:th=\"http://www.thymeleaf.org\">\n<head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n    <style>\n        h1, h2, h3, h4, h5, h6 {\n            font-weight: 400\n        }\n\n        ul {\n            list-style: none\n        }\n\n        html {\n            box-sizing: border-box\n        }\n\n        *, :after, :before {\n            box-sizing: inherit\n        }\n\n        table {\n            border-collapse: collapse;\n            border-spacing: 0\n        }\n\n        td, th {\n            text-align: left\n        }\n\n        body, button {\n            font-family: BlinkMacSystemFont, -apple-system, \"Segoe UI\", Roboto, Oxygen, Ubuntu, Cantarell, \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\", Helvetica, Arial, sans-serif\n        }\n\n        code, pre {\n            -moz-osx-font-smoothing: auto;\n            -webkit-font-smoothing: auto;\n            font-family: monospace\n        }\n    </style>\n</head>\n<body>\n<th:block th:remove=\"all\">\n    <!-- This block will not appear in the body and is used for the subject -->\n    <th:block th:remove=\"tag\" th:fragment=\"subject\">\n        [[${instance.registration.name}]] ([[(${instance.id})]]) is [[${event.statusInfo.status}]]\n    </th:block>\n</th:block>\n<h1><span th:text=\"${instance.registration.name}\"/> (<span th:text=\"${instance.id}\"/>)\n    is <span th:text=\"${event.statusInfo.status}\"/>\n</h1>\n<p>\n    Instance <a th:if=\"${baseUrl}\" th:href=\"@{${baseUrl + '/#/instances/' + instance.id + '/'}}\"><span\n        th:text=\"${instance.id}\"/></a>\n    <span th:unless=\"${baseUrl}\" th:text=\"${instance.id}\"/>\n    changed status from <span th:text=\"${lastStatus}\"/> to <span th:text=\"${event.statusInfo.status}\"/>\n</p>\n\n<h2>Status Details</h2>\n<dl th:fragment=\"statusDetails\" th:with=\"details = ${details ?: event.statusInfo.details}\">\n    <th:block th:each=\"detail : ${details}\">\n        <dt th:text=\"${detail.key}\"/>\n        <dd th:unless=\"${detail.value instanceof T(java.util.Map)}\" th:text=\"${detail.value}\"/>\n        <dd th:if=\"${detail.value instanceof T(java.util.Map)}\">\n            <dl th:replace=\"${#execInfo.templateName} :: statusDetails (details = ${detail.value})\"/>\n        </dd>\n    </th:block>\n</dl>\n\n<h2>Registration</h2>\n<table>\n    <tr th:if=\"${instance.registration.serviceUrl}\">\n        <td>Service Url</td>\n        <td>\n            <a th:href=\"${instance.registration.serviceUrl}\" th:text=\"${instance.registration.serviceUrl}\"></a>\n        </td>\n    </tr>\n    <tr>\n        <td>Health Url</td>\n        <td>\n            <a th:href=\"${instance.registration.healthUrl}\" th:text=\"${instance.registration.healthUrl}\"></a>\n        </td>\n    </tr>\n    <tr th:if=\"${instance.registration.managementUrl}\">\n        <td>Management Url</td>\n        <td>\n            <a th:href=\"${instance.registration.managementUrl}\" th:text=\"${instance.registration.managementUrl}\"></a>\n        </td>\n    </tr>\n</table>\n</body>\n</html>\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/AbstractAdminApplicationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server;\n\nimport java.net.URI;\nimport java.time.Duration;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport lombok.Getter;\nimport org.json.JSONObject;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.http.MediaType;\nimport org.springframework.http.codec.json.JacksonJsonDecoder;\nimport org.springframework.http.codec.json.JacksonJsonEncoder;\nimport org.springframework.test.web.reactive.server.WebTestClient;\nimport org.springframework.web.reactive.function.client.ExchangeStrategies;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\nimport tools.jackson.databind.json.JsonMapper;\nimport tools.jackson.datatype.jsonorg.JsonOrgModule;\n\nimport de.codecentric.boot.admin.server.domain.values.Registration;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n@Getter\npublic abstract class AbstractAdminApplicationTest {\n\n\tprivate WebTestClient webClient;\n\n\tprivate int port;\n\n\tpublic void setUp(int port) {\n\t\tthis.port = port;\n\t\tthis.webClient = createWebClient(port);\n\t}\n\n\t@Test\n\tpublic void lifecycle() {\n\t\tAtomicReference<URI> location = new AtomicReference<>();\n\n\t\tStepVerifier.create(getEventStream().log()).expectSubscription().then(() -> {\n\t\t\tStepVerifier.create(listEmptyInstances()).expectNext(true).verifyComplete();\n\t\t\tlocation.set(registerInstance());\n\t\t})\n\t\t\t.assertNext((event) -> assertThat(event.opt(\"type\")).isEqualTo(\"REGISTERED\"))\n\t\t\t.assertNext((event) -> assertThat(event.opt(\"type\")).isEqualTo(\"STATUS_CHANGED\"))\n\t\t\t.assertNext((event) -> assertThat(event.opt(\"type\")).isEqualTo(\"ENDPOINTS_DETECTED\"))\n\t\t\t.assertNext((event) -> assertThat(event.opt(\"type\")).isEqualTo(\"INFO_CHANGED\"))\n\t\t\t.then(() -> {\n\t\t\t\tStepVerifier.create(getInstance(location.get())).expectNext(true).verifyComplete();\n\t\t\t\tStepVerifier.create(listInstances()).expectNext(true).verifyComplete();\n\t\t\t\tStepVerifier.create(deregisterInstance(location.get())).expectNext(true).verifyComplete();\n\t\t\t})\n\t\t\t.assertNext((event) -> assertThat(event.opt(\"type\")).isEqualTo(\"DEREGISTERED\"))\n\t\t\t.then(() -> StepVerifier.create(listEmptyInstances()).expectNext(true).verifyComplete())\n\t\t\t.thenCancel()\n\t\t\t.verify(Duration.ofSeconds(120));\n\t}\n\n\tprotected Flux<JSONObject> getEventStream() {\n\t\t//@formatter:off\n\t\treturn this.webClient.get().uri(\"/instances/events\")\n\t\t\t\t\t\t\t.accept(MediaType.TEXT_EVENT_STREAM)\n\t\t\t\t\t\t\t.exchange()\n\t\t\t\t\t\t\t.expectStatus().isOk()\n\t\t\t\t\t\t\t.expectHeader().contentTypeCompatibleWith(MediaType.TEXT_EVENT_STREAM)\n\t\t\t\t\t\t\t.returnResult(JSONObject.class).getResponseBody();\n\t\t//@formatter:on\n\t}\n\n\tprotected URI registerInstance() {\n\t\t//@formatter:off\n\t\treturn this.webClient.post().uri(\"/instances\")\n\t\t\t\t\t\t\t.contentType(MediaType.APPLICATION_JSON)\n\t\t\t\t\t\t\t.bodyValue(createRegistration())\n\t\t\t\t\t\t\t.exchange()\n\t\t\t\t\t\t\t.expectStatus().isCreated()\n\t\t\t\t\t\t\t.expectHeader().valueMatches(\"location\", \"^http://localhost:\" + this.port + \"/instances/[a-f0-9]+$\")\n\t\t\t\t\t\t\t.returnResult(Void.class).getResponseHeaders().getLocation();\n\t\t//@formatter:on\n\t}\n\n\tprotected Mono<Boolean> getInstance(URI uri) {\n\t\t//@formatter:off\n\t\treturn this.webClient.get().uri(uri)\n\t\t\t\t\t.accept(MediaType.APPLICATION_JSON)\n\t\t\t\t\t.exchange()\n\t\t\t\t\t.returnResult(String.class).getResponseBody().single()\n\t\t\t\t\t.map((body) -> {\n\t\t\t\t\t\tassertThat(body).contains(\"\\\"name\\\":\\\"Test-Instance\\\"\");\n\t\t\t\t\t\tassertThat(body).contains(\"\\\"status\\\":\\\"UP\\\"\");\n\t\t\t\t\t\tassertThat(body).contains(\"\\\"test\\\":\\\"foobar\\\"\");\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t});\n\t\t//@formatter:on\n\t}\n\n\tprotected Mono<Boolean> listInstances() {\n\t\t//@formatter:off\n\t\treturn this.webClient.get().uri(\"/instances\")\n\t\t\t\t\t.accept(MediaType.APPLICATION_JSON)\n\t\t\t\t\t.exchange()\n\t\t\t\t\t.returnResult(String.class).getResponseBody().single()\n\t\t\t\t\t.map((body) -> {\n\t\t\t\t\t\tassertThat(body).contains(\"\\\"name\\\":\\\"Test-Instance\\\"\");\n\t\t\t\t\t\tassertThat(body).contains(\"\\\"status\\\":\\\"UP\\\"\");\n\t\t\t\t\t\tassertThat(body).contains(\"\\\"test\\\":\\\"foobar\\\"\");\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t});\n\t\t//@formatter:on\n\t}\n\n\tprotected Mono<Boolean> listEmptyInstances() {\n\t\t//@formatter:off\n\t\treturn this.webClient.get().uri(\"/instances\")\n\t\t\t\t\t.accept(MediaType.APPLICATION_JSON)\n\t\t\t\t\t.exchange()\n\t\t\t\t\t.returnResult(String.class).getResponseBody()\n\t\t\t\t\t.collectList()\n\t\t\t\t\t.map((list) -> {\n\t\t\t\t\t\tassertThat(list).hasSize(1);\n\t\t\t\t\t\tassertThat(list.get(0)).isEqualTo(\"[]\");\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t});\n\t\t//@formatter:on\n\t}\n\n\tprotected Mono<Boolean> deregisterInstance(URI uri) {\n\t\t//@formatter:off\n\t\treturn this.webClient.delete().uri(uri)\n\t\t\t\t\t.exchange()\n\t\t\t\t\t.returnResult(Void.class).getResponseBody()\n\t\t\t\t\t.then(Mono.just(true));\n\t\t//@formatter:on\n\t}\n\n\tprivate Registration createRegistration() {\n\t\treturn Registration.builder()\n\t\t\t.name(\"Test-Instance\")\n\t\t\t.healthUrl(\"http://localhost:\" + this.port + \"/mgmt/health\")\n\t\t\t.managementUrl(\"http://localhost:\" + this.port + \"/mgmt\")\n\t\t\t.serviceUrl(\"http://localhost:\" + this.port)\n\t\t\t.build();\n\t}\n\n\tprotected WebTestClient createWebClient(int port) {\n\t\tJsonMapper mapper = JsonMapper.builder().addModule(new JsonOrgModule()).build();\n\t\treturn WebTestClient.bindToServer()\n\t\t\t.baseUrl(\"http://localhost:\" + port)\n\t\t\t.exchangeStrategies(ExchangeStrategies.builder().codecs((configurer) -> {\n\t\t\t\tconfigurer.defaultCodecs().jacksonJsonDecoder(new JacksonJsonDecoder(mapper));\n\t\t\t\tconfigurer.defaultCodecs().jacksonJsonEncoder(new JacksonJsonEncoder(mapper));\n\t\t\t}).build())\n\t\t\t.build();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/AdminApplicationHazelcastTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server;\n\nimport java.util.stream.Collectors;\n\nimport com.hazelcast.config.Config;\nimport com.hazelcast.config.EvictionConfig;\nimport com.hazelcast.config.EvictionPolicy;\nimport com.hazelcast.config.InMemoryFormat;\nimport com.hazelcast.config.MapConfig;\nimport com.hazelcast.config.MergePolicyConfig;\nimport com.hazelcast.config.TcpIpConfig;\nimport com.hazelcast.spi.merge.PutIfAbsentMergePolicy;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.SpringBootConfiguration;\nimport org.springframework.boot.WebApplicationType;\nimport org.springframework.boot.autoconfigure.EnableAutoConfiguration;\nimport org.springframework.boot.builder.SpringApplicationBuilder;\nimport org.springframework.context.ConfigurableApplicationContext;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.http.MediaType;\nimport org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;\nimport org.springframework.security.config.web.server.ServerHttpSecurity;\nimport org.springframework.security.web.server.SecurityWebFilterChain;\nimport org.springframework.test.web.reactive.server.WebTestClient;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.config.EnableAdminServer;\n\nimport static de.codecentric.boot.admin.server.config.AdminServerHazelcastAutoConfiguration.DEFAULT_NAME_EVENT_STORE_MAP;\nimport static de.codecentric.boot.admin.server.config.AdminServerHazelcastAutoConfiguration.DEFAULT_NAME_SENT_NOTIFICATIONS_MAP;\nimport static java.util.Collections.singletonList;\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Integration test to verify the correct functionality of the REST API with Hazelcast\n *\n * @author Dennis Schulte\n */\nclass AdminApplicationHazelcastTest extends AbstractAdminApplicationTest {\n\n\tprivate ConfigurableApplicationContext instance1;\n\n\tprivate ConfigurableApplicationContext instance2;\n\n\tprivate WebTestClient webClient2;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tSystem.setProperty(\"hazelcast.wait.seconds.before.join\", \"0\");\n\t\tthis.instance1 = new SpringApplicationBuilder().sources(TestAdminApplication.class)\n\t\t\t.web(WebApplicationType.REACTIVE)\n\t\t\t.run(\"--server.port=0\", \"--management.endpoints.web.base-path=/mgmt\",\n\t\t\t\t\t\"--management.endpoints.web.exposure.include=info,health\", \"--info.test=foobar\",\n\t\t\t\t\t\"--spring.jmx.enabled=false\");\n\n\t\tthis.instance2 = new SpringApplicationBuilder().sources(TestAdminApplication.class)\n\t\t\t.web(WebApplicationType.REACTIVE)\n\t\t\t.run(\"--server.port=0\", \"--management.endpoints.web.base-path=/mgmt\",\n\t\t\t\t\t\"--management.endpoints.web.exposure.include=info,health\", \"--info.test=foobar\",\n\t\t\t\t\t\"--spring.jmx.enabled=false\");\n\n\t\tsuper.setUp(this.instance1.getEnvironment().getProperty(\"local.server.port\", Integer.class, 0));\n\t\tthis.webClient2 = createWebClient(\n\t\t\t\tthis.instance2.getEnvironment().getProperty(\"local.server.port\", Integer.class, 0));\n\t}\n\n\t@Test\n\t@Override\n\tpublic void lifecycle() {\n\t\tsuper.lifecycle();\n\n\t\tMono<String> events1 = getWebClient().get()\n\t\t\t.uri(\"/instances/events\")\n\t\t\t.accept(MediaType.APPLICATION_JSON)\n\t\t\t.exchange()\n\t\t\t.expectStatus()\n\t\t\t.isOk()\n\t\t\t.returnResult(String.class)\n\t\t\t.getResponseBody()\n\t\t\t.collect(Collectors.joining());\n\n\t\tMono<String> events2 = this.webClient2.get()\n\t\t\t.uri(\"/instances/events\")\n\t\t\t.accept(MediaType.APPLICATION_JSON)\n\t\t\t.exchange()\n\t\t\t.expectStatus()\n\t\t\t.isOk()\n\t\t\t.returnResult(String.class)\n\t\t\t.getResponseBody()\n\t\t\t.collect(Collectors.joining());\n\n\t\tStepVerifier.create(events1.zipWith(events2))\n\t\t\t.assertNext((t) -> assertThat(t.getT1()).isEqualTo(t.getT2()))\n\t\t\t.verifyComplete();\n\t}\n\n\t@AfterEach\n\tvoid shutdown() {\n\t\tthis.instance1.close();\n\t\tthis.instance2.close();\n\t}\n\n\t@SpringBootConfiguration\n\t@EnableAutoConfiguration\n\t@EnableAdminServer\n\t@EnableWebFluxSecurity\n\tpublic static class TestAdminApplication {\n\n\t\t@Bean\n\t\tSecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {\n\t\t\treturn http.authorizeExchange((authorizeExchange) -> authorizeExchange.anyExchange().permitAll())\n\t\t\t\t.csrf(ServerHttpSecurity.CsrfSpec::disable)\n\t\t\t\t.build();\n\t\t}\n\n\t\t@Bean\n\t\tpublic Config hazelcastConfig() {\n\t\t\tMapConfig eventStoreMap = new MapConfig(DEFAULT_NAME_EVENT_STORE_MAP)\n\t\t\t\t.setInMemoryFormat(InMemoryFormat.OBJECT)\n\t\t\t\t.setBackupCount(1)\n\t\t\t\t.setMergePolicyConfig(new MergePolicyConfig(PutIfAbsentMergePolicy.class.getName(), 100));\n\n\t\t\tMapConfig sentNotificationsMap = new MapConfig(DEFAULT_NAME_SENT_NOTIFICATIONS_MAP)\n\t\t\t\t.setInMemoryFormat(InMemoryFormat.OBJECT)\n\t\t\t\t.setBackupCount(1)\n\t\t\t\t.setEvictionConfig(new EvictionConfig().setEvictionPolicy(EvictionPolicy.LRU))\n\t\t\t\t.setMergePolicyConfig(new MergePolicyConfig(PutIfAbsentMergePolicy.class.getName(), 100));\n\n\t\t\tConfig config = new Config();\n\t\t\tconfig.addMapConfig(eventStoreMap);\n\t\t\tconfig.addMapConfig(sentNotificationsMap);\n\t\t\tconfig.getNetworkConfig().getJoin().getMulticastConfig().setEnabled(false);\n\t\t\tTcpIpConfig tcpIpConfig = config.getNetworkConfig().getJoin().getTcpIpConfig();\n\t\t\ttcpIpConfig.setEnabled(true);\n\t\t\ttcpIpConfig.setMembers(singletonList(\"127.0.0.1\"));\n\t\t\treturn config;\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/AdminReactiveApplicationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.springframework.boot.SpringBootConfiguration;\nimport org.springframework.boot.WebApplicationType;\nimport org.springframework.boot.autoconfigure.EnableAutoConfiguration;\nimport org.springframework.boot.builder.SpringApplicationBuilder;\nimport org.springframework.context.ConfigurableApplicationContext;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;\nimport org.springframework.security.config.web.server.ServerHttpSecurity;\nimport org.springframework.security.web.server.SecurityWebFilterChain;\n\nimport de.codecentric.boot.admin.server.config.EnableAdminServer;\n\npublic class AdminReactiveApplicationTest extends AbstractAdminApplicationTest {\n\n\tprivate ConfigurableApplicationContext instance;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tthis.instance = new SpringApplicationBuilder().sources(TestAdminApplication.class)\n\t\t\t.web(WebApplicationType.REACTIVE)\n\t\t\t.run(\"--server.port=0\", \"--management.endpoints.web.base-path=/mgmt\",\n\t\t\t\t\t\"--management.endpoints.web.exposure.include=info,health\", \"--info.test=foobar\");\n\n\t\tsuper.setUp(this.instance.getEnvironment().getProperty(\"local.server.port\", Integer.class, 0));\n\t}\n\n\t@AfterEach\n\tvoid shutdown() {\n\t\tthis.instance.close();\n\t}\n\n\t@EnableAdminServer\n\t@EnableAutoConfiguration\n\t@SpringBootConfiguration\n\t@EnableWebFluxSecurity\n\tpublic static class TestAdminApplication {\n\n\t\t@Bean\n\t\tpublic SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {\n\t\t\treturn http.authorizeExchange((authorizeExchange) -> authorizeExchange.anyExchange().permitAll())\n\t\t\t\t.csrf(ServerHttpSecurity.CsrfSpec::disable)\n\t\t\t\t.build();\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/AdminServletApplicationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.springframework.boot.SpringBootConfiguration;\nimport org.springframework.boot.WebApplicationType;\nimport org.springframework.boot.autoconfigure.EnableAutoConfiguration;\nimport org.springframework.boot.builder.SpringApplicationBuilder;\nimport org.springframework.context.ConfigurableApplicationContext;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;\nimport org.springframework.security.web.SecurityFilterChain;\n\nimport de.codecentric.boot.admin.server.config.EnableAdminServer;\n\npublic class AdminServletApplicationTest extends AbstractAdminApplicationTest {\n\n\tprivate ConfigurableApplicationContext instance;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tthis.instance = new SpringApplicationBuilder().sources(TestAdminApplication.class)\n\t\t\t.web(WebApplicationType.SERVLET)\n\t\t\t.run(\"--server.port=0\", \"--management.endpoints.web.base-path=/mgmt\",\n\t\t\t\t\t\"--management.endpoints.web.exposure.include=info,health\", \"--info.test=foobar\");\n\n\t\tsuper.setUp(this.instance.getEnvironment().getProperty(\"local.server.port\", Integer.class, 0));\n\t}\n\n\t@AfterEach\n\tvoid shutdown() {\n\t\tthis.instance.close();\n\t}\n\n\t@EnableAdminServer\n\t@EnableAutoConfiguration\n\t@SpringBootConfiguration\n\tpublic static class TestAdminApplication {\n\n\t\t@Configuration(proxyBeanMethods = false)\n\t\tpublic static class SecurityConfiguration {\n\n\t\t\t@Bean\n\t\t\tpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n\t\t\t\thttp.csrf(AbstractHttpConfigurer::disable)\n\t\t\t\t\t.authorizeHttpRequests((authz) -> authz.anyRequest().permitAll());\n\t\t\t\treturn http.build();\n\t\t\t}\n\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/config/AdminServerAutoConfigurationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.config;\n\nimport com.hazelcast.config.Config;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.autoconfigure.AutoConfigurations;\nimport org.springframework.boot.hazelcast.autoconfigure.HazelcastAutoConfiguration;\nimport org.springframework.boot.http.client.autoconfigure.reactive.ReactiveHttpClientAutoConfiguration;\nimport org.springframework.boot.test.context.runner.WebApplicationContextRunner;\nimport org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration;\nimport org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration;\nimport org.springframework.context.annotation.Bean;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.entities.SnapshottingInstanceRepository;\nimport de.codecentric.boot.admin.server.eventstore.ConcurrentMapEventStore;\nimport de.codecentric.boot.admin.server.eventstore.HazelcastEventStore;\nimport de.codecentric.boot.admin.server.eventstore.InstanceEventStore;\nimport de.codecentric.boot.admin.server.notify.HazelcastNotificationTrigger;\nimport de.codecentric.boot.admin.server.notify.MailNotifier;\nimport de.codecentric.boot.admin.server.notify.NotificationTrigger;\nimport de.codecentric.boot.admin.server.notify.Notifier;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass AdminServerAutoConfigurationTest {\n\n\tprivate final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()\n\t\t.withConfiguration(AutoConfigurations.of(ReactiveHttpClientAutoConfiguration.class,\n\t\t\t\tWebClientAutoConfiguration.class, HazelcastAutoConfiguration.class, WebMvcAutoConfiguration.class,\n\t\t\t\tAdminServerHazelcastAutoConfiguration.class, AdminServerAutoConfiguration.class))\n\t\t.withUserConfiguration(AdminServerMarkerConfiguration.class);\n\n\t@Test\n\tvoid simpleConfig() {\n\t\tthis.contextRunner.run((context) -> {\n\t\t\tassertThat(context).getBean(InstanceRepository.class).isInstanceOf(SnapshottingInstanceRepository.class);\n\t\t\tassertThat(context).doesNotHaveBean(MailNotifier.class);\n\t\t\tassertThat(context).getBean(InstanceEventStore.class).isInstanceOf(ConcurrentMapEventStore.class);\n\t\t});\n\t}\n\n\t@Test\n\tvoid hazelcastConfig() {\n\t\tthis.contextRunner.withUserConfiguration(TestHazelcastConfig.class).run((context) -> {\n\t\t\tassertThat(context).getBean(InstanceEventStore.class).isInstanceOf(HazelcastEventStore.class);\n\t\t\tassertThat(context).getBean(NotificationTrigger.class).isInstanceOf(HazelcastNotificationTrigger.class);\n\t\t});\n\t}\n\n\tpublic static class TestHazelcastConfig {\n\n\t\t@Bean\n\t\tpublic Config config() {\n\t\t\treturn new Config();\n\t\t}\n\n\t\t@Bean\n\t\tpublic Notifier notifier() {\n\t\t\treturn (e) -> Mono.empty();\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/config/AdminServerCloudFoundryAutoConfigurationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.config;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.autoconfigure.AutoConfigurations;\nimport org.springframework.boot.hazelcast.autoconfigure.HazelcastAutoConfiguration;\nimport org.springframework.boot.http.client.autoconfigure.reactive.ReactiveHttpClientAutoConfiguration;\nimport org.springframework.boot.test.context.runner.WebApplicationContextRunner;\nimport org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration;\nimport org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration;\n\nimport de.codecentric.boot.admin.server.services.CloudFoundryInstanceIdGenerator;\nimport de.codecentric.boot.admin.server.services.HashingInstanceUrlIdGenerator;\nimport de.codecentric.boot.admin.server.services.InstanceIdGenerator;\nimport de.codecentric.boot.admin.server.web.client.CloudFoundryHttpHeaderProvider;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass AdminServerCloudFoundryAutoConfigurationTest {\n\n\tprivate final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()\n\t\t.withConfiguration(AutoConfigurations.of(ReactiveHttpClientAutoConfiguration.class,\n\t\t\t\tWebClientAutoConfiguration.class, HazelcastAutoConfiguration.class, WebMvcAutoConfiguration.class,\n\t\t\t\tAdminServerAutoConfiguration.class, AdminServerCloudFoundryAutoConfiguration.class))\n\t\t.withUserConfiguration(AdminServerMarkerConfiguration.class);\n\n\t@Test\n\tvoid non_cloud_platform() {\n\t\tthis.contextRunner.run((context) -> {\n\t\t\tassertThat(context).doesNotHaveBean(CloudFoundryHttpHeaderProvider.class);\n\t\t\tassertThat(context).getBean(InstanceIdGenerator.class).isInstanceOf(HashingInstanceUrlIdGenerator.class);\n\t\t});\n\t}\n\n\t@Test\n\tvoid cloudfoundry() {\n\t\tthis.contextRunner.withPropertyValues(\"VCAP_APPLICATION:{}\").run((context) -> {\n\t\t\tassertThat(context).hasSingleBean(CloudFoundryHttpHeaderProvider.class);\n\t\t\tassertThat(context).getBean(InstanceIdGenerator.class).isInstanceOf(CloudFoundryInstanceIdGenerator.class);\n\t\t});\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/config/AdminServerInstanceWebClientConfigurationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.config;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.autoconfigure.AutoConfigurations;\nimport org.springframework.boot.http.client.autoconfigure.reactive.ReactiveHttpClientAutoConfiguration;\nimport org.springframework.boot.test.context.runner.WebApplicationContextRunner;\nimport org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration;\nimport org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration;\n\nimport de.codecentric.boot.admin.server.web.client.BasicAuthHttpHeaderProvider;\nimport de.codecentric.boot.admin.server.web.client.InstanceExchangeFilterFunction;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\nimport de.codecentric.boot.admin.server.web.client.LegacyEndpointConverter;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass AdminServerInstanceWebClientConfigurationTest {\n\n\tprivate final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()\n\t\t.withConfiguration(AutoConfigurations.of(ReactiveHttpClientAutoConfiguration.class,\n\t\t\t\tWebClientAutoConfiguration.class, WebMvcAutoConfiguration.class, AdminServerAutoConfiguration.class,\n\t\t\t\tAdminServerInstanceWebClientConfiguration.class))\n\t\t.withUserConfiguration(AdminServerMarkerConfiguration.class);\n\n\t@Test\n\tvoid simpleConfig() {\n\t\tthis.contextRunner.run((context) -> {\n\t\t\tassertThat(context).hasSingleBean(InstanceWebClient.Builder.class);\n\t\t\tassertThat(context).hasBean(\"filterInstanceWebClientCustomizer\");\n\t\t\tassertThat(context).hasSingleBean(BasicAuthHttpHeaderProvider.class);\n\t\t\tassertThat(context).getBeanNames(InstanceExchangeFilterFunction.class)\n\t\t\t\t.containsExactly(\"addHeadersInstanceExchangeFilter\", \"rewriteEndpointUrlInstanceExchangeFilter\",\n\t\t\t\t\t\t\"setDefaultAcceptHeaderInstanceExchangeFilter\", \"legacyEndpointConverterInstanceExchangeFilter\",\n\t\t\t\t\t\t\"logfileAcceptWorkaround\", \"cookieHandlingInstanceExchangeFilter\",\n\t\t\t\t\t\t\"retryInstanceExchangeFilter\", \"timeoutInstanceExchangeFilter\");\n\t\t\tassertThat(context).getBeanNames(LegacyEndpointConverter.class)\n\t\t\t\t.containsExactly(\"healthLegacyEndpointConverter\", \"infoLegacyEndpointConverter\",\n\t\t\t\t\t\t\"envLegacyEndpointConverter\", \"httptraceLegacyEndpointConverter\",\n\t\t\t\t\t\t\"threaddumpLegacyEndpointConverter\", \"liquibaseLegacyEndpointConverter\",\n\t\t\t\t\t\t\"flywayLegacyEndpointConverter\", \"beansLegacyEndpointConverter\",\n\t\t\t\t\t\t\"configpropsLegacyEndpointConverter\", \"mappingsLegacyEndpointConverter\",\n\t\t\t\t\t\t\"startupLegacyEndpointConverter\");\n\t\t});\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/config/AdminServerNotifierAutoConfigurationTest.java",
    "content": "/*\n * Copyright 2014-2024 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.config;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Qualifier;\nimport org.springframework.boot.autoconfigure.AutoConfigurations;\nimport org.springframework.boot.autoconfigure.AutoConfigureAfter;\nimport org.springframework.boot.hazelcast.autoconfigure.HazelcastAutoConfiguration;\nimport org.springframework.boot.http.client.autoconfigure.reactive.ReactiveHttpClientAutoConfiguration;\nimport org.springframework.boot.test.context.runner.WebApplicationContextRunner;\nimport org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration;\nimport org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.mail.javamail.JavaMailSenderImpl;\n\nimport de.codecentric.boot.admin.server.notify.CompositeNotifier;\nimport de.codecentric.boot.admin.server.notify.DiscordNotifier;\nimport de.codecentric.boot.admin.server.notify.HipchatNotifier;\nimport de.codecentric.boot.admin.server.notify.LetsChatNotifier;\nimport de.codecentric.boot.admin.server.notify.MailNotifier;\nimport de.codecentric.boot.admin.server.notify.MattermostNotifier;\nimport de.codecentric.boot.admin.server.notify.MicrosoftTeamsNotifier;\nimport de.codecentric.boot.admin.server.notify.NotificationTrigger;\nimport de.codecentric.boot.admin.server.notify.Notifier;\nimport de.codecentric.boot.admin.server.notify.NotifierProxyProperties;\nimport de.codecentric.boot.admin.server.notify.OpsGenieNotifier;\nimport de.codecentric.boot.admin.server.notify.PagerdutyNotifier;\nimport de.codecentric.boot.admin.server.notify.RocketChatNotifier;\nimport de.codecentric.boot.admin.server.notify.SlackNotifier;\nimport de.codecentric.boot.admin.server.notify.TelegramNotifier;\nimport de.codecentric.boot.admin.server.notify.TestNotifier;\nimport de.codecentric.boot.admin.server.notify.WebexNotifier;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass AdminServerNotifierAutoConfigurationTest {\n\n\tprivate final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()\n\t\t.withConfiguration(AutoConfigurations.of(ReactiveHttpClientAutoConfiguration.class,\n\t\t\t\tWebClientAutoConfiguration.class, HazelcastAutoConfiguration.class, WebMvcAutoConfiguration.class,\n\t\t\t\tAdminServerAutoConfiguration.class, AdminServerNotifierAutoConfiguration.class))\n\t\t.withUserConfiguration(AdminServerMarkerConfiguration.class);\n\n\t@Test\n\tvoid test_notifierListener() {\n\t\tthis.contextRunner.withUserConfiguration(TestSingleNotifierConfig.class).run((context) -> {\n\t\t\tassertThat(context).getBean(Notifier.class).isInstanceOf(TestNotifier.class);\n\t\t\tassertThat(context).getBeans(Notifier.class).hasSize(1);\n\t\t});\n\t}\n\n\t@Test\n\tvoid test_no_notifierListener() {\n\t\tthis.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(NotificationTrigger.class));\n\t}\n\n\t@Test\n\tvoid test_mail() {\n\t\tthis.contextRunner.withUserConfiguration(MailSenderConfig.class)\n\t\t\t.run((context) -> assertThat(context).getBean(MailNotifier.class).isInstanceOf(MailNotifier.class));\n\t}\n\n\t@Test\n\tvoid test_hipchat() {\n\t\tthis.contextRunner.withPropertyValues(\"spring.boot.admin.notify.hipchat.url:https://example.com\")\n\t\t\t.run((context) -> assertThat(context).hasSingleBean(HipchatNotifier.class));\n\t}\n\n\t@Test\n\tvoid test_letschat() {\n\t\tthis.contextRunner.withPropertyValues(\"spring.boot.admin.notify.letschat.url:https://example.com\")\n\t\t\t.run((context) -> assertThat(context).hasSingleBean(LetsChatNotifier.class));\n\t}\n\n\t@Test\n\tvoid test_slack() {\n\t\tthis.contextRunner.withPropertyValues(\"spring.boot.admin.notify.slack.webhook-url:https://example.com\")\n\t\t\t.run((context) -> assertThat(context).hasSingleBean(SlackNotifier.class));\n\t}\n\n\t@Test\n\tvoid test_mattermost() {\n\t\tthis.contextRunner.withPropertyValues(\"spring.boot.admin.notify.mattermost.api-url:https://example.com\")\n\t\t\t.run((context) -> assertThat(context).hasSingleBean(MattermostNotifier.class));\n\t}\n\n\t@Test\n\tvoid test_pagerduty() {\n\t\tthis.contextRunner.withPropertyValues(\"spring.boot.admin.notify.pagerduty.service-key:foo\")\n\t\t\t.run((context) -> assertThat(context).hasSingleBean(PagerdutyNotifier.class));\n\t}\n\n\t@Test\n\tvoid test_opsgenie() {\n\t\tthis.contextRunner.withPropertyValues(\"spring.boot.admin.notify.opsgenie.api-key:foo\")\n\t\t\t.run((context) -> assertThat(context).hasSingleBean(OpsGenieNotifier.class));\n\t}\n\n\t@Test\n\tvoid test_ms_teams() {\n\t\tthis.contextRunner.withPropertyValues(\"spring.boot.admin.notify.ms-teams.webhook-url:https://example.com\")\n\t\t\t.run((context) -> assertThat(context).hasSingleBean(MicrosoftTeamsNotifier.class));\n\t}\n\n\t@Test\n\tvoid test_telegram() {\n\t\tthis.contextRunner\n\t\t\t.withPropertyValues(\n\t\t\t\t\t\"spring.boot.admin.notify.telegram.auth-token:123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11\")\n\t\t\t.run((context) -> assertThat(context).hasSingleBean(TelegramNotifier.class));\n\t}\n\n\t@Test\n\tvoid test_discord() {\n\t\tthis.contextRunner.withPropertyValues(\"spring.boot.admin.notify.discord.webhook-url:https://example.com\")\n\t\t\t.run((context) -> assertThat(context).hasSingleBean(DiscordNotifier.class));\n\t}\n\n\t@Test\n\tvoid test_rocketchat() {\n\t\tthis.contextRunner.withPropertyValues(\"spring.boot.admin.notify.rocketchat.url:https://example.com\")\n\t\t\t.run((context) -> assertThat(context).hasSingleBean(RocketChatNotifier.class));\n\t}\n\n\t@Test\n\tvoid test_webex() {\n\t\tthis.contextRunner\n\t\t\t.withPropertyValues(\"spring.boot.admin.notify.webex.auth-token:123456:abtshubzztk-abtabhixta-788654\")\n\t\t\t.run((context) -> assertThat(context).hasSingleBean(WebexNotifier.class));\n\t}\n\n\t@Test\n\tvoid test_multipleNotifiers() {\n\t\tthis.contextRunner.withUserConfiguration(TestMultipleNotifierConfig.class).run((context) -> {\n\t\t\tassertThat(context.getBean(Notifier.class)).isInstanceOf(CompositeNotifier.class);\n\t\t\tassertThat(context).getBeans(Notifier.class).hasSize(3);\n\t\t});\n\t}\n\n\t@Test\n\tvoid test_multipleNotifiersWithPrimary() {\n\t\tthis.contextRunner.withUserConfiguration(TestMultipleWithPrimaryNotifierConfig.class).run((context) -> {\n\t\t\tassertThat(context.getBean(Notifier.class)).isInstanceOf(TestNotifier.class);\n\t\t\tassertThat(context).getBeans(Notifier.class).hasSize(2);\n\t\t});\n\t}\n\n\t@Test\n\tvoid test_notifierProxyProperties() {\n\t\tthis.contextRunner.withPropertyValues(\"spring.boot.admin.notify.proxy.host\")\n\t\t\t.run((context) -> assertThat(context).hasSingleBean(NotifierProxyProperties.class));\n\t}\n\n\t@Test\n\tvoid test_autoConfigureAfterAnnotationReferencesExistingClass() {\n\t\t// Get the @AutoConfigureAfter annotation from\n\t\t// AdminServerNotifierAutoConfiguration\n\t\tAutoConfigureAfter autoConfigureAfter = AdminServerNotifierAutoConfiguration.class\n\t\t\t.getAnnotation(org.springframework.boot.autoconfigure.AutoConfigureAfter.class);\n\t\tassertThat(autoConfigureAfter).isNotNull();\n\n\t\t// Get the class names from the annotation\n\t\tString[] classNames = autoConfigureAfter.name();\n\t\tassertThat(classNames).isNotEmpty();\n\n\t\t// Verify that the class can be loaded\n\t\tfor (String className : classNames) {\n\t\t\ttry {\n\t\t\t\tClass.forName(className);\n\t\t\t}\n\t\t\tcatch (ClassNotFoundException ex) {\n\t\t\t\tthrow new AssertionError(\n\t\t\t\t\t\t\"Class referenced in @AutoConfigureAfter annotation does not exist: \" + className, ex);\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic static class TestSingleNotifierConfig {\n\n\t\t@Bean\n\t\t@Qualifier(\"testNotifier\")\n\t\tpublic TestNotifier testNotifier() {\n\t\t\treturn new TestNotifier();\n\t\t}\n\n\t}\n\n\tpublic static class MailSenderConfig {\n\n\t\t@Bean\n\t\tpublic JavaMailSenderImpl mailSender() {\n\t\t\treturn new JavaMailSenderImpl();\n\t\t}\n\n\t}\n\n\tpublic static class TestMultipleNotifierConfig {\n\n\t\t@Bean\n\t\t@Qualifier(\"testNotifier1\")\n\t\tpublic TestNotifier testNotifier1() {\n\t\t\treturn new TestNotifier();\n\t\t}\n\n\t\t@Bean\n\t\t@Qualifier(\"testNotifier2\")\n\t\tpublic TestNotifier testNotifier2() {\n\t\t\treturn new TestNotifier();\n\t\t}\n\n\t}\n\n\tpublic static class TestMultipleWithPrimaryNotifierConfig {\n\n\t\t@Bean\n\t\t@Primary\n\t\t@Qualifier(\"testNotifier\")\n\t\tpublic TestNotifier testNotifierPrimary() {\n\t\t\treturn new TestNotifier();\n\t\t}\n\n\t\t@Bean\n\t\t@Qualifier(\"testNotifier3\")\n\t\tpublic TestNotifier testNotifier2() {\n\t\t\treturn new TestNotifier();\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/config/AdminServerPropertiesTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.config;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.test.context.TestPropertySource;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n@ExtendWith(SpringExtension.class)\n@EnableConfigurationProperties(AdminServerProperties.class)\n@TestPropertySource(\"classpath:server-config-test.properties\")\nclass AdminServerPropertiesTest {\n\n\t@Autowired\n\tprivate AdminServerProperties serverConfig;\n\n\t@Test\n\tvoid testLoadConfigurationProperties() {\n\t\tassertThat(serverConfig.getContextPath()).isEqualTo(\"/admin\");\n\n\t\tassertThat(serverConfig.getInstanceAuth().getDefaultUserName()).isEqualTo(\"admin\");\n\t\tassertThat(serverConfig.getInstanceAuth().getDefaultPassword()).isEqualTo(\"topsecret\");\n\n\t\tassertThat(serverConfig.getInstanceAuth().getServiceMap().get(\"my-service\").getUserName()).isEqualTo(\"me\");\n\t\tassertThat(serverConfig.getInstanceAuth().getServiceMap().get(\"my-service\").getUserPassword())\n\t\t\t.isEqualTo(\"secret\");\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/config/SpringBootAdminServerEnabledConditionTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.config;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.BDDMockito;\nimport org.springframework.context.annotation.ConditionContext;\nimport org.springframework.core.type.AnnotatedTypeMetadata;\nimport org.springframework.mock.env.MockEnvironment;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\n\nclass SpringBootAdminServerEnabledConditionTest {\n\n\tprivate SpringBootAdminServerEnabledCondition condition;\n\n\tprivate AnnotatedTypeMetadata annotatedTypeMetadata;\n\n\tprivate ConditionContext conditionContext;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tcondition = new SpringBootAdminServerEnabledCondition();\n\t\tannotatedTypeMetadata = mock(AnnotatedTypeMetadata.class);\n\t\tconditionContext = mock(ConditionContext.class);\n\t}\n\n\t@Test\n\tvoid test_server_enabled() {\n\t\tMockEnvironment environment = new MockEnvironment();\n\t\tBDDMockito.given(conditionContext.getEnvironment()).willReturn(environment);\n\t\tassertThat(condition.getMatchOutcome(conditionContext, annotatedTypeMetadata).isMatch()).isTrue();\n\t}\n\n\t@Test\n\tvoid test_server_disabled() {\n\t\tMockEnvironment environment = new MockEnvironment();\n\t\tenvironment.setProperty(\"spring.boot.admin.server.enabled\", \"false\");\n\t\tBDDMockito.given(conditionContext.getEnvironment()).willReturn(environment);\n\t\tassertThat(condition.getMatchOutcome(conditionContext, annotatedTypeMetadata).isMatch()).isFalse();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/domain/entities/AbstractInstanceRepositoryTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.entities;\n\nimport java.util.concurrent.atomic.AtomicLong;\n\nimport org.junit.jupiter.api.Test;\nimport org.opentest4j.AssertionFailedError;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.values.Endpoints;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic abstract class AbstractInstanceRepositoryTest {\n\n\tprivate final Instance instance1 = Instance.create(InstanceId.of(\"app-1\"))\n\t\t.register(Registration.create(\"app\", \"https://health\").build());\n\n\tprivate final Instance instance2 = Instance.create(InstanceId.of(\"app-2\"))\n\t\t.register(Registration.create(\"app\", \"https://health\").build());\n\n\tprivate final Instance instance3 = Instance.create(InstanceId.of(\"other-1\"))\n\t\t.register(Registration.create(\"other\", \"https://health\").build());\n\n\tprivate InstanceRepository repository;\n\n\tpublic void setUp(InstanceRepository repository) {\n\t\tthis.repository = repository;\n\t}\n\n\t@Test\n\tpublic void should_save() {\n\t\t// when\n\t\tStepVerifier.create(this.repository.save(this.instance1)).expectNext(this.instance1).verifyComplete();\n\t\t// then\n\t\tStepVerifier.create(this.repository.find(this.instance1.getId())).expectNext(this.instance1).verifyComplete();\n\t}\n\n\t@Test\n\tpublic void should_find_instances() {\n\t\t// given\n\t\tStepVerifier.create(this.repository.save(this.instance1)).expectNextCount(1).verifyComplete();\n\t\tStepVerifier.create(this.repository.save(this.instance2)).expectNextCount(1).verifyComplete();\n\t\tStepVerifier.create(this.repository.save(this.instance3)).expectNextCount(1).verifyComplete();\n\n\t\t// when/then\n\t\tStepVerifier.create(this.repository.find(this.instance2.getId())).expectNext(this.instance2).verifyComplete();\n\n\t\tStepVerifier.create(this.repository.findByName(\"app\").collectList())\n\t\t\t.assertNext((v) -> assertThat(v).containsExactlyInAnyOrder(this.instance1, this.instance2))\n\t\t\t.verifyComplete();\n\n\t\tStepVerifier.create(this.repository.findAll().collectList())\n\t\t\t.assertNext((v) -> assertThat(v).containsExactlyInAnyOrder(this.instance1, this.instance2, this.instance3))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tpublic void should_computeIfPresent() {\n\t\tAtomicLong counter = new AtomicLong(3L);\n\t\tEndpoints infoEndpoint = Endpoints.single(\"info\", \"info\");\n\n\t\t// given\n\t\tStepVerifier.create(this.repository.save(this.instance1)).expectNextCount(1).verifyComplete();\n\n\t\t// when\n\t\tStepVerifier.create(this.repository.computeIfPresent(this.instance1.getId(), (key, value) -> {\n\t\t\tif (counter.getAndDecrement() > 0L) {\n\t\t\t\treturn Mono.just(this.instance1); // causes OptimisticLockingException\n\t\t\t}\n\t\t\telse {\n\t\t\t\treturn Mono.just(value.withEndpoints(infoEndpoint));\n\t\t\t}\n\t\t})).expectNext(this.instance1.withEndpoints(infoEndpoint)).verifyComplete();\n\n\t\t// then\n\t\tStepVerifier.create(this.repository.find(this.instance1.getId()))\n\t\t\t.expectNext(this.instance1.withEndpoints(infoEndpoint))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tpublic void should_not_compute_if_not_present() {\n\t\t// given\n\t\tInstanceId instanceId = InstanceId.of(\"not-existent\");\n\n\t\t// when\n\t\tStepVerifier\n\t\t\t.create(this.repository.computeIfPresent(instanceId,\n\t\t\t\t\t(key, application) -> Mono.error(new AssertionFailedError(\"Should not call any computation\"))))\n\t\t\t.verifyComplete();\n\n\t\t// then\n\t\tStepVerifier.create(this.repository.find(instanceId)).verifyComplete();\n\t}\n\n\t@Test\n\tpublic void should_run_compute_with_null() {\n\t\tInstanceId instanceId = InstanceId.of(\"app-1\");\n\t\tRegistration registration = Registration.create(\"app\", \"https://health\").build();\n\n\t\t// when\n\t\tStepVerifier.create(this.repository.compute(this.instance1.getId(), (key, application) -> {\n\t\t\tassertThat(application).isNull();\n\t\t\treturn Mono.just(Instance.create(key).register(registration));\n\t\t})).assertNext((v) -> {\n\t\t\tassertThat(v.getId()).isEqualTo(instanceId);\n\t\t\tassertThat(v.getRegistration()).isEqualTo(registration);\n\t\t}).verifyComplete();\n\n\t\t// then\n\t\tStepVerifier.create(this.repository.find(instanceId)).assertNext((v) -> {\n\t\t\tassertThat(v.getId()).isEqualTo(instanceId);\n\t\t\tassertThat(v.getRegistration()).isEqualTo(registration);\n\t\t}).verifyComplete();\n\t}\n\n\t@Test\n\tpublic void should_retry_compute() {\n\t\tAtomicLong counter = new AtomicLong(3L);\n\t\tEndpoints infoEndpoint = Endpoints.single(\"info\", \"info\");\n\n\t\t// given\n\t\tStepVerifier.create(this.repository.save(this.instance1)).expectNextCount(1).verifyComplete();\n\n\t\t// when\n\t\tStepVerifier.create(this.repository.compute(this.instance1.getId(), (key, value) -> {\n\t\t\tif (counter.getAndDecrement() > 0L) {\n\t\t\t\treturn Mono.just(this.instance1); // causes OptimisticLockingException\n\t\t\t}\n\t\t\telse {\n\t\t\t\treturn Mono.just(value.withEndpoints(infoEndpoint));\n\t\t\t}\n\t\t})).expectNext(this.instance1.withEndpoints(infoEndpoint)).verifyComplete();\n\n\t\t// then\n\t\tStepVerifier.create(this.repository.find(this.instance1.getId()))\n\t\t\t.expectNext(this.instance1.withEndpoints(infoEndpoint))\n\t\t\t.verifyComplete();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/domain/entities/EventsourcingInstanceRepositoryTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.entities;\n\nimport org.junit.jupiter.api.BeforeEach;\n\nimport de.codecentric.boot.admin.server.eventstore.InMemoryEventStore;\n\nclass EventsourcingInstanceRepositoryTest extends AbstractInstanceRepositoryTest {\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tsuper.setUp(new EventsourcingInstanceRepository(new InMemoryEventStore()));\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/domain/entities/InstanceTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.entities;\n\nimport java.util.List;\n\nimport org.junit.jupiter.api.Test;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceDeregisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceInfoChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.BuildVersion;\nimport de.codecentric.boot.admin.server.domain.values.Endpoints;\nimport de.codecentric.boot.admin.server.domain.values.Info;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\nimport static java.util.Collections.singletonMap;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.assertj.core.api.Assertions.entry;\n\nclass InstanceTest {\n\n\t@Test\n\tvoid invariants() {\n\t\tassertThatThrownBy(() -> Instance.create(null)).isInstanceOf(IllegalArgumentException.class)\n\t\t\t.hasMessage(\"'id' must not be null\");\n\n\t\tassertThatThrownBy(() -> Instance.create(InstanceId.of(\"id\")).register(null))\n\t\t\t.isInstanceOf(IllegalArgumentException.class)\n\t\t\t.hasMessage(\"'registration' must not be null\");\n\n\t\tassertThatThrownBy(() -> Instance.create(InstanceId.of(\"id\")).withInfo(null))\n\t\t\t.isInstanceOf(IllegalArgumentException.class)\n\t\t\t.hasMessage(\"'info' must not be null\");\n\n\t\tassertThatThrownBy(() -> Instance.create(InstanceId.of(\"id\")).withStatusInfo(null))\n\t\t\t.isInstanceOf(IllegalArgumentException.class)\n\t\t\t.hasMessage(\"'statusInfo' must not be null\");\n\n\t\tassertThatThrownBy(() -> Instance.create(InstanceId.of(\"id\")).withEndpoints(null))\n\t\t\t.isInstanceOf(IllegalArgumentException.class)\n\t\t\t.hasMessage(\"'endpoints' must not be null\");\n\t}\n\n\t@Test\n\tvoid should_track_unsaved_events() {\n\t\tRegistration registration = Registration.create(\"foo\", \"https://health\").build();\n\t\tInfo info = Info.from(singletonMap(\"foo\", \"bar\"));\n\t\tInstance newInstance = Instance.create(InstanceId.of(\"id\"));\n\n\t\tassertThat(newInstance.isRegistered()).isFalse();\n\t\tassertThatThrownBy(newInstance::getRegistration).isInstanceOf(IllegalStateException.class);\n\t\tassertThat(newInstance.getInfo()).isEqualTo(Info.empty());\n\t\tassertThat(newInstance.getStatusInfo()).isEqualTo(StatusInfo.ofUnknown());\n\t\tassertThat(newInstance.getUnsavedEvents()).isEmpty();\n\n\t\tInstance instance = newInstance.register(registration).register(registration);\n\t\tassertThat(instance.getRegistration()).isEqualTo(registration);\n\t\tassertThat(instance.isRegistered()).isTrue();\n\t\tassertThat(instance.getVersion()).isZero();\n\n\t\tRegistration registration2 = Registration.create(\"foo2\", \"https://health\").build();\n\t\tinstance = instance.register(registration2);\n\t\tassertThat(instance.getRegistration()).isEqualTo(registration2);\n\t\tassertThat(instance.isRegistered()).isTrue();\n\t\tassertThat(instance.getVersion()).isEqualTo(1L);\n\n\t\tinstance = instance.withStatusInfo(StatusInfo.ofUp()).withStatusInfo(StatusInfo.ofUp());\n\t\tassertThat(instance.getStatusInfo()).isEqualTo(StatusInfo.ofUp());\n\t\tassertThat(instance.getVersion()).isEqualTo(2L);\n\n\t\tinstance = instance.withInfo(info).withInfo(info);\n\t\tassertThat(instance.getInfo()).isEqualTo(info);\n\t\tassertThat(instance.getVersion()).isEqualTo(3L);\n\n\t\tinstance = instance.deregister().deregister();\n\t\tassertThat(instance.isRegistered()).isFalse();\n\t\tassertThat(instance.getRegistration()).isEqualTo(registration2);\n\t\tassertThat(instance.getInfo()).isEqualTo(Info.empty());\n\t\tassertThat(instance.getStatusInfo()).isEqualTo(StatusInfo.ofUnknown());\n\t\tassertThat(instance.getVersion()).isEqualTo(4L);\n\n\t\tassertThat(instance.getUnsavedEvents().stream().map(InstanceEvent::getType)).containsExactly(\"REGISTERED\",\n\t\t\t\t\"REGISTRATION_UPDATED\", \"STATUS_CHANGED\", \"INFO_CHANGED\", \"DEREGISTERED\");\n\t}\n\n\t@Test\n\tvoid should_yield_same_status_from_replaying() {\n\t\tRegistration registration = Registration.create(\"foo-instance\", \"https://health\")\n\t\t\t.metadata(\"version\", \"1.0.0\")\n\t\t\t.build();\n\t\tInstance instance = Instance.create(InstanceId.of(\"id\"))\n\t\t\t.register(registration.toBuilder().clearMetadata().build())\n\t\t\t.register(registration)\n\t\t\t.withEndpoints(Endpoints.single(\"info\", \"info\"))\n\t\t\t.withStatusInfo(StatusInfo.ofUp())\n\t\t\t.withInfo(Info.from(singletonMap(\"foo\", \"bar\")));\n\n\t\tInstance loaded = Instance.create(InstanceId.of(\"id\")).apply(instance.getUnsavedEvents());\n\t\tassertThat(loaded.getUnsavedEvents()).isEmpty();\n\t\tassertThat(loaded.getRegistration()).isEqualTo(registration);\n\t\tassertThat(loaded.isRegistered()).isTrue();\n\t\tassertThat(loaded.getStatusInfo()).isEqualTo(StatusInfo.ofUp());\n\t\tassertThat(loaded.getStatusTimestamp()).isEqualTo(instance.getStatusTimestamp());\n\t\tassertThat(loaded.getInfo()).isEqualTo(Info.from(singletonMap(\"foo\", \"bar\")));\n\t\tassertThat(loaded.getEndpoints())\n\t\t\t.isEqualTo(Endpoints.single(\"info\", \"info\").withEndpoint(\"health\", \"https://health\"));\n\t\tassertThat(loaded.getVersion()).isEqualTo(4L);\n\t\tassertThat(loaded.getBuildVersion()).isEqualTo(BuildVersion.valueOf(\"1.0.0\"));\n\n\t\tInstance deregisteredInstance = instance.deregister();\n\t\tloaded = Instance.create(InstanceId.of(\"id\")).apply(deregisteredInstance.getUnsavedEvents());\n\t\tassertThat(loaded.getUnsavedEvents()).isEmpty();\n\t\tassertThat(loaded.isRegistered()).isFalse();\n\t\tassertThat(loaded.getInfo()).isEqualTo(Info.empty());\n\t\tassertThat(loaded.getStatusInfo()).isEqualTo(StatusInfo.ofUnknown());\n\t\tassertThat(loaded.getStatusTimestamp()).isEqualTo(deregisteredInstance.getStatusTimestamp());\n\t\tassertThat(loaded.getEndpoints()).isEqualTo(Endpoints.empty());\n\t\tassertThat(loaded.getVersion()).isEqualTo(5L);\n\t\tassertThat(loaded.getBuildVersion()).isNull();\n\t}\n\n\t@Test\n\tvoid should_throw_when_applied_wrong_event() {\n\t\tInstance instance = Instance.create(InstanceId.of(\"id\"));\n\t\tassertThatThrownBy(() -> instance.apply((InstanceEvent) null)).isInstanceOf(IllegalArgumentException.class)\n\t\t\t.hasMessage(\"'event' must not be null\");\n\n\t\tassertThatThrownBy(() -> instance.apply(new InstanceDeregisteredEvent(InstanceId.of(\"wrong\"), 0L)))\n\t\t\t.isInstanceOf(IllegalArgumentException.class)\n\t\t\t.hasMessage(\"'event' must refer the same instance\");\n\n\t\tassertThatThrownBy(() -> instance.apply(new InstanceDeregisteredEvent(InstanceId.of(\"id\"), 1L))\n\t\t\t.apply(new InstanceDeregisteredEvent(InstanceId.of(\"id\"), 1L))).isInstanceOf(IllegalArgumentException.class)\n\t\t\t.hasMessage(\"Event 1 must be greater or equal to 2\");\n\t}\n\n\t@Test\n\tvoid should_update_buildVersion() {\n\t\tInstance instance = Instance.create(InstanceId.of(\"id\"));\n\n\t\tassertThat(instance.getBuildVersion()).isNull();\n\n\t\tRegistration registration = Registration.create(\"foo-instance\", \"https://health\")\n\t\t\t.metadata(\"version\", \"1.0.0\")\n\t\t\t.build();\n\t\tinstance = instance.register(registration).withInfo(Info.empty());\n\t\tassertThat(instance.getBuildVersion()).isEqualTo(BuildVersion.valueOf(\"1.0.0\"));\n\n\t\tinstance = instance.register(registration.toBuilder().clearMetadata().build());\n\t\tassertThat(instance.getBuildVersion()).isNull();\n\n\t\tinstance = instance.withInfo(Info.from(singletonMap(\"build\", singletonMap(\"version\", \"2.1.1\"))));\n\t\tassertThat(instance.getBuildVersion()).isEqualTo(BuildVersion.valueOf(\"2.1.1\"));\n\n\t\tinstance = instance.deregister();\n\t\tassertThat(instance.getBuildVersion()).isNull();\n\t}\n\n\t@Test\n\tvoid should_extract_tags() {\n\t\tInstance instance = Instance.create(InstanceId.of(\"id\"));\n\n\t\tassertThat(instance.getTags().getValues()).isEmpty();\n\n\t\tRegistration registration = Registration.create(\"foo-instance\", \"https://health\")\n\t\t\t.metadata(\"tags.environment\", \"test\")\n\t\t\t.metadata(\"tags.region\", \"EU\")\n\t\t\t.build();\n\n\t\tinstance = instance.register(registration);\n\t\tassertThat(instance.getTags().getValues()).containsExactly(entry(\"environment\", \"test\"), entry(\"region\", \"EU\"));\n\n\t\tinstance = instance.withInfo(Info.from(singletonMap(\"tags\", singletonMap(\"region\", \"US-East\"))));\n\t\tassertThat(instance.getTags().getValues()).containsExactly(entry(\"environment\", \"test\"),\n\t\t\t\tentry(\"region\", \"US-East\"));\n\n\t\tinstance = instance.deregister();\n\t\tassertThat(instance.getTags().getValues()).isEmpty();\n\n\t\tinstance = instance.register(registration.toBuilder().clearMetadata().build());\n\t\tassertThat(instance.getTags().getValues()).isEmpty();\n\t}\n\n\t@Test\n\tvoid should_rebuild_instance() {\n\t\tInstance instance = Instance.create(InstanceId.of(\"id\"))\n\t\t\t.register(Registration.create(\"test\", \"http://test\").build())\n\t\t\t.withInfo(Info.from(singletonMap(\"info\", \"remove\")))\n\t\t\t.withInfo(Info.from(singletonMap(\"info\", \"test2\")));\n\n\t\tList<InstanceEvent> relevantEvents = instance.getUnsavedEvents()\n\t\t\t.stream()\n\t\t\t.filter((e) -> !(e instanceof InstanceInfoChangedEvent infoChangedEvent\n\t\t\t\t\t&& infoChangedEvent.getInfo().getValues().get(\"info\").equals(\"remove\")))\n\t\t\t.toList();\n\n\t\tInstance rebuilt = Instance.create(InstanceId.of(\"id\")).apply(relevantEvents);\n\n\t\tassertThat(rebuilt).isEqualTo(instance);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/domain/entities/SnapshottingInstanceRepositoryTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.entities;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegisteredEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\nimport de.codecentric.boot.admin.server.eventstore.InMemoryEventStore;\nimport de.codecentric.boot.admin.server.eventstore.OptimisticLockingException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.reset;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass SnapshottingInstanceRepositoryTest extends AbstractInstanceRepositoryTest {\n\n\tprivate final Instance instance = Instance.create(InstanceId.of(\"app-1\"))\n\t\t.register(Registration.create(\"app\", \"https://health\").build());\n\n\tprivate final InMemoryEventStore eventStore = spy(new InMemoryEventStore());\n\n\tprivate SnapshottingInstanceRepository repository;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tthis.repository = new SnapshottingInstanceRepository(this.eventStore);\n\t\tthis.repository.start();\n\t\tsuper.setUp(this.repository);\n\t}\n\n\t@AfterEach\n\tvoid tearDown() {\n\t\tthis.repository.stop();\n\t}\n\n\t@Test\n\tvoid should_return_instance_from_cache() {\n\t\t// given\n\t\tStepVerifier.create(this.repository.save(this.instance)).expectNext(this.instance).verifyComplete();\n\t\t// when\n\t\treset(this.eventStore);\n\t\tStepVerifier.create(this.repository.find(this.instance.getId())).expectNext(this.instance).verifyComplete();\n\t\t// then\n\t\tverify(this.eventStore, never()).find(any());\n\t}\n\n\t@Test\n\tvoid should_return_all_instances_from_cache() {\n\t\t// given\n\t\tStepVerifier.create(this.repository.save(this.instance)).expectNext(this.instance).verifyComplete();\n\t\t// when\n\t\treset(this.eventStore);\n\t\tStepVerifier.create(this.repository.findAll()).expectNext(this.instance).verifyComplete();\n\t\t// then\n\t\tverify(this.eventStore, never()).findAll();\n\t}\n\n\t@Test\n\tvoid should_update_cache_after_error() {\n\t\t// given\n\t\tthis.repository.stop();\n\t\twhen(this.eventStore.findAll()).thenReturn(\n\t\t\t\tFlux.just(new InstanceRegisteredEvent(InstanceId.of(\"broken\"), 0L, this.instance.getRegistration()),\n\t\t\t\t\t\tnew InstanceRegisteredEvent(InstanceId.of(\"broken\"), 0L, this.instance.getRegistration()),\n\t\t\t\t\t\tnew InstanceRegisteredEvent(this.instance.getId(), 0L, this.instance.getRegistration()),\n\t\t\t\t\t\tnew InstanceRegisteredEvent(InstanceId.of(\"broken\"), 1L, this.instance.getRegistration())));\n\t\t// when\n\t\tthis.repository.start();\n\t\t// then\n\t\treset(this.eventStore);\n\t\tStepVerifier.create(this.repository.find(this.instance.getId())).expectNext(this.instance).verifyComplete();\n\t\tStepVerifier.create(this.repository.find(InstanceId.of(\"broken\")))\n\t\t\t.assertNext((i) -> assertThat(i.getVersion()).isEqualTo(1L))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_return_outdated_instance_not_present_in_cache() {\n\t\tthis.repository.stop();\n\t\t// given\n\t\tStepVerifier.create(this.repository.save(this.instance)).expectNext(this.instance).verifyComplete();\n\t\tStepVerifier.create(this.repository.save(this.instance)).verifyError(OptimisticLockingException.class);\n\t\t// when\n\t\tStepVerifier.create(this.repository.find(this.instance.getId())).expectNext(this.instance).verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_refresh_snapshots_eagerly_on_optimistic_locking_exception() {\n\t\t// given\n\t\tStepVerifier.create(this.repository.save(this.instance)).expectNextCount(1L).verifyComplete();\n\t\tthis.repository.stop();\n\t\tStepVerifier\n\t\t\t.create(this.repository.save(this.instance.clearUnsavedEvents().withStatusInfo(StatusInfo.ofDown())))\n\t\t\t.expectNextCount(1L)\n\t\t\t.verifyComplete();\n\t\t// when\n\t\tStepVerifier\n\t\t\t.create(this.repository.computeIfPresent(this.instance.getId(),\n\t\t\t\t\t(id, i) -> Mono.just(i.withStatusInfo(StatusInfo.ofUp()))))\n\t\t\t.expectNextCount(1L)\n\t\t\t.verifyComplete();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/domain/values/BuildVersionTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.values;\n\nimport org.junit.jupiter.api.Test;\n\nimport static java.util.Collections.emptyMap;\nimport static java.util.Collections.singletonMap;\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass BuildVersionTest {\n\n\t@Test\n\tvoid should_return_version() {\n\t\tassertThat(BuildVersion.valueOf(null).getValue()).isEqualTo(\"UNKNOWN\");\n\t\tassertThat(BuildVersion.from(emptyMap())).isNull();\n\t\tassertThat(BuildVersion.from(singletonMap(\"version\", \"1.0.0\"))).isEqualTo(BuildVersion.valueOf(\"1.0.0\"));\n\t\tassertThat(BuildVersion.from(singletonMap(\"build.version\", \"1.0.0\"))).isEqualTo(BuildVersion.valueOf(\"1.0.0\"));\n\t\tassertThat(BuildVersion.from(singletonMap(\"build\", singletonMap(\"version\", \"1.0.0\"))))\n\t\t\t.isEqualTo(BuildVersion.valueOf(\"1.0.0\"));\n\t}\n\n\t@Test\n\tvoid should_return_simple_string() {\n\t\tassertThat(BuildVersion.valueOf(\"1.0.0\")).hasToString(\"1.0.0\");\n\t}\n\n\t@Test\n\tvoid compare() {\n\t\tassertThat(doCompare(\"1.0.0\", \"1.0.0\")).isZero();\n\t\tassertThat(doCompare(\"1.0.1\", \"1.0.0\")).isEqualTo(1);\n\t\tassertThat(doCompare(\"1.0.0\", \"1.0.1\")).isEqualTo(-1);\n\n\t\tassertThat(doCompare(\"1.1.0\", \"1.0.0\")).isEqualTo(1);\n\t\tassertThat(doCompare(\"1.0.0\", \"1.1.0\")).isEqualTo(-1);\n\n\t\tassertThat(doCompare(\"2.0.0\", \"1.0.0\")).isEqualTo(1);\n\t\tassertThat(doCompare(\"1.0.0\", \"2.0.0\")).isEqualTo(-1);\n\n\t\tassertThat(doCompare(\"1.0.0.0\", \"1.0.0\")).isEqualTo(1);\n\t\tassertThat(doCompare(\"1.0.0\", \"1.0.0.0\")).isEqualTo(-1);\n\n\t\tassertThat(doCompare(\"1.11.0\", \"1.2.0\")).isEqualTo(1);\n\t\tassertThat(doCompare(\"1.2.0\", \"1.11.0\")).isEqualTo(-1);\n\n\t\tassertThat(doCompare(\"1.0.0.RC1\", \"1.0.0.RC1\")).isZero();\n\t\tassertThat(doCompare(\"1.0.0.RC2\", \"1.0.0.RC1\")).isEqualTo(1);\n\t\tassertThat(doCompare(\"1.0.0.RC1\", \"1.0.0.RC2\")).isEqualTo(-1);\n\t\tassertThat(doCompare(\"1.0.1.RC1\", \"1.0.0.RC1\")).isEqualTo(1);\n\t\tassertThat(doCompare(\"1.0.0.RC1\", \"1.0.1.RC1\")).isEqualTo(-1);\n\n\t\tassertThat(doCompare(\"1.0.0-beta1\", \"1.0.0-beta1\")).isZero();\n\t\tassertThat(doCompare(\"1.0.0-beta2\", \"1.0.0-beta1\")).isEqualTo(1);\n\t\tassertThat(doCompare(\"1.0.0-beta1\", \"1.0.0-beta2\")).isEqualTo(-1);\n\t}\n\n\tprivate int doCompare(String v1, String v2) {\n\t\treturn BuildVersion.valueOf(v1).compareTo(BuildVersion.valueOf(v2));\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/domain/values/EndpointTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.values;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass EndpointTest {\n\n\t@Test\n\tvoid invariants() {\n\t\tassertThatThrownBy(() -> Endpoint.of(\"\", \"\")).isInstanceOf(IllegalArgumentException.class)\n\t\t\t.hasMessage(\"'id' must not be empty.\");\n\t\tassertThatThrownBy(() -> Endpoint.of(\"id\", \"\")).isInstanceOf(IllegalArgumentException.class)\n\t\t\t.hasMessage(\"'url' must not be empty.\");\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/domain/values/EndpointsTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.values;\n\nimport java.util.Collections;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass EndpointsTest {\n\n\t@Test\n\tvoid should_return_endpoint_or_empty() {\n\t\tEndpoints endpoints = Endpoints.single(\"id\", \"path\");\n\t\tassertThat(endpoints.isPresent(\"id\")).isTrue();\n\t\tassertThat(endpoints.get(\"id\")).contains(Endpoint.of(\"id\", \"path\"));\n\t\tassertThat(endpoints.get(\"none!\")).isEmpty();\n\t}\n\n\t@Test\n\tvoid factory_methods() {\n\t\tassertThat(Endpoints.empty()).isEqualTo(Endpoints.of(Collections.emptyList())).isEqualTo(Endpoints.of(null));\n\t\tassertThat(Endpoints.of(Collections.singletonList(Endpoint.of(\"id\", \"path\"))))\n\t\t\t.isEqualTo(Endpoints.empty().withEndpoint(\"id\", \"path\"))\n\t\t\t.isEqualTo(Endpoints.single(\"id\", \"path\"));\n\t}\n\n\t@Test\n\tvoid should_throw_on_iterator_modification() {\n\t\tEndpoints endpoints = Endpoints.single(\"id\", \"path\");\n\t\tassertThatThrownBy(() -> endpoints.iterator().remove()).isInstanceOf(UnsupportedOperationException.class);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/domain/values/InfoTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.values;\n\nimport java.util.Iterator;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\nimport org.assertj.core.data.MapEntry;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass InfoTest {\n\n\t@Test\n\tvoid should_keep_order() {\n\t\tMap<String, Object> map = new LinkedHashMap<>();\n\t\tmap.put(\"z\", \"1\");\n\t\tmap.put(\"x\", \"2\");\n\n\t\tIterator<Map.Entry<String, Object>> iterator = Info.from(map).getValues().entrySet().iterator();\n\n\t\tassertThat(iterator.next()).isEqualTo(MapEntry.entry(\"z\", \"1\"));\n\t\tassertThat(iterator.next()).isEqualTo(MapEntry.entry(\"x\", \"2\"));\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/domain/values/InstanceIdTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.values;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass InstanceIdTest {\n\n\t@Test\n\tvoid invariants() {\n\t\tassertThatThrownBy(() -> InstanceId.of(null)).isInstanceOf(IllegalArgumentException.class)\n\t\t\t.hasMessage(\"'value' must have text\");\n\t\tassertThatThrownBy(() -> InstanceId.of(\"\")).isInstanceOf(IllegalArgumentException.class)\n\t\t\t.hasMessage(\"'value' must have text\");\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/domain/values/RegistrationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.values;\n\nimport org.assertj.core.api.WithAssertions;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass RegistrationTest implements WithAssertions {\n\n\t@Test\n\tvoid invariants() {\n\t\tassertThatThrownBy(() -> Registration.create(null, null).build()).isInstanceOf(IllegalArgumentException.class)\n\t\t\t.hasMessage(\"'name' must not be empty.\");\n\n\t\tassertThatThrownBy(() -> Registration.create(\"test\", null).build()).isInstanceOf(IllegalArgumentException.class)\n\t\t\t.hasMessage(\"'healthUrl' must not be empty.\");\n\n\t\tassertThatThrownBy(() -> Registration.create(\"test\", \"invalid\").build())\n\t\t\t.isInstanceOf(IllegalArgumentException.class)\n\t\t\t.hasMessage(\"'healthUrl' is not valid: invalid\");\n\n\t\tassertThatThrownBy(() -> Registration.create(\"test\", \"https://example.com\").managementUrl(\"invalid\").build())\n\t\t\t.isInstanceOf(IllegalArgumentException.class)\n\t\t\t.hasMessage(\"'managementUrl' is not valid: invalid\");\n\n\t\tassertThatThrownBy(() -> Registration.create(\"test\", \"https://example.com\").serviceUrl(\"invalid\").build())\n\t\t\t.isInstanceOf(IllegalArgumentException.class)\n\t\t\t.hasMessage(\"'serviceUrl' is not valid: invalid\");\n\t}\n\n\t@Test\n\tvoid returnsNull_whenServiceUrlIsNull_evenIfMetadataHasOverride() {\n\t\tRegistration reg = Registration.builder()\n\t\t\t.name(\"app\")\n\t\t\t.healthUrl(\"https://example.com/actuator/health\")\n\t\t\t.metadata(\"service-url\", \"https://override.example.com\")\n\t\t\t.build();\n\n\t\tassertThat(reg.getServiceUrl()).isNull();\n\t}\n\n\t@Test\n\tvoid usesMetadataOverride_whenValidAbsoluteUrlProvided() {\n\t\tRegistration reg = Registration.create(\"app\", \"https://example.com/actuator/health\")\n\t\t\t.serviceUrl(\"https://base.example.com\")\n\t\t\t.metadata(\"service-url\", \"https://override.example.com\")\n\t\t\t.build();\n\n\t\tassertThat(reg.getServiceUrl()).isEqualTo(\"https://override.example.com\");\n\t}\n\n\t@Test\n\tvoid fallsBackToOriginal_whenMetadataOverrideIsInvalidSyntax() {\n\t\tRegistration reg = Registration.create(\"app\", \"https://example.com/actuator/health\")\n\t\t\t.serviceUrl(\"https://base.example.com\")\n\t\t\t.metadata(\"service-url\", \"http://exa mple.com\") // invalide URI (Leerzeichen)\n\t\t\t.build();\n\n\t\tassertThat(reg.getServiceUrl()).isEqualTo(\"https://base.example.com\");\n\t}\n\n\t@Test\n\tvoid keepsOriginal_whenNoMetadataOverridePresent() {\n\t\tRegistration reg = Registration.create(\"app\", \"https://example.com/actuator/health\")\n\t\t\t.serviceUrl(\"https://base.example.com\")\n\t\t\t.metadata(\"other\", \"value\")\n\t\t\t.build();\n\n\t\tassertThat(reg.getServiceUrl()).isEqualTo(\"https://base.example.com\");\n\t}\n\n\t@Test\n\tvoid acceptsEmptyStringFromMetadata_evenThoughItIsRelative() {\n\t\tRegistration reg = Registration.create(\"app\", \"https://example.com/actuator/health\")\n\t\t\t.serviceUrl(\"https://base.example.com\")\n\t\t\t.metadata(\"service-url\", \"\")\n\t\t\t.build();\n\n\t\t// new URI(\"\") erzeugt eine relative, aber syntaktisch valide URI -> Methode gibt\n\t\t// \"\" zurück\n\t\tassertThat(reg.getServiceUrl()).isEqualTo(\"\");\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/domain/values/StatusInfoTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.values;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\n\nimport static de.codecentric.boot.admin.server.domain.values.StatusInfo.STATUS_DOWN;\nimport static de.codecentric.boot.admin.server.domain.values.StatusInfo.STATUS_OFFLINE;\nimport static de.codecentric.boot.admin.server.domain.values.StatusInfo.STATUS_OUT_OF_SERVICE;\nimport static de.codecentric.boot.admin.server.domain.values.StatusInfo.STATUS_RESTRICTED;\nimport static de.codecentric.boot.admin.server.domain.values.StatusInfo.STATUS_UNKNOWN;\nimport static de.codecentric.boot.admin.server.domain.values.StatusInfo.STATUS_UP;\nimport static java.util.Arrays.asList;\nimport static java.util.Collections.singletonMap;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass StatusInfoTest {\n\n\t@Test\n\tvoid invariants() {\n\t\tassertThatThrownBy(() -> StatusInfo.valueOf(\"\")).isInstanceOf(IllegalArgumentException.class)\n\t\t\t.hasMessage(\"'status' must not be empty.\");\n\t}\n\n\t@Test\n\tvoid test_isMethods() {\n\t\tassertThat(StatusInfo.valueOf(\"FOO\").isUp()).isFalse();\n\t\tassertThat(StatusInfo.valueOf(\"FOO\").isDown()).isFalse();\n\t\tassertThat(StatusInfo.valueOf(\"FOO\").isUnknown()).isFalse();\n\t\tassertThat(StatusInfo.valueOf(\"FOO\").isOffline()).isFalse();\n\t\tassertThat(StatusInfo.valueOf(\"FOO\").isOutOfService()).isFalse();\n\t\tassertThat(StatusInfo.valueOf(\"FOO\").isRestricted()).isFalse();\n\n\t\tassertThat(StatusInfo.ofUp().isUp()).isTrue();\n\t\tassertThat(StatusInfo.ofUp().isDown()).isFalse();\n\t\tassertThat(StatusInfo.ofUp().isUnknown()).isFalse();\n\t\tassertThat(StatusInfo.ofUp().isOffline()).isFalse();\n\t\tassertThat(StatusInfo.ofUp().isOutOfService()).isFalse();\n\t\tassertThat(StatusInfo.ofUp().isRestricted()).isFalse();\n\n\t\tassertThat(StatusInfo.ofDown().isUp()).isFalse();\n\t\tassertThat(StatusInfo.ofDown().isDown()).isTrue();\n\t\tassertThat(StatusInfo.ofDown().isUnknown()).isFalse();\n\t\tassertThat(StatusInfo.ofDown().isOffline()).isFalse();\n\t\tassertThat(StatusInfo.ofDown().isOutOfService()).isFalse();\n\t\tassertThat(StatusInfo.ofDown().isRestricted()).isFalse();\n\n\t\tassertThat(StatusInfo.ofUnknown().isUp()).isFalse();\n\t\tassertThat(StatusInfo.ofUnknown().isDown()).isFalse();\n\t\tassertThat(StatusInfo.ofUnknown().isUnknown()).isTrue();\n\t\tassertThat(StatusInfo.ofUnknown().isOffline()).isFalse();\n\t\tassertThat(StatusInfo.ofUnknown().isOutOfService()).isFalse();\n\t\tassertThat(StatusInfo.ofUnknown().isRestricted()).isFalse();\n\n\t\tassertThat(StatusInfo.ofOffline().isUp()).isFalse();\n\t\tassertThat(StatusInfo.ofOffline().isDown()).isFalse();\n\t\tassertThat(StatusInfo.ofOffline().isUnknown()).isFalse();\n\t\tassertThat(StatusInfo.ofOffline().isOffline()).isTrue();\n\t\tassertThat(StatusInfo.ofOffline().isOutOfService()).isFalse();\n\t\tassertThat(StatusInfo.ofOffline().isRestricted()).isFalse();\n\n\t\tassertThat(StatusInfo.ofOutOfService().isUp()).isFalse();\n\t\tassertThat(StatusInfo.ofOutOfService().isDown()).isFalse();\n\t\tassertThat(StatusInfo.ofOutOfService().isUnknown()).isFalse();\n\t\tassertThat(StatusInfo.ofOutOfService().isOffline()).isFalse();\n\t\tassertThat(StatusInfo.ofOutOfService().isOutOfService()).isTrue();\n\t\tassertThat(StatusInfo.ofOutOfService().isRestricted()).isFalse();\n\n\t\tassertThat(StatusInfo.ofRestricted().isUp()).isFalse();\n\t\tassertThat(StatusInfo.ofRestricted().isDown()).isFalse();\n\t\tassertThat(StatusInfo.ofRestricted().isUnknown()).isFalse();\n\t\tassertThat(StatusInfo.ofRestricted().isOffline()).isFalse();\n\t\tassertThat(StatusInfo.ofRestricted().isOutOfService()).isFalse();\n\t\tassertThat(StatusInfo.ofRestricted().isRestricted()).isTrue();\n\t}\n\n\t@Test\n\tvoid from_map_should_return_same_result() {\n\t\tMap<String, Object> map = new HashMap<>();\n\t\tmap.put(\"status\", \"UP\");\n\t\tmap.put(\"details\", singletonMap(\"foo\", \"bar\"));\n\n\t\tassertThat(StatusInfo.from(map)).isEqualTo(StatusInfo.ofUp(singletonMap(\"foo\", \"bar\")));\n\t}\n\n\t@Test\n\tvoid factory_methods_with_details() {\n\t\tMap<String, Object> details = singletonMap(\"reason\", \"maintenance\");\n\n\t\tStatusInfo outOfService = StatusInfo.ofOutOfService(details);\n\t\tassertThat(outOfService.getStatus()).isEqualTo(STATUS_OUT_OF_SERVICE);\n\t\tassertThat(outOfService.getDetails()).containsEntry(\"reason\", \"maintenance\");\n\t\tassertThat(outOfService.isOutOfService()).isTrue();\n\n\t\tStatusInfo restricted = StatusInfo.ofRestricted(details);\n\t\tassertThat(restricted.getStatus()).isEqualTo(STATUS_RESTRICTED);\n\t\tassertThat(restricted.getDetails()).containsEntry(\"reason\", \"maintenance\");\n\t\tassertThat(restricted.isRestricted()).isTrue();\n\t}\n\n\t@Test\n\tvoid when_first_level_key_is_components() {\n\t\tMap<String, Object> map = new HashMap<>();\n\t\tmap.put(\"status\", \"UP\");\n\t\tmap.put(\"components\", singletonMap(\"foo\", \"bar\"));\n\n\t\tassertThat(StatusInfo.from(map)).isEqualTo(StatusInfo.ofUp(singletonMap(\"foo\", \"bar\")));\n\t}\n\n\t@Test\n\tvoid should_sort_by_status_order() {\n\t\tList<String> unordered = asList(STATUS_OUT_OF_SERVICE, STATUS_UNKNOWN, STATUS_OFFLINE, STATUS_DOWN, STATUS_UP,\n\t\t\t\tSTATUS_RESTRICTED);\n\n\t\tList<String> ordered = unordered.stream().sorted(StatusInfo.severity()).toList();\n\t\tassertThat(ordered).containsExactly(STATUS_DOWN, STATUS_OUT_OF_SERVICE, STATUS_OFFLINE, STATUS_UNKNOWN,\n\t\t\t\tSTATUS_RESTRICTED, STATUS_UP);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/domain/values/TagsTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.domain.values;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\n\nimport static java.util.Collections.singletonMap;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.entry;\n\nclass TagsTest {\n\n\t@Test\n\tvoid should_return_empty_from_factory_method() {\n\t\tassertThat(Tags.empty().getValues()).isEmpty();\n\t\tassertThat(Tags.from(Collections.emptyMap())).isSameAs(Tags.empty());\n\t}\n\n\t@Test\n\tvoid should_return_tags_from_flat_map() {\n\t\tMap<String, String> flatTags = new LinkedHashMap<>();\n\t\tflatTags.put(\"tags.env\", \"test\");\n\t\tflatTags.put(\"tags.foo\", \"bar\");\n\t\tflatTags.put(\"ignore\", \"ignored\");\n\t\tflatTags.put(\"tagsi\", \"ignored\");\n\n\t\tassertThat(Tags.from(flatTags, \"tags\").getValues()).containsExactly(entry(\"env\", \"test\"), entry(\"foo\", \"bar\"));\n\t}\n\n\t@Test\n\tvoid should_return_tags_from_nested_map() {\n\t\tMap<String, String> tags = new LinkedHashMap<>();\n\t\ttags.put(\"env\", \"test\");\n\t\ttags.put(\"foo\", \"bar\");\n\n\t\tMap<String, Object> nestedTags = new HashMap<>();\n\t\tnestedTags.put(\"tags\", tags);\n\t\tnestedTags.put(\"tagsi\", singletonMap(\"ignore\", \"ignored\"));\n\n\t\tassertThat(Tags.from(nestedTags, \"tags\").getValues()).containsExactly(entry(\"env\", \"test\"),\n\t\t\t\tentry(\"foo\", \"bar\"));\n\t}\n\n\t@Test\n\tvoid should_append_tags() {\n\t\tTags tags = Tags.empty()\n\t\t\t.append(Tags.from(singletonMap(\"tags.env\", \"test\"), \"tags\"))\n\t\t\t.append(Tags.from(singletonMap(\"env\", \"test2\")))\n\t\t\t.append(Tags.from(singletonMap(\"foo\", \"bar\")));\n\n\t\tassertThat(tags.getValues()).containsExactly(entry(\"env\", \"test2\"), entry(\"foo\", \"bar\"));\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/eventstore/AbstractEventStoreTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.eventstore;\n\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.function.Function;\nimport java.util.stream.LongStream;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.core.scheduler.Schedulers;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceDeregisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\nimport static java.util.Arrays.asList;\nimport static java.util.Collections.singletonList;\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic abstract class AbstractEventStoreTest {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(AbstractEventStoreTest.class);\n\n\tprivate final InstanceId id = InstanceId.of(\"id\");\n\n\tprivate final Registration registration = Registration.create(\"foo\", \"https://health\")\n\t\t.metadata(\"test\", \"dummy\")\n\t\t.build();\n\n\tprotected abstract InstanceEventStore createStore(int maxLogSizePerAggregate);\n\n\tprotected abstract void shutdownStore();\n\n\t@AfterEach\n\tvoid tearDown() {\n\t\tthis.shutdownStore();\n\t}\n\n\t@Test\n\tpublic void should_store_events() {\n\t\tInstanceEventStore store = createStore(100);\n\t\tStepVerifier.create(store.findAll()).verifyComplete();\n\n\t\tInstant now = Instant.now();\n\t\tInstanceEvent event1 = new InstanceRegisteredEvent(id, 0L, now, registration);\n\t\tInstanceEvent eventOther = new InstanceRegisteredEvent(InstanceId.of(\"other\"), 0L, now.plusMillis(10),\n\t\t\t\tregistration);\n\t\tInstanceEvent event2 = new InstanceDeregisteredEvent(id, 1L, now.plusMillis(20));\n\n\t\tStepVerifier.create(store)\n\t\t\t.expectSubscription()\n\t\t\t.then(() -> StepVerifier.create(store.append(singletonList(event1))).verifyComplete())\n\t\t\t.expectNext(event1)\n\t\t\t.then(() -> StepVerifier.create(store.append(singletonList(eventOther))).verifyComplete())\n\t\t\t.expectNext(eventOther)\n\t\t\t.then(() -> StepVerifier.create(store.append(singletonList(event2))).verifyComplete())\n\t\t\t.expectNext(event2)\n\t\t\t.thenCancel()\n\t\t\t.verify();\n\n\t\tStepVerifier.create(store.find(id)).expectNext(event1, event2).verifyComplete();\n\t\tStepVerifier.create(store.find(InstanceId.of(\"-\"))).verifyComplete();\n\t\tStepVerifier.create(store.findAll()).expectNext(event1, eventOther, event2).verifyComplete();\n\t}\n\n\t@Test\n\tpublic void should_shorten_log_on_exceeded_capacity() {\n\t\tInstanceEventStore store = createStore(2);\n\n\t\tInstanceEvent event1 = new InstanceRegisteredEvent(id, 0L, registration);\n\t\tInstanceEvent event2 = new InstanceStatusChangedEvent(id, 1L, StatusInfo.ofDown());\n\t\tInstanceEvent event3 = new InstanceStatusChangedEvent(id, 2L, StatusInfo.ofUp());\n\n\t\tStepVerifier.create(store.append(asList(event1, event2, event3))).verifyComplete();\n\n\t\tStepVerifier.create(store.findAll()).expectNext(event1, event3).verifyComplete();\n\t}\n\n\t@Test\n\tpublic void should_throw_optimistic_locking_exception() {\n\t\tInstanceEvent event0 = new InstanceRegisteredEvent(id, 0L, registration);\n\t\tInstanceEvent event1 = new InstanceStatusChangedEvent(id, 1L, StatusInfo.ofDown());\n\t\tInstanceEvent event1b = new InstanceDeregisteredEvent(id, 1L);\n\n\t\tInstanceEventStore store = createStore(100);\n\t\tStepVerifier.create(store.append(asList(event0, event1))).verifyComplete();\n\n\t\tStepVerifier.create(store.append(singletonList(event1b))).verifyError(OptimisticLockingException.class);\n\t}\n\n\t@Test\n\tpublic void concurrent_read_writes() {\n\t\tInstanceId instanceId = InstanceId.of(\"a\");\n\t\tInstanceEventStore store = createStore(500);\n\n\t\tFunction<Integer, InstanceEvent> eventFactory = (i) -> new InstanceDeregisteredEvent(instanceId, i);\n\t\tFlux<Void> eventGenerator = Flux.range(0, 500)\n\t\t\t.map(eventFactory)\n\t\t\t.buffer(2)\n\t\t\t.flatMap((events) -> store.append(events).onErrorResume(OptimisticLockingException.class, (ex) -> {\n\t\t\t\tlog.info(\"skipped {}\", ex.getMessage());\n\t\t\t\treturn Mono.empty();\n\t\t\t}).delayElement(Duration.ofMillis(5L)));\n\n\t\tStepVerifier\n\t\t\t.create(eventGenerator.subscribeOn(Schedulers.newSingle(\"a\"))\n\t\t\t\t.mergeWith(eventGenerator.subscribeOn(Schedulers.newSingle(\"a\")))\n\t\t\t\t.mergeWith(eventGenerator.subscribeOn(Schedulers.newSingle(\"a\")))\n\t\t\t\t.mergeWith(eventGenerator.subscribeOn(Schedulers.newSingle(\"a\")))\n\t\t\t\t.then())\n\t\t\t.verifyComplete();\n\n\t\tList<Long> versions = store.find(instanceId).map(InstanceEvent::getVersion).collectList().block();\n\t\tList<Long> expected = LongStream.range(0, 500).boxed().toList();\n\t\tassertThat(versions).containsExactlyElementsOf(expected);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/eventstore/HazelcastEventStoreTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.eventstore;\n\nimport com.hazelcast.config.Config;\nimport com.hazelcast.core.Hazelcast;\nimport com.hazelcast.core.HazelcastInstance;\n\npublic class HazelcastEventStoreTest extends AbstractEventStoreTest {\n\n\tHazelcastInstance hazelcast;\n\n\t@Override\n\tprotected InstanceEventStore createStore(int maxLogSizePerAggregate) {\n\t\tConfig config = new Config();\n\t\tconfig.getNetworkConfig().getJoin().getMulticastConfig().setEnabled(false);\n\t\tconfig.getNetworkConfig().getJoin().getAutoDetectionConfig().setEnabled(false);\n\t\thazelcast = Hazelcast.newHazelcastInstance(config);\n\t\treturn new HazelcastEventStore(maxLogSizePerAggregate,\n\t\t\t\thazelcast.getMap(\"testList\" + System.currentTimeMillis()));\n\t}\n\n\t@Override\n\tprotected void shutdownStore() {\n\t\tif (this.hazelcast != null) {\n\t\t\tthis.hazelcast.shutdown();\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/eventstore/HazelcastEventStoreWithClientConfigTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.eventstore;\n\nimport java.util.List;\n\nimport com.hazelcast.client.HazelcastClient;\nimport com.hazelcast.client.config.ClientConfig;\nimport com.hazelcast.core.HazelcastInstance;\nimport com.hazelcast.map.IMap;\nimport org.junit.jupiter.api.Tag;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.junit.jupiter.Container;\nimport org.testcontainers.junit.jupiter.Testcontainers;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\n@Testcontainers(disabledWithoutDocker = true)\n@Tag(\"docker\")\npublic class HazelcastEventStoreWithClientConfigTest extends AbstractEventStoreTest {\n\n\t@Container\n\tprivate static final GenericContainer<?> hazelcastServer = new GenericContainer<>(\"hazelcast/hazelcast:4.2.2\")\n\t\t.withExposedPorts(5701);\n\n\tprivate final HazelcastInstance hazelcast;\n\n\tpublic HazelcastEventStoreWithClientConfigTest() {\n\t\tthis.hazelcast = createHazelcastInstance();\n\t}\n\n\t@Override\n\tprotected InstanceEventStore createStore(int maxLogSizePerAggregate) {\n\t\tIMap<InstanceId, List<InstanceEvent>> eventLog = this.hazelcast.getMap(\"testList\" + System.currentTimeMillis());\n\t\treturn new HazelcastEventStore(maxLogSizePerAggregate, eventLog);\n\t}\n\n\t@Override\n\tprotected void shutdownStore() {\n\t\tthis.hazelcast.shutdown();\n\t}\n\n\tprivate HazelcastInstance createHazelcastInstance() {\n\t\tString address = hazelcastServer.getHost() + \":\" + hazelcastServer.getMappedPort(5701);\n\n\t\tClientConfig clientConfig = new ClientConfig();\n\t\tclientConfig.getNetworkConfig().addAddress(address);\n\n\t\treturn HazelcastClient.newHazelcastClient(clientConfig);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/eventstore/HazelcastEventStoreWithServerConfigTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.eventstore;\n\nimport java.util.List;\n\nimport com.hazelcast.config.Config;\nimport com.hazelcast.config.EvictionConfig;\nimport com.hazelcast.config.EvictionPolicy;\nimport com.hazelcast.config.InMemoryFormat;\nimport com.hazelcast.config.MapConfig;\nimport com.hazelcast.config.MergePolicyConfig;\nimport com.hazelcast.config.TcpIpConfig;\nimport com.hazelcast.core.Hazelcast;\nimport com.hazelcast.core.HazelcastInstance;\nimport com.hazelcast.map.IMap;\nimport com.hazelcast.spi.merge.PutIfAbsentMergePolicy;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\nimport static de.codecentric.boot.admin.server.config.AdminServerHazelcastAutoConfiguration.DEFAULT_NAME_EVENT_STORE_MAP;\nimport static de.codecentric.boot.admin.server.config.AdminServerHazelcastAutoConfiguration.DEFAULT_NAME_SENT_NOTIFICATIONS_MAP;\nimport static java.util.Collections.singletonList;\n\npublic class HazelcastEventStoreWithServerConfigTest extends AbstractEventStoreTest {\n\n\tprivate final HazelcastInstance hazelcast;\n\n\tpublic HazelcastEventStoreWithServerConfigTest() {\n\t\tthis.hazelcast = createHazelcastInstance();\n\t}\n\n\t@Override\n\tprotected InstanceEventStore createStore(int maxLogSizePerAggregate) {\n\t\tIMap<InstanceId, List<InstanceEvent>> eventLogs = this.hazelcast\n\t\t\t.getMap(\"testList\" + System.currentTimeMillis());\n\t\treturn new HazelcastEventStore(maxLogSizePerAggregate, eventLogs);\n\t}\n\n\t@Override\n\tprotected void shutdownStore() {\n\t\thazelcast.shutdown();\n\t}\n\n\tprivate HazelcastInstance createHazelcastInstance() {\n\t\tConfig config = createHazelcastConfig();\n\t\treturn Hazelcast.newHazelcastInstance(config);\n\t}\n\n\t// config from sample project\n\tprivate Config createHazelcastConfig() {\n\t\t// This map is used to store the events.\n\t\t// It should be configured to reliably hold all the data,\n\t\t// Spring Boot Admin will compact the events, if there are too many\n\t\tMapConfig eventStoreMap = new MapConfig(DEFAULT_NAME_EVENT_STORE_MAP).setInMemoryFormat(InMemoryFormat.OBJECT)\n\t\t\t.setBackupCount(1)\n\t\t\t.setMergePolicyConfig(new MergePolicyConfig(PutIfAbsentMergePolicy.class.getName(), 100));\n\n\t\t// This map is used to deduplicate the notifications.\n\t\t// If data in this map gets lost it should not be a big issue as it will at most\n\t\t// lead to the same notification to be sent by multiple instances\n\t\tMapConfig sentNotificationsMap = new MapConfig(DEFAULT_NAME_SENT_NOTIFICATIONS_MAP)\n\t\t\t.setInMemoryFormat(InMemoryFormat.OBJECT)\n\t\t\t.setBackupCount(1)\n\t\t\t.setEvictionConfig(new EvictionConfig().setEvictionPolicy(EvictionPolicy.LRU))\n\t\t\t.setMergePolicyConfig(new MergePolicyConfig(PutIfAbsentMergePolicy.class.getName(), 100));\n\n\t\tConfig config = new Config();\n\t\tconfig.addMapConfig(eventStoreMap);\n\t\tconfig.addMapConfig(sentNotificationsMap);\n\t\tconfig.setProperty(\"hazelcast.jmx\", \"true\");\n\n\t\t// WARNING: This code setups a local cluster, you change it to fit your needs.\n\t\tconfig.getNetworkConfig().getJoin().getMulticastConfig().setEnabled(false);\n\t\tTcpIpConfig tcpIpConfig = config.getNetworkConfig().getJoin().getTcpIpConfig();\n\t\ttcpIpConfig.setEnabled(true);\n\t\ttcpIpConfig.setMembers(singletonList(\"127.0.0.1\"));\n\t\treturn config;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/eventstore/InMemoryEventStoreTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.eventstore;\n\npublic class InMemoryEventStoreTest extends AbstractEventStoreTest {\n\n\t@Override\n\tprotected InstanceEventStore createStore(int maxLogSizePerAggregate) {\n\t\treturn new InMemoryEventStore(maxLogSizePerAggregate);\n\t}\n\n\t@Override\n\tprotected void shutdownStore() {\n\t\t// NOOP;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/notify/CompositeNotifierTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.util.Arrays;\n\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass CompositeNotifierTest {\n\n\tprivate static final InstanceEvent APP_DOWN = new InstanceStatusChangedEvent(InstanceId.of(\"-\"), 0L,\n\t\t\tStatusInfo.ofDown());\n\n\t@Test\n\tvoid should_throw_for_invariants() {\n\t\tassertThatThrownBy(() -> new CompositeNotifier(null)).isInstanceOf(IllegalArgumentException.class);\n\t}\n\n\t@Test\n\tvoid should_trigger_all_notifiers() {\n\t\tTestNotifier notifier1 = new TestNotifier();\n\t\tTestNotifier notifier2 = new TestNotifier();\n\t\tCompositeNotifier compositeNotifier = new CompositeNotifier(Arrays.asList(notifier1, notifier2));\n\n\t\tStepVerifier.create(compositeNotifier.notify(APP_DOWN)).verifyComplete();\n\n\t\tassertThat(notifier1.getEvents()).containsOnly(APP_DOWN);\n\t\tassertThat(notifier2.getEvents()).containsOnly(APP_DOWN);\n\t}\n\n\t@Test\n\tvoid should_continue_on_exception() {\n\t\tNotifier notifier1 = (ev) -> Mono.error(new IllegalStateException(\"Test\"));\n\t\tTestNotifier notifier2 = new TestNotifier();\n\t\tCompositeNotifier compositeNotifier = new CompositeNotifier(Arrays.asList(notifier1, notifier2));\n\n\t\tStepVerifier.create(compositeNotifier.notify(APP_DOWN)).verifyComplete();\n\n\t\tassertThat(notifier2.getEvents()).containsOnly(APP_DOWN);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/notify/DingTalkNotifierTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.MediaType;\nimport org.springframework.web.client.RestTemplate;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.clearInvocations;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass DingTalkNotifierTest {\n\n\tprivate final Instance instance = Instance.create(InstanceId.of(\"-id-\"))\n\t\t.register(Registration.create(\"DingTalk\", \"https://health\").build());\n\n\tprivate DingTalkNotifier notifier;\n\n\tprivate RestTemplate restTemplate;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tInstanceRepository repository = mock(InstanceRepository.class);\n\t\twhen(repository.find(instance.getId())).thenReturn(Mono.just(instance));\n\n\t\trestTemplate = mock(RestTemplate.class);\n\t\tnotifier = new DingTalkNotifier(repository, restTemplate);\n\t\tnotifier.setWebhookUrl(\"https://dingtalk.com/\");\n\t\tnotifier.setSecret(\"-secret-\");\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_resolve() {\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofDown())))\n\t\t\t.verifyComplete();\n\t\tclearInvocations(restTemplate);\n\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\n\t\tObject expected = expectedMessage(standardMessage(\"UP\"));\n\n\t\tverify(restTemplate).postForEntity(any(String.class), eq(expected), eq(Void.class));\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_trigger() {\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofDown())))\n\t\t\t.verifyComplete();\n\n\t\tObject expected = expectedMessage(standardMessage(\"DOWN\"));\n\n\t\tverify(restTemplate).postForEntity(any(String.class), eq(expected), eq(Void.class));\n\t}\n\n\tprivate HttpEntity<Map<String, Object>> expectedMessage(String message) {\n\t\tMap<String, Object> messageJson = new HashMap<>();\n\t\tmessageJson.put(\"msgtype\", \"text\");\n\n\t\tMap<String, Object> content = new HashMap<>();\n\t\tcontent.put(\"content\", message);\n\t\tmessageJson.put(\"text\", content);\n\n\t\tHttpHeaders headers = new HttpHeaders();\n\t\theaders.setContentType(MediaType.APPLICATION_JSON);\n\t\treturn new HttpEntity<>(messageJson, headers);\n\t}\n\n\tprivate String standardMessage(String status) {\n\t\treturn instance.getRegistration().getName() + \" \" + instance.getId() + \" is \" + status;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/notify/DiscordNotifierTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.net.URI;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.MediaType;\nimport org.springframework.web.client.RestTemplate;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\nimport static org.mockito.Mockito.clearInvocations;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass DiscordNotifierTest {\n\n\tprivate static final String AVATAR_URL = \"http://avatarUrl\";\n\n\tprivate static final String USER_NAME = \"user\";\n\n\tprivate static final String APP_NAME = \"App\";\n\n\tprivate static final URI WEBHOOK_URI = URI.create(\"http://localhost/\");\n\n\tprivate static final Instance INSTANCE = Instance.create(InstanceId.of(\"-id-\"))\n\t\t.register(Registration.create(APP_NAME, \"https://health\").build());\n\n\tprivate DiscordNotifier notifier;\n\n\tprivate RestTemplate restTemplate;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tInstanceRepository repository = mock(InstanceRepository.class);\n\t\twhen(repository.find(INSTANCE.getId())).thenReturn(Mono.just(INSTANCE));\n\t\trestTemplate = mock(RestTemplate.class);\n\t\tnotifier = new DiscordNotifier(repository, restTemplate);\n\t\tnotifier.setWebhookUrl(WEBHOOK_URI);\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_resolve() {\n\t\tnotifier.setUsername(USER_NAME);\n\t\tnotifier.setAvatarUrl(AVATAR_URL);\n\t\tnotifier.setTts(true);\n\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion(), StatusInfo.ofDown())))\n\t\t\t.verifyComplete();\n\t\tclearInvocations(restTemplate);\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\n\t\tObject expected = expectedMessage(USER_NAME, true, AVATAR_URL, standardMessage(\"UP\"));\n\n\t\tverify(restTemplate).postForEntity(WEBHOOK_URI, expected, Void.class);\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_resolve_minimum_configuration() {\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion(), StatusInfo.ofDown())))\n\t\t\t.verifyComplete();\n\t\tclearInvocations(restTemplate);\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\n\t\tObject expected = expectedMessage(null, false, null, standardMessage(\"UP\"));\n\n\t\tverify(restTemplate).postForEntity(WEBHOOK_URI, expected, Void.class);\n\t}\n\n\tprivate HttpEntity<Map<String, Object>> expectedMessage(String username, boolean tts, String avatarUrl,\n\t\t\tString message) {\n\t\tMap<String, Object> body = new HashMap<>();\n\t\tbody.put(\"content\", message);\n\t\tbody.put(\"tts\", tts);\n\n\t\tif (avatarUrl != null) {\n\t\t\tbody.put(\"avatar_url\", avatarUrl);\n\t\t}\n\t\tif (username != null) {\n\t\t\tbody.put(\"username\", username);\n\t\t}\n\n\t\tHttpHeaders headers = new HttpHeaders();\n\t\theaders.setContentType(MediaType.APPLICATION_JSON);\n\t\theaders.add(HttpHeaders.USER_AGENT, \"RestTemplate\");\n\n\t\treturn new HttpEntity<>(body, headers);\n\t}\n\n\tprivate String standardMessage(String status) {\n\t\treturn \"*\" + INSTANCE.getRegistration().getName() + \"* (\" + INSTANCE.getId() + \") is *\" + status + \"*\";\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/notify/FeiShuNotifierTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.net.URI;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentCaptor;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.client.RestTemplate;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\npublic class FeiShuNotifierTest {\n\n\tpublic static final String WEBHOOK_URL = \"http://localhost/v2\";\n\n\tprivate final Instance instance = Instance.create(InstanceId.of(\"-id-\"))\n\t\t.register(Registration.create(\"App\", \"https://health\").build());\n\n\tprivate FeiShuNotifier notifier;\n\n\tprivate RestTemplate restTemplate;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tInstanceRepository instanceRepository = mock(InstanceRepository.class);\n\t\twhen(instanceRepository.find(instance.getId())).thenReturn(Mono.just(instance));\n\n\t\trestTemplate = mock(RestTemplate.class);\n\t\tnotifier = new FeiShuNotifier(instanceRepository, restTemplate);\n\t\tnotifier.setWebhookUrl(URI.create(WEBHOOK_URL));\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_resolve() {\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tArgumentCaptor<HttpEntity<Map<String, Object>>> httpRequest = ArgumentCaptor\n\t\t\t.forClass((Class<HttpEntity<Map<String, Object>>>) (Class<?>) HttpEntity.class);\n\n\t\twhen(restTemplate.postForEntity(any(), httpRequest.capture(), eq(String.class)))\n\t\t\t.thenReturn(ResponseEntity.ok().build());\n\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofDown())))\n\t\t\t.verifyComplete();\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\n\t\tassertThat(httpRequest.getValue().getHeaders().toSingleValueMap()).containsEntry(\"Content-Type\",\n\t\t\t\t\"application/json\");\n\n\t\tMap<String, Object> body = httpRequest.getValue().getBody();\n\t\tassertThat(body).containsEntry(\"card\",\n\t\t\t\t\"{\\\"elements\\\":[{\\\"tag\\\":\\\"div\\\",\\\"text\\\":{\\\"tag\\\":\\\"plain_text\\\",\\\"content\\\":\\\"ServiceName: App(-id-) \\\\nServiceUrl:  \\\\nStatus: changed status from [DOWN] to [UP]\\\"}},{\\\"tag\\\":\\\"div\\\",\\\"text\\\":{\\\"tag\\\":\\\"lark_md\\\",\\\"content\\\":\\\"<at id=all></at>\\\"}}],\\\"header\\\":{\\\"template\\\":\\\"red\\\",\\\"title\\\":{\\\"tag\\\":\\\"plain_text\\\",\\\"content\\\":\\\"Codecentric's Spring Boot Admin notice\\\"}}}\");\n\t\tassertThat(body).containsEntry(\"msg_type\", FeiShuNotifier.MessageType.interactive);\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_trigger() {\n\t\tStatusInfo infoDown = StatusInfo.ofDown();\n\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tArgumentCaptor<HttpEntity<Map<String, Object>>> httpRequest = ArgumentCaptor\n\t\t\t.forClass((Class<HttpEntity<Map<String, Object>>>) (Class<?>) HttpEntity.class);\n\n\t\twhen(restTemplate.postForEntity(any(), httpRequest.capture(), eq(String.class)))\n\t\t\t.thenReturn(ResponseEntity.ok().build());\n\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\t\tStepVerifier\n\t\t\t.create(notifier.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), infoDown)))\n\t\t\t.verifyComplete();\n\n\t\tassertThat(httpRequest.getValue().getHeaders().toSingleValueMap()).containsEntry(\"Content-Type\",\n\t\t\t\t\"application/json\");\n\t\tMap<String, Object> body = httpRequest.getValue().getBody();\n\t\tassertThat(body).containsEntry(\"card\",\n\t\t\t\t\"{\\\"elements\\\":[{\\\"tag\\\":\\\"div\\\",\\\"text\\\":{\\\"tag\\\":\\\"plain_text\\\",\\\"content\\\":\\\"ServiceName: App(-id-) \\\\nServiceUrl:  \\\\nStatus: changed status from [UP] to [DOWN]\\\"}},{\\\"tag\\\":\\\"div\\\",\\\"text\\\":{\\\"tag\\\":\\\"lark_md\\\",\\\"content\\\":\\\"<at id=all></at>\\\"}}],\\\"header\\\":{\\\"template\\\":\\\"red\\\",\\\"title\\\":{\\\"tag\\\":\\\"plain_text\\\",\\\"content\\\":\\\"Codecentric's Spring Boot Admin notice\\\"}}}\");\n\t\tassertThat(body).containsEntry(\"msg_type\", FeiShuNotifier.MessageType.interactive);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/notify/HazelcastNotificationTriggerTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.util.concurrent.ConcurrentHashMap;\n\nimport org.junit.jupiter.api.Test;\nimport reactor.test.publisher.TestPublisher;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\n\nclass HazelcastNotificationTriggerTest {\n\n\tprivate final Instance instance = Instance.create(InstanceId.of(\"id-1\"))\n\t\t.register(Registration.create(\"foo\", \"http://health-1\").build());\n\n\tprivate final Notifier notifier = mock(Notifier.class);\n\n\tprivate final TestPublisher<InstanceEvent> events = TestPublisher.create();\n\n\tprivate final ConcurrentHashMap<InstanceId, Long> sentNotifications = new ConcurrentHashMap<>();\n\n\tprivate final HazelcastNotificationTrigger trigger = new HazelcastNotificationTrigger(this.notifier, this.events,\n\t\t\tthis.sentNotifications);\n\n\t@Test\n\tvoid should_trigger_notifications() {\n\t\t// given then notifier has subscribed to the events and no notification was sent\n\t\t// before\n\t\tthis.sentNotifications.clear();\n\t\tthis.trigger.start();\n\t\tawait().until(this.events::wasSubscribed);\n\n\t\t// when registered event is emitted\n\t\tInstanceStatusChangedEvent event = new InstanceStatusChangedEvent(this.instance.getId(),\n\t\t\t\tthis.instance.getVersion(), StatusInfo.ofDown());\n\t\tthis.events.next(event);\n\t\t// then should notify\n\t\tverify(this.notifier, times(1)).notify(event);\n\t}\n\n\t@Test\n\tvoid should_not_trigger_notifications() {\n\t\t// given the event is in the already sent notifications.\n\t\tInstanceStatusChangedEvent event = new InstanceStatusChangedEvent(this.instance.getId(),\n\t\t\t\tthis.instance.getVersion(), StatusInfo.ofDown());\n\t\tthis.sentNotifications.put(event.getInstance(), event.getVersion());\n\t\tthis.trigger.start();\n\t\tawait().until(this.events::wasSubscribed);\n\n\t\t// when registered event is emitted\n\t\tthis.events.next(event);\n\t\t// then should not notify\n\t\tverify(this.notifier, never()).notify(event);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/notify/HipchatNotifierTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.net.URI;\nimport java.util.Collections;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentCaptor;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.client.RestTemplate;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.ArgumentMatchers.isA;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n/**\n * @author Jamie Brown\n */\nclass HipchatNotifierTest {\n\n\tprivate final Instance instance = Instance.create(InstanceId.of(\"-id-\"))\n\t\t.register(Registration.create(\"App\", \"https://health\").build());\n\n\tprivate HipchatNotifier notifier;\n\n\tprivate RestTemplate restTemplate;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tInstanceRepository repository = mock(InstanceRepository.class);\n\t\twhen(repository.find(instance.getId())).thenReturn(Mono.just(instance));\n\n\t\trestTemplate = mock(RestTemplate.class);\n\t\tnotifier = new HipchatNotifier(repository, restTemplate);\n\t\tnotifier.setNotify(true);\n\t\tnotifier.setAuthToken(\"--token-\");\n\t\tnotifier.setRoomId(\"-room-\");\n\t\tnotifier.setUrl(URI.create(\"http://localhost/v2\"));\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_resolve() {\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tArgumentCaptor<HttpEntity<Map<String, Object>>> httpRequest = ArgumentCaptor\n\t\t\t.forClass((Class<HttpEntity<Map<String, Object>>>) (Class<?>) HttpEntity.class);\n\n\t\twhen(restTemplate.postForEntity(isA(String.class), httpRequest.capture(), eq(Void.class)))\n\t\t\t.thenReturn(ResponseEntity.ok().build());\n\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofDown())))\n\t\t\t.verifyComplete();\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\n\t\tassertThat(httpRequest.getValue().getHeaders().asMultiValueMap()).containsEntry(\"Content-Type\",\n\t\t\t\tCollections.singletonList(\"application/json\"));\n\n\t\tMap<String, Object> body = httpRequest.getValue().getBody();\n\t\tassertThat(body).containsEntry(\"color\", \"green\");\n\t\tassertThat(body).containsEntry(\"message\", \"<strong>App</strong>/-id- is <strong>UP</strong>\");\n\t\tassertThat(body).containsEntry(\"notify\", Boolean.TRUE);\n\t\tassertThat(body).containsEntry(\"message_format\", \"html\");\n\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_trigger() {\n\t\tStatusInfo infoDown = StatusInfo.ofDown();\n\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tArgumentCaptor<HttpEntity<Map<String, Object>>> httpRequest = ArgumentCaptor\n\t\t\t.forClass((Class<HttpEntity<Map<String, Object>>>) (Class<?>) HttpEntity.class);\n\n\t\twhen(restTemplate.postForEntity(isA(String.class), httpRequest.capture(), eq(Void.class)))\n\t\t\t.thenReturn(ResponseEntity.ok().build());\n\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\t\tStepVerifier\n\t\t\t.create(notifier.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), infoDown)))\n\t\t\t.verifyComplete();\n\n\t\tassertThat(httpRequest.getValue().getHeaders().toSingleValueMap()).containsEntry(\"Content-Type\",\n\t\t\t\t\"application/json\");\n\t\tMap<String, Object> body = httpRequest.getValue().getBody();\n\t\tassertThat(body).containsEntry(\"color\", \"red\");\n\t\tassertThat(body).containsEntry(\"message\", \"<strong>App</strong>/-id- is <strong>DOWN</strong>\");\n\t\tassertThat(body).containsEntry(\"notify\", Boolean.TRUE);\n\t\tassertThat(body).containsEntry(\"message_format\", \"html\");\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/notify/LetsChatNotifierTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.net.URI;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Base64;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.http.MediaType;\nimport org.springframework.web.client.RestTemplate;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\nimport static org.mockito.Mockito.clearInvocations;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass LetsChatNotifierTest {\n\n\tprivate static final String ROOM = \"text_room\";\n\n\tprivate static final String TOKEN = \"text_token\";\n\n\tprivate static final String USER = \"api_user\";\n\n\tprivate static final String HOST = \"http://localhost\";\n\n\tprivate static final Instance instance = Instance.create(InstanceId.of(\"-id-\"))\n\t\t.register(Registration.create(\"App\", \"https://health\").build());\n\n\tprivate LetsChatNotifier notifier;\n\n\tprivate RestTemplate restTemplate;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tInstanceRepository repository = mock(InstanceRepository.class);\n\t\twhen(repository.find(instance.getId())).thenReturn(Mono.just(instance));\n\n\t\trestTemplate = mock(RestTemplate.class);\n\t\tnotifier = new LetsChatNotifier(repository, restTemplate);\n\t\tnotifier.setUsername(USER);\n\t\tnotifier.setUrl(URI.create(HOST));\n\t\tnotifier.setRoom(ROOM);\n\t\tnotifier.setToken(TOKEN);\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_resolve() {\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofDown())))\n\t\t\t.verifyComplete();\n\t\tclearInvocations(restTemplate);\n\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\n\t\tHttpEntity<?> expected = expectedMessage(standardMessage(\"UP\"));\n\t\tverify(restTemplate).exchange(URI.create(String.format(\"%s/rooms/%s/messages\", HOST, ROOM)), HttpMethod.POST,\n\t\t\t\texpected, Void.class);\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_resolve_with_custom_message() {\n\t\tnotifier.setMessage(\"TEST\");\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofDown())))\n\t\t\t.verifyComplete();\n\t\tclearInvocations(restTemplate);\n\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\n\t\tHttpEntity<?> expected = expectedMessage(\"TEST\");\n\t\tverify(restTemplate).exchange(URI.create(String.format(\"%s/rooms/%s/messages\", HOST, ROOM)), HttpMethod.POST,\n\t\t\t\texpected, Void.class);\n\t}\n\n\tprivate HttpEntity<?> expectedMessage(String message) {\n\t\tHttpHeaders httpHeaders = new HttpHeaders();\n\t\thttpHeaders.setContentType(MediaType.APPLICATION_JSON);\n\t\tString auth = Base64.getEncoder()\n\t\t\t.encodeToString(String.format(\"%s:%s\", TOKEN, USER).getBytes(StandardCharsets.UTF_8));\n\t\thttpHeaders.add(HttpHeaders.AUTHORIZATION, String.format(\"Basic %s\", auth));\n\t\tMap<String, Object> messageJson = new HashMap<>();\n\t\tmessageJson.put(\"text\", message);\n\t\treturn new HttpEntity<>(messageJson, httpHeaders);\n\t}\n\n\tprivate String standardMessage(String status) {\n\t\treturn \"*\" + instance.getRegistration().getName() + \"* (\" + instance.getId() + \") is *\" + status + \"*\";\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/notify/MailNotifierIntegrationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.io.FileNotFoundException;\nimport java.net.URL;\n\nimport org.assertj.core.api.WithAssertions;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.SpringBootConfiguration;\nimport org.springframework.boot.autoconfigure.EnableAutoConfiguration;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.thymeleaf.context.Context;\n\nimport de.codecentric.boot.admin.server.config.EnableAdminServer;\n\n@SpringBootTest(properties = { \"spring.mail.host=localhost\", \"spring.boot.admin.notify.mail=true\" })\nclass MailNotifierIntegrationTest implements WithAssertions {\n\n\t@Autowired\n\tMailNotifier mailNotifier;\n\n\t@Test\n\tvoid fileProtocolIsNotAllowed() {\n\t\tassertThatThrownBy(() -> {\n\t\t\tURL resource = getClass().getClassLoader().getResource(\".\");\n\t\t\tmailNotifier.setTemplate(\n\t\t\t\t\t\"file://\" + resource.getFile() + \"de/codecentric/boot/admin/server/notify/vulnerable-file.html\");\n\t\t\tmailNotifier.getBody(new Context());\n\t\t}).hasCauseInstanceOf(FileNotFoundException.class);\n\t}\n\n\t@Test\n\tvoid httpProtocolIsNotAllowed() {\n\t\tassertThatThrownBy(() -> {\n\t\t\tmailNotifier.setTemplate(\n\t\t\t\t\t\"https://raw.githubusercontent.com/codecentric/spring-boot-admin/gh-pages/vulnerable-file.html\");\n\t\t\tmailNotifier.getBody(new Context());\n\t\t}).hasCauseInstanceOf(FileNotFoundException.class);\n\t}\n\n\t@Test\n\tvoid classpathProtocolIsAllowed() {\n\t\tassertThatNoException().isThrownBy(() -> {\n\t\t\tmailNotifier.setTemplate(\"/de/codecentric/boot/admin/server/notify/allowed-file.html\");\n\t\t\tmailNotifier.getBody(new Context());\n\t\t});\n\t}\n\n\t@Test\n\tvoid callToReflectionUtilsAreNotAllowed() {\n\t\tassertThatThrownBy(() -> {\n\t\t\tmailNotifier.setTemplate(\"/de/codecentric/boot/admin/server/notify/vulnerable-file.html\");\n\t\t\tmailNotifier.getBody(new Context());\n\t\t}).rootCause()\n\t\t\t.hasMessageContaining(\n\t\t\t\t\t\"Access is forbidden for type 'org.springframework.util.ReflectionUtils' in this expression context.\");\n\t}\n\n\t@EnableAdminServer\n\t@EnableAutoConfiguration\n\t@SpringBootConfiguration\n\tpublic static class TestAdminApplication {\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/notify/MailNotifierTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Properties;\n\nimport jakarta.activation.DataHandler;\nimport jakarta.mail.Message;\nimport jakarta.mail.MessagingException;\nimport jakarta.mail.Session;\nimport jakarta.mail.internet.InternetAddress;\nimport jakarta.mail.internet.MimeMessage;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentCaptor;\nimport org.springframework.mail.javamail.JavaMailSender;\nimport org.springframework.util.StreamUtils;\nimport org.thymeleaf.spring6.SpringTemplateEngine;\nimport org.thymeleaf.templatemode.TemplateMode;\nimport org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\nimport static java.util.Collections.singletonMap;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoMoreInteractions;\nimport static org.mockito.Mockito.when;\n\nclass MailNotifierTest {\n\n\tprivate final Instance instance = Instance.create(InstanceId.of(\"cafebabe\"))\n\t\t.register(Registration.create(\"application-name\", \"http://localhost:8081/actuator/health\")\n\t\t\t.managementUrl(\"http://localhost:8081/actuator\")\n\t\t\t.serviceUrl(\"http://localhost:8081/\")\n\t\t\t.build());\n\n\tprivate JavaMailSender sender;\n\n\tprivate MailNotifier notifier;\n\n\tprivate InstanceRepository repository;\n\n\t@BeforeEach\n\tvoid setup() {\n\t\trepository = mock(InstanceRepository.class);\n\t\twhen(repository.find(instance.getId())).thenReturn(Mono.just(instance));\n\n\t\tsender = mock(JavaMailSender.class);\n\t\twhen(sender.createMimeMessage()).thenAnswer((args) -> new MimeMessage(Session.getInstance(new Properties())));\n\n\t\tSpringTemplateEngine templateEngine = new SpringTemplateEngine();\n\t\tClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();\n\t\tresolver.setTemplateMode(TemplateMode.HTML);\n\t\tresolver.setCharacterEncoding(StandardCharsets.UTF_8.name());\n\t\ttemplateEngine.addTemplateResolver(resolver);\n\n\t\tnotifier = new MailNotifier(sender, repository, templateEngine);\n\t\tnotifier.setTo(new String[] { \"foo@bar.com\" });\n\t\tnotifier.setCc(new String[] { \"bar@foo.com\" });\n\t\tnotifier.setFrom(\"SBA <no-reply@example.com>\");\n\t\tnotifier.setBaseUrl(\"http://localhost:8080\");\n\t\tnotifier.setTemplate(\"/META-INF/spring-boot-admin-server/mail/status-changed.html\");\n\t}\n\n\t@Test\n\tvoid should_send_mail_using_default_template() throws IOException, MessagingException {\n\t\tMap<String, Object> details = new HashMap<>();\n\t\tdetails.put(\"Simple Value\", 1234);\n\t\tdetails.put(\"Complex Value\", singletonMap(\"Nested Simple Value\", \"99!\"));\n\n\t\tStepVerifier.create(notifier.notify(\n\t\t\t\tnew InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofDown(details))))\n\t\t\t.verifyComplete();\n\n\t\tArgumentCaptor<MimeMessage> mailCaptor = ArgumentCaptor.forClass(MimeMessage.class);\n\t\tverify(sender).send(mailCaptor.capture());\n\n\t\tMimeMessage mail = mailCaptor.getValue();\n\n\t\tassertThat(mail.getSubject()).isEqualTo(\"application-name (cafebabe) is DOWN\");\n\t\tassertThat(mail.getRecipients(Message.RecipientType.TO)).containsExactly(new InternetAddress(\"foo@bar.com\"));\n\t\tassertThat(mail.getRecipients(Message.RecipientType.CC)).containsExactly(new InternetAddress(\"bar@foo.com\"));\n\t\tassertThat(mail.getFrom()).containsExactly(new InternetAddress(\"SBA <no-reply@example.com>\"));\n\t\tassertThat(mail.getDataHandler().getContentType()).isEqualTo(\"text/html;charset=UTF-8\");\n\n\t\tString body = extractBody(mail.getDataHandler());\n\t\tassertThat(body).isEqualTo(loadExpectedBody(\"expected-default-mail\"));\n\t}\n\n\t@Test\n\tvoid should_send_mail_using_custom_template_with_additional_properties() throws IOException, MessagingException {\n\t\tnotifier.setTemplate(\"/de/codecentric/boot/admin/server/notify/custom-mail.html\");\n\t\tnotifier.getAdditionalProperties().put(\"customProperty\", \"HELLO WORLD!\");\n\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofDown())))\n\t\t\t.verifyComplete();\n\n\t\tArgumentCaptor<MimeMessage> mailCaptor = ArgumentCaptor.forClass(MimeMessage.class);\n\t\tverify(sender).send(mailCaptor.capture());\n\n\t\tMimeMessage mail = mailCaptor.getValue();\n\t\tString body = extractBody(mail.getDataHandler());\n\t\tassertThat(body).isEqualTo(loadExpectedBody(\"expected-custom-mail\"));\n\t}\n\n\t// The following tests are rather for AbstractNotifier\n\n\t@Test\n\tvoid should_not_send_mail_when_disabled() {\n\t\tnotifier.setEnabled(false);\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\n\t\tverifyNoMoreInteractions(sender);\n\t}\n\n\t@Test\n\tvoid should_not_send_when_unknown_to_up() {\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\n\t\tverifyNoMoreInteractions(sender);\n\t}\n\n\t@Test\n\tvoid should_not_send_on_wildcard_ignore() {\n\t\tnotifier.setIgnoreChanges(new String[] { \"*:UP\" });\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\n\t\tverifyNoMoreInteractions(sender);\n\t}\n\n\t@Test\n\tvoid should_not_propagate_error() {\n\t\tNotifier statusChangeNotifier = new AbstractStatusChangeNotifier(repository) {\n\t\t\t@Override\n\t\t\tprotected Mono<Void> doNotify(InstanceEvent event, Instance application) {\n\t\t\t\treturn Mono.error(new IllegalStateException(\"test\"));\n\t\t\t}\n\t\t};\n\t\tStepVerifier\n\t\t\t.create(statusChangeNotifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\t}\n\n\tprivate String loadExpectedBody(String resource) throws IOException {\n\t\treturn StreamUtils.copyToString(this.getClass().getResourceAsStream(resource), StandardCharsets.UTF_8);\n\t}\n\n\tprivate String extractBody(DataHandler dataHandler) throws IOException {\n\t\tByteArrayOutputStream os = new ByteArrayOutputStream(4096);\n\t\tdataHandler.writeTo(os);\n\t\treturn os.toString(StandardCharsets.UTF_8).replaceAll(\"\\\\r?\\\\n\", \"\\n\");\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/notify/MattermostNotifierTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.net.URI;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.MediaType;\nimport org.springframework.web.client.RestTemplate;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.clearInvocations;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass MattermostNotifierTest {\n\n\tprivate static final String CHANNEL_ID = \"channel\";\n\n\tprivate static final String BOT_ACCESS_TOKEN = \"bot_access_token\";\n\n\tprivate static final String APP_NAME = \"App\";\n\n\tprivate static final Instance INSTANCE = Instance.create(InstanceId.of(\"-id-\"))\n\t\t.register(Registration.create(APP_NAME, \"https://health\").build());\n\n\tprivate static final String MESSAGE = \"test\";\n\n\tprivate MattermostNotifier notifier;\n\n\tprivate RestTemplate restTemplate;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tInstanceRepository repository = mock(InstanceRepository.class);\n\t\twhen(repository.find(INSTANCE.getId())).thenReturn(Mono.just(INSTANCE));\n\t\trestTemplate = mock(RestTemplate.class);\n\n\t\tnotifier = new MattermostNotifier(repository, restTemplate);\n\t\tnotifier.setBotAccessToken(BOT_ACCESS_TOKEN);\n\t\tnotifier.setApiUrl(URI.create(\"http://localhost/\"));\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_resolve() {\n\t\tnotifier.setChannelId(CHANNEL_ID);\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion(), StatusInfo.ofDown())))\n\t\t\t.verifyComplete();\n\t\tclearInvocations(restTemplate);\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\n\t\tObject expected = expectedMessage(\"#2eb885\", CHANNEL_ID, BOT_ACCESS_TOKEN, standardMessage(\"UP\"));\n\n\t\tverify(restTemplate).postForEntity(any(URI.class), eq(expected), eq(Void.class));\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_resolve_with_given_message() {\n\t\tnotifier.setMessage(MESSAGE);\n\t\tnotifier.setChannelId(CHANNEL_ID);\n\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion(), StatusInfo.ofDown())))\n\t\t\t.verifyComplete();\n\t\tclearInvocations(restTemplate);\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\n\t\tObject expected = expectedMessage(\"#2eb885\", CHANNEL_ID, BOT_ACCESS_TOKEN, MESSAGE);\n\n\t\tverify(restTemplate).postForEntity(any(URI.class), eq(expected), eq(Void.class));\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_trigger() {\n\t\tnotifier.setChannelId(CHANNEL_ID);\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\t\tclearInvocations(restTemplate);\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion(), StatusInfo.ofDown())))\n\t\t\t.verifyComplete();\n\n\t\tObject expected = expectedMessage(\"#a30100\", CHANNEL_ID, BOT_ACCESS_TOKEN, standardMessage(\"DOWN\"));\n\n\t\tverify(restTemplate).postForEntity(any(URI.class), eq(expected), eq(Void.class));\n\t}\n\n\tprivate HttpEntity<Map<String, Object>> expectedMessage(String color, String channelId, String botAccessToken,\n\t\t\tString message) {\n\t\tMap<String, Object> messageJson = new HashMap<>();\n\t\tmessageJson.put(\"channel_id\", channelId);\n\n\t\tMap<String, Object> attachments = new HashMap<>();\n\t\tattachments.put(\"text\", message);\n\t\tattachments.put(\"fallback\", message);\n\t\tattachments.put(\"color\", color);\n\n\t\tMap<String, Object> props = new HashMap<>();\n\t\tprops.put(\"attachments\", Collections.singletonList(attachments));\n\n\t\tmessageJson.put(\"props\", props);\n\n\t\tHttpHeaders headers = new HttpHeaders();\n\t\theaders.setContentType(MediaType.APPLICATION_JSON);\n\t\theaders.setBearerAuth(botAccessToken);\n\t\treturn new HttpEntity<>(messageJson, headers);\n\t}\n\n\tprivate String standardMessage(String status) {\n\t\treturn \"**\" + INSTANCE.getRegistration().getName() + \"** (\" + INSTANCE.getId() + \") is **\" + status + \"**\";\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/notify/MicrosoftTeamsNotifierTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.net.URI;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentCaptor;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.MediaType;\nimport org.springframework.web.client.RestTemplate;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceDeregisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\nimport de.codecentric.boot.admin.server.notify.MicrosoftTeamsNotifier.Message;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass MicrosoftTeamsNotifierTest {\n\n\tprivate static final String BLUE = \"439fe0\";\n\n\tprivate static final String RED = \"b32d36\";\n\n\tprivate static final String GREEN = \"6db33f\";\n\n\tprivate static final String APP_NAME = \"Test App\";\n\n\tprivate static final String APP_ID = \"TestAppId\";\n\n\tprivate static final String HEALTH_URL = \"https://health\";\n\n\tprivate static final String MANAGEMENT_URL = \"https://management\";\n\n\tprivate static final String SERVICE_URL = \"https://service\";\n\n\tprivate MicrosoftTeamsNotifier notifier;\n\n\tprivate RestTemplate mockRestTemplate;\n\n\tprivate Instance instance;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tinstance = Instance.create(InstanceId.of(APP_ID))\n\t\t\t.register(Registration.create(APP_NAME, HEALTH_URL)\n\t\t\t\t.managementUrl(MANAGEMENT_URL)\n\t\t\t\t.serviceUrl(SERVICE_URL)\n\t\t\t\t.build());\n\n\t\tInstanceRepository repository = mock(InstanceRepository.class);\n\t\twhen(repository.find(instance.getId())).thenReturn(Mono.just(instance));\n\n\t\tmockRestTemplate = mock(RestTemplate.class);\n\t\tnotifier = new MicrosoftTeamsNotifier(repository, mockRestTemplate);\n\t\tnotifier.setWebhookUrl(URI.create(\"https://example.com\"));\n\t}\n\n\t@Test\n\t@SuppressWarnings(\"unchecked\")\n\tvoid test_onClientApplicationDeRegisteredEvent_resolve() {\n\t\tInstanceDeregisteredEvent event = new InstanceDeregisteredEvent(instance.getId(), 1L);\n\n\t\tStepVerifier.create(notifier.doNotify(event, instance)).verifyComplete();\n\n\t\tArgumentCaptor<HttpEntity<Message>> entity = ArgumentCaptor.forClass(HttpEntity.class);\n\t\tverify(mockRestTemplate).postForEntity(eq(URI.create(\"https://example.com\")), entity.capture(), eq(Void.class));\n\n\t\tassertThat(entity.getValue().getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);\n\t\tassertThat(entity.getValue().getBody()).isNotNull();\n\t\tassertMessage(entity.getValue().getBody(), notifier.getDeRegisteredTitle(), notifier.getMessageSummary(),\n\t\t\t\t\"Test App with id TestAppId has de-registered from Spring Boot Admin\", BLUE);\n\t}\n\n\t@Test\n\t@SuppressWarnings(\"unchecked\")\n\tvoid test_onApplicationRegisteredEvent_resolve() {\n\t\tInstanceRegisteredEvent event = new InstanceRegisteredEvent(instance.getId(), 1L, instance.getRegistration());\n\n\t\tStepVerifier.create(notifier.doNotify(event, instance)).verifyComplete();\n\n\t\tArgumentCaptor<HttpEntity<Message>> entity = ArgumentCaptor.forClass(HttpEntity.class);\n\t\tverify(mockRestTemplate).postForEntity(eq(URI.create(\"https://example.com\")), entity.capture(), eq(Void.class));\n\n\t\tassertThat(entity.getValue().getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);\n\t\tassertThat(entity.getValue().getBody()).isNotNull();\n\t\tassertMessage(entity.getValue().getBody(), notifier.getRegisteredTitle(), notifier.getMessageSummary(),\n\t\t\t\t\"Test App with id TestAppId has registered with Spring Boot Admin\", BLUE);\n\t}\n\n\t@Test\n\t@SuppressWarnings(\"unchecked\")\n\tvoid test_onApplicationStatusChangedEvent_resolve() {\n\t\tInstanceStatusChangedEvent event = new InstanceStatusChangedEvent(instance.getId(), 1L, StatusInfo.ofUp());\n\n\t\tStepVerifier.create(notifier.doNotify(event, instance)).verifyComplete();\n\n\t\tArgumentCaptor<HttpEntity<Message>> entity = ArgumentCaptor.forClass(HttpEntity.class);\n\t\tverify(mockRestTemplate).postForEntity(eq(URI.create(\"https://example.com\")), entity.capture(), eq(Void.class));\n\n\t\tassertThat(entity.getValue().getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);\n\t\tassertThat(entity.getValue().getBody()).isNotNull();\n\t\tassertMessage(entity.getValue().getBody(), notifier.getStatusChangedTitle(), notifier.getMessageSummary(),\n\t\t\t\t\"Test App with id TestAppId changed status from UNKNOWN to UP\", GREEN);\n\t}\n\n\t@Test\n\tvoid test_shouldNotifyWithRegisteredEventReturns_true() {\n\t\tInstanceRegisteredEvent event = new InstanceRegisteredEvent(instance.getId(), 1L, instance.getRegistration());\n\t\tassertThat(notifier.shouldNotify(event, instance)).isTrue();\n\t}\n\n\t@Test\n\tvoid test_shouldNotifyWithDeRegisteredEventReturns_true() {\n\t\tInstanceDeregisteredEvent event = new InstanceDeregisteredEvent(instance.getId(), 1L);\n\t\tassertThat(notifier.shouldNotify(event, instance)).isTrue();\n\t}\n\n\t@Test\n\tvoid test_getDeregisteredMessageForAppReturns_correctContent() {\n\t\tMessage message = notifier.getDeregisteredMessage(instance,\n\t\t\t\tnotifier.createEvaluationContext(new InstanceDeregisteredEvent(instance.getId(), 1L), instance));\n\n\t\tassertMessage(message, notifier.getDeRegisteredTitle(), notifier.getMessageSummary(),\n\t\t\t\t\"Test App with id TestAppId has de-registered from Spring Boot Admin\", BLUE);\n\t}\n\n\t@Test\n\tvoid test_getRegisteredMessageForAppReturns_correctContent() {\n\t\tMessage message = notifier.getRegisteredMessage(instance,\n\t\t\t\tnotifier.createEvaluationContext(new InstanceDeregisteredEvent(instance.getId(), 1L), instance));\n\n\t\tassertMessage(message, notifier.getRegisteredTitle(), notifier.getMessageSummary(),\n\t\t\t\t\"Test App with id TestAppId has registered with Spring Boot Admin\", BLUE);\n\t}\n\n\t@Test\n\tvoid test_getStatusChangedMessageForAppReturns_correctContent() {\n\t\tMessage message = notifier.getStatusChangedMessage(instance, notifier.createEvaluationContext(\n\t\t\t\tnew InstanceStatusChangedEvent(instance.getId(), 1L, StatusInfo.ofDown()), instance));\n\n\t\tassertMessage(message, notifier.getStatusChangedTitle(), notifier.getMessageSummary(),\n\t\t\t\t\"Test App with id TestAppId changed status from UNKNOWN to DOWN\", RED);\n\t}\n\n\t@Test\n\tvoid test_getStatusChangedMessageForAppReturns_UP_to_DOWN() {\n\t\tnotifier.updateLastStatus(new InstanceStatusChangedEvent(instance.getId(), 1L, StatusInfo.ofUp()));\n\n\t\tMessage message = notifier.getStatusChangedMessage(instance, notifier.createEvaluationContext(\n\t\t\t\tnew InstanceStatusChangedEvent(instance.getId(), 1L, StatusInfo.ofDown()), instance));\n\n\t\tassertMessage(message, notifier.getStatusChangedTitle(), notifier.getMessageSummary(),\n\t\t\t\t\"Test App with id TestAppId changed status from UP to DOWN\", RED);\n\t}\n\n\t@Test\n\tvoid test_getStatusChangedMessageWithExtraFormatArgumentReturns_activitySubtitlePatternWithAppName() {\n\t\tnotifier.setStatusActivitySubtitle(\"STATUS_ACTIVITY_PATTERN_#{instance.registration.name}\");\n\t\tMessage message = notifier.getStatusChangedMessage(instance,\n\t\t\t\tnotifier.createEvaluationContext(new InstanceDeregisteredEvent(instance.getId(), 1L), instance));\n\n\t\tassertThat(message.getSections().get(0).getActivitySubtitle()).isEqualTo(\"STATUS_ACTIVITY_PATTERN_\" + APP_NAME);\n\t}\n\n\t@Test\n\tvoid test_getRegisterMessageWithExtraFormatArgumentReturns_activitySubtitlePatternWithAppName() {\n\t\tnotifier.setRegisterActivitySubtitle(\"REGISTER_ACTIVITY_PATTERN_#{instance.registration.name}\");\n\t\tMessage message = notifier.getRegisteredMessage(instance,\n\t\t\t\tnotifier.createEvaluationContext(new InstanceDeregisteredEvent(instance.getId(), 1L), instance));\n\n\t\tassertThat(message.getSections().get(0).getActivitySubtitle())\n\t\t\t.isEqualTo(\"REGISTER_ACTIVITY_PATTERN_\" + APP_NAME);\n\t}\n\n\t@Test\n\tvoid test_getDeRegisterMessageWithExtraFormatArgumentReturns_activitySubtitlePatternWithAppName() {\n\t\tnotifier.setDeregisterActivitySubtitle(\"DEREGISTER_ACTIVITY_PATTERN_#{instance.registration.name}\");\n\t\tMessage message = notifier.getDeregisteredMessage(instance,\n\t\t\t\tnotifier.createEvaluationContext(new InstanceDeregisteredEvent(instance.getId(), 1L), instance));\n\n\t\tassertThat(message.getSections().get(0).getActivitySubtitle())\n\t\t\t.isEqualTo(\"DEREGISTER_ACTIVITY_PATTERN_\" + APP_NAME);\n\t}\n\n\t@Test\n\tvoid test_getStatusChangedMessage_parsesThemeColorFromSpelExpression() {\n\t\tnotifier.setThemeColor(\n\t\t\t\t\"#{event.type == 'STATUS_CHANGED' ? (event.statusInfo.status=='UP' ? 'green' : 'red') : 'blue'}\");\n\n\t\tMessage message = notifier.getStatusChangedMessage(instance, notifier.createEvaluationContext(\n\t\t\t\tnew InstanceStatusChangedEvent(instance.getId(), 1L, StatusInfo.ofUp()), instance));\n\n\t\tassertThat(message.getThemeColor()).isEqualTo(\"green\");\n\t}\n\n\tprivate void assertMessage(Message message, String expectedTitle, String expectedSummary, String expectedSubTitle,\n\t\t\tString expectedColor) {\n\t\tassertThat(message.getTitle()).isEqualTo(expectedTitle);\n\t\tassertThat(message.getSummary()).isEqualTo(expectedSummary);\n\t\tassertThat(message.getThemeColor()).isEqualTo(expectedColor);\n\n\t\tassertThat(message.getSections()).hasSize(1).anySatisfy((section) -> {\n\t\t\tassertThat(section.getActivityTitle()).isEqualTo(instance.getRegistration().getName());\n\t\t\tassertThat(section.getActivitySubtitle()).isEqualTo(expectedSubTitle);\n\n\t\t\tassertThat(section.getFacts()).hasSize(5).anySatisfy((fact) -> {\n\t\t\t\tassertThat(fact.name()).isEqualTo(\"Status\");\n\t\t\t\tassertThat(fact.value()).isEqualTo(\"UNKNOWN\");\n\t\t\t}).anySatisfy((fact) -> {\n\t\t\t\tassertThat(fact.name()).isEqualTo(\"Service URL\");\n\t\t\t\tassertThat(fact.value()).isEqualTo(SERVICE_URL);\n\t\t\t}).anySatisfy((fact) -> {\n\t\t\t\tassertThat(fact.name()).isEqualTo(\"Health URL\");\n\t\t\t\tassertThat(fact.value()).isEqualTo(HEALTH_URL);\n\t\t\t}).anySatisfy((fact) -> {\n\t\t\t\tassertThat(fact.name()).isEqualTo(\"Management URL\");\n\t\t\t\tassertThat(fact.value()).isEqualTo(MANAGEMENT_URL);\n\t\t\t}).anySatisfy((fact) -> {\n\t\t\t\tassertThat(fact.name()).isEqualTo(\"Source\");\n\t\t\t\tassertThat(fact.value()).isNull();\n\t\t\t});\n\t\t});\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/notify/NotificationTriggerTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Mono;\nimport reactor.test.publisher.TestPublisher;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.clearInvocations;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass NotificationTriggerTest {\n\n\tprivate final Instance instance = Instance.create(InstanceId.of(\"id-1\"))\n\t\t.register(Registration.create(\"foo\", \"http://health-1\").build());\n\n\tprivate final Notifier notifier = mock(Notifier.class);\n\n\tprivate final TestPublisher<InstanceEvent> events = TestPublisher.create();\n\n\tprivate final NotificationTrigger trigger = new NotificationTrigger(this.notifier, this.events);\n\n\tNotificationTriggerTest() {\n\t\twhen(this.notifier.notify(any())).thenReturn(Mono.empty());\n\t}\n\n\t@Test\n\tvoid should_notify_on_event() {\n\t\t// given the notifier subscribed to the events\n\t\tthis.trigger.start();\n\t\tawait().until(this.events::wasSubscribed);\n\n\t\t// when registered event is emitted\n\t\tInstanceStatusChangedEvent event = new InstanceStatusChangedEvent(this.instance.getId(),\n\t\t\t\tthis.instance.getVersion(), StatusInfo.ofDown());\n\t\tthis.events.next(event);\n\n\t\t// then should notify\n\t\tverify(this.notifier, times(1)).notify(event);\n\n\t\t// when registered event is emitted but the trigger has been stopped\n\t\tthis.trigger.stop();\n\t\tclearInvocations(this.notifier);\n\t\tthis.events.next(new InstanceRegisteredEvent(this.instance.getId(), this.instance.getVersion(),\n\t\t\t\tthis.instance.getRegistration()));\n\t\t// then should not notify\n\t\tverify(this.notifier, never()).notify(event);\n\t}\n\n\t@Test\n\tvoid should_resume_on_exception() {\n\t\t// given\n\t\tthis.trigger.start();\n\t\tawait().until(this.events::wasSubscribed);\n\n\t\twhen(this.notifier.notify(any())).thenReturn(Mono.error(new IllegalStateException(\"Test\")))\n\t\t\t.thenReturn(Mono.empty());\n\n\t\t// when exception for the first event is thrown and a subsequent event is fired\n\t\tInstanceStatusChangedEvent event = new InstanceStatusChangedEvent(this.instance.getId(),\n\t\t\t\tthis.instance.getVersion(), StatusInfo.ofDown());\n\t\tthis.events.next(event);\n\t\tthis.events.next(event);\n\n\t\t// the notifier was after the exception\n\t\tverify(this.notifier, times(2)).notify(event);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/notify/OpsGenieNotifierTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.http.MediaType;\nimport org.springframework.web.client.RestTemplate;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.reset;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass OpsGenieNotifierTest {\n\n\tprivate OpsGenieNotifier notifier;\n\n\tprivate RestTemplate restTemplate;\n\n\tprivate InstanceRepository repository;\n\n\tprivate static final Instance INSTANCE = Instance.create(InstanceId.of(\"-id-\"))\n\t\t.register(Registration.create(\"App\", \"https://health\").build());\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\trepository = mock(InstanceRepository.class);\n\t\twhen(repository.find(INSTANCE.getId())).thenReturn(Mono.just(INSTANCE));\n\t\trestTemplate = mock(RestTemplate.class);\n\n\t\tnotifier = new OpsGenieNotifier(repository, restTemplate);\n\t\tnotifier.setApiKey(\"--service--\");\n\t\tnotifier.setUser(\"--user--\");\n\t\tnotifier.setSource(\"--source--\");\n\t\tnotifier.setEntity(\"--entity--\");\n\t\tnotifier.setTags(\"--tag1--,--tag2--\");\n\t\tnotifier.setActions(\"--action1--,--action2--\");\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_resolve() {\n\t\tStepVerifier\n\t\t\t.create(notifier.notify(\n\t\t\t\t\tnew InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion() + 1, StatusInfo.ofDown())))\n\t\t\t.verifyComplete();\n\t\treset(restTemplate);\n\t\twhen(repository.find(INSTANCE.getId())).thenReturn(Mono.just(INSTANCE.withStatusInfo(StatusInfo.ofUp())));\n\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion() + 2, StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\n\t\tverify(restTemplate).exchange(\"https://api.opsgenie.com/v2/alerts/App_-id-/close?identifierType=alias\",\n\t\t\t\tHttpMethod.POST, expectedRequest(\"DOWN\", \"UP\"), Void.class);\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_trigger() {\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion() + 1, StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\t\treset(restTemplate);\n\t\twhen(repository.find(INSTANCE.getId())).thenReturn(Mono.just(INSTANCE.withStatusInfo(StatusInfo.ofDown())));\n\n\t\tStepVerifier\n\t\t\t.create(notifier.notify(\n\t\t\t\t\tnew InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion() + 2, StatusInfo.ofDown())))\n\t\t\t.verifyComplete();\n\n\t\tverify(restTemplate).exchange(\"https://api.opsgenie.com/v2/alerts\", HttpMethod.POST,\n\t\t\t\texpectedRequest(\"UP\", \"DOWN\"), Void.class);\n\t}\n\n\tprivate String getMessage(String expectedStatus) {\n\t\treturn String.format(\"App/-id- is %s\", expectedStatus);\n\t}\n\n\tprivate String getDescription(String expectedOldStatus, String expectedNewStatus) {\n\t\treturn String.format(\"Instance App (-id-) went from %s to %s\", expectedOldStatus, expectedNewStatus);\n\t}\n\n\tprivate HttpEntity<Map<String, Object>> expectedRequest(String expectedOldStatus, String expectedNewStatus) {\n\t\tMap<String, Object> expected = new HashMap<>();\n\n\t\texpected.put(\"user\", \"--user--\");\n\t\texpected.put(\"source\", \"--source--\");\n\n\t\tif (!\"UP\".equals(expectedNewStatus)) {\n\t\t\texpected.put(\"message\", getMessage(expectedNewStatus));\n\t\t\texpected.put(\"alias\", \"App_-id-\");\n\t\t\texpected.put(\"description\", getDescription(expectedOldStatus, expectedNewStatus));\n\t\t\texpected.put(\"entity\", \"--entity--\");\n\t\t\texpected.put(\"tags\", \"--tag1--,--tag2--\");\n\t\t\texpected.put(\"actions\", \"--action1--,--action2--\");\n\n\t\t\tMap<String, Object> details = new HashMap<>();\n\t\t\tdetails.put(\"type\", \"link\");\n\t\t\tdetails.put(\"href\", \"https://health\");\n\t\t\tdetails.put(\"text\", \"Instance health-endpoint\");\n\t\t\texpected.put(\"details\", details);\n\t\t}\n\n\t\tHttpHeaders headers = new HttpHeaders();\n\t\theaders.setContentType(MediaType.APPLICATION_JSON);\n\t\theaders.set(HttpHeaders.AUTHORIZATION, \"GenieKey --service--\");\n\t\treturn new HttpEntity<>(expected, headers);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/notify/PagerdutyNotifierTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.net.URI;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.web.client.RestTemplate;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.reset;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass PagerdutyNotifierTest {\n\n\tprivate PagerdutyNotifier notifier;\n\n\tprivate RestTemplate restTemplate;\n\n\tprivate InstanceRepository repository;\n\n\tprivate static final String APP_NAME = \"App\";\n\n\tprivate static final Instance INSTANCE = Instance.create(InstanceId.of(\"-id-\"))\n\t\t.register(Registration.create(APP_NAME, \"https://health\").build());\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\trepository = mock(InstanceRepository.class);\n\t\twhen(repository.find(INSTANCE.getId())).thenReturn(Mono.just(INSTANCE));\n\t\trestTemplate = mock(RestTemplate.class);\n\n\t\tnotifier = new PagerdutyNotifier(repository, restTemplate);\n\t\tnotifier.setServiceKey(\"--service--\");\n\t\tnotifier.setClient(\"TestClient\");\n\t\tnotifier.setClientUrl(URI.create(\"http://localhost\"));\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_resolve() {\n\t\tStepVerifier\n\t\t\t.create(notifier.notify(\n\t\t\t\t\tnew InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion() + 1, StatusInfo.ofDown())))\n\t\t\t.verifyComplete();\n\t\treset(restTemplate);\n\n\t\tStatusInfo up = StatusInfo.ofUp();\n\t\twhen(repository.find(INSTANCE.getId())).thenReturn(Mono.just(INSTANCE.withStatusInfo(up)));\n\t\tStepVerifier\n\t\t\t.create(notifier.notify(new InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion() + 2, up)))\n\t\t\t.verifyComplete();\n\n\t\tMap<String, Object> expected = new HashMap<>();\n\t\texpected.put(\"service_key\", \"--service--\");\n\t\texpected.put(\"incident_key\", \"App/-id-\");\n\t\texpected.put(\"event_type\", \"resolve\");\n\t\texpected.put(\"description\", \"App/-id- is UP\");\n\t\tMap<String, Object> details = new HashMap<>();\n\t\tdetails.put(\"from\", \"DOWN\");\n\t\tdetails.put(\"to\", up);\n\t\texpected.put(\"details\", details);\n\n\t\tverify(restTemplate).postForEntity(PagerdutyNotifier.DEFAULT_URI, expected, Void.class);\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_trigger() {\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion() + 1, StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\t\treset(restTemplate);\n\n\t\tStatusInfo down = StatusInfo.ofDown();\n\t\twhen(repository.find(INSTANCE.getId())).thenReturn(Mono.just(INSTANCE.withStatusInfo(down)));\n\t\tStepVerifier\n\t\t\t.create(notifier.notify(new InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion() + 2, down)))\n\t\t\t.verifyComplete();\n\n\t\tMap<String, Object> expected = new HashMap<>();\n\t\texpected.put(\"service_key\", \"--service--\");\n\t\texpected.put(\"incident_key\", \"App/-id-\");\n\t\texpected.put(\"event_type\", \"trigger\");\n\t\texpected.put(\"description\", \"App/-id- is DOWN\");\n\t\texpected.put(\"client\", \"TestClient\");\n\t\texpected.put(\"client_url\", URI.create(\"http://localhost\"));\n\t\tMap<String, Object> details = new HashMap<>();\n\t\tdetails.put(\"from\", \"UP\");\n\t\tdetails.put(\"to\", down);\n\t\texpected.put(\"details\", details);\n\t\tMap<String, Object> context = new HashMap<>();\n\t\tcontext.put(\"type\", \"link\");\n\t\tcontext.put(\"href\", \"https://health\");\n\t\tcontext.put(\"text\", \"Application health-endpoint\");\n\t\texpected.put(\"contexts\", List.of(context));\n\n\t\tverify(restTemplate).postForEntity(PagerdutyNotifier.DEFAULT_URI, expected, Void.class);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/notify/RemindingNotifierTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.time.Duration;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.NullSource;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\nimport reactor.test.publisher.TestPublisher;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceDeregisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEndpointsDetectedEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.Endpoints;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass RemindingNotifierTest {\n\n\tprivate static final Instance instance1 = Instance.create(InstanceId.of(\"id-1\"))\n\t\t.register(Registration.create(\"App\", \"https://health\").build())\n\t\t.withStatusInfo(StatusInfo.ofDown());\n\n\tprivate static final Instance instance2 = Instance.create(InstanceId.of(\"id-2\"))\n\t\t.register(Registration.create(\"App\", \"https://health\").build())\n\t\t.withStatusInfo(StatusInfo.ofDown());\n\n\tprivate static final InstanceEvent appDown = new InstanceStatusChangedEvent(instance1.getId(), 0L,\n\t\t\tStatusInfo.ofDown());\n\n\tprivate static final InstanceEvent appUp = new InstanceStatusChangedEvent(instance1.getId(), 0L, StatusInfo.ofUp());\n\n\tprivate static final InstanceEvent appEndpointsDiscovered = new InstanceEndpointsDetectedEvent(instance1.getId(),\n\t\t\t0L, Endpoints.empty());\n\n\tprivate static final InstanceEvent appDeregister = new InstanceDeregisteredEvent(instance1.getId(), 0L);\n\n\tprivate static final InstanceEvent otherAppUp = new InstanceStatusChangedEvent(instance2.getId(), 0L,\n\t\t\tStatusInfo.ofUp());\n\n\tprivate static final InstanceEndpointsDetectedEvent errorTriggeringEvent = new InstanceEndpointsDetectedEvent(\n\t\t\tinstance1.getId(), 999L, Endpoints.empty());\n\n\tprivate InstanceRepository repository;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tthis.repository = mock(InstanceRepository.class);\n\t\twhen(this.repository.find(any())).thenReturn(Mono.empty());\n\t\twhen(this.repository.find(instance1.getId())).thenReturn(Mono.just(instance1));\n\t\twhen(this.repository.find(instance2.getId())).thenReturn(Mono.just(instance2));\n\t}\n\n\t@ParameterizedTest\n\t@NullSource\n\tvoid should_throw_on_invalid_ctor(Iterable<Notifier> delegates) {\n\t\tassertThatThrownBy(() -> new CompositeNotifier(delegates)).isInstanceOf(IllegalArgumentException.class);\n\t}\n\n\t@Test\n\tvoid should_remind_only_down_events() {\n\t\tTestNotifier notifier = new TestNotifier();\n\t\tRemindingNotifier reminder = new RemindingNotifier(notifier, this.repository);\n\t\treminder.setReminderPeriod(Duration.ZERO);\n\n\t\tStepVerifier.create(reminder.notify(appDown)).verifyComplete();\n\t\tStepVerifier.create(reminder.notify(appEndpointsDiscovered)).verifyComplete();\n\t\tStepVerifier.create(reminder.notify(otherAppUp)).verifyComplete();\n\n\t\tawait().pollDelay(Duration.ofMillis(10))\n\t\t\t.untilAsserted(() -> StepVerifier.create(reminder.sendReminders()).verifyComplete());\n\t\tawait().pollDelay(Duration.ofMillis(10))\n\t\t\t.untilAsserted(() -> StepVerifier.create(reminder.sendReminders()).verifyComplete());\n\n\t\tassertThat(notifier.getEvents()).containsExactlyInAnyOrder(appDown, appEndpointsDiscovered, otherAppUp, appDown,\n\t\t\t\tappDown);\n\t}\n\n\t@Test\n\tvoid should_not_remind_remind_after_up() {\n\t\tTestNotifier notifier = new TestNotifier();\n\t\tRemindingNotifier reminder = new RemindingNotifier(notifier, this.repository);\n\t\treminder.setReminderPeriod(Duration.ZERO);\n\n\t\tStepVerifier.create(reminder.notify(appDown)).verifyComplete();\n\t\tStepVerifier.create(reminder.notify(appUp)).verifyComplete();\n\t\tStepVerifier.create(reminder.sendReminders()).verifyComplete();\n\n\t\tassertThat(notifier.getEvents()).containsExactlyInAnyOrder(appDown, appUp);\n\t}\n\n\t@Test\n\tvoid should_not_remind_remind_after_deregister() {\n\t\tTestNotifier notifier = new TestNotifier();\n\t\tRemindingNotifier reminder = new RemindingNotifier(notifier, this.repository);\n\t\treminder.setReminderPeriod(Duration.ZERO);\n\n\t\tStepVerifier.create(reminder.notify(appDown)).verifyComplete();\n\t\tStepVerifier.create(reminder.notify(appDeregister)).verifyComplete();\n\t\tStepVerifier.create(reminder.sendReminders()).verifyComplete();\n\n\t\tassertThat(notifier.getEvents()).containsExactlyInAnyOrder(appDown, appDeregister);\n\t}\n\n\t@Test\n\tvoid should_not_remind_remind_before_period_ends() {\n\t\tTestNotifier notifier = new TestNotifier();\n\t\tRemindingNotifier reminder = new RemindingNotifier(notifier, this.repository);\n\t\treminder.setReminderPeriod(Duration.ofHours(24));\n\n\t\tStepVerifier.create(reminder.notify(appDown)).verifyComplete();\n\t\tStepVerifier.create(reminder.sendReminders()).verifyComplete();\n\n\t\tassertThat(notifier.getEvents()).containsExactlyInAnyOrder(appDown);\n\t}\n\n\t@Test\n\tvoid should_resubscribe_after_error() {\n\t\tTestPublisher<InstanceEvent> eventPublisher = TestPublisher.create();\n\n\t\tFlux<InstanceEvent> emittedNotifications = Flux.create((emitter) -> {\n\t\t\tNotifier notifier = (event) -> {\n\t\t\t\temitter.next(event);\n\t\t\t\tif (event.equals(errorTriggeringEvent)) {\n\t\t\t\t\treturn Mono.error(new IllegalArgumentException(\"TEST-ERROR\"));\n\t\t\t\t}\n\t\t\t\treturn Mono.empty();\n\t\t\t};\n\n\t\t\tRemindingNotifier reminder = new RemindingNotifier(notifier, this.repository);\n\t\t\teventPublisher.flux().flatMap(reminder::notify).subscribe();\n\t\t\treminder.setCheckReminderInverval(Duration.ofMillis(10));\n\t\t\treminder.setReminderPeriod(Duration.ofMillis(10));\n\t\t\treminder.start();\n\t\t});\n\n\t\tStepVerifier.create(emittedNotifications)\n\t\t\t.expectSubscription()\n\t\t\t.then(() -> eventPublisher.next(appDown))\n\t\t\t.expectNext(appDown, appDown)\n\t\t\t.then(() -> eventPublisher.next(errorTriggeringEvent))\n\t\t\t.thenConsumeWhile((e) -> !e.equals(errorTriggeringEvent))\n\t\t\t.expectNext(errorTriggeringEvent, appDown, appDown)\n\t\t\t.thenCancel()\n\t\t\t.verify();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/notify/RocketChatNotifierTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.net.URI;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.http.MediaType;\nimport org.springframework.web.client.RestTemplate;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\nimport static org.mockito.Mockito.clearInvocations;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass RocketChatNotifierTest {\n\n\tprivate static final String ROOM_ID = \"roomId\";\n\n\tprivate static final String TOKEN = \"tokenApi\";\n\n\tprivate static final String USER_ID = \"userId\";\n\n\tprivate static final String HOST = \"http://localhost\";\n\n\tprivate static final String MESSAGE = \"test\";\n\n\tprivate static final Instance instance = Instance.create(InstanceId.of(\"-id-\"))\n\t\t.register(Registration.create(\"App\", \"https://health\").build());\n\n\tprivate RocketChatNotifier notifier;\n\n\tprivate RestTemplate restTemplate;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tInstanceRepository repository = mock(InstanceRepository.class);\n\t\twhen(repository.find(instance.getId())).thenReturn(Mono.just(instance));\n\n\t\trestTemplate = mock(RestTemplate.class);\n\t\tnotifier = new RocketChatNotifier(repository, restTemplate);\n\t\tnotifier.setUrl(HOST);\n\t\tnotifier.setUserId(USER_ID);\n\t\tnotifier.setToken(TOKEN);\n\t\tnotifier.setRoomId(ROOM_ID);\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_resolve() {\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofDown())))\n\t\t\t.verifyComplete();\n\t\tclearInvocations(restTemplate);\n\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\n\t\tHttpEntity<?> expected = expectedMessage(standardMessage(StatusInfo.ofUp().getStatus()));\n\t\tverify(restTemplate).exchange(URI.create(String.format(\"%s/api/v1/chat.sendMessage\", HOST)), HttpMethod.POST,\n\t\t\t\texpected, Void.class);\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_resolve_with_given_message() {\n\t\tnotifier.setMessage(MESSAGE);\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofDown())))\n\t\t\t.verifyComplete();\n\t\tclearInvocations(restTemplate);\n\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\n\t\tHttpEntity<?> expected = expectedMessage(MESSAGE);\n\t\tverify(restTemplate).exchange(URI.create(String.format(\"%s/api/v1/chat.sendMessage\", HOST)), HttpMethod.POST,\n\t\t\t\texpected, Void.class);\n\t}\n\n\tprivate HttpEntity<?> expectedMessage(String message) {\n\t\tHttpHeaders httpHeaders = new HttpHeaders();\n\t\thttpHeaders.setContentType(MediaType.APPLICATION_JSON);\n\t\thttpHeaders.add(\"X-Auth-Token\", TOKEN);\n\t\thttpHeaders.add(\"X-User-Id\", USER_ID);\n\t\tMap<String, String> messageJsonData = new HashMap<>();\n\t\tmessageJsonData.put(\"rid\", ROOM_ID);\n\t\tmessageJsonData.put(\"msg\", message);\n\t\tMap<String, Object> messageJson = new HashMap<>();\n\t\tmessageJson.put(\"message\", messageJsonData);\n\t\treturn new HttpEntity<>(messageJson, httpHeaders);\n\t}\n\n\tprivate String standardMessage(String status) {\n\t\treturn \"*\" + instance.getRegistration().getName() + \"* (\" + instance.getId() + \") is *\" + status + \"*\";\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/notify/SlackNotifierTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.net.URI;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.MediaType;\nimport org.springframework.web.client.RestTemplate;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.clearInvocations;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass SlackNotifierTest {\n\n\tprivate static final String CHANNEL = \"channel\";\n\n\tprivate static final String ICON = \"icon\";\n\n\tprivate static final String USER = \"user\";\n\n\tprivate static final String APP_NAME = \"App\";\n\n\tprivate static final Instance INSTANCE = Instance.create(InstanceId.of(\"-id-\"))\n\t\t.register(Registration.create(APP_NAME, \"https://health\").build());\n\n\tprivate static final String MESSAGE = \"test\";\n\n\tprivate SlackNotifier notifier;\n\n\tprivate RestTemplate restTemplate;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tInstanceRepository repository = mock(InstanceRepository.class);\n\t\twhen(repository.find(INSTANCE.getId())).thenReturn(Mono.just(INSTANCE));\n\t\trestTemplate = mock(RestTemplate.class);\n\n\t\tnotifier = new SlackNotifier(repository, restTemplate);\n\t\tnotifier.setUsername(USER);\n\t\tnotifier.setWebhookUrl(URI.create(\"http://localhost/\"));\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_resolve() {\n\t\tnotifier.setChannel(CHANNEL);\n\t\tnotifier.setIcon(ICON);\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion(), StatusInfo.ofDown())))\n\t\t\t.verifyComplete();\n\t\tclearInvocations(restTemplate);\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\n\t\tObject expected = expectedMessage(\"good\", USER, ICON, CHANNEL, standardMessage(\"UP\"));\n\n\t\tverify(restTemplate).postForEntity(any(URI.class), eq(expected), eq(Void.class));\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_resolve_without_channel_and_icon() {\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion(), StatusInfo.ofDown())))\n\t\t\t.verifyComplete();\n\t\tclearInvocations(restTemplate);\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\n\t\tObject expected = expectedMessage(\"good\", USER, null, null, standardMessage(\"UP\"));\n\n\t\tverify(restTemplate).postForEntity(any(URI.class), eq(expected), eq(Void.class));\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_resolve_with_given_user() {\n\t\tString anotherUser = \"another user\";\n\t\tnotifier.setUsername(anotherUser);\n\t\tnotifier.setChannel(CHANNEL);\n\t\tnotifier.setIcon(ICON);\n\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion(), StatusInfo.ofDown())))\n\t\t\t.verifyComplete();\n\t\tclearInvocations(restTemplate);\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\n\t\tObject expected = expectedMessage(\"good\", anotherUser, ICON, CHANNEL, standardMessage(\"UP\"));\n\n\t\tverify(restTemplate).postForEntity(any(URI.class), eq(expected), eq(Void.class));\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_resolve_with_given_message() {\n\t\tnotifier.setMessage(MESSAGE);\n\t\tnotifier.setChannel(CHANNEL);\n\t\tnotifier.setIcon(ICON);\n\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion(), StatusInfo.ofDown())))\n\t\t\t.verifyComplete();\n\t\tclearInvocations(restTemplate);\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\n\t\tObject expected = expectedMessage(\"good\", USER, ICON, CHANNEL, MESSAGE);\n\n\t\tverify(restTemplate).postForEntity(any(URI.class), eq(expected), eq(Void.class));\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_trigger() {\n\t\tnotifier.setChannel(CHANNEL);\n\t\tnotifier.setIcon(ICON);\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\t\tclearInvocations(restTemplate);\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(INSTANCE.getId(), INSTANCE.getVersion(), StatusInfo.ofDown())))\n\t\t\t.verifyComplete();\n\n\t\tObject expected = expectedMessage(\"danger\", USER, ICON, CHANNEL, standardMessage(\"DOWN\"));\n\n\t\tverify(restTemplate).postForEntity(any(URI.class), eq(expected), eq(Void.class));\n\t}\n\n\tprivate HttpEntity<Map<String, Object>> expectedMessage(String color, String user, String icon, String channel,\n\t\t\tString message) {\n\t\tMap<String, Object> messageJson = new HashMap<>();\n\t\tmessageJson.put(\"username\", user);\n\t\tif (icon != null) {\n\t\t\tmessageJson.put(\"icon_emoji\", \":\" + icon + \":\");\n\t\t}\n\t\tif (channel != null) {\n\t\t\tmessageJson.put(\"channel\", channel);\n\t\t}\n\n\t\tMap<String, Object> attachments = new HashMap<>();\n\t\tattachments.put(\"text\", message);\n\t\tattachments.put(\"color\", color);\n\t\tattachments.put(\"mrkdwn_in\", Collections.singletonList(\"text\"));\n\n\t\tmessageJson.put(\"attachments\", Collections.singletonList(attachments));\n\n\t\tHttpHeaders headers = new HttpHeaders();\n\t\theaders.setContentType(MediaType.APPLICATION_JSON);\n\t\treturn new HttpEntity<>(messageJson, headers);\n\t}\n\n\tprivate String standardMessage(String status) {\n\t\treturn \"*\" + INSTANCE.getRegistration().getName() + \"* (\" + INSTANCE.getId() + \") is *\" + status + \"*\";\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/notify/TelegramNotifierTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentCaptor;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.client.RestTemplate;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.ArgumentMatchers.isA;\nimport static org.mockito.Mockito.clearInvocations;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass TelegramNotifierTest {\n\n\tprivate final Instance instance = Instance.create(InstanceId.of(\"-id-\"))\n\t\t.register(Registration.create(\"Telegram\", \"https://health\").build());\n\n\tprivate TelegramNotifier notifier;\n\n\tprivate RestTemplate restTemplate;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tInstanceRepository repository = mock(InstanceRepository.class);\n\t\twhen(repository.find(instance.getId())).thenReturn(Mono.just(instance));\n\n\t\trestTemplate = mock(RestTemplate.class);\n\t\tnotifier = new TelegramNotifier(repository, restTemplate);\n\t\tnotifier.setDisableNotify(false);\n\t\tnotifier.setAuthToken(\"--token-\");\n\t\tnotifier.setChatId(\"-room-\");\n\t\tnotifier.setParseMode(\"HTML\");\n\t\tnotifier.setApiUrl(\"https://telegram.com\");\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_resolve() {\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofDown())))\n\t\t\t.verifyComplete();\n\t\tclearInvocations(restTemplate);\n\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\n\t\tverify(restTemplate).getForObject(\n\t\t\t\t\"https://telegram.com/bot--token-/sendmessage?chat_id={chat_id}&text={text}\"\n\t\t\t\t\t\t+ \"&parse_mode={parse_mode}&disable_notification={disable_notification}\",\n\t\t\t\tVoid.class, getParameters(\"UP\"));\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_trigger() {\n\t\tStatusInfo infoDown = StatusInfo.ofDown();\n\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tArgumentCaptor<HttpEntity<Map<String, Object>>> httpRequest = ArgumentCaptor\n\t\t\t.forClass((Class<HttpEntity<Map<String, Object>>>) (Class<?>) HttpEntity.class);\n\n\t\twhen(restTemplate.postForEntity(isA(String.class), httpRequest.capture(), eq(Void.class)))\n\t\t\t.thenReturn(ResponseEntity.ok().build());\n\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\t\tStepVerifier\n\t\t\t.create(notifier.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), infoDown)))\n\t\t\t.verifyComplete();\n\n\t\tverify(restTemplate).getForObject(\n\t\t\t\t\"https://telegram.com/bot--token-/sendmessage?chat_id={chat_id}&text={text}\"\n\t\t\t\t\t\t+ \"&parse_mode={parse_mode}&disable_notification={disable_notification}\",\n\t\t\t\tVoid.class, getParameters(\"DOWN\"));\n\t}\n\n\tprivate Map<String, Object> getParameters(String status) {\n\t\tMap<String, Object> parameters = new HashMap<>();\n\t\tparameters.put(\"chat_id\", \"-room-\");\n\t\tparameters.put(\"text\", getMessage(\"Telegram\", \"-id-\", status));\n\t\tparameters.put(\"parse_mode\", \"HTML\");\n\t\tparameters.put(\"disable_notification\", false);\n\t\treturn parameters;\n\t}\n\n\tprivate String getMessage(String name, String id, String status) {\n\t\treturn \"<strong>\" + name + \"</strong>/\" + id + \" is <strong>\" + status + \"</strong>\";\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/notify/TestNotifier.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport lombok.Getter;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\n\n@Getter\npublic class TestNotifier implements Notifier {\n\n\tprivate final List<InstanceEvent> events = new ArrayList<>();\n\n\t@Override\n\tpublic Mono<Void> notify(InstanceEvent event) {\n\t\tthis.events.add(event);\n\t\treturn Mono.empty();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/notify/WebexNotifierTest.java",
    "content": "/*\n * Copyright 2014-2024 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify;\n\nimport java.net.URI;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentCaptor;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.MediaType;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.client.RestTemplate;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.ArgumentMatchers.isA;\nimport static org.mockito.Mockito.clearInvocations;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass WebexNotifierTest {\n\n\tprivate final Instance instance = Instance.create(InstanceId.of(\"-id-\"))\n\t\t.register(Registration.create(\"webex\", \"https://health\").build());\n\n\tprivate WebexNotifier notifier;\n\n\tprivate RestTemplate restTemplate;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tInstanceRepository repository = mock(InstanceRepository.class);\n\t\twhen(repository.find(instance.getId())).thenReturn(Mono.just(instance));\n\n\t\trestTemplate = mock(RestTemplate.class);\n\t\tnotifier = new WebexNotifier(repository, restTemplate);\n\t\tnotifier.setAuthToken(\"--token-\");\n\t\tnotifier.setRoomId(\"--room--\");\n\t\tnotifier.setUrl(URI.create(\"https://webexapis.com/v1/messages\"));\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_resolve() {\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofDown())))\n\t\t\t.verifyComplete();\n\t\tclearInvocations(restTemplate);\n\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\n\t\tURI defaultUrl = URI.create(\"https://webexapis.com/v1/messages\");\n\n\t\tHttpEntity<Map<String, Object>> entity = new HttpEntity<>(createMessage(\"UP\"), createHeaders());\n\n\t\tverify(restTemplate).postForEntity(defaultUrl, entity, Void.class);\n\t}\n\n\t@Test\n\tvoid test_onApplicationEvent_trigger() {\n\t\tStatusInfo infoDown = StatusInfo.ofDown();\n\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tArgumentCaptor<HttpEntity<Map<String, Object>>> httpRequest = ArgumentCaptor\n\t\t\t.forClass((Class<HttpEntity<Map<String, Object>>>) (Class<?>) HttpEntity.class);\n\n\t\twhen(restTemplate.postForEntity(isA(String.class), httpRequest.capture(), eq(Void.class)))\n\t\t\t.thenReturn(ResponseEntity.ok().build());\n\n\t\tStepVerifier\n\t\t\t.create(notifier\n\t\t\t\t.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofUp())))\n\t\t\t.verifyComplete();\n\t\tStepVerifier\n\t\t\t.create(notifier.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), infoDown)))\n\t\t\t.verifyComplete();\n\n\t\tURI defaultUrl = URI.create(\"https://webexapis.com/v1/messages\");\n\n\t\tHttpEntity<Map<String, Object>> entity = new HttpEntity<>(createMessage(\"DOWN\"), createHeaders());\n\n\t\tverify(restTemplate).postForEntity(defaultUrl, entity, Void.class);\n\t}\n\n\tprivate HttpHeaders createHeaders() {\n\t\tHttpHeaders headers = new HttpHeaders();\n\t\theaders.setContentType(MediaType.APPLICATION_JSON);\n\t\theaders.setBearerAuth(\"--token-\");\n\t\treturn headers;\n\t}\n\n\tprivate Map<String, Object> createMessage(String status) {\n\t\tMap<String, Object> parameters = new HashMap<>();\n\t\tparameters.put(\"roomId\", \"--room--\");\n\t\tparameters.put(\"markdown\", getMessage(\"webex\", \"-id-\", status));\n\t\treturn parameters;\n\t}\n\n\tprivate String getMessage(String name, String id, String status) {\n\t\treturn \"<strong>\" + name + \"</strong>/\" + id + \" is <strong>\" + status + \"</strong>\";\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/notify/filter/FilteringNotifierTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify.filter;\n\nimport java.time.Duration;\nimport java.time.Instant;\n\nimport org.assertj.core.api.Assertions;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegisteredEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.notify.TestNotifier;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass FilteringNotifierTest {\n\n\tprivate final Instance instance = Instance.create(InstanceId.of(\"-\"))\n\t\t.register(Registration.create(\"foo\", \"https://health\").build());\n\n\tprivate final InstanceRegisteredEvent event = new InstanceRegisteredEvent(instance.getId(), instance.getVersion(),\n\t\t\tinstance.getRegistration());\n\n\tprivate InstanceRepository repository;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\trepository = mock(InstanceRepository.class);\n\t\twhen(repository.find(instance.getId())).thenReturn(Mono.just(instance));\n\t}\n\n\t@Test\n\tvoid test_ctor_assert() {\n\t\tAssertions.assertThatThrownBy(() -> new FilteringNotifier(null, repository))\n\t\t\t.isInstanceOf(IllegalArgumentException.class);\n\t}\n\n\t@Test\n\tvoid test_expired_removal() {\n\t\tFilteringNotifier notifier = new FilteringNotifier(new TestNotifier(), repository);\n\t\tnotifier.setCleanupInterval(Duration.ZERO);\n\n\t\tApplicationNameNotificationFilter filter1 = new ApplicationNameNotificationFilter(\"foo\",\n\t\t\t\tInstant.now().minus(Duration.ofSeconds(1)));\n\t\tnotifier.addFilter(filter1);\n\t\tApplicationNameNotificationFilter filter2 = new ApplicationNameNotificationFilter(\"bar\", null);\n\t\tnotifier.addFilter(filter2);\n\n\t\tassertThat(notifier.getNotificationFilters()).containsKey(filter1.getId()).containsKey(filter2.getId());\n\n\t\tStepVerifier.create(notifier.notify(event)).verifyComplete();\n\n\t\tassertThat(notifier.getNotificationFilters()).doesNotContainKey(filter1.getId()).containsKey(filter2.getId());\n\n\t\tnotifier.removeFilter(filter2.getId());\n\t\tassertThat(notifier.getNotificationFilters()).doesNotContainKey(filter2.getId());\n\t}\n\n\t@Test\n\tvoid test_filter() {\n\t\tTestNotifier delegate = new TestNotifier();\n\t\tFilteringNotifier notifier = new FilteringNotifier(delegate, repository);\n\n\t\tAbstractNotificationFilter trueFilter = new AbstractNotificationFilter() {\n\t\t\t@Override\n\t\t\tpublic boolean filter(InstanceEvent event, Instance instance) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t};\n\t\tnotifier.addFilter(trueFilter);\n\n\t\tStepVerifier.create(notifier.notify(event)).verifyComplete();\n\n\t\tassertThat(delegate.getEvents()).doesNotContain(event);\n\n\t\tnotifier.removeFilter(trueFilter.getId());\n\t\tStepVerifier.create(notifier.notify(event)).verifyComplete();\n\n\t\tassertThat(delegate.getEvents()).contains(event);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/notify/filter/InstanceIdNotificationFilterTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify.filter;\n\nimport org.junit.jupiter.api.Test;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegisteredEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass InstanceIdNotificationFilterTest {\n\n\t@Test\n\tvoid test_filterByName() {\n\t\tNotificationFilter filter = new InstanceIdNotificationFilter(InstanceId.of(\"cafebabe\"), null);\n\n\t\tInstance filteredInstance = Instance.create(InstanceId.of(\"cafebabe\"))\n\t\t\t.register(Registration.create(\"foo\", \"https://health\").build());\n\t\tInstanceRegisteredEvent filteredEvent = new InstanceRegisteredEvent(filteredInstance.getId(),\n\t\t\t\tfilteredInstance.getVersion(), filteredInstance.getRegistration());\n\t\tassertThat(filter.filter(filteredEvent, filteredInstance)).isTrue();\n\n\t\tInstance ignoredInstance = Instance.create(InstanceId.of(\"-\"))\n\t\t\t.register(Registration.create(\"foo\", \"https://health\").build());\n\t\tInstanceRegisteredEvent ignoredEvent = new InstanceRegisteredEvent(ignoredInstance.getId(),\n\t\t\t\tignoredInstance.getVersion(), ignoredInstance.getRegistration());\n\t\tassertThat(filter.filter(ignoredEvent, ignoredInstance)).isFalse();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/notify/filter/InstanceNameNotificationFilterTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify.filter;\n\nimport java.time.Duration;\nimport java.time.Instant;\n\nimport org.junit.jupiter.api.Test;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegisteredEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\n\nclass InstanceNameNotificationFilterTest {\n\n\t@Test\n\tvoid test_filterByName() {\n\t\tNotificationFilter filter = new ApplicationNameNotificationFilter(\"foo\", null);\n\n\t\tInstance filteredInstance = Instance.create(InstanceId.of(\"-\"))\n\t\t\t.register(Registration.create(\"foo\", \"https://health\").build());\n\t\tInstanceRegisteredEvent filteredEvent = new InstanceRegisteredEvent(filteredInstance.getId(),\n\t\t\t\tfilteredInstance.getVersion(), filteredInstance.getRegistration());\n\t\tassertThat(filter.filter(filteredEvent, filteredInstance)).isTrue();\n\n\t\tInstance ignoredInstance = Instance.create(InstanceId.of(\"-\"))\n\t\t\t.register(Registration.create(\"bar\", \"https://health\").build());\n\t\tInstanceRegisteredEvent ignoredEvent = new InstanceRegisteredEvent(ignoredInstance.getId(),\n\t\t\t\tignoredInstance.getVersion(), ignoredInstance.getRegistration());\n\t\tassertThat(filter.filter(ignoredEvent, ignoredInstance)).isFalse();\n\t}\n\n\t@Test\n\tvoid test_expiry() {\n\t\tExpiringNotificationFilter filterForever = new ApplicationNameNotificationFilter(\"foo\", null);\n\t\tExpiringNotificationFilter filterExpired = new ApplicationNameNotificationFilter(\"foo\",\n\t\t\t\tInstant.now().minus(Duration.ofSeconds(1)));\n\t\tExpiringNotificationFilter filterLong = new ApplicationNameNotificationFilter(\"foo\",\n\t\t\t\tInstant.now().plus(Duration.ofMillis(100)));\n\n\t\tassertThat(filterForever.isExpired()).isFalse();\n\t\tassertThat(filterLong.isExpired()).isFalse();\n\t\tassertThat(filterExpired.isExpired()).isTrue();\n\n\t\tawait().atMost(Duration.ofMillis(200))\n\t\t\t.pollInterval(Duration.ofMillis(10))\n\t\t\t.untilAsserted(() -> assertThat(filterLong.isExpired()).isTrue());\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/notify/filter/web/NotificationFilterControllerTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.notify.filter.web;\n\nimport java.io.IOException;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.test.web.servlet.MockMvc;\nimport org.springframework.test.web.servlet.setup.MockMvcBuilders;\nimport tools.jackson.databind.json.JsonMapper;\n\nimport de.codecentric.boot.admin.server.domain.entities.EventsourcingInstanceRepository;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.eventstore.InMemoryEventStore;\nimport de.codecentric.boot.admin.server.notify.LoggingNotifier;\nimport de.codecentric.boot.admin.server.notify.filter.FilteringNotifier;\n\nimport static org.hamcrest.Matchers.emptyString;\nimport static org.hamcrest.Matchers.not;\nimport static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;\nimport static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;\nimport static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;\n\nclass NotificationFilterControllerTest {\n\n\tprivate final InstanceRepository repository = new EventsourcingInstanceRepository(new InMemoryEventStore());\n\n\tprivate final NotificationFilterController controller = new NotificationFilterController(\n\t\t\tnew FilteringNotifier(new LoggingNotifier(this.repository), this.repository));\n\n\tprivate final MockMvc mvc = MockMvcBuilders.standaloneSetup(this.controller)\n\t\t.setCustomHandlerMapping(\n\t\t\t\t() -> new de.codecentric.boot.admin.server.web.servlet.AdminControllerHandlerMapping(\"/\"))\n\t\t.build();\n\n\t@Test\n\tvoid test_missing_parameters() throws Exception {\n\t\tthis.mvc.perform(post(\"/notifications/filters\")).andExpect(status().isBadRequest());\n\t}\n\n\t@Test\n\tvoid test_delete_notfound() throws Exception {\n\t\tthis.mvc.perform(delete(\"/notifications/filters/abcdef\")).andExpect(status().isNotFound());\n\t}\n\n\t@Test\n\tvoid test_post_delete() throws Exception {\n\t\tString response = this.mvc.perform(post(\"/notifications/filters?instanceId=1337&ttl=10000\"))\n\t\t\t.andExpect(status().isOk())\n\t\t\t.andExpect(content().string(not(emptyString())))\n\t\t\t.andReturn()\n\t\t\t.getResponse()\n\t\t\t.getContentAsString();\n\t\tString id = extractId(response);\n\n\t\tthis.mvc.perform(get(\"/notifications/filters\")).andExpect(status().isOk());\n\n\t\tthis.mvc.perform(delete(\"/notifications/filters/{id}\", id)).andExpect(status().isOk());\n\n\t\tthis.mvc.perform(get(\"/notifications/filters\")).andExpect(status().isOk()).andExpect(jsonPath(\"$\").isEmpty());\n\t}\n\n\tprivate String extractId(String response) throws IOException {\n\t\tMap<?, ?> map = new JsonMapper().readerFor(Map.class).readValue(response);\n\t\treturn map.get(\"id\").toString();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/services/AbstractEventHandlerTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services;\n\nimport java.time.Duration;\n\nimport lombok.Getter;\nimport org.junit.jupiter.api.Test;\nimport org.reactivestreams.Publisher;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.core.publisher.Sinks;\nimport reactor.test.StepVerifier;\nimport reactor.test.publisher.TestPublisher;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceDeregisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegisteredEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\n\nclass AbstractEventHandlerTest {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(AbstractEventHandlerTest.class);\n\n\tprivate static final Registration registration = Registration.create(\"foo\", \"https://health\").build();\n\n\tprivate static final InstanceRegisteredEvent firstEvent = new InstanceRegisteredEvent(InstanceId.of(\"id\"), 0L,\n\t\t\tregistration);\n\n\tprivate static final InstanceRegisteredEvent secondEvent = new InstanceRegisteredEvent(InstanceId.of(\"id\"), 1L,\n\t\t\tregistration);\n\n\tprivate static final InstanceRegisteredEvent errorEvent = new InstanceRegisteredEvent(InstanceId.of(\"err\"), 2L,\n\t\t\tregistration);\n\n\tprivate static final InstanceDeregisteredEvent ignoredEvent = new InstanceDeregisteredEvent(InstanceId.of(\"id\"),\n\t\t\t2L);\n\n\t@Test\n\tvoid should_resubscribe_after_error() {\n\t\tTestPublisher<InstanceEvent> testPublisher = TestPublisher.create();\n\n\t\tTestEventHandler eventHandler = new TestEventHandler(testPublisher.flux());\n\t\teventHandler.start();\n\n\t\tStepVerifier.create(eventHandler.getFlux())\n\t\t\t.expectSubscription()\n\t\t\t.then(() -> testPublisher.next(firstEvent, errorEvent, secondEvent))\n\t\t\t.expectNext(firstEvent, secondEvent)\n\t\t\t.thenCancel()\n\t\t\t.verify(Duration.ofSeconds(2));\n\t}\n\n\t@Test\n\tvoid should_filter() {\n\t\tTestPublisher<InstanceEvent> testPublisher = TestPublisher.create();\n\n\t\tTestEventHandler eventHandler = new TestEventHandler(testPublisher.flux());\n\t\teventHandler.start();\n\n\t\tStepVerifier.create(eventHandler.getFlux())\n\t\t\t.expectSubscription()\n\t\t\t.then(() -> testPublisher.next(firstEvent, ignoredEvent, secondEvent))\n\t\t\t.expectNext(firstEvent, secondEvent)\n\t\t\t.thenCancel()\n\t\t\t.verify(Duration.ofSeconds(1));\n\t}\n\n\tpublic static final class TestEventHandler extends AbstractEventHandler<InstanceRegisteredEvent> {\n\n\t\tprivate final Sinks.Many<InstanceEvent> unicast;\n\n\t\t@Getter\n\t\tprivate final Flux<InstanceEvent> flux;\n\n\t\tprivate TestEventHandler(Publisher<InstanceEvent> publisher) {\n\t\t\tsuper(publisher, InstanceRegisteredEvent.class);\n\t\t\tthis.unicast = Sinks.many().unicast().onBackpressureBuffer();\n\t\t\tthis.flux = this.unicast.asFlux();\n\t\t}\n\n\t\t@Override\n\t\tprotected Publisher<Void> handle(Flux<InstanceRegisteredEvent> publisher) {\n\t\t\treturn publisher.flatMap((event) -> {\n\t\t\t\tif (event.equals(errorEvent)) {\n\t\t\t\t\treturn Mono.error(new IllegalStateException(\"TestError\"));\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tlog.info(\"Event {}\", event);\n\t\t\t\t\tthis.unicast.tryEmitNext(event);\n\t\t\t\t\treturn Mono.empty();\n\t\t\t\t}\n\t\t\t}).then();\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/services/ApplicationRegistryTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.entities.Application;\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.values.BuildVersion;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\nimport de.codecentric.boot.admin.server.eventstore.InstanceEventPublisher;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass ApplicationRegistryTest {\n\n\tprivate InstanceRegistry instanceRegistry;\n\n\tprivate ApplicationRegistry applicationRegistry;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tthis.instanceRegistry = mock(InstanceRegistry.class);\n\t\tInstanceEventPublisher instanceEventPublisher = mock(InstanceEventPublisher.class);\n\t\tthis.applicationRegistry = new ApplicationRegistry(this.instanceRegistry, instanceEventPublisher);\n\t}\n\n\t@Test\n\tvoid getApplications_noRegisteredApplications() {\n\t\twhen(this.instanceRegistry.getInstances()).thenReturn(Flux.just());\n\n\t\tStepVerifier.create(this.applicationRegistry.getApplications()).verifyComplete();\n\t}\n\n\t@Test\n\tvoid getApplications_oneRegisteredAndOneUnregisteredApplication() {\n\t\tInstance instance1 = getInstance(\"App1\");\n\t\tInstance instance2 = getInstance(\"App2\").deregister();\n\n\t\twhen(this.instanceRegistry.getInstances()).thenReturn(Flux.just(instance1, instance2));\n\n\t\tStepVerifier.create(this.applicationRegistry.getApplications())\n\t\t\t.assertNext((app) -> assertThat(app.getName()).isEqualTo(\"App1\"))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid getApplications_allRegisteredApplications() {\n\t\tInstance instance1 = getInstance(\"App1\");\n\t\tInstance instance2 = getInstance(\"App2\");\n\n\t\twhen(this.instanceRegistry.getInstances()).thenReturn(Flux.just(instance1, instance2));\n\n\t\tStepVerifier.create(this.applicationRegistry.getApplications())\n\t\t\t.recordWith(ArrayList::new)\n\t\t\t.thenConsumeWhile((a) -> true)\n\t\t\t.consumeRecordedWith((applications) -> assertThat(applications.stream().map(Application::getName))\n\t\t\t\t.containsExactlyInAnyOrder(\"App1\", \"App2\"))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid getApplication_noRegisteredApplications() {\n\t\twhen(this.instanceRegistry.getInstances(any(String.class))).thenReturn(Flux.just());\n\n\t\tStepVerifier.create(this.applicationRegistry.getApplication(\"App1\")).verifyComplete();\n\t}\n\n\t@Test\n\tvoid getApplication_noMatchingRegisteredApplications() {\n\t\twhen(this.instanceRegistry.getInstances(\"App2\")).thenReturn(Flux.just(getInstance(\"App2\")));\n\t\twhen(this.instanceRegistry.getInstances(any(String.class))).thenReturn(Flux.just());\n\n\t\tStepVerifier.create(this.applicationRegistry.getApplication(\"App1\")).verifyComplete();\n\t}\n\n\t@Test\n\tvoid getApplication_matchingUnregisteredApplications() {\n\t\tInstance instance = getInstance(\"App1\").deregister();\n\t\twhen(this.instanceRegistry.getInstances(\"App1\")).thenReturn(Flux.just(instance));\n\n\t\tStepVerifier.create(this.applicationRegistry.getApplication(\"App1\")).verifyComplete();\n\t}\n\n\t@Test\n\tvoid getApplication_matchingRegisteredApplications() {\n\t\tInstance instance = getInstance(\"App1\");\n\t\twhen(this.instanceRegistry.getInstances(\"App1\")).thenReturn(Flux.just(instance));\n\n\t\tStepVerifier.create(this.applicationRegistry.getApplication(\"App1\"))\n\t\t\t.assertNext((app) -> assertThat(app.getName()).isEqualTo(\"App1\"))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid deregister() {\n\t\tInstance instance1 = getInstance(\"App1\");\n\t\tInstanceId instance1Id = instance1.getId();\n\n\t\twhen(this.instanceRegistry.getInstances(\"App1\")).thenReturn(Flux.just(instance1));\n\t\twhen(this.instanceRegistry.deregister(instance1Id)).thenReturn(Mono.just(instance1Id));\n\n\t\tStepVerifier.create(this.applicationRegistry.deregister(\"App1\"))\n\t\t\t.assertNext((instanceId) -> assertThat(instanceId).isEqualTo(instance1Id))\n\t\t\t.verifyComplete();\n\n\t\tverify(this.instanceRegistry).deregister(instance1Id);\n\t}\n\n\t@Test\n\tvoid getBuildVersion() {\n\t\tInstance instance1 = getInstance(\"App1\", \"0.1\");\n\t\tInstance instance2 = getInstance(\"App2\", \"0.2\");\n\n\t\t// Empty list should return null:\n\t\tassertThat(this.applicationRegistry.getBuildVersion(Collections.emptyList())).isNull();\n\n\t\t// Single instance should return the version number:\n\t\tassertThat(this.applicationRegistry.getBuildVersion(Collections.singletonList(instance1)))\n\t\t\t.isEqualTo(BuildVersion.valueOf(\"0.1\"));\n\n\t\t// Multiple instances should return the version number range:\n\t\tassertThat(this.applicationRegistry.getBuildVersion(Arrays.asList(instance1, instance2)))\n\t\t\t.isEqualTo(BuildVersion.valueOf(\"0.1 ... 0.2\"));\n\t}\n\n\t@ParameterizedTest\n\t@CsvSource({ \"UP, UP, UP\", \"DOWN, DOWN, DOWN\", \"UNKNOWN, UNKNOWN, UNKNOWN\", \"UP, DOWN, RESTRICTED\",\n\t\t\t\"UP, UNKNOWN, RESTRICTED\", \"UP, OUT_OF_SERVICE, RESTRICTED\", \"UP, OFFLINE, RESTRICTED\",\n\t\t\t\"UP, RESTRICTED, RESTRICTED\", \"DOWN, UP, RESTRICTED\" })\n\tvoid getStatus(String instance1Status, String instance2Status, String expectedApplicationStatus) {\n\t\tInstance instance1 = getInstance(\"App1\").withStatusInfo(StatusInfo.valueOf(instance1Status));\n\t\tInstance instance2 = getInstance(\"App1\").withStatusInfo(StatusInfo.valueOf(instance2Status));\n\n\t\twhen(this.instanceRegistry.getInstances()).thenReturn(Flux.just(instance1, instance2));\n\n\t\tStepVerifier.create(this.applicationRegistry.getApplications())\n\t\t\t.recordWith(ArrayList::new)\n\t\t\t.thenConsumeWhile((a) -> true)\n\t\t\t.consumeRecordedWith((applications) -> assertThat(applications.stream().map(Application::getStatus))\n\t\t\t\t.containsExactly(expectedApplicationStatus))\n\t\t\t.verifyComplete();\n\t}\n\n\tprivate Instance getInstance(String applicationName, String version) {\n\t\tRegistration registration = Registration.create(applicationName, \"http://localhost:8080/health\")\n\t\t\t.metadata(\"version\", version)\n\t\t\t.build();\n\t\tInstanceId id = InstanceId.of(\"TEST\" + applicationName);\n\t\treturn Instance.create(id).register(registration);\n\t}\n\n\tprivate Instance getInstance(String applicationName) {\n\t\treturn getInstance(applicationName, \"FooBarVersion\");\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/services/CloudFoundryInstanceIdGeneratorTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services;\n\nimport org.junit.jupiter.api.Test;\n\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass CloudFoundryInstanceIdGeneratorTest {\n\n\tprivate final CloudFoundryInstanceIdGenerator instance = new CloudFoundryInstanceIdGenerator(\n\t\t\tnew HashingInstanceUrlIdGenerator());\n\n\t@Test\n\tvoid test_cloud_foundry_instance_id() {\n\t\tRegistration registration = Registration.create(\"foo\", \"https://health\")\n\t\t\t.metadata(\"applicationId\", \"549e64cf-a478-423d-9d6d-02d803a028a8\")\n\t\t\t.metadata(\"instanceId\", \"0\")\n\t\t\t.build();\n\t\tassertThat(instance.generateId(registration))\n\t\t\t.isEqualTo(InstanceId.of(\"549e64cf-a478-423d-9d6d-02d803a028a8:0\"));\n\t}\n\n\t@Test\n\tvoid test_health_url_instance_id() {\n\t\tRegistration registration = Registration.create(\"foo\", \"https://health\").build();\n\t\tassertThat(instance.generateId(registration)).isEqualTo(InstanceId.of(\"cff917ccf90e\"));\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/services/EndpointDetectionTriggerTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Mono;\nimport reactor.test.publisher.TestPublisher;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegistrationUpdatedEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.clearInvocations;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass EndpointDetectionTriggerTest {\n\n\tprivate final Instance instance = Instance.create(InstanceId.of(\"id-1\"))\n\t\t.register(Registration.create(\"foo\", \"http://health-1\").build());\n\n\tprivate final TestPublisher<InstanceEvent> events = TestPublisher.create();\n\n\tprivate final EndpointDetector detector = mock(EndpointDetector.class);\n\n\tprivate EndpointDetectionTrigger trigger;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\twhen(this.detector.detectEndpoints(any(InstanceId.class))).thenReturn(Mono.empty());\n\t\tthis.trigger = new EndpointDetectionTrigger(this.detector, this.events.flux());\n\t\tthis.trigger.start();\n\t\tawait().until(this.events::wasSubscribed);\n\t}\n\n\t@Test\n\tvoid should_detect_on_status_changed() {\n\t\t// when status-change event is emitted\n\t\tthis.events.next(\n\t\t\t\tnew InstanceStatusChangedEvent(this.instance.getId(), this.instance.getVersion(), StatusInfo.ofDown()));\n\t\t// then should update\n\t\tverify(this.detector, times(1)).detectEndpoints(this.instance.getId());\n\t}\n\n\t@Test\n\tvoid should_detect_on_registration_updated() {\n\t\t// when status-change event is emitted\n\t\tthis.events.next(new InstanceRegistrationUpdatedEvent(this.instance.getId(), this.instance.getVersion(),\n\t\t\t\tthis.instance.getRegistration()));\n\t\t// then should update\n\t\tverify(this.detector, times(1)).detectEndpoints(this.instance.getId());\n\t}\n\n\t@Test\n\tvoid should_not_detect_on_non_relevant_event() {\n\t\t// when some non-status-change event is emitted\n\t\tthis.events.next(new InstanceRegisteredEvent(this.instance.getId(), this.instance.getVersion(),\n\t\t\t\tthis.instance.getRegistration()));\n\t\t// then should not update\n\t\tverify(this.detector, never()).detectEndpoints(this.instance.getId());\n\t}\n\n\t@Test\n\tvoid should_not_detect_on_trigger_stopped() {\n\t\t// when registered event is emitted but the trigger has been stopped\n\t\tthis.trigger.stop();\n\t\tclearInvocations(this.detector);\n\t\tthis.events.next(new InstanceRegisteredEvent(this.instance.getId(), this.instance.getVersion(),\n\t\t\t\tthis.instance.getRegistration()));\n\t\t// then should not update\n\t\tverify(this.detector, never()).detectEndpoints(this.instance.getId());\n\t}\n\n\t@Test\n\tvoid should_continue_detection_after_error() {\n\t\t// when status-change event is emitted and an error is emitted\n\t\twhen(this.detector.detectEndpoints(any())).thenReturn(Mono.error(IllegalStateException::new))\n\t\t\t.thenReturn(Mono.empty());\n\n\t\tthis.events.next(\n\t\t\t\tnew InstanceStatusChangedEvent(this.instance.getId(), this.instance.getVersion(), StatusInfo.ofDown()));\n\t\tthis.events\n\t\t\t.next(new InstanceStatusChangedEvent(this.instance.getId(), this.instance.getVersion(), StatusInfo.ofUp()));\n\n\t\t// then should update\n\t\tverify(this.detector, times(2)).detectEndpoints(this.instance.getId());\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/services/EndpointDetectorTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services;\n\nimport java.time.Duration;\nimport java.util.logging.Level;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.entities.EventsourcingInstanceRepository;\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEndpointsDetectedEvent;\nimport de.codecentric.boot.admin.server.domain.values.Endpoints;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\nimport de.codecentric.boot.admin.server.eventstore.ConcurrentMapEventStore;\nimport de.codecentric.boot.admin.server.eventstore.InMemoryEventStore;\nimport de.codecentric.boot.admin.server.services.endpoints.EndpointDetectionStrategy;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass EndpointDetectorTest {\n\n\tprivate EndpointDetector detector;\n\n\tprivate InstanceRepository repository;\n\n\tprivate ConcurrentMapEventStore eventStore;\n\n\tprivate EndpointDetectionStrategy strategy;\n\n\t@BeforeEach\n\tvoid setup() {\n\t\teventStore = new InMemoryEventStore();\n\t\trepository = new EventsourcingInstanceRepository(eventStore);\n\t\tstrategy = mock(EndpointDetectionStrategy.class);\n\t\tdetector = new EndpointDetector(repository, strategy);\n\t}\n\n\t@Test\n\tvoid should_update_endpoints() {\n\t\t// given\n\t\tRegistration registration = Registration.create(\"foo\", \"https://health\").managementUrl(\"https://mgmt\").build();\n\t\tInstance instance = Instance.create(InstanceId.of(\"onl\"))\n\t\t\t.register(registration)\n\t\t\t.withStatusInfo(StatusInfo.ofUp());\n\t\tStepVerifier.create(repository.save(instance)).expectNextCount(1).verifyComplete();\n\n\t\tInstance noActuator = Instance.create(InstanceId.of(\"noActuator\"))\n\t\t\t.register(Registration.create(\"foo\", \"https://health\").build())\n\t\t\t.withStatusInfo(StatusInfo.ofUp());\n\t\tStepVerifier.create(repository.save(noActuator)).expectNextCount(1).verifyComplete();\n\n\t\tInstance offline = Instance.create(InstanceId.of(\"off\"))\n\t\t\t.register(registration)\n\t\t\t.withStatusInfo(StatusInfo.ofOffline());\n\t\tStepVerifier.create(repository.save(offline)).expectNextCount(1).verifyComplete();\n\n\t\tInstance unknown = Instance.create(InstanceId.of(\"unk\"))\n\t\t\t.register(registration)\n\t\t\t.withStatusInfo(StatusInfo.ofUnknown());\n\t\tStepVerifier.create(repository.save(unknown)).expectNextCount(1).verifyComplete();\n\n\t\twhen(strategy.detectEndpoints(any(Instance.class))).thenReturn(Mono.just(Endpoints.single(\"id\", \"url\")));\n\n\t\t// when/then\n\t\tStepVerifier.create(Flux.from(eventStore).log(\"FOO\", Level.SEVERE))\n\t\t\t.expectSubscription()\n\t\t\t.then(() -> StepVerifier.create(detector.detectEndpoints(offline.getId())).verifyComplete())\n\t\t\t.then(() -> StepVerifier.create(detector.detectEndpoints(unknown.getId())).verifyComplete())\n\t\t\t.then(() -> StepVerifier.create(detector.detectEndpoints(noActuator.getId())).verifyComplete())\n\t\t\t.expectNoEvent(Duration.ofMillis(100L))\n\t\t\t.then(() -> StepVerifier.create(detector.detectEndpoints(instance.getId())).verifyComplete())\n\t\t\t.assertNext((event) -> assertThat(event).isInstanceOf(InstanceEndpointsDetectedEvent.class))\n\t\t\t.thenCancel()\n\t\t\t.verify();\n\n\t\tStepVerifier.create(repository.find(instance.getId()))\n\t\t\t.assertNext((app) -> assertThat(app.getEndpoints())\n\t\t\t\t.isEqualTo(Endpoints.single(\"id\", \"url\").withEndpoint(\"health\", \"https://health\")))\n\t\t\t.verifyComplete();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/services/InfoUpdateTriggerTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services;\n\nimport java.time.Duration;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Mono;\nimport reactor.test.publisher.TestPublisher;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEndpointsDetectedEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceInfoChangedEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegistrationUpdatedEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.Info;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.atLeast;\nimport static org.mockito.Mockito.clearInvocations;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass InfoUpdateTriggerTest {\n\n\tprivate final Instance instance = Instance.create(InstanceId.of(\"id-1\"))\n\t\t.register(Registration.create(\"foo\", \"http://health-1\").build());\n\n\tprivate final InfoUpdater updater = mock(InfoUpdater.class);\n\n\tprivate final TestPublisher<InstanceEvent> events = TestPublisher.create();\n\n\tprivate InfoUpdateTrigger trigger;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\twhen(this.updater.updateInfo(any(InstanceId.class))).thenReturn(Mono.empty());\n\n\t\tthis.trigger = new InfoUpdateTrigger(this.updater, this.events.flux(), Duration.ofMinutes(5),\n\t\t\t\tDuration.ofMinutes(1), Duration.ofMinutes(10));\n\t\tthis.trigger.start();\n\t\tawait().until(this.events::wasSubscribed);\n\t}\n\n\t@Test\n\tvoid should_start_and_stop_monitor() {\n\t\t// given\n\t\tthis.trigger.stop();\n\t\tthis.trigger.setInterval(Duration.ofMillis(100));\n\t\tthis.trigger.setLifetime(Duration.ofMillis(50));\n\t\tthis.trigger.start();\n\t\tawait().until(this.events::wasSubscribed);\n\n\t\t// when an event is emitted\n\t\tthis.events.next(\n\t\t\t\tnew InstanceStatusChangedEvent(this.instance.getId(), this.instance.getVersion(), StatusInfo.ofDown()));\n\n\t\t// then it should update at least once for the event\n\t\tawait().atMost(Duration.ofMillis(200))\n\t\t\t.untilAsserted(() -> verify(this.updater, atLeast(1)).updateInfo(this.instance.getId()));\n\n\t\t// and then at least one more time due to monitoring interval (after lifetime\n\t\t// expires)\n\t\tawait().atMost(Duration.ofMillis(400))\n\t\t\t.pollInterval(Duration.ofMillis(30))\n\t\t\t.untilAsserted(() -> verify(this.updater, atLeast(2)).updateInfo(this.instance.getId()));\n\n\t\t// given long lifetime\n\t\tthis.trigger.setLifetime(Duration.ofSeconds(10));\n\t\tclearInvocations(this.updater);\n\n\t\t// when the lifetime is not expired should not update via interval monitoring\n\t\tawait().pollDelay(Duration.ofMillis(150))\n\t\t\t.atMost(Duration.ofMillis(200))\n\t\t\t.untilAsserted(() -> verify(this.updater, never()).updateInfo(any(InstanceId.class)));\n\n\t\t// when trigger is stopped\n\t\tthis.trigger.setLifetime(Duration.ofMillis(50));\n\t\tthis.trigger.stop();\n\t\tclearInvocations(this.updater);\n\n\t\t// then it should stop updating\n\t\tawait().pollDelay(Duration.ofMillis(150))\n\t\t\t.atMost(Duration.ofMillis(200))\n\t\t\t.untilAsserted(() -> verify(this.updater, never()).updateInfo(any(InstanceId.class)));\n\t}\n\n\t@Test\n\tvoid should_not_update_when_stopped() {\n\t\t// when registered event is emitted but the trigger has been stopped\n\t\tthis.trigger.stop();\n\t\tclearInvocations(this.updater);\n\t\tthis.events.next(new InstanceRegisteredEvent(this.instance.getId(), this.instance.getVersion(),\n\t\t\t\tthis.instance.getRegistration()));\n\t\t// then should not update\n\t\tverify(this.updater, never()).updateInfo(this.instance.getId());\n\t}\n\n\t@Test\n\tvoid should_update_on_endpoints_detects_event() {\n\t\t// when registered event is emitted\n\t\tthis.events.next(new InstanceEndpointsDetectedEvent(this.instance.getId(), this.instance.getVersion(),\n\t\t\t\tthis.instance.getEndpoints()));\n\t\t// then should update\n\t\tverify(this.updater, times(1)).updateInfo(this.instance.getId());\n\t}\n\n\t@Test\n\tvoid should_update_on_status_changed_event() {\n\t\t// when registered event is emitted\n\t\tthis.events.next(\n\t\t\t\tnew InstanceStatusChangedEvent(this.instance.getId(), this.instance.getVersion(), StatusInfo.ofDown()));\n\t\t// then should update\n\t\tverify(this.updater, times(1)).updateInfo(this.instance.getId());\n\t}\n\n\t@Test\n\tvoid should_update_on_instance_registration_update_event() {\n\t\t// when registered event is emitted\n\t\tthis.events.next(new InstanceRegistrationUpdatedEvent(this.instance.getId(), this.instance.getVersion(),\n\t\t\t\tthis.instance.getRegistration()));\n\t\t// then should update\n\t\tverify(this.updater, times(1)).updateInfo(this.instance.getId());\n\t}\n\n\t@Test\n\tvoid should_not_update_on_non_relevant_event() {\n\t\t// when some non-registered event is emitted\n\t\tthis.events.next(new InstanceInfoChangedEvent(this.instance.getId(), this.instance.getVersion(), Info.empty()));\n\t\t// then should not update\n\t\tverify(this.updater, never()).updateInfo(this.instance.getId());\n\t}\n\n\t@Test\n\tvoid should_continue_update_after_error() {\n\t\t// when status-change event is emitted and an error is emitted\n\t\twhen(this.updater.updateInfo(any())).thenReturn(Mono.error(IllegalStateException::new))\n\t\t\t.thenReturn(Mono.empty());\n\n\t\tthis.events.next(\n\t\t\t\tnew InstanceStatusChangedEvent(this.instance.getId(), this.instance.getVersion(), StatusInfo.ofDown()));\n\t\tthis.events\n\t\t\t.next(new InstanceStatusChangedEvent(this.instance.getId(), this.instance.getVersion(), StatusInfo.ofUp()));\n\n\t\t// then should update\n\t\tverify(this.updater, times(2)).updateInfo(this.instance.getId());\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/services/InfoUpdaterTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services;\n\nimport java.time.Duration;\n\nimport com.github.tomakehurst.wiremock.WireMockServer;\nimport com.github.tomakehurst.wiremock.core.Options;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.entities.EventsourcingInstanceRepository;\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceInfoChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.Endpoint;\nimport de.codecentric.boot.admin.server.domain.values.Endpoints;\nimport de.codecentric.boot.admin.server.domain.values.Info;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\nimport de.codecentric.boot.admin.server.eventstore.InMemoryEventStore;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.aResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.get;\nimport static com.github.tomakehurst.wiremock.client.WireMock.okJson;\nimport static com.github.tomakehurst.wiremock.client.WireMock.serverError;\nimport static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED;\nimport static de.codecentric.boot.admin.server.web.client.InstanceExchangeFilterFunctions.retry;\nimport static de.codecentric.boot.admin.server.web.client.InstanceExchangeFilterFunctions.rewriteEndpointUrl;\nimport static de.codecentric.boot.admin.server.web.client.InstanceExchangeFilterFunctions.timeout;\nimport static java.util.Collections.emptyMap;\nimport static java.util.Collections.singletonMap;\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass InfoUpdaterTest {\n\n\tpublic final WireMockServer wireMock = new WireMockServer(Options.DYNAMIC_PORT);\n\n\tprivate InfoUpdater updater;\n\n\tprivate InMemoryEventStore eventStore;\n\n\tprivate InstanceRepository repository;\n\n\tprivate final ApiMediaTypeHandler apiMediaTypeHandler = new ApiMediaTypeHandler();\n\n\t@BeforeEach\n\tvoid setup() {\n\t\tthis.eventStore = new InMemoryEventStore();\n\t\tthis.repository = new EventsourcingInstanceRepository(this.eventStore);\n\t\tthis.updater = new InfoUpdater(this.repository,\n\t\t\t\tInstanceWebClient.builder()\n\t\t\t\t\t.filter(rewriteEndpointUrl())\n\t\t\t\t\t.filter(retry(0, singletonMap(Endpoint.INFO, 1)))\n\t\t\t\t\t.filter(timeout(Duration.ofSeconds(2), emptyMap()))\n\t\t\t\t\t.build(),\n\t\t\t\tthis.apiMediaTypeHandler);\n\t\tthis.wireMock.start();\n\t}\n\n\t@AfterEach\n\tvoid teardown() {\n\t\tthis.wireMock.stop();\n\t}\n\n\t@BeforeAll\n\tstatic void setUp() {\n\t\tStepVerifier.setDefaultTimeout(Duration.ofSeconds(5));\n\t}\n\n\t@AfterAll\n\tstatic void tearDown() {\n\t\tStepVerifier.resetDefaultTimeout();\n\t}\n\n\t@Test\n\tvoid should_update_info_for_online_with_info_endpoint_only() {\n\t\t// given\n\t\tRegistration registration = Registration.create(\"foo\", this.wireMock.url(\"/health\")).build();\n\t\tInstance instance = Instance.create(InstanceId.of(\"onl\"))\n\t\t\t.register(registration)\n\t\t\t.withEndpoints(Endpoints.single(\"info\", this.wireMock.url(\"/info\")))\n\t\t\t.withStatusInfo(StatusInfo.ofUp());\n\t\tStepVerifier.create(this.repository.save(instance)).expectNextCount(1).verifyComplete();\n\t\tString body = \"{ \\\"foo\\\": \\\"bar\\\" }\";\n\t\tthis.wireMock.stubFor(\n\t\t\t\tget(\"/info\").willReturn(okJson(body).withHeader(\"Content-Length\", Integer.toString(body.length()))));\n\n\t\tInstance noInfo = Instance.create(InstanceId.of(\"noinfo\"))\n\t\t\t.register(registration)\n\t\t\t.withEndpoints(Endpoints.single(\"beans\", this.wireMock.url(\"/beans\")))\n\t\t\t.withStatusInfo(StatusInfo.ofUp());\n\t\tStepVerifier.create(this.repository.save(noInfo)).expectNextCount(1).verifyComplete();\n\n\t\tInstance offline = Instance.create(InstanceId.of(\"off\"))\n\t\t\t.register(registration)\n\t\t\t.withStatusInfo(StatusInfo.ofOffline());\n\t\tStepVerifier.create(this.repository.save(offline)).expectNextCount(1).verifyComplete();\n\n\t\tInstance unknown = Instance.create(InstanceId.of(\"unk\"))\n\t\t\t.register(registration)\n\t\t\t.withStatusInfo(StatusInfo.ofUnknown());\n\t\tStepVerifier.create(this.repository.save(unknown)).expectNextCount(1).verifyComplete();\n\n\t\t// when\n\t\tStepVerifier.create(this.eventStore)\n\t\t\t.expectSubscription()\n\t\t\t.then(() -> StepVerifier.create(this.updater.updateInfo(offline.getId())).verifyComplete())\n\t\t\t.then(() -> StepVerifier.create(this.updater.updateInfo(unknown.getId())).verifyComplete())\n\t\t\t.then(() -> StepVerifier.create(this.updater.updateInfo(noInfo.getId())).verifyComplete())\n\t\t\t.expectNoEvent(Duration.ofMillis(100L))\n\t\t\t.then(() -> StepVerifier.create(this.updater.updateInfo(instance.getId())).verifyComplete())\n\t\t\t// then\n\t\t\t.assertNext((event) -> assertThat(event).isInstanceOf(InstanceInfoChangedEvent.class))\n\t\t\t.thenCancel()\n\t\t\t.verify();\n\n\t\tStepVerifier.create(this.repository.find(instance.getId()))\n\t\t\t.assertNext((app) -> assertThat(app.getInfo()).isEqualTo(Info.from(singletonMap(\"foo\", \"bar\"))))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_clear_info_on_http_error() {\n\t\t// given\n\t\tInstance instance = Instance.create(InstanceId.of(\"onl\"))\n\t\t\t.register(Registration.create(\"foo\", this.wireMock.url(\"/health\")).build())\n\t\t\t.withEndpoints(Endpoints.single(\"info\", this.wireMock.url(\"/info\")))\n\t\t\t.withStatusInfo(StatusInfo.ofUp())\n\t\t\t.withInfo(Info.from(singletonMap(\"foo\", \"bar\")));\n\t\tStepVerifier.create(this.repository.save(instance)).expectNextCount(1).verifyComplete();\n\n\t\tthis.wireMock.stubFor(get(\"/info\").willReturn(serverError()));\n\n\t\t// when\n\t\tStepVerifier.create(this.eventStore)\n\t\t\t.expectSubscription()\n\t\t\t.then(() -> StepVerifier.create(this.updater.updateInfo(instance.getId())).verifyComplete())\n\t\t\t// then\n\t\t\t.assertNext((event) -> assertThat(event).isInstanceOf(InstanceInfoChangedEvent.class))\n\t\t\t.thenCancel()\n\t\t\t.verify();\n\n\t\tStepVerifier.create(this.repository.find(instance.getId()))\n\t\t\t.assertNext((app) -> assertThat(app.getInfo()).isEqualTo(Info.empty()))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_clear_info_on_exception() {\n\n\t\t// given\n\t\tInstance instance = Instance.create(InstanceId.of(\"onl\"))\n\t\t\t.register(Registration.create(\"foo\", this.wireMock.url(\"/health\")).build())\n\t\t\t.withEndpoints(Endpoints.single(\"info\", this.wireMock.url(\"/info\")))\n\t\t\t.withStatusInfo(StatusInfo.ofUp())\n\t\t\t.withInfo(Info.from(singletonMap(\"foo\", \"bar\")));\n\t\tStepVerifier.create(this.repository.save(instance)).expectNextCount(1).verifyComplete();\n\n\t\tthis.wireMock.stubFor(get(\"/info\").willReturn(okJson(\"{ \\\"foo\\\": \\\"bar\\\" }\").withFixedDelay(2100)));\n\n\t\t// when\n\t\tStepVerifier.create(this.eventStore)\n\t\t\t.expectSubscription()\n\t\t\t.then(() -> StepVerifier.create(this.updater.updateInfo(instance.getId())).verifyComplete())\n\t\t\t// then\n\t\t\t.assertNext((event) -> assertThat(event).isInstanceOf(InstanceInfoChangedEvent.class))\n\t\t\t.thenCancel()\n\t\t\t.verify();\n\n\t\tStepVerifier.create(this.repository.find(instance.getId()))\n\t\t\t.assertNext((app) -> assertThat(app.getInfo()).isEqualTo(Info.empty()))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_retry() {\n\t\t// given\n\t\tRegistration registration = Registration.create(\"foo\", this.wireMock.url(\"/health\")).build();\n\t\tInstance instance = Instance.create(InstanceId.of(\"onl\"))\n\t\t\t.register(registration)\n\t\t\t.withEndpoints(Endpoints.single(\"info\", this.wireMock.url(\"/info\")))\n\t\t\t.withStatusInfo(StatusInfo.ofUp());\n\t\tStepVerifier.create(this.repository.save(instance)).expectNextCount(1).verifyComplete();\n\n\t\tthis.wireMock.stubFor(get(\"/info\").inScenario(\"retry\")\n\t\t\t.whenScenarioStateIs(STARTED)\n\t\t\t.willReturn(aResponse().withFixedDelay(5000))\n\t\t\t.willSetStateTo(\"recovered\"));\n\n\t\tString body = \"{ \\\"foo\\\": \\\"bar\\\" }\";\n\t\tthis.wireMock.stubFor(get(\"/info\").inScenario(\"retry\")\n\t\t\t.whenScenarioStateIs(\"recovered\")\n\t\t\t.willReturn(okJson(body).withHeader(\"Content-Length\", Integer.toString(body.length()))));\n\n\t\t// when\n\t\tStepVerifier.create(this.eventStore)\n\t\t\t.expectSubscription()\n\t\t\t.then(() -> StepVerifier.create(this.updater.updateInfo(instance.getId())).verifyComplete())\n\t\t\t// then\n\t\t\t.assertNext((event) -> assertThat(event).isInstanceOf(InstanceInfoChangedEvent.class))\n\t\t\t.thenCancel()\n\t\t\t.verify();\n\n\t\tStepVerifier.create(this.repository.find(instance.getId()))\n\t\t\t.assertNext((app) -> assertThat(app.getInfo()).isEqualTo(Info.from(singletonMap(\"foo\", \"bar\"))))\n\t\t\t.verifyComplete();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/services/InstanceRegistryTest.java",
    "content": "/*\n * Copyright 2014-2024 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services;\n\nimport java.util.ArrayList;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.entities.EventsourcingInstanceRepository;\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.values.Info;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\nimport de.codecentric.boot.admin.server.eventstore.InMemoryEventStore;\n\nimport static java.util.Collections.singletonMap;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass InstanceRegistryTest {\n\n\tprivate InstanceRepository repository;\n\n\tprivate InstanceIdGenerator idGenerator;\n\n\tprivate InstanceRegistry registry;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\trepository = new EventsourcingInstanceRepository(new InMemoryEventStore());\n\t\tidGenerator = new HashingInstanceUrlIdGenerator();\n\t\tregistry = new InstanceRegistry(repository, idGenerator, (instance) -> {\n\t\t\tMap<String, String> metadata = instance.getRegistration().getMetadata();\n\t\t\treturn !metadata.containsKey(\"displayed\") || !metadata.get(\"displayed\").equals(\"false\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid registerFailed_null() {\n\t\tassertThatThrownBy(() -> registry.register(null)).isInstanceOf(IllegalArgumentException.class);\n\t}\n\n\t@Test\n\tvoid register() {\n\t\tRegistration registration = Registration.create(\"abc\", \"http://localhost:8080/health\").build();\n\t\tInstanceId id = registry.register(registration).block();\n\n\t\tStepVerifier.create(registry.getInstance(id)).assertNext((app) -> {\n\t\t\tassertThat(app.getRegistration()).isEqualTo(registration);\n\t\t\tassertThat(app.getId()).isNotNull();\n\t\t}).verifyComplete();\n\n\t\tStepVerifier.create(registry.getInstances()).assertNext((app) -> {\n\t\t\tassertThat(app.getRegistration()).isEqualTo(registration);\n\t\t\tassertThat(app.getId()).isNotNull();\n\t\t}).verifyComplete();\n\t}\n\n\t@Test\n\tvoid deregister() {\n\t\tInstanceId id = registry.register(Registration.create(\"abc\", \"http://localhost:8080/health\").build()).block();\n\t\tregistry.deregister(id).block();\n\n\t\tStepVerifier.create(registry.getInstance(id))\n\t\t\t.assertNext((app) -> assertThat(app.isRegistered()).isFalse())\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid refresh() {\n\t\t// Given instance is already registered and has status and info.\n\t\tStatusInfo status = StatusInfo.ofUp();\n\t\tInfo info = Info.from(singletonMap(\"foo\", \"bar\"));\n\t\tRegistration registration = Registration.create(\"abc\", \"http://localhost:8080/health\").build();\n\t\tInstanceId id = idGenerator.generateId(registration);\n\t\tInstance app = Instance.create(id).register(registration).withStatusInfo(status).withInfo(info);\n\t\tStepVerifier.create(repository.save(app)).expectNextCount(1).verifyComplete();\n\n\t\t// When instance registers second time\n\t\tInstanceId refreshId = registry.register(Registration.create(\"abc\", \"http://localhost:8080/health\").build())\n\t\t\t.block();\n\n\t\tassertThat(refreshId).isEqualTo(id);\n\t\tStepVerifier.create(registry.getInstance(id)).assertNext((registered) -> {\n\t\t\t// Then info and status are retained\n\t\t\tassertThat(registered.getInfo()).isEqualTo(info);\n\t\t\tassertThat(registered.getStatusInfo()).isEqualTo(status);\n\t\t}).verifyComplete();\n\t}\n\n\t@Test\n\tvoid findByName() {\n\t\tInstanceId id1 = registry.register(Registration.create(\"abc\", \"http://localhost:8080/health\").build()).block();\n\t\tInstanceId id2 = registry.register(Registration.create(\"abc\", \"http://localhost:8081/health\").build()).block();\n\t\tInstanceId id3 = registry.register(Registration.create(\"zzz\", \"http://localhost:9999/health\").build()).block();\n\n\t\tStepVerifier.create(registry.getInstances(\"abc\"))\n\t\t\t.recordWith(ArrayList::new)\n\t\t\t.thenConsumeWhile((a) -> true)\n\t\t\t.consumeRecordedWith(\n\t\t\t\t\t(applications) -> assertThat(applications.stream().map(Instance::getId)).doesNotContain(id3)\n\t\t\t\t\t\t.containsExactlyInAnyOrder(id1, id2))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid findByNameAndFilter() {\n\t\tInstanceId id1 = registry.register(Registration.create(\"abc\", \"http://localhost:8080/health\").build()).block();\n\t\tregistry\n\t\t\t.register(Registration.create(\"abc\", \"http://localhost:8081/health\").metadata(\"displayed\", \"false\").build())\n\t\t\t.block();\n\n\t\tStepVerifier.create(registry.getInstances(\"abc\"))\n\t\t\t.recordWith(ArrayList::new)\n\t\t\t.thenConsumeWhile((a) -> true)\n\t\t\t.consumeRecordedWith(\n\t\t\t\t\t(applications) -> assertThat(applications.stream().map(Instance::getId)).containsExactly(id1))\n\t\t\t.verifyComplete();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/services/IntervalCheckTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services;\n\nimport java.time.Duration;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.CopyOnWriteArrayList;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\nimport java.util.stream.IntStream;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.invocation.InvocationOnMock;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.atLeast;\nimport static org.mockito.Mockito.atLeastOnce;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.reset;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass IntervalCheckTest {\n\n\tprivate static final InstanceId INSTANCE_ID = InstanceId.of(\"Test\");\n\n\t@SuppressWarnings(\"unchecked\")\n\tprivate final Function<InstanceId, Mono<Void>> checkFn = mock(Function.class, (i) -> Mono.empty());\n\n\tprivate IntervalCheck intervalCheck;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\treset(this.checkFn);\n\t\tthis.intervalCheck = new IntervalCheck(\"test\", this.checkFn, Duration.ofMillis(10), Duration.ofMillis(10),\n\t\t\t\tDuration.ofSeconds(1));\n\t}\n\n\t@Test\n\tvoid should_check_after_being_started() {\n\t\tthis.intervalCheck.markAsChecked(INSTANCE_ID);\n\n\t\tthis.intervalCheck.start();\n\t\tawait().atMost(Duration.ofMillis(100))\n\t\t\t.pollInterval(Duration.ofMillis(10))\n\t\t\t.untilAsserted(() -> verify(this.checkFn, atLeastOnce()).apply(INSTANCE_ID));\n\t}\n\n\t@Test\n\tvoid should_not_check_when_stopped() {\n\t\tthis.intervalCheck.markAsChecked(INSTANCE_ID);\n\n\t\tthis.intervalCheck.stop();\n\t\tawait().pollDelay(Duration.ofMillis(100)).untilAsserted(() -> verify(this.checkFn, never()).apply(any()));\n\t}\n\n\t@Test\n\tvoid should_not_check_in_retention_period() {\n\t\tthis.intervalCheck.setMinRetention(Duration.ofSeconds(100));\n\t\tthis.intervalCheck.markAsChecked(INSTANCE_ID);\n\n\t\tthis.intervalCheck.start();\n\t\tawait().pollDelay(Duration.ofMillis(100)).untilAsserted(() -> verify(this.checkFn, never()).apply(any()));\n\t}\n\n\t@Test\n\tvoid should_recheck_after_retention_period() {\n\t\tthis.intervalCheck.setMinRetention(Duration.ofMillis(10));\n\t\tthis.intervalCheck.markAsChecked(INSTANCE_ID);\n\n\t\tthis.intervalCheck.start();\n\t\tawait().atMost(Duration.ofMillis(100))\n\t\t\t.pollInterval(Duration.ofMillis(10))\n\t\t\t.untilAsserted(() -> verify(this.checkFn, atLeast(2)).apply(INSTANCE_ID));\n\t}\n\n\t@Test\n\tvoid should_not_wait_longer_than_maxBackoff() {\n\t\tthis.intervalCheck.setInterval(Duration.ofMillis(10));\n\t\tthis.intervalCheck.setMinRetention(Duration.ofMillis(10));\n\t\tthis.intervalCheck.setMaxBackoff(Duration.ofSeconds(2));\n\t\tthis.intervalCheck.markAsChecked(INSTANCE_ID);\n\n\t\twhen(this.checkFn.apply(any())).thenReturn(Mono.error(new RuntimeException(\"Test\")));\n\n\t\tthis.intervalCheck.start();\n\t\tawait().atMost(Duration.ofSeconds(10)).untilAsserted(() -> verify(this.checkFn, atLeast(7)).apply(INSTANCE_ID));\n\t}\n\n\t@Test\n\tvoid should_check_after_error() {\n\t\tthis.intervalCheck.markAsChecked(INSTANCE_ID);\n\n\t\twhen(this.checkFn.apply(any())).thenReturn(Mono.error(new RuntimeException(\"Test\"))).thenReturn(Mono.empty());\n\n\t\tthis.intervalCheck.start();\n\t\tawait().atMost(Duration.ofMillis(1500))\n\t\t\t.untilAsserted(() -> verify(this.checkFn, atLeast(2)).apply(InstanceId.of(\"Test\")));\n\t}\n\n\t@Test\n\tvoid should_not_overflow_when_checks_timeout_randomly() {\n\t\tDuration CHECK_INTERVAL = Duration.ofMillis(500);\n\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tFunction<InstanceId, Mono<Void>> timeoutCheckFn = mock(Function.class);\n\n\t\tjava.util.concurrent.atomic.AtomicInteger invocationCount = new java.util.concurrent.atomic.AtomicInteger(0);\n\t\tdoAnswer((invocation) -> {\n\t\t\tif (invocationCount.getAndIncrement() % 2 == 0) {\n\t\t\t\t// Succeed quickly on even invocations\n\t\t\t\treturn Mono.empty();\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// Timeout on odd invocations\n\t\t\t\treturn Mono.just(\"slow response\").delayElement(CHECK_INTERVAL.plus(Duration.ofSeconds(1))).then();\n\t\t\t}\n\t\t}).when(timeoutCheckFn).apply(any());\n\n\t\tIntervalCheck timeoutCheck = new IntervalCheck(\"overflow-test\", timeoutCheckFn, CHECK_INTERVAL, CHECK_INTERVAL,\n\t\t\t\tDuration.ofSeconds(1));\n\n\t\tList<Throwable> retryErrors = new CopyOnWriteArrayList<>();\n\n\t\ttimeoutCheck.setRetryConsumer(retryErrors::add);\n\t\ttimeoutCheck.markAsChecked(INSTANCE_ID);\n\t\ttimeoutCheck.start();\n\t\ttry {\n\t\t\tawait().atMost(Duration.ofSeconds(5))\n\t\t\t\t.until(() -> retryErrors.stream()\n\t\t\t\t\t.noneMatch((Throwable er) -> \"OverflowException\".equalsIgnoreCase(er.getClass().getSimpleName())));\n\n\t\t\tassertThat(retryErrors).noneMatch((Throwable e) -> e.getCause() != null\n\t\t\t\t\t&& \"OverflowException\".equalsIgnoreCase(e.getCause().getClass().getSimpleName()));\n\t\t}\n\t\tfinally {\n\t\t\ttimeoutCheck.stop();\n\t\t}\n\t}\n\n\t@Test\n\tvoid should_not_lose_checks_under_backpressure() {\n\t\tDuration CHECK_INTERVAL = Duration.ofMillis(100);\n\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tFunction<InstanceId, Mono<Void>> slowCheckFn = mock(Function.class);\n\t\tIntervalCheck slowCheck = new IntervalCheck(\"backpressure-test\", slowCheckFn, CHECK_INTERVAL,\n\t\t\t\tDuration.ofMillis(50), Duration.ofSeconds(1));\n\n\t\tList<Long> checkTimes = new CopyOnWriteArrayList<>();\n\t\tdoAnswer((invocation) -> {\n\t\t\tcheckTimes.add(System.currentTimeMillis());\n\t\t\treturn Mono.empty();\n\t\t}).when(slowCheckFn).apply(any());\n\n\t\tslowCheck.markAsChecked(INSTANCE_ID);\n\t\tslowCheck.start();\n\n\t\ttry {\n\t\t\tawait().atMost(Duration.ofSeconds(2)).until(() -> checkTimes.size() >= 5);\n\t\t\t// With onBackpressureLatest, we should have processed multiple checks without\n\t\t\t// drops\n\t\t\tassertThat(checkTimes).hasSizeGreaterThanOrEqualTo(5);\n\t\t}\n\t\tfinally {\n\t\t\tslowCheck.stop();\n\t\t}\n\t}\n\n\t@Test\n\tvoid should_not_lose_checks_under_backpressure_latest() {\n\t\tDuration CHECK_INTERVAL = Duration.ofMillis(100);\n\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tFunction<InstanceId, Mono<Void>> slowCheckFn = mock(Function.class);\n\n\t\tIntervalCheck slowCheck = new IntervalCheck(\"backpressure-test\", slowCheckFn, CHECK_INTERVAL, CHECK_INTERVAL,\n\t\t\t\tDuration.ofSeconds(1));\n\n\t\t// Add multiple instances to increase load and cause drops\n\t\tSet<InstanceId> instanceIds = IntStream.range(0, 50)\n\t\t\t.mapToObj((i) -> InstanceId.of(\"Test\" + i))\n\t\t\t.collect(Collectors.toSet());\n\n\t\tinstanceIds.forEach((InstanceId instanceId) -> slowCheck.markAsChecked(instanceId));\n\n\t\tList<Long> checkTimes = new CopyOnWriteArrayList<>();\n\t\tMap<String, List<Long>> checkTimesPerInstance = new ConcurrentHashMap<>();\n\n\t\tjava.util.concurrent.atomic.AtomicInteger invocationCount = new java.util.concurrent.atomic.AtomicInteger(0);\n\t\tdoAnswer((invocation) -> {\n\t\t\tlong checkTime = System.currentTimeMillis();\n\t\t\tString instanceId = instanceIdString(invocation);\n\t\t\tList<Long> checkTimesInstance = checkTimesPerInstance.computeIfAbsent(instanceId,\n\t\t\t\t\t(String k) -> new CopyOnWriteArrayList<>());\n\t\t\tcheckTimesInstance.add(checkTime);\n\t\t\tcheckTimes.add(checkTime);\n\t\t\tif (invocationCount.getAndIncrement() % 2 == 0) {\n\t\t\t\t// Sometimes succeed quickly\n\t\t\t\treturn Mono.empty();\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// Sometimes slow\n\t\t\t\treturn Mono.delay(CHECK_INTERVAL.plus(Duration.ofMillis(500))).then();\n\t\t\t}\n\t\t}).when(slowCheckFn).apply(any());\n\n\t\tslowCheck.start();\n\n\t\ttry {\n\t\t\tawait().atMost(Duration.ofSeconds(5)).until(() -> checkTimes.size() >= 500);\n\t\t\t// With onBackpressureLatest, we should process more checks without drops\n\t\t\tinstanceIds.forEach((InstanceId instanceId) -> assertThat(checkTimesPerInstance.get(instanceId.getValue()))\n\t\t\t\t.hasSizeGreaterThanOrEqualTo(10));\n\t\t}\n\t\tfinally {\n\t\t\tslowCheck.stop();\n\t\t}\n\t}\n\n\t@AfterEach\n\tvoid tearDown() {\n\t\tthis.intervalCheck.stop();\n\t}\n\n\tprivate static String instanceIdString(InvocationOnMock invocation) {\n\t\treturn invocation.getArguments()[0].toString();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/services/StatusUpdateTriggerTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services;\n\nimport java.time.Duration;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Mono;\nimport reactor.test.publisher.TestPublisher;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceInfoChangedEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegistrationUpdatedEvent;\nimport de.codecentric.boot.admin.server.domain.values.Info;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\n\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.atLeast;\nimport static org.mockito.Mockito.clearInvocations;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass StatusUpdateTriggerTest {\n\n\tprivate final Instance instance = Instance.create(InstanceId.of(\"id-1\"))\n\t\t.register(Registration.create(\"foo\", \"http://health-1\").build());\n\n\tprivate final StatusUpdater updater = mock(StatusUpdater.class);\n\n\tprivate final TestPublisher<InstanceEvent> events = TestPublisher.create();\n\n\tprivate StatusUpdateTrigger trigger;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\twhen(this.updater.updateStatus(any(InstanceId.class))).thenReturn(Mono.empty());\n\t\twhen(this.updater.timeout(any())).thenReturn(this.updater);\n\n\t\tthis.trigger = new StatusUpdateTrigger(this.updater, this.events.flux(), Duration.ofSeconds(10),\n\t\t\t\tDuration.ofSeconds(10), Duration.ofSeconds(60));\n\t\tthis.trigger.start();\n\t\tawait().until(this.events::wasSubscribed);\n\t}\n\n\t@Test\n\tvoid should_start_and_stop_monitor() {\n\t\t// given\n\t\tthis.trigger.stop();\n\t\tthis.trigger.setInterval(Duration.ofMillis(10));\n\t\tthis.trigger.setLifetime(Duration.ofMillis(10));\n\t\tthis.trigger.start();\n\t\tawait().until(this.events::wasSubscribed);\n\n\t\tthis.events.next(new InstanceRegisteredEvent(this.instance.getId(), 0L, this.instance.getRegistration()));\n\t\t// it should start updating one time for registration and at least once for\n\t\t// monitor\n\t\tawait().atMost(Duration.ofMillis(50))\n\t\t\t.pollInterval(Duration.ofMillis(10))\n\t\t\t.untilAsserted(() -> verify(this.updater, atLeast(2)).updateStatus(this.instance.getId()));\n\n\t\t// given long lifetime\n\t\tthis.trigger.setLifetime(Duration.ofSeconds(10));\n\t\tclearInvocations(this.updater);\n\t\t// when the lifetime is not expired should never update\n\t\tawait().pollDelay(Duration.ofMillis(50))\n\t\t\t.untilAsserted(() -> verify(this.updater, never()).updateStatus(any(InstanceId.class)));\n\n\t\tthis.trigger.setLifetime(Duration.ofMillis(10));\n\t\tthis.trigger.stop();\n\t\tclearInvocations(this.updater);\n\t\t// when trigger ist destroyed it should stop updating\n\t\tawait().pollDelay(Duration.ofMillis(15))\n\t\t\t.untilAsserted(() -> verify(this.updater, never()).updateStatus(any(InstanceId.class)));\n\t}\n\n\t@Test\n\tvoid should_not_update_when_stopped() {\n\t\t// when registered event is emitted but the trigger has been stopped\n\t\tthis.trigger.stop();\n\t\tclearInvocations(this.updater);\n\t\tthis.events.next(new InstanceRegisteredEvent(this.instance.getId(), this.instance.getVersion(),\n\t\t\t\tthis.instance.getRegistration()));\n\t\t// then should not update\n\t\tverify(this.updater, never()).updateStatus(this.instance.getId());\n\t}\n\n\t@Test\n\tvoid should_update_on_instance_registered_event() {\n\t\t// when registered event is emitted\n\t\tthis.events.next(new InstanceRegisteredEvent(this.instance.getId(), this.instance.getVersion(),\n\t\t\t\tthis.instance.getRegistration()));\n\t\t// then should update\n\t\tverify(this.updater, times(1)).updateStatus(this.instance.getId());\n\t}\n\n\t@Test\n\tvoid should_update_on_instance_registration_update_event() {\n\t\t// when registered event is emitted\n\t\tthis.events.next(new InstanceRegistrationUpdatedEvent(this.instance.getId(), this.instance.getVersion(),\n\t\t\t\tthis.instance.getRegistration()));\n\t\t// then should update\n\t\tverify(this.updater, times(1)).updateStatus(this.instance.getId());\n\t}\n\n\t@Test\n\tvoid should_not_update_on_non_relevant_event() {\n\t\t// when some non-registered event is emitted\n\t\tthis.events.next(new InstanceInfoChangedEvent(this.instance.getId(), this.instance.getVersion(), Info.empty()));\n\t\t// then should not update\n\t\tverify(this.updater, never()).updateStatus(this.instance.getId());\n\t}\n\n\t@Test\n\tvoid should_continue_update_after_error() {\n\t\t// when status-change event is emitted and an error is emitted\n\t\twhen(this.updater.updateStatus(any())).thenReturn(Mono.error(IllegalStateException::new))\n\t\t\t.thenReturn(Mono.empty());\n\n\t\tthis.events.next(new InstanceRegistrationUpdatedEvent(this.instance.getId(), this.instance.getVersion(),\n\t\t\t\tthis.instance.getRegistration()));\n\t\tthis.events.next(new InstanceRegistrationUpdatedEvent(this.instance.getId(), this.instance.getVersion(),\n\t\t\t\tthis.instance.getRegistration()));\n\n\t\t// then should update\n\t\tverify(this.updater, times(2)).updateStatus(this.instance.getId());\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/services/StatusUpdaterTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services;\n\nimport java.time.Duration;\n\nimport com.github.tomakehurst.wiremock.WireMockServer;\nimport com.github.tomakehurst.wiremock.core.Options;\nimport com.github.tomakehurst.wiremock.http.Fault;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.actuate.endpoint.ApiVersion;\nimport org.springframework.http.MediaType;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.entities.EventsourcingInstanceRepository;\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.Endpoint;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.eventstore.ConcurrentMapEventStore;\nimport de.codecentric.boot.admin.server.eventstore.InMemoryEventStore;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.aResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.get;\nimport static com.github.tomakehurst.wiremock.client.WireMock.ok;\nimport static com.github.tomakehurst.wiremock.client.WireMock.okForContentType;\nimport static com.github.tomakehurst.wiremock.client.WireMock.okJson;\nimport static com.github.tomakehurst.wiremock.client.WireMock.status;\nimport static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED;\nimport static de.codecentric.boot.admin.server.web.client.InstanceExchangeFilterFunctions.retry;\nimport static de.codecentric.boot.admin.server.web.client.InstanceExchangeFilterFunctions.rewriteEndpointUrl;\nimport static de.codecentric.boot.admin.server.web.client.InstanceExchangeFilterFunctions.timeout;\nimport static java.util.Collections.emptyMap;\nimport static java.util.Collections.singletonMap;\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass StatusUpdaterTest {\n\n\t// @Rule\n\tpublic final WireMockServer wireMock = new WireMockServer(Options.DYNAMIC_PORT);\n\n\tprivate StatusUpdater updater;\n\n\tprivate ConcurrentMapEventStore eventStore;\n\n\tprivate InstanceRepository repository;\n\n\tprivate Instance instance;\n\n\t@BeforeAll\n\tstatic void setUp() {\n\t\tStepVerifier.setDefaultTimeout(Duration.ofSeconds(5));\n\t}\n\n\t@AfterAll\n\tstatic void tearDown() {\n\t\tStepVerifier.resetDefaultTimeout();\n\t}\n\n\t@BeforeEach\n\tvoid setup() {\n\t\tthis.wireMock.start();\n\t\tthis.eventStore = new InMemoryEventStore();\n\t\tthis.repository = new EventsourcingInstanceRepository(this.eventStore);\n\t\tthis.instance = Instance.create(InstanceId.of(\"id\"))\n\t\t\t.register(Registration.create(\"foo\", this.wireMock.url(\"/health\")).build());\n\t\tStepVerifier.create(this.repository.save(this.instance)).expectNextCount(1).verifyComplete();\n\n\t\tthis.updater = new StatusUpdater(this.repository,\n\t\t\t\tInstanceWebClient.builder()\n\t\t\t\t\t.filter(rewriteEndpointUrl())\n\t\t\t\t\t.filter(retry(0, singletonMap(Endpoint.HEALTH, 1)))\n\t\t\t\t\t.filter(timeout(Duration.ofSeconds(2), emptyMap()))\n\t\t\t\t\t.build(),\n\t\t\t\tnew ApiMediaTypeHandler());\n\t}\n\n\t@AfterEach\n\tvoid teardown() {\n\t\tthis.wireMock.stop();\n\t}\n\n\t@Test\n\tvoid should_change_status_to_down() {\n\t\tString body = \"{ \\\"status\\\" : \\\"UP\\\", \\\"details\\\" : { \\\"foo\\\" : \\\"bar\\\" } }\";\n\t\tthis.wireMock.stubFor(\n\t\t\t\tget(\"/health\").willReturn(okForContentType(ApiVersion.LATEST.getProducedMimeType().toString(), body)\n\t\t\t\t\t.withHeader(\"Content-Length\", Integer.toString(body.length()))));\n\n\t\tStepVerifier.create(this.eventStore)\n\t\t\t.expectSubscription()\n\t\t\t.then(() -> StepVerifier.create(this.updater.updateStatus(this.instance.getId())).verifyComplete())\n\t\t\t.assertNext((event) -> {\n\t\t\t\tassertThat(event).isInstanceOf(InstanceStatusChangedEvent.class);\n\t\t\t\tassertThat(event.getInstance()).isEqualTo(this.instance.getId());\n\t\t\t\tInstanceStatusChangedEvent statusChangedEvent = (InstanceStatusChangedEvent) event;\n\t\t\t\tassertThat(statusChangedEvent.getStatusInfo().getStatus()).isEqualTo(\"UP\");\n\t\t\t\tassertThat(statusChangedEvent.getStatusInfo().getDetails()).isEqualTo(singletonMap(\"foo\", \"bar\"));\n\t\t\t})\n\t\t\t.thenCancel()\n\t\t\t.verify();\n\n\t\tStepVerifier.create(this.repository.find(this.instance.getId()))\n\t\t\t.assertNext((app) -> assertThat(app.getStatusInfo().getStatus()).isEqualTo(\"UP\"))\n\t\t\t.verifyComplete();\n\n\t\tStepVerifier\n\t\t\t.create(this.repository.computeIfPresent(this.instance.getId(),\n\t\t\t\t\t(key, instance) -> Mono.just(instance.deregister())))\n\t\t\t.then(() -> StepVerifier.create(this.updater.updateStatus(this.instance.getId())).verifyComplete())\n\t\t\t.thenCancel()\n\t\t\t.verify();\n\n\t\tStepVerifier.create(this.repository.find(this.instance.getId()))\n\t\t\t.assertNext((app) -> assertThat(app.getStatusInfo().getStatus()).isEqualTo(\"UNKNOWN\"))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_not_change_status() {\n\t\tString body = \"{ \\\"status\\\" : \\\"UNKNOWN\\\" }\";\n\t\tthis.wireMock.stubFor(\n\t\t\t\tget(\"/health\").willReturn(okJson(body).withHeader(\"Content-Type\", Integer.toString(body.length()))));\n\n\t\tStepVerifier.create(this.eventStore)\n\t\t\t.expectSubscription()\n\t\t\t.then(() -> StepVerifier.create(this.updater.updateStatus(this.instance.getId())).verifyComplete())\n\t\t\t.expectNoEvent(Duration.ofMillis(100L))\n\t\t\t.thenCancel()\n\t\t\t.verify();\n\t}\n\n\t@Test\n\tvoid should_change_status_to_up() {\n\t\tthis.wireMock.stubFor(get(\"/health\").willReturn(ok()));\n\n\t\tStepVerifier.create(this.eventStore)\n\t\t\t.expectSubscription()\n\t\t\t.then(() -> StepVerifier.create(this.updater.updateStatus(this.instance.getId())).verifyComplete())\n\t\t\t.assertNext((event) -> assertThat(event).isInstanceOf(InstanceStatusChangedEvent.class))\n\t\t\t.thenCancel()\n\t\t\t.verify();\n\n\t\tStepVerifier.create(this.repository.find(this.instance.getId()))\n\t\t\t.assertNext((app) -> assertThat(app.getStatusInfo().getStatus()).isEqualTo(\"UP\"))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_change_status_to_down_with_details() {\n\t\tString body = \"{ \\\"foo\\\" : \\\"bar\\\" }\";\n\t\tthis.wireMock\n\t\t\t.stubFor(get(\"/health\").willReturn(status(503).withHeader(\"Content-Type\", MediaType.APPLICATION_JSON_VALUE)\n\t\t\t\t.withHeader(\"Content-Length\", Integer.toString(body.length()))\n\t\t\t\t.withBody(body)));\n\n\t\tStepVerifier.create(this.eventStore)\n\t\t\t.expectSubscription()\n\t\t\t.then(() -> StepVerifier.create(this.updater.updateStatus(this.instance.getId())).verifyComplete())\n\t\t\t.assertNext((event) -> assertThat(event).isInstanceOf(InstanceStatusChangedEvent.class))\n\t\t\t.thenCancel()\n\t\t\t.verify();\n\n\t\tStepVerifier.create(this.repository.find(this.instance.getId())).assertNext((app) -> {\n\t\t\tassertThat(app.getStatusInfo().getStatus()).isEqualTo(\"DOWN\");\n\t\t\tassertThat(app.getStatusInfo().getDetails()).containsEntry(\"foo\", \"bar\");\n\t\t}).verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_change_status_to_down_without_details_incompatible_content_type() {\n\t\tthis.wireMock.stubFor(get(\"/health\").willReturn(status(503)));\n\n\t\tStepVerifier.create(this.eventStore)\n\t\t\t.expectSubscription()\n\t\t\t.then(() -> StepVerifier.create(this.updater.updateStatus(this.instance.getId())).verifyComplete())\n\t\t\t.assertNext((event) -> assertThat(event).isInstanceOf(InstanceStatusChangedEvent.class))\n\t\t\t.thenCancel()\n\t\t\t.verify();\n\n\t\tStepVerifier.create(this.repository.find(this.instance.getId())).assertNext((app) -> {\n\t\t\tassertThat(app.getStatusInfo().getStatus()).isEqualTo(\"DOWN\");\n\t\t\tassertThat(app.getStatusInfo().getDetails()).containsEntry(\"status\", 503)\n\t\t\t\t.containsEntry(\"error\", \"Service Unavailable\");\n\t\t}).verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_change_status_to_down_without_details_no_body() {\n\t\tthis.wireMock.stubFor(\n\t\t\t\tget(\"/health\").willReturn(status(503).withHeader(\"Content-Type\", MediaType.APPLICATION_JSON_VALUE)));\n\n\t\tStepVerifier.create(this.eventStore)\n\t\t\t.expectSubscription()\n\t\t\t.then(() -> StepVerifier.create(this.updater.updateStatus(this.instance.getId())).verifyComplete())\n\t\t\t.assertNext((event) -> assertThat(event).isInstanceOf(InstanceStatusChangedEvent.class))\n\t\t\t.thenCancel()\n\t\t\t.verify();\n\n\t\tStepVerifier.create(this.repository.find(this.instance.getId())).assertNext((app) -> {\n\t\t\tassertThat(app.getStatusInfo().getStatus()).isEqualTo(\"DOWN\");\n\t\t\tassertThat(app.getStatusInfo().getDetails()).containsEntry(\"status\", 503)\n\t\t\t\t.containsEntry(\"error\", \"Service Unavailable\");\n\t\t}).verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_change_status_to_offline() {\n\t\tthis.wireMock.stubFor(get(\"/health\").willReturn(aResponse().withFault(Fault.EMPTY_RESPONSE)));\n\n\t\tStepVerifier.create(this.eventStore)\n\t\t\t.expectSubscription()\n\t\t\t.then(() -> StepVerifier.create(this.updater.updateStatus(this.instance.getId())).verifyComplete())\n\t\t\t.assertNext((event) -> assertThat(event).isInstanceOf(InstanceStatusChangedEvent.class))\n\t\t\t.thenCancel()\n\t\t\t.verify();\n\n\t\tStepVerifier.create(this.repository.find(this.instance.getId())).assertNext((app) -> {\n\t\t\tassertThat(app.getStatusInfo().getStatus()).isEqualTo(\"OFFLINE\");\n\t\t\tassertThat(app.getStatusInfo().getDetails()).containsKeys(\"message\", \"exception\");\n\t\t}).verifyComplete();\n\n\t\tStepVerifier.create(this.updater.updateStatus(this.instance.getId())).verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_retry() {\n\t\tthis.wireMock.stubFor(get(\"/health\").inScenario(\"retry\")\n\t\t\t.whenScenarioStateIs(STARTED)\n\t\t\t.willReturn(aResponse().withFixedDelay(5000))\n\t\t\t.willSetStateTo(\"recovered\"));\n\t\tthis.wireMock.stubFor(get(\"/health\").inScenario(\"retry\").whenScenarioStateIs(\"recovered\").willReturn(ok()));\n\n\t\tStepVerifier.create(this.eventStore)\n\t\t\t.expectSubscription()\n\t\t\t.then(() -> StepVerifier.create(this.updater.updateStatus(this.instance.getId())).verifyComplete())\n\t\t\t.assertNext((event) -> assertThat(event).isInstanceOf(InstanceStatusChangedEvent.class))\n\t\t\t.thenCancel()\n\t\t\t.verify();\n\n\t\tStepVerifier.create(this.repository.find(this.instance.getId()))\n\t\t\t.assertNext((app) -> assertThat(app.getStatusInfo().getStatus()).isEqualTo(\"UP\"))\n\t\t\t.verifyComplete();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/services/endpoints/ChainingStrategyTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services.endpoints;\n\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.values.Endpoints;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass ChainingStrategyTest {\n\n\t@Test\n\tvoid invariants() {\n\t\tassertThatThrownBy(() -> new ChainingStrategy((EndpointDetectionStrategy[]) null))\n\t\t\t.isInstanceOf(IllegalArgumentException.class)\n\t\t\t.hasMessage(\"'delegates' must not be null.\");\n\t\tassertThatThrownBy(() -> new ChainingStrategy((EndpointDetectionStrategy) null))\n\t\t\t.isInstanceOf(IllegalArgumentException.class)\n\t\t\t.hasMessage(\"'delegates' must not contain null.\");\n\t}\n\n\t@Test\n\tvoid should_chain_on_empty() {\n\t\t// given\n\t\tInstance instance = Instance.create(InstanceId.of(\"id\"));\n\t\tChainingStrategy strategy = new ChainingStrategy((a) -> Mono.empty(), (a) -> Mono.empty(),\n\t\t\t\t(a) -> Mono.just(Endpoints.single(\"id\", \"path\")));\n\t\t// when/then\n\t\tStepVerifier.create(strategy.detectEndpoints(instance))\n\t\t\t.expectNext(Endpoints.single(\"id\", \"path\"))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_return_empty_endpoints_when_all_empty() {\n\t\t// given\n\t\tInstance instance = Instance.create(InstanceId.of(\"id\"));\n\t\tChainingStrategy strategy = new ChainingStrategy((a) -> Mono.empty());\n\t\t// when/then\n\t\tStepVerifier.create(strategy.detectEndpoints(instance)).expectNext(Endpoints.empty()).verifyComplete();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/services/endpoints/ProbeEndpointsStrategyTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services.endpoints;\n\nimport java.time.Duration;\n\nimport com.github.tomakehurst.wiremock.WireMockServer;\nimport com.github.tomakehurst.wiremock.core.Options;\nimport com.github.tomakehurst.wiremock.http.Fault;\nimport org.eclipse.jetty.http.HttpStatus;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.values.Endpoints;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.aResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.notFound;\nimport static com.github.tomakehurst.wiremock.client.WireMock.ok;\nimport static com.github.tomakehurst.wiremock.client.WireMock.options;\nimport static com.github.tomakehurst.wiremock.client.WireMock.serverError;\nimport static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;\nimport static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED;\nimport static de.codecentric.boot.admin.server.web.client.InstanceExchangeFilterFunctions.retry;\nimport static de.codecentric.boot.admin.server.web.client.InstanceExchangeFilterFunctions.timeout;\nimport static java.util.Collections.emptyMap;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass ProbeEndpointsStrategyTest {\n\n\tpublic final WireMockServer wireMock = new WireMockServer(Options.DYNAMIC_PORT);\n\n\tprivate final InstanceWebClient instanceWebClient = InstanceWebClient.builder()\n\t\t.filter(retry(1, emptyMap()))\n\t\t.filter(timeout(Duration.ofSeconds(1), emptyMap()))\n\t\t.build();\n\n\t@BeforeAll\n\tstatic void setUp() {\n\t\tStepVerifier.setDefaultTimeout(Duration.ofSeconds(5));\n\t}\n\n\t@AfterAll\n\tstatic void tearDown() {\n\t\tStepVerifier.resetDefaultTimeout();\n\t}\n\n\t@BeforeEach\n\tvoid setup() {\n\t\twireMock.start();\n\t}\n\n\t@AfterEach\n\tvoid teardown() {\n\t\twireMock.stop();\n\t}\n\n\t@Test\n\tvoid invariants() {\n\t\tassertThatThrownBy(() -> new ProbeEndpointsStrategy(this.instanceWebClient, null))\n\t\t\t.isInstanceOf(IllegalArgumentException.class)\n\t\t\t.hasMessage(\"'endpoints' must not be null.\");\n\t\tassertThatThrownBy(() -> new ProbeEndpointsStrategy(this.instanceWebClient, new String[] { null }))\n\t\t\t.isInstanceOf(IllegalArgumentException.class)\n\t\t\t.hasMessage(\"'endpoints' must not contain null.\");\n\t}\n\n\t@Test\n\tvoid should_return_detect_endpoints() {\n\t\t// given\n\t\tInstance instance = Instance.create(InstanceId.of(\"id\"))\n\t\t\t.register(Registration.create(\"test\", this.wireMock.url(\"/mgmt/health\"))\n\t\t\t\t.managementUrl(this.wireMock.url(\"/mgmt\"))\n\t\t\t\t.build());\n\n\t\tthis.wireMock.stubFor(options(urlEqualTo(\"/mgmt/metrics\")).willReturn(ok()));\n\t\tthis.wireMock.stubFor(options(urlEqualTo(\"/mgmt/stats\")).willReturn(ok()));\n\t\tthis.wireMock.stubFor(options(urlEqualTo(\"/mgmt/info\")).willReturn(ok()));\n\t\tthis.wireMock.stubFor(options(urlEqualTo(\"/mgmt/non-exist\")).willReturn(notFound()));\n\t\tthis.wireMock.stubFor(options(urlEqualTo(\"/mgmt/error\")).willReturn(serverError()));\n\t\tthis.wireMock\n\t\t\t.stubFor(options(urlEqualTo(\"/mgmt/exception\")).willReturn(aResponse().withFault(Fault.EMPTY_RESPONSE)));\n\n\t\tProbeEndpointsStrategy strategy = new ProbeEndpointsStrategy(this.instanceWebClient,\n\t\t\t\tnew String[] { \"metrics:stats\", \"metrics\", \"info\", \"non-exist\", \"error\", \"exception\" });\n\n\t\t// when\n\t\tStepVerifier.create(strategy.detectEndpoints(instance))\n\t\t\t// then\n\t\t\t.expectNext(Endpoints.single(\"metrics\", this.wireMock.url(\"/mgmt/stats\"))\n\t\t\t\t.withEndpoint(\"info\", this.wireMock.url(\"/mgmt/info\")))//\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_return_empty() {\n\t\t// given\n\t\tInstance instance = Instance.create(InstanceId.of(\"id\"))\n\t\t\t.register(Registration.create(\"test\", this.wireMock.url(\"/mgmt/health\"))\n\t\t\t\t.managementUrl(this.wireMock.url(\"/mgmt\"))\n\t\t\t\t.build());\n\n\t\tthis.wireMock\n\t\t\t.stubFor(options(urlEqualTo(\"/mgmt/stats\")).willReturn(aResponse().withStatus(HttpStatus.NOT_FOUND_404)));\n\n\t\tProbeEndpointsStrategy strategy = new ProbeEndpointsStrategy(this.instanceWebClient,\n\t\t\t\tnew String[] { \"metrics:stats\" });\n\n\t\t// when\n\t\tStepVerifier.create(strategy.detectEndpoints(instance))\n\t\t\t// then\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_retry() {\n\t\t// given\n\t\tInstance instance = Instance.create(InstanceId.of(\"id\"))\n\t\t\t.register(Registration.create(\"test\", this.wireMock.url(\"/mgmt/health\"))\n\t\t\t\t.managementUrl(this.wireMock.url(\"/mgmt\"))\n\t\t\t\t.build());\n\n\t\tthis.wireMock.stubFor(options(urlEqualTo(\"/mgmt/metrics\")).inScenario(\"retry\")\n\t\t\t.whenScenarioStateIs(STARTED)\n\t\t\t.willReturn(aResponse().withFixedDelay(5000))\n\t\t\t.willSetStateTo(\"recovered\"));\n\n\t\tthis.wireMock.stubFor(options(urlEqualTo(\"/mgmt/metrics\")).inScenario(\"retry\")\n\t\t\t.whenScenarioStateIs(\"recovered\")\n\t\t\t.willReturn(ok()));\n\t\tthis.wireMock.stubFor(options(urlEqualTo(\"/mgmt/stats\")).willReturn(ok()));\n\t\tthis.wireMock.stubFor(options(urlEqualTo(\"/mgmt/info\")).willReturn(ok()));\n\t\tthis.wireMock.stubFor(options(urlEqualTo(\"/mgmt/non-exist\")).willReturn(notFound()));\n\n\t\tProbeEndpointsStrategy strategy = new ProbeEndpointsStrategy(this.instanceWebClient,\n\t\t\t\tnew String[] { \"metrics:stats\", \"metrics\", \"info\", \"non-exist\" });\n\n\t\t// when\n\t\tStepVerifier.create(strategy.detectEndpoints(instance))\n\t\t\t// then\n\t\t\t.expectNext(Endpoints.single(\"metrics\", this.wireMock.url(\"/mgmt/stats\"))\n\t\t\t\t.withEndpoint(\"info\", this.wireMock.url(\"/mgmt/info\")))//\n\t\t\t.verifyComplete();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/services/endpoints/QueryIndexEndpointStrategyTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.services.endpoints;\n\nimport java.time.Duration;\n\nimport javax.net.ssl.SSLException;\n\nimport com.github.tomakehurst.wiremock.WireMockServer;\nimport com.github.tomakehurst.wiremock.http.Fault;\nimport io.netty.handler.ssl.SslContextBuilder;\nimport io.netty.handler.ssl.util.InsecureTrustManagerFactory;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.actuate.endpoint.ApiVersion;\nimport org.springframework.http.MediaType;\nimport org.springframework.http.client.reactive.ReactorClientHttpConnector;\nimport org.springframework.web.reactive.function.client.WebClient;\nimport reactor.netty.http.client.HttpClient;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.values.Endpoint;\nimport de.codecentric.boot.admin.server.domain.values.Endpoints;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.services.ApiMediaTypeHandler;\nimport de.codecentric.boot.admin.server.web.client.InstanceWebClient;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.aResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.anyRequestedFor;\nimport static com.github.tomakehurst.wiremock.client.WireMock.get;\nimport static com.github.tomakehurst.wiremock.client.WireMock.notFound;\nimport static com.github.tomakehurst.wiremock.client.WireMock.ok;\nimport static com.github.tomakehurst.wiremock.client.WireMock.okJson;\nimport static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\nimport static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED;\nimport static de.codecentric.boot.admin.server.web.client.InstanceExchangeFilterFunctions.retry;\nimport static de.codecentric.boot.admin.server.web.client.InstanceExchangeFilterFunctions.rewriteEndpointUrl;\nimport static de.codecentric.boot.admin.server.web.client.InstanceExchangeFilterFunctions.timeout;\nimport static java.util.Collections.emptyMap;\nimport static java.util.Collections.singletonMap;\n\nclass QueryIndexEndpointStrategyTest {\n\n\tprivate final ApiMediaTypeHandler apiMediaTypeHandler = new ApiMediaTypeHandler();\n\n\tpublic final WireMockServer wireMock = new WireMockServer(wireMockConfig().dynamicPort().dynamicHttpsPort());\n\n\tprivate final InstanceWebClient instanceWebClient = InstanceWebClient.builder()\n\t\t.webClient(WebClient.builder().clientConnector(httpConnector()))\n\t\t.filter(rewriteEndpointUrl())\n\t\t.filter(retry(0, singletonMap(Endpoint.ACTUATOR_INDEX, 1)))\n\t\t.filter(timeout(Duration.ofSeconds(1), emptyMap()))\n\t\t.build();\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\twireMock.start();\n\t}\n\n\t@AfterEach\n\tvoid tearDown() {\n\t\twireMock.stop();\n\t}\n\n\t@Test\n\tvoid should_return_endpoints() {\n\t\t// given\n\t\tInstance instance = Instance.create(InstanceId.of(\"id\"))\n\t\t\t.register(Registration.create(\"test\", this.wireMock.url(\"/mgmt/health\"))\n\t\t\t\t.managementUrl(this.wireMock.url(\"/mgmt\"))\n\t\t\t\t.build());\n\n\t\tString host = \"https://localhost:\" + this.wireMock.httpsPort();\n\t\tString body = \"{\\\"_links\\\":{\\\"metrics-requiredMetricName\\\":{\\\"templated\\\":true,\\\"href\\\":\\\"\" + host\n\t\t\t\t+ \"/mgmt/metrics/{requiredMetricName}\\\"},\\\"self\\\":{\\\"templated\\\":false,\\\"href\\\":\\\"\" + host\n\t\t\t\t+ \"/mgmt\\\"},\\\"metrics\\\":{\\\"templated\\\":false,\\\"href\\\":\\\"\" + host\n\t\t\t\t+ \"/mgmt/stats\\\"},\\\"info\\\":{\\\"templated\\\":false,\\\"href\\\":\\\"\" + host + \"/mgmt/info\\\"}}}\";\n\n\t\tthis.wireMock.stubFor(get(\"/mgmt\")\n\t\t\t.willReturn(ok(body).withHeader(\"Content-Type\", ApiVersion.LATEST.getProducedMimeType().toString())));\n\n\t\tQueryIndexEndpointStrategy strategy = new QueryIndexEndpointStrategy(this.instanceWebClient,\n\t\t\t\tthis.apiMediaTypeHandler);\n\n\t\t// when\n\t\tStepVerifier.create(strategy.detectEndpoints(instance))\n\t\t\t// then\n\t\t\t.expectNext(Endpoints.single(\"metrics\", host + \"/mgmt/stats\").withEndpoint(\"info\", host + \"/mgmt/info\"))//\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_return_endpoints_with_aligned_scheme() {\n\t\t// given\n\t\tInstance instance = Instance.create(InstanceId.of(\"id\"))\n\t\t\t.register(Registration.create(\"test\", this.wireMock.url(\"/mgmt/health\"))\n\t\t\t\t.managementUrl(this.wireMock.url(\"/mgmt\"))\n\t\t\t\t.build());\n\n\t\tString host = \"http://localhost:\" + this.wireMock.httpsPort();\n\t\tString body = \"{\\\"_links\\\":{\\\"metrics-requiredMetricName\\\":{\\\"templated\\\":true,\\\"href\\\":\\\"\" + host\n\t\t\t\t+ \"/mgmt/metrics/{requiredMetricName}\\\"},\\\"self\\\":{\\\"templated\\\":false,\\\"href\\\":\\\"\" + host\n\t\t\t\t+ \"/mgmt\\\"},\\\"metrics\\\":{\\\"templated\\\":false,\\\"href\\\":\\\"\" + host\n\t\t\t\t+ \"/mgmt/stats\\\"},\\\"info\\\":{\\\"templated\\\":false,\\\"href\\\":\\\"\" + host + \"/mgmt/info\\\"}}}\";\n\n\t\tthis.wireMock.stubFor(get(\"/mgmt\")\n\t\t\t.willReturn(ok(body).withHeader(\"Content-Type\", ApiVersion.LATEST.getProducedMimeType().toString())));\n\n\t\tQueryIndexEndpointStrategy strategy = new QueryIndexEndpointStrategy(this.instanceWebClient,\n\t\t\t\tthis.apiMediaTypeHandler);\n\n\t\t// when\n\t\tString secureHost = \"https://localhost:\" + this.wireMock.httpsPort();\n\t\tStepVerifier.create(strategy.detectEndpoints(instance))\n\t\t\t// then\n\t\t\t.expectNext(Endpoints.single(\"metrics\", secureHost + \"/mgmt/stats\")\n\t\t\t\t.withEndpoint(\"info\", secureHost + \"/mgmt/info\"))//\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_return_empty_on_empty_endpoints() {\n\t\t// given\n\t\tInstance instance = Instance.create(InstanceId.of(\"id\"))\n\t\t\t.register(Registration.create(\"test\", this.wireMock.url(\"/mgmt/health\"))\n\t\t\t\t.managementUrl(this.wireMock.url(\"/mgmt\"))\n\t\t\t\t.build());\n\n\t\tString body = \"{\\\"_links\\\":{}}\";\n\t\tthis.wireMock.stubFor(get(\"/mgmt\")\n\t\t\t.willReturn(okJson(body).withHeader(\"Content-Type\", ApiVersion.LATEST.getProducedMimeType().toString())));\n\n\t\tQueryIndexEndpointStrategy strategy = new QueryIndexEndpointStrategy(this.instanceWebClient,\n\t\t\t\tthis.apiMediaTypeHandler);\n\n\t\t// when\n\t\tStepVerifier.create(strategy.detectEndpoints(instance))\n\t\t\t// then\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_return_empty_on_not_found() {\n\t\t// given\n\t\tInstance instance = Instance.create(InstanceId.of(\"id\"))\n\t\t\t.register(Registration.create(\"test\", this.wireMock.url(\"/mgmt/health\"))\n\t\t\t\t.managementUrl(this.wireMock.url(\"/mgmt\"))\n\t\t\t\t.build());\n\n\t\tthis.wireMock.stubFor(get(\"/mgmt\").willReturn(notFound()));\n\n\t\tQueryIndexEndpointStrategy strategy = new QueryIndexEndpointStrategy(this.instanceWebClient,\n\t\t\t\tthis.apiMediaTypeHandler);\n\n\t\t// when\n\t\tStepVerifier.create(strategy.detectEndpoints(instance))\n\t\t\t// then\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_return_empty_on_error() {\n\t\t// given\n\t\tInstance instance = Instance.create(InstanceId.of(\"id\"))\n\t\t\t.register(Registration.create(\"test\", this.wireMock.url(\"/mgmt/health\"))\n\t\t\t\t.managementUrl(this.wireMock.url(\"/mgmt\"))\n\t\t\t\t.build());\n\n\t\tthis.wireMock.stubFor(get(\"/mgmt\").willReturn(aResponse().withFault(Fault.EMPTY_RESPONSE)));\n\n\t\tQueryIndexEndpointStrategy strategy = new QueryIndexEndpointStrategy(this.instanceWebClient,\n\t\t\t\tthis.apiMediaTypeHandler);\n\n\t\t// when\n\t\tStepVerifier.create(strategy.detectEndpoints(instance))\n\t\t\t// then\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_return_empty_on_wrong_content_type() {\n\t\t// given\n\t\tInstance instance = Instance.create(InstanceId.of(\"id\"))\n\t\t\t.register(Registration.create(\"test\", this.wireMock.url(\"/mgmt/health\"))\n\t\t\t\t.managementUrl(this.wireMock.url(\"/mgmt\"))\n\t\t\t\t.build());\n\n\t\tString body = \"HELLO WORLD\";\n\t\tthis.wireMock.stubFor(get(\"/mgmt\").willReturn(ok(body).withHeader(\"Content-Type\", MediaType.TEXT_PLAIN_VALUE)));\n\n\t\tQueryIndexEndpointStrategy strategy = new QueryIndexEndpointStrategy(this.instanceWebClient,\n\t\t\t\tthis.apiMediaTypeHandler);\n\n\t\t// when\n\t\tStepVerifier.create(strategy.detectEndpoints(instance))\n\t\t\t// then\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_return_empty_when_mgmt_equals_service_url() {\n\t\t// given\n\t\tInstance instance = Instance.create(InstanceId.of(\"id\"))\n\t\t\t.register(Registration.create(\"test\", this.wireMock.url(\"/app/health\"))\n\t\t\t\t.managementUrl(this.wireMock.url(\"/app\"))\n\t\t\t\t.serviceUrl(this.wireMock.url(\"/app\"))\n\t\t\t\t.build());\n\n\t\tQueryIndexEndpointStrategy strategy = new QueryIndexEndpointStrategy(this.instanceWebClient,\n\t\t\t\tthis.apiMediaTypeHandler);\n\n\t\t// when/then\n\t\tStepVerifier.create(strategy.detectEndpoints(instance)).verifyComplete();\n\t\tthis.wireMock.verify(0, anyRequestedFor(urlPathEqualTo(\"/app\")));\n\t}\n\n\t@Test\n\tvoid should_retry() {\n\t\t// given\n\t\tInstance instance = Instance.create(InstanceId.of(\"id\"))\n\t\t\t.register(Registration.create(\"test\", this.wireMock.url(\"/mgmt/health\"))\n\t\t\t\t.managementUrl(this.wireMock.url(\"/mgmt\"))\n\t\t\t\t.build());\n\n\t\tString body = \"{\\\"_links\\\":{\\\"metrics-requiredMetricName\\\":{\\\"templated\\\":true,\\\"href\\\":\\\"/mgmt/metrics/{requiredMetricName}\\\"},\\\"self\\\":{\\\"templated\\\":false,\\\"href\\\":\\\"/mgmt\\\"},\\\"metrics\\\":{\\\"templated\\\":false,\\\"href\\\":\\\"/mgmt/stats\\\"},\\\"info\\\":{\\\"templated\\\":false,\\\"href\\\":\\\"/mgmt/info\\\"}}}\";\n\n\t\tthis.wireMock.stubFor(get(\"/mgmt\").inScenario(\"retry\")\n\t\t\t.whenScenarioStateIs(STARTED)\n\t\t\t.willReturn(aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER))\n\t\t\t.willSetStateTo(\"recovered\"));\n\n\t\tthis.wireMock.stubFor(get(\"/mgmt\").inScenario(\"retry\")\n\t\t\t.whenScenarioStateIs(\"recovered\")\n\t\t\t.willReturn(ok(body).withHeader(\"Content-Type\", ApiVersion.LATEST.getProducedMimeType().toString())));\n\n\t\tQueryIndexEndpointStrategy strategy = new QueryIndexEndpointStrategy(this.instanceWebClient,\n\t\t\t\tthis.apiMediaTypeHandler);\n\n\t\t// when\n\t\tStepVerifier.create(strategy.detectEndpoints(instance))\n\t\t\t// then\n\t\t\t.expectNext(Endpoints.single(\"metrics\", \"/mgmt/stats\").withEndpoint(\"info\", \"/mgmt/info\"))//\n\t\t\t.verifyComplete();\n\t}\n\n\tprivate ReactorClientHttpConnector httpConnector() {\n\t\tHttpClient client = HttpClient.create().secure((ssl) -> {\n\t\t\ttry {\n\t\t\t\tSslContextBuilder sslCtx = SslContextBuilder.forClient()\n\t\t\t\t\t.trustManager(InsecureTrustManagerFactory.INSTANCE);\n\t\t\t\tssl.sslContext(sslCtx.build());\n\t\t\t}\n\t\t\tcatch (SSLException ex) {\n\t\t\t\tthrow new RuntimeException(ex);\n\t\t\t}\n\t\t});\n\t\treturn new ReactorClientHttpConnector(client);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/utils/jackson/BuildVersionMixinTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport org.junit.jupiter.api.Test;\nimport tools.jackson.core.JacksonException;\nimport tools.jackson.databind.DeserializationFeature;\nimport tools.jackson.databind.json.JsonMapper;\n\nimport de.codecentric.boot.admin.server.domain.values.BuildVersion;\n\nimport static java.util.Collections.singletonMap;\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass BuildVersionMixinTest {\n\n\tprivate final JsonMapper jsonMapper;\n\n\tprotected BuildVersionMixinTest() {\n\t\tAdminServerModule adminServerModule = new AdminServerModule(new String[] { \".*password$\" });\n\t\tjsonMapper = JsonMapper.builder()\n\t\t\t.addModule(adminServerModule)\n\t\t\t.disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)\n\t\t\t.build();\n\t}\n\n\t@Test\n\tvoid verifyDeserialize() throws JacksonException {\n\t\tBuildVersion buildVersion = jsonMapper.readValue(\"\\\"1.0.0\\\"\", BuildVersion.class);\n\t\tassertThat(buildVersion).isEqualTo(BuildVersion.valueOf(\"1.0.0\"));\n\t}\n\n\t@Test\n\tvoid verifySerialize() throws JacksonException {\n\t\tBuildVersion buildVersion = BuildVersion.valueOf(\"1.0.0\");\n\n\t\tString result = jsonMapper.writeValueAsString(buildVersion);\n\t\tassertThat(result).isEqualTo(\"\\\"1.0.0\\\"\");\n\t}\n\n\t@Test\n\tvoid verifySerializeWithMapEntryVersion() throws JacksonException {\n\t\tBuildVersion buildVersion = BuildVersion.from(singletonMap(\"version\", \"1.0.0\"));\n\n\t\tString result = jsonMapper.writeValueAsString(buildVersion);\n\t\tassertThat(result).isEqualTo(\"\\\"1.0.0\\\"\");\n\t}\n\n\t@Test\n\tvoid verifySerializeWithNestedMapEntryVersion() throws JacksonException {\n\t\tBuildVersion buildVersion = BuildVersion.from(singletonMap(\"build\", singletonMap(\"version\", \"1.0.0\")));\n\n\t\tString result = jsonMapper.writeValueAsString(buildVersion);\n\t\tassertThat(result).isEqualTo(\"\\\"1.0.0\\\"\");\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/utils/jackson/EndpointMixinTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport java.io.IOException;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.json.JacksonTester;\nimport org.springframework.boot.test.json.JsonContent;\nimport tools.jackson.core.JacksonException;\nimport tools.jackson.databind.DeserializationFeature;\nimport tools.jackson.databind.json.JsonMapper;\n\nimport de.codecentric.boot.admin.server.domain.values.Endpoint;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass EndpointMixinTest {\n\n\tprivate final JsonMapper jsonMapper;\n\n\tprivate JacksonTester<Endpoint> jsonTester;\n\n\tprotected EndpointMixinTest() {\n\t\tAdminServerModule adminServerModule = new AdminServerModule(new String[] { \".*password$\" });\n\t\tjsonMapper = JsonMapper.builder()\n\t\t\t.addModule(adminServerModule)\n\t\t\t.disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)\n\t\t\t.build();\n\t}\n\n\t@BeforeEach\n\tvoid setup() {\n\t\tJacksonTester.initFields(this, jsonMapper);\n\t}\n\n\t@Test\n\tvoid verifyDeserialize() throws JSONException, JacksonException {\n\t\tString json = new JSONObject().put(\"id\", \"info\").put(\"url\", \"http://localhost:8080/info\").toString();\n\n\t\tEndpoint endpoint = jsonMapper.readValue(json, Endpoint.class);\n\t\tassertThat(endpoint).isNotNull();\n\t\tassertThat(endpoint.getId()).isEqualTo(\"info\");\n\t\tassertThat(endpoint.getUrl()).isEqualTo(\"http://localhost:8080/info\");\n\t}\n\n\t@Test\n\tvoid verifySerialize() throws IOException {\n\t\tEndpoint endpoint = Endpoint.of(\"info\", \"http://localhost:8080/info\");\n\n\t\tJsonContent<Endpoint> jsonContent = jsonTester.write(endpoint);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.id\").isEqualTo(\"info\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.url\").isEqualTo(\"http://localhost:8080/info\");\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/utils/jackson/EndpointsMixinTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport java.io.IOException;\n\nimport org.json.JSONArray;\nimport org.json.JSONException;\nimport org.json.JSONObject;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.json.JacksonTester;\nimport org.springframework.boot.test.json.JsonContent;\nimport tools.jackson.core.JacksonException;\nimport tools.jackson.databind.DeserializationFeature;\nimport tools.jackson.databind.json.JsonMapper;\n\nimport de.codecentric.boot.admin.server.domain.values.Endpoint;\nimport de.codecentric.boot.admin.server.domain.values.Endpoints;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass EndpointsMixinTest {\n\n\tprivate final JsonMapper jsonMapper;\n\n\tprivate JacksonTester<Endpoints> jsonTester;\n\n\tprotected EndpointsMixinTest() {\n\t\tAdminServerModule adminServerModule = new AdminServerModule(new String[] { \".*password$\" });\n\t\tjsonMapper = JsonMapper.builder()\n\t\t\t.addModule(adminServerModule)\n\t\t\t.disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)\n\t\t\t.build();\n\t}\n\n\t@BeforeEach\n\tvoid setup() {\n\t\tJacksonTester.initFields(this, jsonMapper);\n\t}\n\n\t@Test\n\tvoid verifyDeserialize() throws JSONException, JacksonException {\n\t\tString json = new JSONArray().put(new JSONObject().put(\"id\", \"info\").put(\"url\", \"http://localhost:8080/info\"))\n\t\t\t.put(new JSONObject().put(\"id\", \"health\").put(\"url\", \"http://localhost:8080/health\"))\n\t\t\t.toString();\n\n\t\tEndpoints endpoints = jsonMapper.readValue(json, Endpoints.class);\n\t\tassertThat(endpoints).isNotNull()\n\t\t\t.containsExactlyInAnyOrder(Endpoint.of(\"info\", \"http://localhost:8080/info\"),\n\t\t\t\t\tEndpoint.of(\"health\", \"http://localhost:8080/health\"));\n\t}\n\n\t@Test\n\tvoid verifySerialize() throws IOException {\n\t\tEndpoints endpoints = Endpoints.single(\"info\", \"http://localhost:8080/info\")\n\t\t\t.withEndpoint(\"health\", \"http://localhost:8080/health\");\n\n\t\tJsonContent<Endpoints> jsonContent = jsonTester.write(endpoints);\n\t\tassertThat(jsonContent).extractingJsonPathArrayValue(\"$\").hasSize(2);\n\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$[0].id\").isIn(\"info\", \"health\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$[0].url\")\n\t\t\t.isIn(\"http://localhost:8080/info\", \"http://localhost:8080/health\");\n\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$[1].id\").isIn(\"info\", \"health\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$[1].url\")\n\t\t\t.isIn(\"http://localhost:8080/info\", \"http://localhost:8080/health\");\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/utils/jackson/InfoMixinTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.json.JacksonTester;\nimport org.springframework.boot.test.json.JsonContent;\nimport tools.jackson.core.JacksonException;\nimport tools.jackson.databind.DeserializationFeature;\nimport tools.jackson.databind.json.JsonMapper;\n\nimport de.codecentric.boot.admin.server.domain.values.Info;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.entry;\n\nclass InfoMixinTest {\n\n\tprivate final JsonMapper jsonMapper;\n\n\tprivate JacksonTester<Info> jsonTester;\n\n\tprotected InfoMixinTest() {\n\t\tAdminServerModule adminServerModule = new AdminServerModule(new String[] { \".*password$\" });\n\t\tjsonMapper = JsonMapper.builder()\n\t\t\t.addModule(adminServerModule)\n\t\t\t.disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)\n\t\t\t.build();\n\t}\n\n\t@BeforeEach\n\tvoid setup() {\n\t\tJacksonTester.initFields(this, jsonMapper);\n\t}\n\n\t@Test\n\tvoid verifyDeserialize() throws JSONException, JacksonException {\n\t\tString json = new JSONObject().put(\"build\", new JSONObject().put(\"version\", \"1.0.0\"))\n\t\t\t.put(\"foo\", \"bar\")\n\t\t\t.toString();\n\n\t\tInfo info = jsonMapper.readValue(json, Info.class);\n\t\tassertThat(info).isNotNull();\n\t\tassertThat(info.getValues()).containsOnly(entry(\"build\", Collections.singletonMap(\"version\", \"1.0.0\")),\n\t\t\t\tentry(\"foo\", \"bar\"));\n\t}\n\n\t@Test\n\tvoid verifySerialize() throws IOException {\n\t\tMap<String, Object> data = new HashMap<>();\n\t\tdata.put(\"build\", Collections.singletonMap(\"version\", \"1.0.0\"));\n\t\tdata.put(\"foo\", \"bar\");\n\t\tInfo info = Info.from(data);\n\n\t\tJsonContent<Info> jsonContent = jsonTester.write(info);\n\t\tassertThat(jsonContent).extractingJsonPathMapValue(\"$\").containsOnlyKeys(\"build\", \"foo\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$['build'].['version']\").isEqualTo(\"1.0.0\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$['foo']\").isEqualTo(\"bar\");\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/utils/jackson/InstanceDeregisteredEventMixinTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport java.io.IOException;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.json.JacksonTester;\nimport org.springframework.boot.test.json.JsonContent;\nimport tools.jackson.core.JacksonException;\nimport tools.jackson.databind.DeserializationFeature;\nimport tools.jackson.databind.json.JsonMapper;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceDeregisteredEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass InstanceDeregisteredEventMixinTest {\n\n\tprivate final JsonMapper jsonMapper;\n\n\tprivate JacksonTester<InstanceDeregisteredEvent> jsonTester;\n\n\tprotected InstanceDeregisteredEventMixinTest() {\n\t\tAdminServerModule adminServerModule = new AdminServerModule(new String[] { \".*password$\" });\n\t\tjsonMapper = JsonMapper.builder()\n\t\t\t.addModule(adminServerModule)\n\t\t\t.disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)\n\t\t\t.build();\n\t}\n\n\t@BeforeEach\n\tvoid setup() {\n\t\tJacksonTester.initFields(this, jsonMapper);\n\t}\n\n\t@Test\n\tvoid verifyDeserialize() throws JSONException, JacksonException {\n\t\tString json = new JSONObject().put(\"instance\", \"test123\")\n\t\t\t.put(\"version\", 12345678L)\n\t\t\t.put(\"timestamp\", 1587751031.000000000)\n\t\t\t.put(\"type\", \"DEREGISTERED\")\n\t\t\t.toString();\n\n\t\tInstanceDeregisteredEvent event = jsonMapper.readValue(json, InstanceDeregisteredEvent.class);\n\t\tassertThat(event).isNotNull();\n\t\tassertThat(event.getInstance()).isEqualTo(InstanceId.of(\"test123\"));\n\t\tassertThat(event.getVersion()).isEqualTo(12345678L);\n\t\tassertThat(event.getTimestamp()).isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS));\n\t}\n\n\t@Test\n\tvoid verifyDeserializeWithOnlyRequiredProperties() throws JSONException, JacksonException {\n\t\tString json = new JSONObject().put(\"instance\", \"test123\")\n\t\t\t.put(\"timestamp\", 1587751031.000000000)\n\t\t\t.put(\"type\", \"DEREGISTERED\")\n\t\t\t.toString();\n\n\t\tInstanceDeregisteredEvent event = jsonMapper.readValue(json, InstanceDeregisteredEvent.class);\n\t\tassertThat(event).isNotNull();\n\t\tassertThat(event.getInstance()).isEqualTo(InstanceId.of(\"test123\"));\n\t\tassertThat(event.getVersion()).isZero();\n\t\tassertThat(event.getTimestamp()).isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS));\n\t}\n\n\t@Test\n\tvoid verifySerialize() throws IOException {\n\t\tInstanceId id = InstanceId.of(\"test123\");\n\t\tInstant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS);\n\t\tInstanceDeregisteredEvent event = new InstanceDeregisteredEvent(id, 12345678L, timestamp);\n\n\t\tJsonContent<InstanceDeregisteredEvent> jsonContent = jsonTester.write(event);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.instance\").isEqualTo(\"test123\");\n\t\tassertThat(jsonContent).extractingJsonPathNumberValue(\"$.version\").isEqualTo(12345678);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.timestamp\").isEqualTo(\"2020-04-24T17:57:11Z\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.type\").isEqualTo(\"DEREGISTERED\");\n\t}\n\n\t@Test\n\tvoid verifySerializeWithOnlyRequiredProperties() throws IOException {\n\t\tInstanceId id = InstanceId.of(\"test123\");\n\t\tInstant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS);\n\t\tInstanceDeregisteredEvent event = new InstanceDeregisteredEvent(id, 0L, timestamp);\n\n\t\tJsonContent<InstanceDeregisteredEvent> jsonContent = jsonTester.write(event);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.instance\").isEqualTo(\"test123\");\n\t\tassertThat(jsonContent).extractingJsonPathNumberValue(\"$.version\").isEqualTo(0);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.timestamp\").isEqualTo(\"2020-04-24T17:57:11Z\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.type\").isEqualTo(\"DEREGISTERED\");\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/utils/jackson/InstanceEndpointsDetectedEventMixinTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport java.io.IOException;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\n\nimport org.json.JSONArray;\nimport org.json.JSONException;\nimport org.json.JSONObject;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.json.JacksonTester;\nimport org.springframework.boot.test.json.JsonContent;\nimport tools.jackson.core.JacksonException;\nimport tools.jackson.databind.DeserializationFeature;\nimport tools.jackson.databind.json.JsonMapper;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceEndpointsDetectedEvent;\nimport de.codecentric.boot.admin.server.domain.values.Endpoint;\nimport de.codecentric.boot.admin.server.domain.values.Endpoints;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass InstanceEndpointsDetectedEventMixinTest {\n\n\tprivate final JsonMapper jsonMapper;\n\n\tprivate JacksonTester<InstanceEndpointsDetectedEvent> jsonTester;\n\n\tprotected InstanceEndpointsDetectedEventMixinTest() {\n\t\tAdminServerModule adminServerModule = new AdminServerModule(new String[] { \".*password$\" });\n\t\tjsonMapper = JsonMapper.builder()\n\t\t\t.addModule(adminServerModule)\n\t\t\t.disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)\n\t\t\t.build();\n\t}\n\n\t@BeforeEach\n\tvoid setup() {\n\t\tJacksonTester.initFields(this, jsonMapper);\n\t}\n\n\t@Test\n\tvoid verifyDeserialize() throws JSONException, JacksonException {\n\t\tString json = new JSONObject().put(\"instance\", \"test123\")\n\t\t\t.put(\"version\", 12345678L)\n\t\t\t.put(\"timestamp\", 1587751031.000000000)\n\t\t\t.put(\"type\", \"ENDPOINTS_DETECTED\")\n\t\t\t.put(\"endpoints\",\n\t\t\t\t\tnew JSONArray().put(new JSONObject().put(\"id\", \"info\").put(\"url\", \"http://localhost:8080/info\"))\n\t\t\t\t\t\t.put(new JSONObject().put(\"id\", \"health\").put(\"url\", \"http://localhost:8080/health\")))\n\t\t\t.toString();\n\n\t\tInstanceEndpointsDetectedEvent event = jsonMapper.readValue(json, InstanceEndpointsDetectedEvent.class);\n\t\tassertThat(event).isNotNull();\n\t\tassertThat(event.getInstance()).isEqualTo(InstanceId.of(\"test123\"));\n\t\tassertThat(event.getVersion()).isEqualTo(12345678L);\n\t\tassertThat(event.getTimestamp()).isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS));\n\t\tassertThat(event.getEndpoints()).containsExactlyInAnyOrder(Endpoint.of(\"info\", \"http://localhost:8080/info\"),\n\t\t\t\tEndpoint.of(\"health\", \"http://localhost:8080/health\"));\n\t}\n\n\t@Test\n\tvoid verifyDeserializeWithEmptyEndpoints() throws JSONException, JacksonException {\n\t\tString json = new JSONObject().put(\"instance\", \"test123\")\n\t\t\t.put(\"version\", 12345678L)\n\t\t\t.put(\"timestamp\", 1587751031.000000000)\n\t\t\t.put(\"type\", \"ENDPOINTS_DETECTED\")\n\t\t\t.put(\"endpoints\", new JSONArray())\n\t\t\t.toString();\n\n\t\tInstanceEndpointsDetectedEvent event = jsonMapper.readValue(json, InstanceEndpointsDetectedEvent.class);\n\t\tassertThat(event).isNotNull();\n\t\tassertThat(event.getInstance()).isEqualTo(InstanceId.of(\"test123\"));\n\t\tassertThat(event.getVersion()).isEqualTo(12345678L);\n\t\tassertThat(event.getTimestamp()).isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS));\n\t\tassertThat(event.getEndpoints()).isEmpty();\n\t}\n\n\t@Test\n\tvoid verifyDeserializeWithOnlyRequiredProperties() throws JSONException, JacksonException {\n\t\tString json = new JSONObject().put(\"instance\", \"test123\")\n\t\t\t.put(\"timestamp\", 1587751031.000000000)\n\t\t\t.put(\"type\", \"ENDPOINTS_DETECTED\")\n\t\t\t.toString();\n\n\t\tInstanceEndpointsDetectedEvent event = jsonMapper.readValue(json, InstanceEndpointsDetectedEvent.class);\n\t\tassertThat(event).isNotNull();\n\t\tassertThat(event.getInstance()).isEqualTo(InstanceId.of(\"test123\"));\n\t\tassertThat(event.getVersion()).isZero();\n\t\tassertThat(event.getTimestamp()).isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS));\n\t\tassertThat(event.getEndpoints()).isNull();\n\t}\n\n\t@Test\n\tvoid verifySerialize() throws IOException {\n\t\tInstanceId id = InstanceId.of(\"test123\");\n\t\tInstant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS);\n\t\tEndpoints endpoints = Endpoints.single(\"info\", \"http://localhost:8080/info\")\n\t\t\t.withEndpoint(\"health\", \"http://localhost:8080/health\");\n\t\tInstanceEndpointsDetectedEvent event = new InstanceEndpointsDetectedEvent(id, 12345678L, timestamp, endpoints);\n\n\t\tJsonContent<InstanceEndpointsDetectedEvent> jsonContent = jsonTester.write(event);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.instance\").isEqualTo(\"test123\");\n\t\tassertThat(jsonContent).extractingJsonPathNumberValue(\"$.version\").isEqualTo(12345678);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.timestamp\").isEqualTo(\"2020-04-24T17:57:11Z\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.type\").isEqualTo(\"ENDPOINTS_DETECTED\");\n\t\tassertThat(jsonContent).extractingJsonPathArrayValue(\"$.endpoints\").hasSize(2);\n\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.endpoints[0].id\").isIn(\"info\", \"health\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.endpoints[0].url\")\n\t\t\t.isIn(\"http://localhost:8080/info\", \"http://localhost:8080/health\");\n\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.endpoints[1].id\").isIn(\"info\", \"health\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.endpoints[1].url\")\n\t\t\t.isIn(\"http://localhost:8080/info\", \"http://localhost:8080/health\");\n\t}\n\n\t@Test\n\tvoid verifySerializeWithOnlyRequiredProperties() throws IOException {\n\t\tInstanceId id = InstanceId.of(\"test123\");\n\t\tInstant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS);\n\t\tInstanceEndpointsDetectedEvent event = new InstanceEndpointsDetectedEvent(id, 0L, timestamp, null);\n\n\t\tJsonContent<InstanceEndpointsDetectedEvent> jsonContent = jsonTester.write(event);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.instance\").isEqualTo(\"test123\");\n\t\tassertThat(jsonContent).extractingJsonPathNumberValue(\"$.version\").isEqualTo(0);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.timestamp\").isEqualTo(\"2020-04-24T17:57:11Z\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.type\").isEqualTo(\"ENDPOINTS_DETECTED\");\n\t\tassertThat(jsonContent).extractingJsonPathArrayValue(\"$.endpoints\").isNull();\n\t}\n\n\t@Test\n\tvoid verifySerializeWithEmptyEndpoints() throws IOException {\n\t\tInstanceId id = InstanceId.of(\"test123\");\n\t\tInstant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS);\n\t\tInstanceEndpointsDetectedEvent event = new InstanceEndpointsDetectedEvent(id, 0L, timestamp, Endpoints.empty());\n\n\t\tJsonContent<InstanceEndpointsDetectedEvent> jsonContent = jsonTester.write(event);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.instance\").isEqualTo(\"test123\");\n\t\tassertThat(jsonContent).extractingJsonPathNumberValue(\"$.version\").isEqualTo(0);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.timestamp\").isEqualTo(\"2020-04-24T17:57:11Z\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.type\").isEqualTo(\"ENDPOINTS_DETECTED\");\n\t\tassertThat(jsonContent).extractingJsonPathArrayValue(\"$.endpoints\").isEmpty();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/utils/jackson/InstanceEventMixinTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.json.JacksonTester;\nimport tools.jackson.core.JacksonException;\nimport tools.jackson.databind.DeserializationFeature;\nimport tools.jackson.databind.json.JsonMapper;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceDeregisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEndpointsDetectedEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceInfoChangedEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegistrationUpdatedEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class InstanceEventMixinTest {\n\n\tprivate final JsonMapper jsonMapper;\n\n\tprotected InstanceEventMixinTest() {\n\t\tAdminServerModule adminServerModule = new AdminServerModule(new String[] { \".*password$\" });\n\t\tjsonMapper = JsonMapper.builder()\n\t\t\t.addModule(adminServerModule)\n\t\t\t.disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)\n\t\t\t.build();\n\t}\n\n\t@Nested\n\tclass InstanceEventTests {\n\n\t\tprivate JacksonTester<InstanceEvent> jsonTester;\n\n\t\t@BeforeEach\n\t\tvoid setup() {\n\t\t\tJacksonTester.initFields(this, jsonMapper);\n\t\t}\n\n\t\t@Test\n\t\tvoid verifyDeserializeOfInstanceDeregisteredEvent() throws JSONException, JacksonException {\n\t\t\tString json = new JSONObject().put(\"instance\", \"test123\")\n\t\t\t\t.put(\"timestamp\", 1587751031.000000000)\n\t\t\t\t.put(\"type\", \"DEREGISTERED\")\n\t\t\t\t.toString();\n\n\t\t\tInstanceEvent event = jsonMapper.readValue(json, InstanceEvent.class);\n\t\t\tassertThat(event).isInstanceOf(InstanceDeregisteredEvent.class);\n\t\t}\n\n\t\t@Test\n\t\tvoid verifyDeserializeOfInstanceEndpointsDetectedEvent() throws JSONException, JacksonException {\n\t\t\tString json = new JSONObject().put(\"instance\", \"test123\")\n\t\t\t\t.put(\"timestamp\", 1587751031.000000000)\n\t\t\t\t.put(\"type\", \"ENDPOINTS_DETECTED\")\n\t\t\t\t.toString();\n\n\t\t\tInstanceEvent event = jsonMapper.readValue(json, InstanceEvent.class);\n\t\t\tassertThat(event).isInstanceOf(InstanceEndpointsDetectedEvent.class);\n\t\t}\n\n\t\t@Test\n\t\tvoid verifyDeserializeOfInstanceInfoChangedEvent() throws JSONException, JacksonException {\n\t\t\tString json = new JSONObject().put(\"instance\", \"test123\")\n\t\t\t\t.put(\"timestamp\", 1587751031.000000000)\n\t\t\t\t.put(\"type\", \"INFO_CHANGED\")\n\t\t\t\t.toString();\n\n\t\t\tInstanceEvent event = jsonMapper.readValue(json, InstanceEvent.class);\n\t\t\tassertThat(event).isInstanceOf(InstanceInfoChangedEvent.class);\n\t\t}\n\n\t\t@Test\n\t\tvoid verifyDeserializeOfInstanceRegisteredEvent() throws JSONException, JacksonException {\n\t\t\tString json = new JSONObject().put(\"instance\", \"test123\")\n\t\t\t\t.put(\"timestamp\", 1587751031.000000000)\n\t\t\t\t.put(\"type\", \"REGISTERED\")\n\t\t\t\t.put(\"registration\",\n\t\t\t\t\t\tnew JSONObject().put(\"name\", \"test\").put(\"healthUrl\", \"http://localhost:9080/heath\"))\n\t\t\t\t.toString();\n\n\t\t\tInstanceEvent event = jsonMapper.readValue(json, InstanceEvent.class);\n\t\t\tassertThat(event).isInstanceOf(InstanceRegisteredEvent.class);\n\t\t}\n\n\t\t@Test\n\t\tvoid verifyDeserializeOfInstanceRegistrationUpdatedEvent() throws JSONException, JacksonException {\n\t\t\tString json = new JSONObject().put(\"instance\", \"test123\")\n\t\t\t\t.put(\"timestamp\", 1587751031.000000000)\n\t\t\t\t.put(\"type\", \"REGISTRATION_UPDATED\")\n\t\t\t\t.put(\"registration\",\n\t\t\t\t\t\tnew JSONObject().put(\"name\", \"test\").put(\"healthUrl\", \"http://localhost:9080/heath\"))\n\t\t\t\t.toString();\n\n\t\t\tInstanceEvent event = jsonMapper.readValue(json, InstanceEvent.class);\n\t\t\tassertThat(event).isInstanceOf(InstanceRegistrationUpdatedEvent.class);\n\t\t}\n\n\t\t@Test\n\t\tvoid verifyDeserializeOfInstanceStatusChangedEvent() throws JSONException, JacksonException {\n\t\t\tString json = new JSONObject().put(\"instance\", \"test123\")\n\t\t\t\t.put(\"timestamp\", 1587751031.000000000)\n\t\t\t\t.put(\"type\", \"STATUS_CHANGED\")\n\t\t\t\t.put(\"statusInfo\", new JSONObject().put(\"status\", \"OFFLINE\"))\n\t\t\t\t.toString();\n\n\t\t\tInstanceEvent event = jsonMapper.readValue(json, InstanceEvent.class);\n\t\t\tassertThat(event).isInstanceOf(InstanceStatusChangedEvent.class);\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/utils/jackson/InstanceIdMixinTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport java.io.IOException;\n\nimport org.junit.jupiter.api.Test;\nimport tools.jackson.core.JacksonException;\nimport tools.jackson.databind.DeserializationFeature;\nimport tools.jackson.databind.json.JsonMapper;\n\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass InstanceIdMixinTest {\n\n\tprivate final JsonMapper jsonMapper;\n\n\tprotected InstanceIdMixinTest() {\n\t\tAdminServerModule adminServerModule = new AdminServerModule(new String[] { \".*password$\" });\n\t\tjsonMapper = JsonMapper.builder()\n\t\t\t.addModule(adminServerModule)\n\t\t\t.disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)\n\t\t\t.build();\n\t}\n\n\t@Test\n\tvoid verifyDeserialize() throws JacksonException {\n\t\tInstanceId id = jsonMapper.readValue(\"\\\"abc\\\"\", InstanceId.class);\n\t\tassertThat(id).isEqualTo(InstanceId.of(\"abc\"));\n\t}\n\n\t@Test\n\tvoid verifySerialize() throws IOException {\n\t\tInstanceId id = InstanceId.of(\"abc\");\n\n\t\tString result = jsonMapper.writeValueAsString(id);\n\t\tassertThat(result).isEqualTo(\"\\\"abc\\\"\");\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/utils/jackson/InstanceInfoChangedEventMixinTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport java.io.IOException;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.json.JacksonTester;\nimport org.springframework.boot.test.json.JsonContent;\nimport tools.jackson.core.JacksonException;\nimport tools.jackson.databind.DeserializationFeature;\nimport tools.jackson.databind.json.JsonMapper;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceInfoChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.Info;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.entry;\n\nclass InstanceInfoChangedEventMixinTest {\n\n\tprivate final JsonMapper jsonMapper;\n\n\tprivate JacksonTester<InstanceInfoChangedEvent> jsonTester;\n\n\tprotected InstanceInfoChangedEventMixinTest() {\n\t\tAdminServerModule adminServerModule = new AdminServerModule(new String[] { \".*password$\" });\n\t\tjsonMapper = JsonMapper.builder()\n\t\t\t.addModule(adminServerModule)\n\t\t\t.disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)\n\t\t\t.build();\n\t}\n\n\t@BeforeEach\n\tvoid setup() {\n\t\tJacksonTester.initFields(this, jsonMapper);\n\t}\n\n\t@Test\n\tvoid verifyDeserialize() throws JSONException, JacksonException {\n\t\tString json = new JSONObject().put(\"instance\", \"test123\")\n\t\t\t.put(\"version\", 12345678L)\n\t\t\t.put(\"timestamp\", 1587751031.000000000)\n\t\t\t.put(\"type\", \"INFO_CHANGED\")\n\t\t\t.put(\"info\", new JSONObject().put(\"build\", new JSONObject().put(\"version\", \"1.0.0\")).put(\"foo\", \"bar\"))\n\t\t\t.toString();\n\n\t\tInstanceInfoChangedEvent event = jsonMapper.readValue(json, InstanceInfoChangedEvent.class);\n\t\tassertThat(event).isNotNull();\n\t\tassertThat(event.getInstance()).isEqualTo(InstanceId.of(\"test123\"));\n\t\tassertThat(event.getVersion()).isEqualTo(12345678L);\n\t\tassertThat(event.getTimestamp()).isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS));\n\n\t\tInfo info = event.getInfo();\n\t\tassertThat(info).isNotNull();\n\t\tassertThat(info.getValues()).containsOnly(entry(\"build\", Collections.singletonMap(\"version\", \"1.0.0\")),\n\t\t\t\tentry(\"foo\", \"bar\"));\n\t}\n\n\t@Test\n\tvoid verifyDeserializeWithOnlyRequiredProperties() throws JSONException {\n\t\tString json = new JSONObject().put(\"instance\", \"test123\")\n\t\t\t.put(\"timestamp\", 1587751031.000000000)\n\t\t\t.put(\"type\", \"INFO_CHANGED\")\n\t\t\t.toString();\n\n\t\tInstanceInfoChangedEvent event = jsonMapper.readValue(json, InstanceInfoChangedEvent.class);\n\t\tassertThat(event).isNotNull();\n\t\tassertThat(event.getInstance()).isEqualTo(InstanceId.of(\"test123\"));\n\t\tassertThat(event.getVersion()).isZero();\n\t\tassertThat(event.getTimestamp()).isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS));\n\t\tassertThat(event.getInfo()).isNull();\n\t}\n\n\t@Test\n\tvoid verifyDeserializeWithEmptyInfo() throws JSONException {\n\t\tString json = new JSONObject().put(\"instance\", \"test123\")\n\t\t\t.put(\"version\", 12345678L)\n\t\t\t.put(\"timestamp\", 1587751031.000000000)\n\t\t\t.put(\"type\", \"INFO_CHANGED\")\n\t\t\t.put(\"info\", new JSONObject())\n\t\t\t.toString();\n\n\t\tInstanceInfoChangedEvent event = jsonMapper.readValue(json, InstanceInfoChangedEvent.class);\n\t\tassertThat(event).isNotNull();\n\t\tassertThat(event.getInstance()).isEqualTo(InstanceId.of(\"test123\"));\n\t\tassertThat(event.getVersion()).isEqualTo(12345678L);\n\t\tassertThat(event.getTimestamp()).isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS));\n\n\t\tInfo info = event.getInfo();\n\t\tassertThat(info).isNotNull();\n\t\tassertThat(info.getValues()).isEmpty();\n\t}\n\n\t@Test\n\tvoid verifySerialize() throws IOException {\n\t\tInstanceId id = InstanceId.of(\"test123\");\n\t\tInstant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS);\n\t\tMap<String, Object> data = new HashMap<>();\n\t\tdata.put(\"build\", Collections.singletonMap(\"version\", \"1.0.0\"));\n\t\tdata.put(\"foo\", \"bar\");\n\t\tInstanceInfoChangedEvent event = new InstanceInfoChangedEvent(id, 12345678L, timestamp, Info.from(data));\n\n\t\tJsonContent<InstanceInfoChangedEvent> jsonContent = jsonTester.write(event);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.instance\").isEqualTo(\"test123\");\n\t\tassertThat(jsonContent).extractingJsonPathNumberValue(\"$.version\").isEqualTo(12345678);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.timestamp\").isEqualTo(\"2020-04-24T17:57:11Z\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.type\").isEqualTo(\"INFO_CHANGED\");\n\t\tassertThat(jsonContent).extractingJsonPathMapValue(\"$.info\").containsOnlyKeys(\"build\", \"foo\");\n\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.info['build'].['version']\").isEqualTo(\"1.0.0\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.info['foo']\").isEqualTo(\"bar\");\n\t}\n\n\t@Test\n\tvoid verifySerializeWithOnlyRequiredProperties() throws IOException {\n\t\tInstanceId id = InstanceId.of(\"test123\");\n\t\tInstant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS);\n\t\tInstanceInfoChangedEvent event = new InstanceInfoChangedEvent(id, 0L, timestamp, null);\n\n\t\tJsonContent<InstanceInfoChangedEvent> jsonContent = jsonTester.write(event);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.instance\").isEqualTo(\"test123\");\n\t\tassertThat(jsonContent).extractingJsonPathNumberValue(\"$.version\").isEqualTo(0);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.timestamp\").isEqualTo(\"2020-04-24T17:57:11Z\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.type\").isEqualTo(\"INFO_CHANGED\");\n\t\tassertThat(jsonContent).extractingJsonPathMapValue(\"$.info\").isNull();\n\t}\n\n\t@Test\n\tvoid verifySerializeWithEmptyInfo() throws IOException {\n\t\tInstanceId id = InstanceId.of(\"test123\");\n\t\tInstant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS);\n\t\tInstanceInfoChangedEvent event = new InstanceInfoChangedEvent(id, 12345678L, timestamp,\n\t\t\t\tInfo.from(Collections.emptyMap()));\n\n\t\tJsonContent<InstanceInfoChangedEvent> jsonContent = jsonTester.write(event);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.instance\").isEqualTo(\"test123\");\n\t\tassertThat(jsonContent).extractingJsonPathNumberValue(\"$.version\").isEqualTo(12345678);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.timestamp\").isEqualTo(\"2020-04-24T17:57:11Z\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.type\").isEqualTo(\"INFO_CHANGED\");\n\t\tassertThat(jsonContent).extractingJsonPathMapValue(\"$.info\").isEmpty();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/utils/jackson/InstanceRegisteredEventMixinTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport java.io.IOException;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.json.JacksonTester;\nimport org.springframework.boot.test.json.JsonContent;\nimport tools.jackson.core.JacksonException;\nimport tools.jackson.databind.DatabindException;\nimport tools.jackson.databind.DeserializationFeature;\nimport tools.jackson.databind.json.JsonMapper;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegisteredEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.assertj.core.api.Assertions.entry;\n\nclass InstanceRegisteredEventMixinTest {\n\n\tprivate final JsonMapper jsonMapper;\n\n\tprivate JacksonTester<InstanceRegisteredEvent> jsonTester;\n\n\tprotected InstanceRegisteredEventMixinTest() {\n\t\tAdminServerModule adminServerModule = new AdminServerModule(new String[] { \".*password$\" });\n\t\tjsonMapper = JsonMapper.builder()\n\t\t\t.addModule(adminServerModule)\n\t\t\t.disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)\n\t\t\t.build();\n\t}\n\n\t@BeforeEach\n\tvoid setup() {\n\t\tJacksonTester.initFields(this, jsonMapper);\n\t}\n\n\t@Test\n\tvoid verifyDeserialize() throws JSONException, JacksonException {\n\t\tString json = new JSONObject().put(\"instance\", \"test123\")\n\t\t\t.put(\"version\", 12345678L)\n\t\t\t.put(\"timestamp\", 1587751031.000000000)\n\t\t\t.put(\"type\", \"REGISTERED\")\n\t\t\t.put(\"registration\",\n\t\t\t\t\tnew JSONObject().put(\"name\", \"test\")\n\t\t\t\t\t\t.put(\"managementUrl\", \"http://localhost:9080/\")\n\t\t\t\t\t\t.put(\"healthUrl\", \"http://localhost:9080/heath\")\n\t\t\t\t\t\t.put(\"serviceUrl\", \"http://localhost:8080/\")\n\t\t\t\t\t\t.put(\"source\", \"http-api\")\n\t\t\t\t\t\t.put(\"metadata\", new JSONObject().put(\"PASSWORD\", \"******\").put(\"user\", \"humptydumpty\")))\n\t\t\t.toString();\n\n\t\tInstanceRegisteredEvent event = jsonMapper.readValue(json, InstanceRegisteredEvent.class);\n\t\tassertThat(event).isNotNull();\n\t\tassertThat(event.getInstance()).isEqualTo(InstanceId.of(\"test123\"));\n\t\tassertThat(event.getVersion()).isEqualTo(12345678L);\n\t\tassertThat(event.getTimestamp()).isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS));\n\n\t\tRegistration registration = event.getRegistration();\n\t\tassertThat(registration).isNotNull();\n\t\tassertThat(registration.getName()).isEqualTo(\"test\");\n\t\tassertThat(registration.getManagementUrl()).isEqualTo(\"http://localhost:9080/\");\n\t\tassertThat(registration.getHealthUrl()).isEqualTo(\"http://localhost:9080/heath\");\n\t\tassertThat(registration.getServiceUrl()).isEqualTo(\"http://localhost:8080/\");\n\t\tassertThat(registration.getSource()).isEqualTo(\"http-api\");\n\t\tassertThat(registration.getMetadata()).containsOnly(entry(\"PASSWORD\", \"******\"), entry(\"user\", \"humptydumpty\"));\n\t}\n\n\t@Test\n\tvoid verifyDeserializeWithOnlyRequiredProperties() throws JSONException, JacksonException {\n\t\tString json = new JSONObject().put(\"instance\", \"test123\")\n\t\t\t.put(\"timestamp\", 1587751031.000000000)\n\t\t\t.put(\"type\", \"REGISTERED\")\n\t\t\t.put(\"registration\", new JSONObject().put(\"name\", \"test\").put(\"healthUrl\", \"http://localhost:9080/heath\"))\n\t\t\t.toString();\n\n\t\tInstanceRegisteredEvent event = jsonMapper.readValue(json, InstanceRegisteredEvent.class);\n\t\tassertThat(event).isNotNull();\n\t\tassertThat(event.getInstance()).isEqualTo(InstanceId.of(\"test123\"));\n\t\tassertThat(event.getVersion()).isZero();\n\t\tassertThat(event.getTimestamp()).isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS));\n\n\t\tRegistration registration = event.getRegistration();\n\t\tassertThat(registration).isNotNull();\n\t\tassertThat(registration.getName()).isEqualTo(\"test\");\n\t\tassertThat(registration.getManagementUrl()).isNull();\n\t\tassertThat(registration.getHealthUrl()).isEqualTo(\"http://localhost:9080/heath\");\n\t\tassertThat(registration.getServiceUrl()).isNull();\n\t\tassertThat(registration.getSource()).isNull();\n\t\tassertThat(registration.getMetadata()).isEmpty();\n\t}\n\n\t@Test\n\tvoid verifyDeserializeWithoutRegistration() throws JSONException, JacksonException {\n\t\tString json = new JSONObject().put(\"instance\", \"test123\")\n\t\t\t.put(\"version\", 12345678L)\n\t\t\t.put(\"timestamp\", 1587751031.000000000)\n\t\t\t.put(\"type\", \"REGISTERED\")\n\t\t\t.toString();\n\n\t\tInstanceRegisteredEvent event = jsonMapper.readValue(json, InstanceRegisteredEvent.class);\n\t\tassertThat(event).isNotNull();\n\t\tassertThat(event.getInstance()).isEqualTo(InstanceId.of(\"test123\"));\n\t\tassertThat(event.getVersion()).isEqualTo(12345678L);\n\t\tassertThat(event.getTimestamp()).isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS));\n\t\tassertThat(event.getRegistration()).isNull();\n\n\t}\n\n\t@Test\n\tvoid verifyDeserializeWithEmptyRegistration() throws JSONException {\n\t\tString json = new JSONObject().put(\"instance\", \"test123\")\n\t\t\t.put(\"version\", 12345678L)\n\t\t\t.put(\"timestamp\", 1587751031.000000000)\n\t\t\t.put(\"type\", \"REGISTERED\")\n\t\t\t.put(\"registration\", new JSONObject())\n\t\t\t.toString();\n\n\t\tassertThatThrownBy(() -> jsonMapper.readValue(json, InstanceRegisteredEvent.class))\n\t\t\t.isInstanceOf(DatabindException.class)\n\t\t\t.hasCauseInstanceOf(IllegalArgumentException.class)\n\t\t\t.hasMessageContaining(\"must not be empty\");\n\t}\n\n\t@Test\n\tvoid verifySerialize() throws IOException {\n\t\tInstanceId id = InstanceId.of(\"test123\");\n\t\tInstant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS);\n\t\tRegistration registration = Registration.create(\"test\", \"http://localhost:9080/heath\")\n\t\t\t.managementUrl(\"http://localhost:9080/\")\n\t\t\t.serviceUrl(\"http://localhost:8080/\")\n\t\t\t.source(\"http-api\")\n\t\t\t.metadata(\"PASSWORD\", \"qwertz123\")\n\t\t\t.metadata(\"user\", \"humptydumpty\")\n\t\t\t.build();\n\n\t\tInstanceRegisteredEvent event = new InstanceRegisteredEvent(id, 12345678L, timestamp, registration);\n\n\t\tJsonContent<InstanceRegisteredEvent> jsonContent = jsonTester.write(event);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.instance\").isEqualTo(\"test123\");\n\t\tassertThat(jsonContent).extractingJsonPathNumberValue(\"$.version\").isEqualTo(12345678);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.timestamp\").isEqualTo(\"2020-04-24T17:57:11Z\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.type\").isEqualTo(\"REGISTERED\");\n\t\tassertThat(jsonContent).extractingJsonPathValue(\"$.registration\").isNotNull();\n\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.registration.name\").isEqualTo(\"test\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.registration.managementUrl\")\n\t\t\t.isEqualTo(\"http://localhost:9080/\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.registration.healthUrl\")\n\t\t\t.isEqualTo(\"http://localhost:9080/heath\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.registration.serviceUrl\")\n\t\t\t.isEqualTo(\"http://localhost:8080/\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.registration.source\").isEqualTo(\"http-api\");\n\t\tassertThat(jsonContent).extractingJsonPathMapValue(\"$.registration.metadata\")\n\t\t\t.containsOnly(entry(\"PASSWORD\", \"******\"), entry(\"user\", \"humptydumpty\"));\n\t}\n\n\t@Test\n\tvoid verifySerializeWithOnlyRequiredProperties() throws IOException {\n\t\tInstanceId id = InstanceId.of(\"test123\");\n\t\tInstant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS);\n\t\tRegistration registration = Registration.create(\"test\", \"http://localhost:9080/heath\").build();\n\n\t\tInstanceRegisteredEvent event = new InstanceRegisteredEvent(id, 0L, timestamp, registration);\n\n\t\tJsonContent<InstanceRegisteredEvent> jsonContent = jsonTester.write(event);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.instance\").isEqualTo(\"test123\");\n\t\tassertThat(jsonContent).extractingJsonPathNumberValue(\"$.version\").isEqualTo(0);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.timestamp\").isEqualTo(\"2020-04-24T17:57:11Z\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.type\").isEqualTo(\"REGISTERED\");\n\t\tassertThat(jsonContent).extractingJsonPathValue(\"$.registration\").isNotNull();\n\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.registration.name\").isEqualTo(\"test\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.registration.managementUrl\").isNull();\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.registration.healthUrl\")\n\t\t\t.isEqualTo(\"http://localhost:9080/heath\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.registration.serviceUrl\").isNull();\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.registration.source\").isNull();\n\t\tassertThat(jsonContent).extractingJsonPathMapValue(\"$.registration.metadata\").isEmpty();\n\t}\n\n\t@Test\n\tvoid verifySerializeWithoutRegistration() throws IOException {\n\t\tInstanceId id = InstanceId.of(\"test123\");\n\t\tInstant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS);\n\t\tInstanceRegisteredEvent event = new InstanceRegisteredEvent(id, 12345678L, timestamp, null);\n\n\t\tJsonContent<InstanceRegisteredEvent> jsonContent = jsonTester.write(event);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.instance\").isEqualTo(\"test123\");\n\t\tassertThat(jsonContent).extractingJsonPathNumberValue(\"$.version\").isEqualTo(12345678);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.timestamp\").isEqualTo(\"2020-04-24T17:57:11Z\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.type\").isEqualTo(\"REGISTERED\");\n\t\tassertThat(jsonContent).extractingJsonPathValue(\"$.registration\").isNull();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/utils/jackson/InstanceRegistrationUpdatedEventMixinTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport java.io.IOException;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.json.JacksonTester;\nimport org.springframework.boot.test.json.JsonContent;\nimport tools.jackson.core.JacksonException;\nimport tools.jackson.databind.DatabindException;\nimport tools.jackson.databind.DeserializationFeature;\nimport tools.jackson.databind.json.JsonMapper;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegistrationUpdatedEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.assertj.core.api.Assertions.entry;\n\nclass InstanceRegistrationUpdatedEventMixinTest {\n\n\tprivate final JsonMapper jsonMapper;\n\n\tprivate JacksonTester<InstanceRegistrationUpdatedEvent> jsonTester;\n\n\tprotected InstanceRegistrationUpdatedEventMixinTest() {\n\t\tAdminServerModule adminServerModule = new AdminServerModule(new String[] { \".*password$\" });\n\t\tjsonMapper = JsonMapper.builder()\n\t\t\t.addModule(adminServerModule)\n\t\t\t.disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)\n\n\t\t\t.build();\n\t}\n\n\t@BeforeEach\n\tvoid setup() {\n\t\tJacksonTester.initFields(this, jsonMapper);\n\t}\n\n\t@Test\n\tvoid verifyDeserialize() throws JSONException, JacksonException {\n\t\tString json = new JSONObject().put(\"instance\", \"test123\")\n\t\t\t.put(\"version\", 12345678L)\n\t\t\t.put(\"timestamp\", 1587751031.000000000)\n\t\t\t.put(\"type\", \"REGISTRATION_UPDATED\")\n\t\t\t.put(\"registration\",\n\t\t\t\t\tnew JSONObject().put(\"name\", \"test\")\n\t\t\t\t\t\t.put(\"managementUrl\", \"http://localhost:9080/\")\n\t\t\t\t\t\t.put(\"healthUrl\", \"http://localhost:9080/heath\")\n\t\t\t\t\t\t.put(\"serviceUrl\", \"http://localhost:8080/\")\n\t\t\t\t\t\t.put(\"source\", \"http-api\")\n\t\t\t\t\t\t.put(\"metadata\", new JSONObject().put(\"PASSWORD\", \"******\").put(\"user\", \"humptydumpty\")))\n\t\t\t.toString();\n\n\t\tInstanceRegistrationUpdatedEvent event = jsonMapper.readValue(json, InstanceRegistrationUpdatedEvent.class);\n\t\tassertThat(event).isNotNull();\n\t\tassertThat(event.getInstance()).isEqualTo(InstanceId.of(\"test123\"));\n\t\tassertThat(event.getVersion()).isEqualTo(12345678L);\n\t\tassertThat(event.getTimestamp()).isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS));\n\n\t\tRegistration registration = event.getRegistration();\n\t\tassertThat(registration).isNotNull();\n\t\tassertThat(registration.getName()).isEqualTo(\"test\");\n\t\tassertThat(registration.getManagementUrl()).isEqualTo(\"http://localhost:9080/\");\n\t\tassertThat(registration.getHealthUrl()).isEqualTo(\"http://localhost:9080/heath\");\n\t\tassertThat(registration.getServiceUrl()).isEqualTo(\"http://localhost:8080/\");\n\t\tassertThat(registration.getSource()).isEqualTo(\"http-api\");\n\t\tassertThat(registration.getMetadata()).containsOnly(entry(\"PASSWORD\", \"******\"), entry(\"user\", \"humptydumpty\"));\n\t}\n\n\t@Test\n\tvoid verifyDeserializeWithOnlyRequiredProperties() throws JSONException, JacksonException {\n\t\tString json = new JSONObject().put(\"instance\", \"test123\")\n\t\t\t.put(\"timestamp\", 1587751031.000000000)\n\t\t\t.put(\"type\", \"REGISTRATION_UPDATED\")\n\t\t\t.put(\"registration\", new JSONObject().put(\"name\", \"test\").put(\"healthUrl\", \"http://localhost:9080/heath\"))\n\t\t\t.toString();\n\n\t\tInstanceRegistrationUpdatedEvent event = jsonMapper.readValue(json, InstanceRegistrationUpdatedEvent.class);\n\t\tassertThat(event).isNotNull();\n\t\tassertThat(event.getInstance()).isEqualTo(InstanceId.of(\"test123\"));\n\t\tassertThat(event.getVersion()).isZero();\n\t\tassertThat(event.getTimestamp()).isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS));\n\n\t\tRegistration registration = event.getRegistration();\n\t\tassertThat(registration).isNotNull();\n\t\tassertThat(registration.getName()).isEqualTo(\"test\");\n\t\tassertThat(registration.getManagementUrl()).isNull();\n\t\tassertThat(registration.getHealthUrl()).isEqualTo(\"http://localhost:9080/heath\");\n\t\tassertThat(registration.getServiceUrl()).isNull();\n\t\tassertThat(registration.getSource()).isNull();\n\t\tassertThat(registration.getMetadata()).isEmpty();\n\t}\n\n\t@Test\n\tvoid verifyDeserializeWithoutRegistration() throws JSONException, JacksonException {\n\t\tString json = new JSONObject().put(\"instance\", \"test123\")\n\t\t\t.put(\"version\", 12345678L)\n\t\t\t.put(\"timestamp\", 1587751031.000000000)\n\t\t\t.put(\"type\", \"REGISTRATION_UPDATED\")\n\t\t\t.toString();\n\n\t\tInstanceRegistrationUpdatedEvent event = jsonMapper.readValue(json, InstanceRegistrationUpdatedEvent.class);\n\t\tassertThat(event).isNotNull();\n\t\tassertThat(event.getInstance()).isEqualTo(InstanceId.of(\"test123\"));\n\t\tassertThat(event.getVersion()).isEqualTo(12345678L);\n\t\tassertThat(event.getTimestamp()).isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS));\n\t\tassertThat(event.getRegistration()).isNull();\n\t}\n\n\t@Test\n\tvoid verifyDeserializeWithEmptyRegistration() throws JSONException {\n\t\tString json = new JSONObject().put(\"instance\", \"test123\")\n\t\t\t.put(\"version\", 12345678L)\n\t\t\t.put(\"timestamp\", 1587751031.000000000)\n\t\t\t.put(\"type\", \"REGISTRATION_UPDATED\")\n\t\t\t.put(\"registration\", new JSONObject())\n\t\t\t.toString();\n\n\t\tassertThatThrownBy(() -> jsonMapper.readValue(json, InstanceRegistrationUpdatedEvent.class))\n\t\t\t.isInstanceOf(DatabindException.class)\n\t\t\t.hasCauseInstanceOf(IllegalArgumentException.class)\n\t\t\t.hasMessageContaining(\"must not be empty\");\n\t}\n\n\t@Test\n\tvoid verifySerialize() throws IOException {\n\t\tInstanceId id = InstanceId.of(\"test123\");\n\t\tInstant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS);\n\t\tRegistration registration = Registration.create(\"test\", \"http://localhost:9080/heath\")\n\t\t\t.managementUrl(\"http://localhost:9080/\")\n\t\t\t.serviceUrl(\"http://localhost:8080/\")\n\t\t\t.source(\"http-api\")\n\t\t\t.metadata(\"PASSWORD\", \"qwertz123\")\n\t\t\t.metadata(\"user\", \"humptydumpty\")\n\t\t\t.build();\n\n\t\tInstanceRegistrationUpdatedEvent event = new InstanceRegistrationUpdatedEvent(id, 12345678L, timestamp,\n\t\t\t\tregistration);\n\n\t\tJsonContent<InstanceRegistrationUpdatedEvent> jsonContent = jsonTester.write(event);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.instance\").isEqualTo(\"test123\");\n\t\tassertThat(jsonContent).extractingJsonPathNumberValue(\"$.version\").isEqualTo(12345678);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.timestamp\").isEqualTo(\"2020-04-24T17:57:11Z\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.type\").isEqualTo(\"REGISTRATION_UPDATED\");\n\t\tassertThat(jsonContent).extractingJsonPathValue(\"$.registration\").isNotNull();\n\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.registration.name\").isEqualTo(\"test\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.registration.managementUrl\")\n\t\t\t.isEqualTo(\"http://localhost:9080/\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.registration.healthUrl\")\n\t\t\t.isEqualTo(\"http://localhost:9080/heath\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.registration.serviceUrl\")\n\t\t\t.isEqualTo(\"http://localhost:8080/\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.registration.source\").isEqualTo(\"http-api\");\n\t\tassertThat(jsonContent).extractingJsonPathMapValue(\"$.registration.metadata\")\n\t\t\t.containsOnly(entry(\"PASSWORD\", \"******\"), entry(\"user\", \"humptydumpty\"));\n\t}\n\n\t@Test\n\tvoid verifySerializeWithOnlyRequiredProperties() throws IOException {\n\t\tInstanceId id = InstanceId.of(\"test123\");\n\t\tInstant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS);\n\t\tRegistration registration = Registration.create(\"test\", \"http://localhost:9080/heath\").build();\n\n\t\tInstanceRegistrationUpdatedEvent event = new InstanceRegistrationUpdatedEvent(id, 0L, timestamp, registration);\n\n\t\tJsonContent<InstanceRegistrationUpdatedEvent> jsonContent = jsonTester.write(event);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.instance\").isEqualTo(\"test123\");\n\t\tassertThat(jsonContent).extractingJsonPathNumberValue(\"$.version\").isEqualTo(0);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.timestamp\").isEqualTo(\"2020-04-24T17:57:11Z\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.type\").isEqualTo(\"REGISTRATION_UPDATED\");\n\t\tassertThat(jsonContent).extractingJsonPathValue(\"$.registration\").isNotNull();\n\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.registration.name\").isEqualTo(\"test\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.registration.managementUrl\").isNull();\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.registration.healthUrl\")\n\t\t\t.isEqualTo(\"http://localhost:9080/heath\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.registration.serviceUrl\").isNull();\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.registration.source\").isNull();\n\t\tassertThat(jsonContent).extractingJsonPathMapValue(\"$.registration.metadata\").isEmpty();\n\t}\n\n\t@Test\n\tvoid verifySerializeWithoutRegistration() throws IOException {\n\t\tInstanceId id = InstanceId.of(\"test123\");\n\t\tInstant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS);\n\t\tInstanceRegistrationUpdatedEvent event = new InstanceRegistrationUpdatedEvent(id, 12345678L, timestamp, null);\n\n\t\tJsonContent<InstanceRegistrationUpdatedEvent> jsonContent = jsonTester.write(event);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.instance\").isEqualTo(\"test123\");\n\t\tassertThat(jsonContent).extractingJsonPathNumberValue(\"$.version\").isEqualTo(12345678);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.timestamp\").isEqualTo(\"2020-04-24T17:57:11Z\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.type\").isEqualTo(\"REGISTRATION_UPDATED\");\n\t\tassertThat(jsonContent).extractingJsonPathMapValue(\"$.registration\").isNull();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/utils/jackson/InstanceStatusChangedEventMixinTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport java.io.IOException;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Collections;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.json.JacksonTester;\nimport org.springframework.boot.test.json.JsonContent;\nimport tools.jackson.core.JacksonException;\nimport tools.jackson.databind.DatabindException;\nimport tools.jackson.databind.DeserializationFeature;\nimport tools.jackson.databind.json.JsonMapper;\n\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.assertj.core.api.Assertions.entry;\n\nclass InstanceStatusChangedEventMixinTest {\n\n\tprivate final JsonMapper jsonMapper;\n\n\tprivate JacksonTester<InstanceStatusChangedEvent> jsonTester;\n\n\tprotected InstanceStatusChangedEventMixinTest() {\n\t\tAdminServerModule adminServerModule = new AdminServerModule(new String[] { \".*password$\" });\n\t\tjsonMapper = JsonMapper.builder()\n\t\t\t.addModule(adminServerModule)\n\t\t\t.disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)\n\t\t\t.build();\n\t}\n\n\t@BeforeEach\n\tvoid setup() {\n\t\tJacksonTester.initFields(this, jsonMapper);\n\t}\n\n\t@Test\n\tvoid verifyDeserialize() throws JSONException, JacksonException {\n\t\tString json = new JSONObject().put(\"instance\", \"test123\")\n\t\t\t.put(\"version\", 12345678L)\n\t\t\t.put(\"timestamp\", 1587751031.000000000)\n\t\t\t.put(\"type\", \"STATUS_CHANGED\")\n\t\t\t.put(\"statusInfo\",\n\t\t\t\t\tnew JSONObject().put(\"status\", \"OFFLINE\").put(\"details\", new JSONObject().put(\"foo\", \"bar\")))\n\t\t\t.toString();\n\n\t\tInstanceStatusChangedEvent event = jsonMapper.readValue(json, InstanceStatusChangedEvent.class);\n\t\tassertThat(event).isNotNull();\n\t\tassertThat(event.getInstance()).isEqualTo(InstanceId.of(\"test123\"));\n\t\tassertThat(event.getVersion()).isEqualTo(12345678L);\n\t\tassertThat(event.getTimestamp()).isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS));\n\n\t\tStatusInfo statusInfo = event.getStatusInfo();\n\t\tassertThat(statusInfo).isNotNull();\n\t\tassertThat(statusInfo.getStatus()).isEqualTo(\"OFFLINE\");\n\t\tassertThat(statusInfo.getDetails()).containsOnly(entry(\"foo\", \"bar\"));\n\t}\n\n\t@Test\n\tvoid verifyDeserializeWithOnlyRequiredProperties() throws JSONException, JacksonException {\n\t\tString json = new JSONObject().put(\"instance\", \"test123\")\n\t\t\t.put(\"timestamp\", 1587751031.000000000)\n\t\t\t.put(\"type\", \"STATUS_CHANGED\")\n\t\t\t.put(\"statusInfo\", new JSONObject().put(\"status\", \"OFFLINE\"))\n\t\t\t.toString();\n\n\t\tInstanceStatusChangedEvent event = jsonMapper.readValue(json, InstanceStatusChangedEvent.class);\n\t\tassertThat(event).isNotNull();\n\t\tassertThat(event.getInstance()).isEqualTo(InstanceId.of(\"test123\"));\n\t\tassertThat(event.getVersion()).isZero();\n\t\tassertThat(event.getTimestamp()).isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS));\n\n\t\tStatusInfo statusInfo = event.getStatusInfo();\n\t\tassertThat(statusInfo).isNotNull();\n\t\tassertThat(statusInfo.getStatus()).isEqualTo(\"OFFLINE\");\n\t\tassertThat(statusInfo.getDetails()).isEmpty();\n\t}\n\n\t@Test\n\tvoid verifyDeserializeWithoutStatusInfo() throws JSONException, JacksonException {\n\t\tString json = new JSONObject().put(\"instance\", \"test123\")\n\t\t\t.put(\"version\", 12345678L)\n\t\t\t.put(\"timestamp\", 1587751031.000000000)\n\t\t\t.put(\"type\", \"STATUS_CHANGED\")\n\t\t\t.toString();\n\n\t\tInstanceStatusChangedEvent event = jsonMapper.readValue(json, InstanceStatusChangedEvent.class);\n\t\tassertThat(event).isNotNull();\n\t\tassertThat(event.getInstance()).isEqualTo(InstanceId.of(\"test123\"));\n\t\tassertThat(event.getVersion()).isEqualTo(12345678L);\n\t\tassertThat(event.getTimestamp()).isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS));\n\t\tassertThat(event.getStatusInfo()).isNull();\n\t}\n\n\t@Test\n\tvoid verifyDeserializeWithEmptyStatusInfo() throws JSONException {\n\t\tString json = new JSONObject().put(\"instance\", \"test123\")\n\t\t\t.put(\"version\", 12345678L)\n\t\t\t.put(\"timestamp\", 1587751031.000000000)\n\t\t\t.put(\"type\", \"STATUS_CHANGED\")\n\t\t\t.put(\"statusInfo\", new JSONObject())\n\t\t\t.toString();\n\n\t\tassertThatThrownBy(() -> jsonMapper.readValue(json, InstanceStatusChangedEvent.class))\n\t\t\t.isInstanceOf(DatabindException.class)\n\t\t\t.hasCauseInstanceOf(IllegalArgumentException.class)\n\t\t\t.hasMessageContaining(\"must not be empty\");\n\t}\n\n\t@Test\n\tvoid verifySerialize() throws IOException {\n\t\tInstanceId id = InstanceId.of(\"test123\");\n\t\tInstant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS);\n\t\tStatusInfo statusInfo = StatusInfo.valueOf(\"OFFLINE\", Collections.singletonMap(\"foo\", \"bar\"));\n\n\t\tInstanceStatusChangedEvent event = new InstanceStatusChangedEvent(id, 12345678L, timestamp, statusInfo);\n\n\t\tJsonContent<InstanceStatusChangedEvent> jsonContent = jsonTester.write(event);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.instance\").isEqualTo(\"test123\");\n\t\tassertThat(jsonContent).extractingJsonPathNumberValue(\"$.version\").isEqualTo(12345678);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.timestamp\").isEqualTo(\"2020-04-24T17:57:11Z\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.type\").isEqualTo(\"STATUS_CHANGED\");\n\t\tassertThat(jsonContent).extractingJsonPathValue(\"$.statusInfo\").isNotNull();\n\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.statusInfo.status\").isEqualTo(\"OFFLINE\");\n\t\tassertThat(jsonContent).extractingJsonPathMapValue(\"$.statusInfo.details\").containsOnly(entry(\"foo\", \"bar\"));\n\t}\n\n\t@Test\n\tvoid verifySerializeWithOnlyRequiredProperties() throws IOException {\n\t\tInstanceId id = InstanceId.of(\"test123\");\n\t\tInstant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS);\n\t\tStatusInfo statusInfo = StatusInfo.valueOf(\"OFFLINE\");\n\n\t\tInstanceStatusChangedEvent event = new InstanceStatusChangedEvent(id, 0L, timestamp, statusInfo);\n\n\t\tJsonContent<InstanceStatusChangedEvent> jsonContent = jsonTester.write(event);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.instance\").isEqualTo(\"test123\");\n\t\tassertThat(jsonContent).extractingJsonPathNumberValue(\"$.version\").isEqualTo(0);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.timestamp\").isEqualTo(\"2020-04-24T17:57:11Z\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.type\").isEqualTo(\"STATUS_CHANGED\");\n\t\tassertThat(jsonContent).extractingJsonPathValue(\"$.statusInfo\").isNotNull();\n\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.statusInfo.status\").isEqualTo(\"OFFLINE\");\n\t\tassertThat(jsonContent).extractingJsonPathMapValue(\"$.statusInfo.details\").isEmpty();\n\t}\n\n\t@Test\n\tvoid verifySerializeWithoutStatusInfo() throws IOException {\n\t\tInstanceId id = InstanceId.of(\"test123\");\n\t\tInstant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS);\n\n\t\tInstanceStatusChangedEvent event = new InstanceStatusChangedEvent(id, 12345678L, timestamp, null);\n\n\t\tJsonContent<InstanceStatusChangedEvent> jsonContent = jsonTester.write(event);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.instance\").isEqualTo(\"test123\");\n\t\tassertThat(jsonContent).extractingJsonPathNumberValue(\"$.version\").isEqualTo(12345678);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.timestamp\").isEqualTo(\"2020-04-24T17:57:11Z\");\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.type\").isEqualTo(\"STATUS_CHANGED\");\n\t\tassertThat(jsonContent).extractingJsonPathValue(\"$.statusInfo\").isNull();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/utils/jackson/RegistrationDeserializerTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport org.json.JSONObject;\nimport org.junit.jupiter.api.Test;\nimport tools.jackson.core.JacksonException;\nimport tools.jackson.databind.DeserializationFeature;\nimport tools.jackson.databind.json.JsonMapper;\n\nimport de.codecentric.boot.admin.server.domain.values.Registration;\n\nimport static java.util.Collections.singletonMap;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass RegistrationDeserializerTest {\n\n\tprivate final JsonMapper jsonMapper;\n\n\tprotected RegistrationDeserializerTest() {\n\t\tAdminServerModule adminServerModule = new AdminServerModule(new String[] { \".*password$\" });\n\t\tjsonMapper = JsonMapper.builder()\n\t\t\t.addModule(adminServerModule)\n\t\t\t.disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)\n\t\t\t.build();\n\t}\n\n\t@Test\n\tvoid test_1_2_json_format() throws Exception {\n\t\tString json = new JSONObject().put(\"name\", \"test\").put(\"url\", \"https://test\").toString();\n\t\tRegistration value = jsonMapper.readValue(json, Registration.class);\n\t\tassertThat(value.getName()).isEqualTo(\"test\");\n\t\tassertThat(value.getManagementUrl()).isEqualTo(\"https://test\");\n\t\tassertThat(value.getHealthUrl()).isEqualTo(\"https://test/health\");\n\t\tassertThat(value.getServiceUrl()).isNull();\n\t}\n\n\t@Test\n\tvoid test_1_4_json_format() throws Exception {\n\t\tString json = new JSONObject().put(\"name\", \"test\")\n\t\t\t.put(\"managementUrl\", \"https://test\")\n\t\t\t.put(\"healthUrl\", \"https://health\")\n\t\t\t.put(\"serviceUrl\", \"https://service\")\n\t\t\t.put(\"statusInfo\", new JSONObject().put(\"status\", \"UNKNOWN\"))\n\t\t\t.toString();\n\t\tRegistration value = jsonMapper.readValue(json, Registration.class);\n\t\tassertThat(value.getName()).isEqualTo(\"test\");\n\t\tassertThat(value.getManagementUrl()).isEqualTo(\"https://test\");\n\t\tassertThat(value.getHealthUrl()).isEqualTo(\"https://health\");\n\t\tassertThat(value.getServiceUrl()).isEqualTo(\"https://service\");\n\t}\n\n\t@Test\n\tvoid test_1_5_json_format() throws Exception {\n\t\tString json = new JSONObject().put(\"name\", \"test\")\n\t\t\t.put(\"managementUrl\", \"https://test\")\n\t\t\t.put(\"healthUrl\", \"https://health\")\n\t\t\t.put(\"serviceUrl\", \"https://service\")\n\t\t\t.put(\"metadata\", new JSONObject().put(\"labels\", \"foo,bar\"))\n\t\t\t.toString();\n\t\tRegistration value = jsonMapper.readValue(json, Registration.class);\n\t\tassertThat(value.getName()).isEqualTo(\"test\");\n\t\tassertThat(value.getManagementUrl()).isEqualTo(\"https://test\");\n\t\tassertThat(value.getHealthUrl()).isEqualTo(\"https://health\");\n\t\tassertThat(value.getServiceUrl()).isEqualTo(\"https://service\");\n\t\tassertThat(value.getMetadata()).isEqualTo(singletonMap(\"labels\", \"foo,bar\"));\n\t}\n\n\t@Test\n\tvoid test_onlyHealthUrl() throws Exception {\n\t\tString json = new JSONObject().put(\"name\", \"test\").put(\"healthUrl\", \"https://test\").toString();\n\t\tRegistration value = jsonMapper.readValue(json, Registration.class);\n\t\tassertThat(value.getName()).isEqualTo(\"test\");\n\t\tassertThat(value.getHealthUrl()).isEqualTo(\"https://test\");\n\t\tassertThat(value.getManagementUrl()).isNull();\n\t\tassertThat(value.getServiceUrl()).isNull();\n\t}\n\n\t@Test\n\tvoid test_name_expected() throws Exception {\n\t\tString json = new JSONObject().put(\"name\", \"\")\n\t\t\t.put(\"managementUrl\", \"https://test\")\n\t\t\t.put(\"healthUrl\", \"https://health\")\n\t\t\t.put(\"serviceUrl\", \"https://service\")\n\t\t\t.toString();\n\n\t\tassertThatThrownBy(() -> jsonMapper.readValue(json, Registration.class))\n\t\t\t.isInstanceOf(IllegalArgumentException.class);\n\t}\n\n\t@Test\n\tvoid test_healthUrl_expected() throws Exception {\n\t\tString json = new JSONObject().put(\"name\", \"test\")\n\t\t\t.put(\"managementUrl\", \"https://test\")\n\t\t\t.put(\"healthUrl\", \"\")\n\t\t\t.put(\"serviceUrl\", \"https://service\")\n\t\t\t.toString();\n\t\tassertThatThrownBy(() -> jsonMapper.readValue(json, Registration.class))\n\t\t\t.isInstanceOf(IllegalArgumentException.class);\n\t}\n\n\t@Test\n\tvoid test_sanitize_metadata() throws JacksonException {\n\t\tRegistration app = Registration.create(\"test\", \"https://health\")\n\t\t\t.metadata(\"PASSWORD\", \"qwertz123\")\n\t\t\t.metadata(\"user\", \"humptydumpty\")\n\t\t\t.build();\n\t\tString json = jsonMapper.writeValueAsString(app);\n\n\t\tassertThat(json).doesNotContain(\"qwertz123\").contains(\"humptydumpty\");\n\t}\n\n\t@Test\n\tvoid test_snake_case() throws Exception {\n\t\tString json = new JSONObject().put(\"name\", \"test\")\n\t\t\t.put(\"management_url\", \"https://test\")\n\t\t\t.put(\"health_url\", \"https://health\")\n\t\t\t.put(\"service_url\", \"https://service\")\n\t\t\t.put(\"metadata\", new JSONObject().put(\"labels\", \"foo,bar\"))\n\t\t\t.toString();\n\t\tRegistration value = jsonMapper.readValue(json, Registration.class);\n\t\tassertThat(value.getName()).isEqualTo(\"test\");\n\t\tassertThat(value.getManagementUrl()).isEqualTo(\"https://test\");\n\t\tassertThat(value.getHealthUrl()).isEqualTo(\"https://health\");\n\t\tassertThat(value.getServiceUrl()).isEqualTo(\"https://service\");\n\t\tassertThat(value.getMetadata()).isEqualTo(singletonMap(\"labels\", \"foo,bar\"));\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/utils/jackson/StatusInfoMixinTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport java.io.IOException;\nimport java.util.Collections;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.json.JacksonTester;\nimport org.springframework.boot.test.json.JsonContent;\nimport tools.jackson.core.JacksonException;\nimport tools.jackson.databind.DeserializationFeature;\nimport tools.jackson.databind.json.JsonMapper;\n\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.entry;\n\nclass StatusInfoMixinTest {\n\n\tprivate final JsonMapper jsonMapper;\n\n\tprivate JacksonTester<StatusInfo> jsonTester;\n\n\tprotected StatusInfoMixinTest() {\n\t\tAdminServerModule adminServerModule = new AdminServerModule(new String[] { \".*password$\" });\n\t\tjsonMapper = JsonMapper.builder()\n\t\t\t.addModule(adminServerModule)\n\t\t\t.disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)\n\t\t\t.build();\n\t}\n\n\t@BeforeEach\n\tvoid setup() {\n\t\tJacksonTester.initFields(this, jsonMapper);\n\t}\n\n\t@Test\n\tvoid verifyDeserialize() throws JSONException, JacksonException {\n\t\tString json = new JSONObject().put(\"status\", \"OFFLINE\")\n\t\t\t.put(\"details\", new JSONObject().put(\"foo\", \"bar\"))\n\t\t\t.toString();\n\n\t\tStatusInfo statusInfo = jsonMapper.readValue(json, StatusInfo.class);\n\t\tassertThat(statusInfo).isNotNull();\n\t\tassertThat(statusInfo.getStatus()).isEqualTo(\"OFFLINE\");\n\t\tassertThat(statusInfo.getDetails()).containsOnly(entry(\"foo\", \"bar\"));\n\t}\n\n\t@Test\n\tvoid verifySerialize() throws IOException {\n\t\tStatusInfo statusInfo = StatusInfo.valueOf(\"OFFLINE\", Collections.singletonMap(\"foo\", \"bar\"));\n\n\t\tJsonContent<StatusInfo> jsonContent = jsonTester.write(statusInfo);\n\t\tassertThat(jsonContent).extractingJsonPathStringValue(\"$.status\").isEqualTo(\"OFFLINE\");\n\t\tassertThat(jsonContent).extractingJsonPathMapValue(\"$.details\").containsOnly(entry(\"foo\", \"bar\"));\n\t\tassertThat(jsonContent).doesNotHaveJsonPath(\"$.up\")\n\t\t\t.doesNotHaveJsonPath(\"$.offline\")\n\t\t\t.doesNotHaveJsonPath(\"$.down\")\n\t\t\t.doesNotHaveJsonPath(\"$.unknown\");\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/utils/jackson/TagsMixinTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.utils.jackson;\n\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.json.JacksonTester;\nimport org.springframework.boot.test.json.JsonContent;\nimport tools.jackson.core.JacksonException;\nimport tools.jackson.databind.DeserializationFeature;\nimport tools.jackson.databind.json.JsonMapper;\n\nimport de.codecentric.boot.admin.server.domain.values.Tags;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.entry;\n\nclass TagsMixinTest {\n\n\tprivate final JsonMapper jsonMapper;\n\n\tprivate JacksonTester<Tags> jsonTester;\n\n\tprotected TagsMixinTest() {\n\t\tAdminServerModule adminServerModule = new AdminServerModule(new String[] { \".*password$\" });\n\t\tjsonMapper = JsonMapper.builder()\n\t\t\t.addModule(adminServerModule)\n\t\t\t.disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)\n\t\t\t.build();\n\t}\n\n\t@BeforeEach\n\tvoid setup() {\n\t\tJacksonTester.initFields(this, jsonMapper);\n\t}\n\n\t@Test\n\tvoid verifyDeserialize() throws JSONException, JacksonException {\n\t\tString json = new JSONObject().put(\"env\", \"test\").put(\"foo\", \"bar\").toString();\n\n\t\tTags tags = jsonMapper.readValue(json, Tags.class);\n\t\tassertThat(tags).isNotNull();\n\t\tassertThat(tags.getValues()).containsOnly(entry(\"env\", \"test\"), entry(\"foo\", \"bar\"));\n\t}\n\n\t@Test\n\tvoid verifySerialize() throws IOException {\n\t\tMap<String, Object> data = new HashMap<>();\n\t\tdata.put(\"env\", \"test\");\n\t\tdata.put(\"foo\", \"bar\");\n\t\tTags tags = Tags.from(data);\n\n\t\tJsonContent<Tags> jsonContent = jsonTester.write(tags);\n\t\tassertThat(jsonContent).extractingJsonPathMapValue(\"$\").containsOnly(entry(\"env\", \"test\"), entry(\"foo\", \"bar\"));\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/web/AbstractInstancesProxyControllerIntegrationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web;\n\nimport java.time.Duration;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport com.github.tomakehurst.wiremock.WireMockServer;\nimport com.github.tomakehurst.wiremock.core.WireMockConfiguration;\nimport com.github.tomakehurst.wiremock.http.Fault;\nimport org.jetbrains.annotations.NotNull;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.actuate.endpoint.ApiVersion;\nimport org.springframework.context.ConfigurableApplicationContext;\nimport org.springframework.core.ParameterizedTypeReference;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.MediaType;\nimport org.springframework.test.web.reactive.server.WebTestClient;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.aResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.delete;\nimport static com.github.tomakehurst.wiremock.client.WireMock.deleteRequestedFor;\nimport static com.github.tomakehurst.wiremock.client.WireMock.equalTo;\nimport static com.github.tomakehurst.wiremock.client.WireMock.get;\nimport static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;\nimport static com.github.tomakehurst.wiremock.client.WireMock.ok;\nimport static com.github.tomakehurst.wiremock.client.WireMock.options;\nimport static com.github.tomakehurst.wiremock.client.WireMock.post;\nimport static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;\nimport static com.github.tomakehurst.wiremock.client.WireMock.serverError;\nimport static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.springframework.http.HttpHeaders.ALLOW;\nimport static org.springframework.http.HttpHeaders.CONTENT_TYPE;\n\npublic abstract class AbstractInstancesProxyControllerIntegrationTest {\n\n\tprivate static final String ACTUATOR_CONTENT_TYPE = ApiVersion.LATEST.getProducedMimeType().toString()\n\t\t\t+ \";charset=UTF-8\";\n\n\tprivate static final ParameterizedTypeReference<Map<String, Object>> RESPONSE_TYPE = new ParameterizedTypeReference<>() {\n\t};\n\n\tprivate final WireMockServer wireMock = new WireMockServer(\n\t\t\tWireMockConfiguration.options().dynamicPort().extensions(new ConnectionCloseExtension()));\n\n\tprivate WebTestClient client;\n\n\tprivate String instanceId;\n\n\tprivate ConfigurableApplicationContext context;\n\n\t@BeforeAll\n\tpublic static void setUp() {\n\t\tStepVerifier.setDefaultTimeout(Duration.ofSeconds(600));\n\t}\n\n\t@AfterAll\n\tpublic static void tearDown() {\n\t\tStepVerifier.resetDefaultTimeout();\n\t}\n\n\t@BeforeEach\n\tvoid setup() {\n\t\tthis.wireMock.start();\n\t}\n\n\t@AfterEach\n\tvoid teardown() {\n\t\tthis.wireMock.stop();\n\t}\n\n\tprotected void setUpClient(ConfigurableApplicationContext context) {\n\t\tthis.context = context;\n\t\tthis.client = createWebTestClientBuilder().build();\n\t\tthis.instanceId = registerInstance(\"/instance1\");\n\t}\n\n\t@NotNull\n\tprivate WebTestClient.Builder createWebTestClientBuilder() {\n\t\tint localPort = this.context.getEnvironment().getProperty(\"local.server.port\", Integer.class, 0);\n\t\treturn WebTestClient.bindToServer()\n\t\t\t.baseUrl(\"http://localhost:\" + localPort)\n\t\t\t.responseTimeout(Duration.ofSeconds(10));\n\t}\n\n\t@Test\n\tpublic void should_return_status_503() {\n\t\t// 503 on invalid instance\n\t\tthis.client.get()\n\t\t\t.uri(\"/instances/{instanceId}/actuator/info\", \"UNKNOWN\")\n\t\t\t.accept(new MediaType(ApiVersion.LATEST.getProducedMimeType()))\n\t\t\t.exchange()\n\t\t\t.expectStatus()\n\t\t\t.isEqualTo(HttpStatus.SERVICE_UNAVAILABLE);\n\t}\n\n\t@Test\n\tpublic void should_return_status_404() {\n\t\t// 404 on non-existent endpoint\n\t\tthis.client.get()\n\t\t\t.uri(\"/instances/{instanceId}/actuator/not-exist\", this.instanceId)\n\t\t\t.accept(new MediaType(ApiVersion.LATEST.getProducedMimeType()))\n\t\t\t.exchange()\n\t\t\t.expectStatus()\n\t\t\t.isNotFound();\n\t}\n\n\t@Test\n\tpublic void should_return_status_502() {\n\t\t// 502 on invalid response\n\t\tthis.client.get()\n\t\t\t.uri(\"/instances/{instanceId}/actuator/invalid\", this.instanceId)\n\t\t\t.accept(new MediaType(ApiVersion.LATEST.getProducedMimeType()))\n\t\t\t.exchange()\n\t\t\t.expectStatus()\n\t\t\t.isEqualTo(HttpStatus.BAD_GATEWAY);\n\t}\n\n\t@Test\n\tpublic void should_return_status_504() {\n\t\t// 504 on read timeout\n\t\tthis.client.get()\n\t\t\t.uri(\"/instances/{instanceId}/actuator/timeout\", this.instanceId)\n\t\t\t.accept(new MediaType(ApiVersion.LATEST.getProducedMimeType()))\n\t\t\t.exchange()\n\t\t\t.expectStatus()\n\t\t\t.isEqualTo(HttpStatus.GATEWAY_TIMEOUT);\n\t}\n\n\t@Test\n\tpublic void should_forward_requests() {\n\t\tthis.client.options()\n\t\t\t.uri(\"/instances/{instanceId}/actuator/env\", this.instanceId)\n\t\t\t.accept(new MediaType(ApiVersion.LATEST.getProducedMimeType()))\n\t\t\t.exchange()\n\t\t\t.expectStatus()\n\t\t\t.isOk()\n\t\t\t.expectHeader()\n\t\t\t.valueEquals(ALLOW, HttpMethod.HEAD.name(), HttpMethod.GET.name(), HttpMethod.OPTIONS.name());\n\n\t\tthis.client.get()\n\t\t\t.uri(\"/instances/{instanceId}/actuator/test\", this.instanceId)\n\t\t\t.accept(new MediaType(ApiVersion.LATEST.getProducedMimeType()))\n\t\t\t.exchange()\n\t\t\t.expectStatus()\n\t\t\t.isOk()\n\t\t\t.expectBody(String.class)\n\t\t\t.isEqualTo(\"{ \\\"foo\\\" : \\\"bar\\\" }\");\n\n\t\tthis.client.post()\n\t\t\t.uri(\"/instances/{instanceId}/actuator/post\", this.instanceId)\n\t\t\t.bodyValue(\"PAYLOAD\")\n\t\t\t.exchange()\n\t\t\t.expectStatus()\n\t\t\t.isOk();\n\n\t\tthis.wireMock.verify(postRequestedFor(urlEqualTo(\"/instance1/post\")).withRequestBody(equalTo(\"PAYLOAD\")));\n\n\t\tthis.client.delete()\n\t\t\t.uri(\"/instances/{instanceId}/actuator/delete\", this.instanceId)\n\t\t\t.exchange()\n\t\t\t.expectStatus()\n\t\t\t.isEqualTo(500)\n\t\t\t.expectBody(String.class)\n\t\t\t.isEqualTo(\"{\\\"error\\\": \\\"You're doing it wrong!\\\"}\");\n\n\t\tthis.wireMock.verify(deleteRequestedFor(urlEqualTo(\"/instance1/delete\")));\n\t}\n\n\t@Test\n\tpublic void should_forward_requests_with_spaces_in_path() {\n\t\tthis.client.get()\n\t\t\t.uri(\"/instances/{instanceId}/actuator/test/has spaces\", this.instanceId)\n\t\t\t.accept(new MediaType(ApiVersion.LATEST.getProducedMimeType()))\n\t\t\t.exchange()\n\t\t\t.expectStatus()\n\t\t\t.isOk()\n\t\t\t.expectBody(String.class)\n\t\t\t.isEqualTo(\"{ \\\"foo\\\" : \\\"bar-with-spaces\\\" }\");\n\n\t\tthis.wireMock.verify(getRequestedFor(urlEqualTo(\"/instance1/test/has%20spaces\")));\n\t}\n\n\t@Test\n\tpublic void should_forward_requests_to_multiple_instances() {\n\t\tString instance2Id = registerInstance(\"/instance2\");\n\n\t\t//@formatter:off\n\t\tStepVerifier\n\t\t\t.create(this.client.get()\n\t\t\t\t.uri(\"applications/test/actuator/test\")\n\t\t\t\t.accept(new MediaType(ApiVersion.LATEST.getProducedMimeType()))\n\t\t\t\t.exchange()\n\t\t\t\t.returnResult(String.class).getResponseBody().single())\n\t\t\t.assertNext((body) -> {\n\t\t\t\tassertThat(body).contains(\"\\\"instanceId\\\":\\\"\" + this.instanceId + \"\\\"\");\n\t\t\t\tassertThat(body).contains(\"\\\"instanceId\\\":\\\"\" + instance2Id + \"\\\"\");\n\t\t\t\tassertThat(body).contains(\"\\\"status\\\":200\");\n\t\t\t\tassertThat(body).contains(\"{ \\\\\\\"foo\\\\\\\" : \\\\\\\"bar\\\\\\\" }\");\n\t\t\t})\n\t\t\t.verifyComplete();\n\t\t//@formatter:on\n\n\t\tthis.client.post().uri(\"applications/test/actuator/post\").bodyValue(\"PAYLOAD\").exchange().expectStatus().isOk();\n\n\t\tthis.wireMock.verify(postRequestedFor(urlEqualTo(\"/instance1/post\")).withRequestBody(equalTo(\"PAYLOAD\")));\n\t\tthis.wireMock.verify(postRequestedFor(urlEqualTo(\"/instance2/post\")).withRequestBody(equalTo(\"PAYLOAD\")));\n\n\t\t//@formatter:off\n\t\tStepVerifier\n\t\t\t.create(this.client.delete()\n\t\t\t\t.uri(\"applications/test/actuator/delete\")\n\t\t\t\t.exchange()\n\t\t\t\t.returnResult(String.class).getResponseBody().single())\n\t\t\t.assertNext((body) -> {\n\t\t\t\tassertThat(body).contains(\"\\\"instanceId\\\":\\\"\" + this.instanceId + \"\\\"\");\n\t\t\t\tassertThat(body).contains(\"\\\"instanceId\\\":\\\"\" + instance2Id + \"\\\"\");\n\t\t\t\tassertThat(body).contains(\"\\\"status\\\":500\");\n\t\t\t\tassertThat(body).contains(\"{\\\\\\\"error\\\\\\\": \\\\\\\"You're doing it wrong!\\\\\\\"}\");\n\t\t\t})\n\t\t\t.verifyComplete();\n\t\t//@formatter:on\n\n\t\tthis.wireMock.verify(deleteRequestedFor(urlEqualTo(\"/instance1/delete\")));\n\t\tthis.wireMock.verify(deleteRequestedFor(urlEqualTo(\"/instance2/delete\")));\n\t}\n\n\tprivate void stubForInstance(String managementPath) {\n\t\tString managementUrl = this.wireMock.url(managementPath);\n\n\t\t//@formatter:off\n\t\tString actuatorIndex = \"{ \\\"_links\\\": { \" +\n\t\t\t\"\\\"env\\\": { \\\"href\\\": \\\"\" + managementUrl + \"/env\\\", \\\"templated\\\": false },\" +\n\t\t\t\"\\\"test\\\": { \\\"href\\\": \\\"\" + managementUrl + \"/test\\\", \\\"templated\\\": false },\" +\n\t\t\t\"\\\"post\\\": { \\\"href\\\": \\\"\" + managementUrl + \"/post\\\", \\\"templated\\\": false },\" +\n\t\t\t\"\\\"delete\\\": { \\\"href\\\": \\\"\" + managementUrl + \"/delete\\\", \\\"templated\\\": false },\" +\n\t\t\t\"\\\"invalid\\\": { \\\"href\\\": \\\"\" + managementUrl + \"/invalid\\\", \\\"templated\\\": false },\" +\n\t\t\t\"\\\"timeout\\\": { \\\"href\\\": \\\"\" + managementUrl + \"/timeout\\\", \\\"templated\\\": false }\" +\n\t\t\t\" } }\";\n\t\t//@formatter:on\n\t\tthis.wireMock.stubFor(get(urlEqualTo(managementPath + \"/health\")).willReturn(ok(\"{ \\\"status\\\" : \\\"UP\\\" }\")\n\t\t\t.withHeader(CONTENT_TYPE, ApiVersion.LATEST.getProducedMimeType().toString())));\n\t\tthis.wireMock.stubFor(get(urlEqualTo(managementPath + \"/info\"))\n\t\t\t.willReturn(ok(\"{ }\").withHeader(CONTENT_TYPE, ACTUATOR_CONTENT_TYPE)));\n\t\tthis.wireMock.stubFor(options(urlEqualTo(managementPath + \"/env\")).willReturn(\n\t\t\t\tok().withHeader(ALLOW, HttpMethod.HEAD.name(), HttpMethod.GET.name(), HttpMethod.OPTIONS.name())));\n\t\tthis.wireMock.stubFor(get(urlEqualTo(managementPath))\n\t\t\t.willReturn(ok(actuatorIndex).withHeader(CONTENT_TYPE, ACTUATOR_CONTENT_TYPE)));\n\t\tthis.wireMock.stubFor(get(urlEqualTo(managementPath + \"/invalid\"))\n\t\t\t.willReturn(aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER)));\n\t\tthis.wireMock.stubFor(get(urlEqualTo(managementPath + \"/timeout\")).willReturn(ok().withFixedDelay(10000)));\n\t\tthis.wireMock.stubFor(get(urlEqualTo(managementPath + \"/test\"))\n\t\t\t.willReturn(ok(\"{ \\\"foo\\\" : \\\"bar\\\" }\").withHeader(CONTENT_TYPE, ACTUATOR_CONTENT_TYPE)));\n\t\tthis.wireMock.stubFor(get(urlEqualTo(managementPath + \"/test/has%20spaces\"))\n\t\t\t.willReturn(ok(\"{ \\\"foo\\\" : \\\"bar-with-spaces\\\" }\").withHeader(CONTENT_TYPE, ACTUATOR_CONTENT_TYPE)));\n\t\tthis.wireMock.stubFor(post(urlEqualTo(managementPath + \"/post\")).willReturn(ok()));\n\t\tthis.wireMock.stubFor(delete(urlEqualTo(managementPath + \"/delete\"))\n\t\t\t.willReturn(serverError().withBody(\"{\\\"error\\\": \\\"You're doing it wrong!\\\"}\")\n\t\t\t\t.withHeader(CONTENT_TYPE, ACTUATOR_CONTENT_TYPE)));\n\t}\n\n\tprivate String registerInstance(String managementPath) {\n\t\tstubForInstance(managementPath);\n\n\t\tAtomicReference<String> instanceIdRef = new AtomicReference<>();\n\t\tStepVerifier.create(getEventStream())\n\t\t\t.expectSubscription()\n\t\t\t.then(() -> StepVerifier.create(sendRegistration(managementPath))\n\t\t\t\t.consumeNextWith(instanceIdRef::set)\n\t\t\t\t.verifyComplete())\n\t\t\t.thenConsumeWhile((event) -> !event.get(\"type\").equals(\"ENDPOINTS_DETECTED\"))\n\t\t\t.assertNext((event) -> assertThat(event).containsEntry(\"type\", \"ENDPOINTS_DETECTED\"))\n\t\t\t.thenCancel()\n\t\t\t.verify();\n\t\treturn instanceIdRef.get();\n\t}\n\n\tprivate Mono<String> sendRegistration(String managementPath) {\n\t\tString managementUrl = this.wireMock.url(managementPath);\n\n\t\t//@formatter:off\n\t\tString registration = \"{ \\\"name\\\": \\\"test\\\", \" +\n\t\t\t\"\\\"healthUrl\\\": \\\"\" + managementUrl + \"/health\\\", \" +\n\t\t\t\"\\\"managementUrl\\\": \\\"\" + managementUrl + \"\\\" }\";\n\n\t\treturn this.client.post()\n\t\t\t.uri(\"/instances\")\n\t\t\t.accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON)\n\t\t\t.bodyValue(registration)\n\t\t\t.exchange()\n\t\t\t.expectStatus().isCreated()\n\t\t\t.returnResult(RESPONSE_TYPE).getResponseBody().single()\n\t\t\t.map((body) -> {\n\t\t\t\tassertThat(body).containsKeys(\"id\");\n\t\t\t\treturn body.get(\"id\").toString();\n\t\t\t});\n\t\t//@formatter:on\n\t}\n\n\tprivate Flux<Map<String, Object>> getEventStream() {\n\t\t//@formatter:off\n\t\treturn this.client.get().uri(\"/instances/events\").accept(MediaType.TEXT_EVENT_STREAM)\n\t\t\t.exchange()\n\t\t\t.expectStatus().isOk()\n\t\t\t.returnResult(RESPONSE_TYPE).getResponseBody();\n\t\t//@formatter:on\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/web/ConnectionCloseExtension.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web;\n\nimport com.github.tomakehurst.wiremock.extension.ResponseTransformerV2;\nimport com.github.tomakehurst.wiremock.http.HttpHeader;\nimport com.github.tomakehurst.wiremock.http.HttpHeaders;\nimport com.github.tomakehurst.wiremock.http.Response;\nimport com.github.tomakehurst.wiremock.stubbing.ServeEvent;\n\n// Force the connections to be closed...\n// see https://github.com/tomakehurst/wiremock/issues/485\nclass ConnectionCloseExtension implements ResponseTransformerV2 {\n\n\t@Override\n\tpublic Response transform(Response response, ServeEvent serveEvent) {\n\t\treturn Response.Builder.like(response)\n\t\t\t.headers(HttpHeaders.copyOf(response.getHeaders()).plus(new HttpHeader(\"Connection\", \"Close\")))\n\t\t\t.build();\n\t}\n\n\t@Override\n\tpublic String getName() {\n\t\treturn \"ConnectionCloseExtension\";\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/web/InstancesControllerIntegrationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web;\n\nimport java.time.Duration;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport com.jayway.jsonpath.DocumentContext;\nimport com.jayway.jsonpath.JsonPath;\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.WebApplicationType;\nimport org.springframework.boot.builder.SpringApplicationBuilder;\nimport org.springframework.context.ConfigurableApplicationContext;\nimport org.springframework.core.ParameterizedTypeReference;\nimport org.springframework.http.MediaType;\nimport org.springframework.test.web.reactive.server.WebTestClient;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.AdminReactiveApplicationTest;\n\nimport static java.util.Collections.emptyList;\nimport static java.util.Collections.singletonMap;\nimport static org.assertj.core.api.Assertions.assertThat;\n\n@Slf4j\nclass InstancesControllerIntegrationTest {\n\n\tprivate int localPort;\n\n\tprivate WebTestClient client;\n\n\tprivate String registerAsTest;\n\n\tprivate String registerAsTwice;\n\n\tprivate ConfigurableApplicationContext instance;\n\n\tprivate final ParameterizedTypeReference<Map<String, Object>> responseType = new ParameterizedTypeReference<>() {\n\t};\n\n\t@AfterAll\n\tstatic void tearDown() {\n\t\tStepVerifier.resetDefaultTimeout();\n\t}\n\n\t@BeforeAll\n\tstatic void beforeAll() {\n\t\tStepVerifier.setDefaultTimeout(Duration.ofSeconds(600));\n\t}\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tinstance = new SpringApplicationBuilder().sources(AdminReactiveApplicationTest.TestAdminApplication.class)\n\t\t\t.web(WebApplicationType.REACTIVE)\n\t\t\t.run(\"--server.port=0\", \"--eureka.client.enabled=false\");\n\n\t\tlocalPort = instance.getEnvironment().getProperty(\"local.server.port\", Integer.class, 0);\n\n\t\tthis.client = WebTestClient.bindToServer().baseUrl(\"http://localhost:\" + localPort).build();\n\t\tthis.registerAsTest = \"{ \\\"name\\\": \\\"test\\\", \\\"healthUrl\\\": \\\"http://localhost:\" + localPort\n\t\t\t\t+ \"/application/health\\\" }\";\n\t\tthis.registerAsTwice = \"{ \\\"name\\\": \\\"twice\\\", \\\"healthUrl\\\": \\\"http://localhost:\" + localPort\n\t\t\t\t+ \"/application/health\\\" }\";\n\t}\n\n\t@AfterEach\n\tvoid shutdown() {\n\t\tinstance.close();\n\t}\n\n\t@Test\n\tvoid should_return_not_found_when_get_unknown_instance() {\n\t\tthis.client.get().uri(\"/instances/unknown\").exchange().expectStatus().isNotFound();\n\t}\n\n\t@Test\n\tvoid should_return_empty_list() {\n\t\tthis.client.get()\n\t\t\t.uri(\"/instances?name=unknown\")\n\t\t\t.exchange()\n\t\t\t.expectStatus()\n\t\t\t.isOk()\n\t\t\t.expectBody(java.util.List.class)\n\t\t\t.isEqualTo(emptyList());\n\t}\n\n\t@Test\n\tvoid should_return_not_found_when_deleting_unknown_instance() {\n\t\tthis.client.delete().uri(\"/instances/unknown\").exchange().expectStatus().isNotFound();\n\t}\n\n\t@Test\n\tvoid should_return_registered_instances() {\n\t\tAtomicReference<String> id = new AtomicReference<>();\n\n\t\tStepVerifier.create(this.getEventStream().log())\n\t\t\t.expectSubscription()\n\t\t\t.then(() -> StepVerifier.create(register()).consumeNextWith(id::set).verifyComplete())\n\t\t\t.assertNext((body) -> {\n\t\t\t\tassertThat(body).containsEntry(\"version\", 0).containsEntry(\"type\", \"REGISTERED\");\n\t\t\t\t// The id might not be set yet if event arrives before registration\n\t\t\t\t// completes\n\t\t\t\tif (id.get() == null) {\n\t\t\t\t\tid.set((String) body.get(\"instance\"));\n\t\t\t\t}\n\t\t\t\tassertThat(body).containsEntry(\"instance\", id.get());\n\t\t\t})\n\t\t\t.then(() -> {\n\t\t\t\tStepVerifier.create(assertInstances(id.get())).expectNext(true).verifyComplete();\n\t\t\t\tStepVerifier.create(assertInstancesByName(\"test\", id.get())).expectNext(true).verifyComplete();\n\t\t\t\tStepVerifier.create(assertInstanceById(id.get())).expectNext(true).verifyComplete();\n\t\t\t})\n\t\t\t.assertNext((body) -> assertThat(body).containsEntry(\"instance\", id.get())\n\t\t\t\t.containsEntry(\"version\", 1)\n\t\t\t\t.containsEntry(\"type\", \"STATUS_CHANGED\"))\n\t\t\t.then(() -> StepVerifier.create(registerSecondTime(id.get())).expectNext(true).verifyComplete())\n\t\t\t.assertNext((body) -> assertThat(body).containsEntry(\"instance\", id.get())\n\t\t\t\t.containsEntry(\"version\", 2)\n\t\t\t\t.containsEntry(\"type\", \"REGISTRATION_UPDATED\"))\n\t\t\t.then(() -> StepVerifier.create(deregister(id.get())).expectNext(true).verifyComplete())\n\t\t\t.assertNext((body) -> assertThat(body).containsEntry(\"instance\", id.get())\n\t\t\t\t.containsEntry(\"version\", 3)\n\t\t\t\t.containsEntry(\"type\", \"DEREGISTERED\"))\n\t\t\t.then(() -> {\n\t\t\t\tStepVerifier.create(assertInstanceNotFound(id.get())).expectNext(true).verifyComplete();\n\t\t\t\tStepVerifier.create(assertEvents(id.get())).expectNext(true).verifyComplete();\n\t\t\t})\n\t\t\t.thenCancel()\n\t\t\t.verify();\n\t}\n\n\tprivate Mono<Boolean> assertEvents(String id) {\n\t\t//@formatter:off\n\t\treturn this.client.get()\n\t\t\t.uri(\"/instances/events\")\n\t\t\t.accept(MediaType.APPLICATION_JSON)\n\t\t\t.exchange()\n\t\t\t.returnResult(String.class).getResponseBody().single()\n\t\t\t.map((responseBody) -> {\n\t\t\t\tDocumentContext json = JsonPath.parse(responseBody);\n\t\t\t\tassertThat(json.read(\"$[0].instance\", String.class)).isEqualTo(id);\n\t\t\t\tassertThat(json.read(\"$[0].version\", Long.class)).isZero();\n\t\t\t\tassertThat(json.read(\"$[0].type\", String.class)).isEqualTo(\"REGISTERED\");\n\t\t\t\tassertThat(json.read(\"$[1].instance\", String.class)).isEqualTo(id);\n\t\t\t\tassertThat(json.read(\"$[1].version\", Long.class)).isEqualTo(1L);\n\t\t\t\tassertThat(json.read(\"$[1].type\", String.class)).isEqualTo(\"STATUS_CHANGED\");\n\t\t\t\tassertThat(json.read(\"$[2].instance\", String.class)).isEqualTo(id);\n\t\t\t\tassertThat(json.read(\"$[2].version\", Long.class)).isEqualTo(2L);\n\t\t\t\tassertThat(json.read(\"$[2].type\", String.class)).isEqualTo(\"REGISTRATION_UPDATED\");\n\t\t\t\tassertThat(json.read(\"$[3].instance\", String.class)).isEqualTo(id);\n\t\t\t\tassertThat(json.read(\"$[3].version\", Long.class)).isEqualTo(3L);\n\t\t\t\tassertThat(json.read(\"$[3].type\", String.class)).isEqualTo(\"DEREGISTERED\");\n\t\t\t\treturn true;\n\t\t\t});\n\t\t//@formatter:on\n\t}\n\n\tprivate Mono<Boolean> assertInstanceNotFound(String id) {\n\t\t//@formatter:off\n\t\treturn this.client.get()\n\t\t\t.uri(getLocation(id))\n\t\t\t.exchange()\n\t\t\t.expectStatus().isNotFound()\n\t\t\t.returnResult(Void.class).getResponseBody()\n\t\t\t.then(Mono.just(true));\n\t\t//@formatter:on\n\t}\n\n\tprivate Mono<Boolean> deregister(String id) {\n\t\t//@formatter:off\n\t\treturn this.client.delete()\n\t\t\t.uri(getLocation(id))\n\t\t\t.exchange()\n\t\t\t.expectStatus().isNoContent()\n\t\t\t.returnResult(Void.class).getResponseBody()\n\t\t\t.then(Mono.just(true));\n\t\t//@formatter:on\n\t}\n\n\tprivate Mono<Boolean> assertInstanceById(String id) {\n\t\t//@formatter:off\n\t\treturn this.client.get()\n\t\t\t.uri(getLocation(id))\n\t\t\t.accept(MediaType.APPLICATION_JSON)\n\t\t\t.exchange()\n\t\t\t.returnResult(String.class).getResponseBody().single()\n\t\t\t.map((body) -> {\n\t\t\t\tDocumentContext json = JsonPath.parse(body);\n\t\t\t\tassertThat(json.read(\"$.id\", String.class)).isEqualTo(id);\n\t\t\t\treturn true;\n\t\t\t});\n\t\t//@formatter:on\n\t}\n\n\tprivate Mono<Boolean> assertInstancesByName(String name, String id) {\n\t\t//@formatter:off\n\t\treturn this.client.get()\n\t\t\t.uri(\"/instances?name=\" + name)\n\t\t\t.accept(MediaType.APPLICATION_JSON)\n\t\t\t.exchange()\n\t\t\t.returnResult(String.class).getResponseBody().single()\n\t\t\t.map((body) -> {\n\t\t\t\tDocumentContext json = JsonPath.parse(body);\n\t\t\t\tassertThat(json.read(\"$[0].id\", String.class)).isEqualTo(id);\n\t\t\t\treturn true;\n\t\t\t});\n\t\t//@formatter:on\n\t}\n\n\tprivate Mono<Boolean> assertInstances(String id) {\n\t\t//@formatter:off\n\t\treturn this.client.get()\n\t\t\t.uri(\"/instances\")\n\t\t\t.accept(MediaType.APPLICATION_JSON)\n\t\t\t.exchange()\n\t\t\t.returnResult(String.class).getResponseBody().single()\n\t\t\t.map((body) -> {\n\t\t\t\tDocumentContext json = JsonPath.parse(body);\n\t\t\t\tassertThat(json.read(\"$[0].id\", String.class)).isEqualTo(id);\n\t\t\t\treturn true;\n\t\t\t});\n\t\t//@formatter:on\n\t}\n\n\tprivate Mono<Boolean> registerSecondTime(String id) {\n\t\t//@formatter:off\n\t\treturn this.client.post()\n\t\t\t.uri(\"/instances\")\n\t\t\t.accept(MediaType.APPLICATION_JSON)\n\t\t\t.contentType(MediaType.APPLICATION_JSON)\n\t\t\t.bodyValue(registerAsTwice)\n\t\t\t.exchange()\n\t\t\t.expectStatus().isCreated()\n\t\t\t.expectHeader().valueEquals(\"location\", getLocation(id))\n\t\t\t.returnResult(responseType).getResponseBody().single()\n\t\t\t.map((body) -> {\n\t\t\t\tassertThat(body).isEqualTo(singletonMap(\"id\", id));\n\t\t\t\treturn true;\n\t\t\t});\n\t\t//@formatter:on\n\t}\n\n\tprivate Mono<String> register() {\n\t\t//@formatter:off\n\t\treturn this.client.post()\n\t\t\t.uri(\"/instances\")\n\t\t\t.accept(MediaType.APPLICATION_JSON)\n\t\t\t.contentType(MediaType.APPLICATION_JSON)\n\t\t\t.bodyValue(registerAsTest)\n\t\t\t.exchange()\n\t\t\t.expectStatus().isCreated()\n\t\t\t.expectHeader().valueMatches(\"location\", \"http://localhost:\" + localPort + \"/instances/[0-9a-f]+\")\n\t\t\t.returnResult(responseType).getResponseBody().single()\n\t\t\t.map((body) -> {\n\t\t\t\tassertThat(body).containsKeys(\"id\");\n\t\t\t\treturn body.get(\"id\").toString();\n\t\t\t});\n\t\t//@formatter:on\n\t}\n\n\tprivate String getLocation(String id) {\n\t\treturn \"http://localhost:\" + localPort + \"/instances/\" + id;\n\n\t}\n\n\tprivate Flux<Map<String, Object>> getEventStream() {\n\t\t//@formatter:off\n\t\treturn this.client.get().uri(\"/instances/events\").accept(MediaType.TEXT_EVENT_STREAM)\n\t\t\t\t\t\t.exchange()\n\t\t\t\t\t\t.expectStatus().isOk()\n\t\t\t\t\t\t.expectHeader().contentTypeCompatibleWith(MediaType.TEXT_EVENT_STREAM)\n\t\t\t\t\t\t.returnResult(responseType).getResponseBody();\n\t\t//@formatter:on\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/web/PathUtilsTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass PathUtilsTest {\n\n\t@Test\n\tvoid normalizePath() {\n\t\tassertThat(PathUtils.normalizePath(null)).isNull();\n\t\tassertThat(PathUtils.normalizePath(\"\")).isEmpty();\n\t\tassertThat(PathUtils.normalizePath(\"/\")).isEmpty();\n\t\tassertThat(PathUtils.normalizePath(\"admin\")).isEqualTo(\"/admin\");\n\t\tassertThat(PathUtils.normalizePath(\"/admin\")).isEqualTo(\"/admin\");\n\t\tassertThat(PathUtils.normalizePath(\"/admin/\")).isEqualTo(\"/admin\");\n\t\tassertThat(PathUtils.normalizePath(\"/admin/\")).isEqualTo(\"/admin\");\n\t\tassertThat(PathUtils.normalizePath(\"//admin/\")).isEqualTo(\"/admin\");\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/web/client/BasicAuthHttpHeaderProviderTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client;\n\nimport java.util.Collections;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.http.HttpHeaders;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.web.client.BasicAuthHttpHeaderProvider.InstanceCredentials;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass BasicAuthHttpHeaderProviderTest {\n\n\tprivate final BasicAuthHttpHeaderProvider headersProvider = new BasicAuthHttpHeaderProvider();\n\n\tprivate final BasicAuthHttpHeaderProvider headersProviderEnableInstanceAuth = new BasicAuthHttpHeaderProvider(\n\t\t\t\"client\", \"client\", Collections.singletonMap(\"sb-admin-server\", new InstanceCredentials(\"admin\", \"admin\")));\n\n\t@Test\n\tvoid test_auth_header() {\n\t\tRegistration registration = Registration.create(\"foo\", \"https://health\")\n\t\t\t.metadata(\"user.name\", \"test\")\n\t\t\t.metadata(\"user.password\", \"drowssap\")\n\t\t\t.build();\n\t\tInstance instance = Instance.create(InstanceId.of(\"id\")).register(registration);\n\t\tassertThat(this.headersProvider.getHeaders(instance).get(HttpHeaders.AUTHORIZATION))\n\t\t\t.containsOnly(\"Basic dGVzdDpkcm93c3NhcA==\");\n\t}\n\n\t@Test\n\tvoid test_auth_header_with_dashes() {\n\t\tRegistration registration = Registration.create(\"foo\", \"https://health\")\n\t\t\t.metadata(\"user-name\", \"test\")\n\t\t\t.metadata(\"user-password\", \"drowssap\")\n\t\t\t.build();\n\t\tInstance instance = Instance.create(InstanceId.of(\"id\")).register(registration);\n\t\tassertThat(this.headersProvider.getHeaders(instance).get(HttpHeaders.AUTHORIZATION))\n\t\t\t.containsOnly(\"Basic dGVzdDpkcm93c3NhcA==\");\n\t}\n\n\t@Test\n\tvoid test_auth_header_no_separator() {\n\t\tRegistration registration = Registration.create(\"foo\", \"https://health\")\n\t\t\t.metadata(\"username\", \"test\")\n\t\t\t.metadata(\"userpassword\", \"drowssap\")\n\t\t\t.build();\n\t\tInstance instance = Instance.create(InstanceId.of(\"id\")).register(registration);\n\t\tassertThat(this.headersProvider.getHeaders(instance).get(HttpHeaders.AUTHORIZATION))\n\t\t\t.containsOnly(\"Basic dGVzdDpkcm93c3NhcA==\");\n\t}\n\n\t@Test\n\tvoid test_no_header() {\n\t\tRegistration registration = Registration.create(\"foo\", \"https://health\").build();\n\t\tInstance instance = Instance.create(InstanceId.of(\"id\")).register(registration);\n\t\tassertThat(this.headersProvider.getHeaders(instance).toSingleValueMap()).isEmpty();\n\t}\n\n\t@Test\n\tvoid test_auth_instance_enabled_use_default_creds() {\n\t\tRegistration registration = Registration.create(\"foo\", \"https://health\").name(\"xyz-server\").build();\n\t\tInstance instance = Instance.create(InstanceId.of(\"id\")).register(registration);\n\t\tassertThat(this.headersProviderEnableInstanceAuth.getHeaders(instance).get(HttpHeaders.AUTHORIZATION))\n\t\t\t.containsOnly(\"Basic Y2xpZW50OmNsaWVudA==\");\n\t}\n\n\t@Test\n\tvoid test_auth_instance_enabled_use_service_specific_creds() {\n\t\tRegistration registration = Registration.create(\"foo\", \"https://health\").name(\"sb-admin-server\").build();\n\t\tInstance instance = Instance.create(InstanceId.of(\"id\")).register(registration);\n\t\tassertThat(this.headersProviderEnableInstanceAuth.getHeaders(instance).get(HttpHeaders.AUTHORIZATION))\n\t\t\t.containsOnly(\"Basic YWRtaW46YWRtaW4=\");\n\t}\n\n\t@Test\n\tvoid test_auth_instance_enabled_use_metadata_over_props() {\n\t\tRegistration registration = Registration.create(\"foo\", \"https://health\")\n\t\t\t.metadata(\"username\", \"test\")\n\t\t\t.metadata(\"userpassword\", \"drowssap\")\n\t\t\t.name(\"xyz-server\")\n\t\t\t.build();\n\t\tInstance instance = Instance.create(InstanceId.of(\"id\")).register(registration);\n\t\tassertThat(this.headersProviderEnableInstanceAuth.getHeaders(instance).get(HttpHeaders.AUTHORIZATION))\n\t\t\t.containsOnly(\"Basic dGVzdDpkcm93c3NhcA==\");\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/web/client/CloudFoundryHttpHeaderProviderTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client;\n\nimport org.junit.jupiter.api.Test;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass CloudFoundryHttpHeaderProviderTest {\n\n\tprivate final CloudFoundryHttpHeaderProvider headersProvider = new CloudFoundryHttpHeaderProvider();\n\n\t@Test\n\tvoid test_cloud_foundry_header() {\n\t\tRegistration registration = Registration.create(\"foo\", \"https://health\")\n\t\t\t.metadata(\"applicationId\", \"549e64cf-a478-423d-9d6d-02d803a028a8\")\n\t\t\t.metadata(\"instanceId\", \"0\")\n\t\t\t.build();\n\t\tInstance instance = Instance.create(InstanceId.of(\"id\")).register(registration);\n\t\tassertThat(headersProvider.getHeaders(instance).get(\"X-CF-APP-INSTANCE\"))\n\t\t\t.containsOnly(\"549e64cf-a478-423d-9d6d-02d803a028a8:0\");\n\t}\n\n\t@Test\n\tvoid test_no_header() {\n\t\tRegistration registration = Registration.create(\"foo\", \"https://health\").build();\n\t\tInstance instance = Instance.create(InstanceId.of(\"id\")).register(registration);\n\t\tassertThat(headersProvider.getHeaders(instance).toSingleValueMap()).isEmpty();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/web/client/CompositeHttpHeadersProviderTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.http.HttpHeaders;\n\nimport static java.util.Arrays.asList;\nimport static java.util.Collections.emptyList;\nimport static java.util.Collections.singletonList;\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass CompositeHttpHeadersProviderTest {\n\n\t@Test\n\tvoid should_return_all_headers() {\n\t\tHttpHeadersProvider provider = new CompositeHttpHeadersProvider(asList((i) -> {\n\t\t\tHttpHeaders headers = new HttpHeaders();\n\t\t\theaders.set(\"a\", \"1\");\n\t\t\theaders.set(\"b\", \"2-a\");\n\t\t\treturn headers;\n\t\t}, (i) -> {\n\t\t\tHttpHeaders headers = new HttpHeaders();\n\t\t\theaders.set(\"b\", \"2-b\");\n\t\t\theaders.set(\"c\", \"3\");\n\t\t\treturn headers;\n\t\t}));\n\n\t\tHttpHeaders headers = provider.getHeaders(null);\n\t\tassertThat(headers.asMultiValueMap()).containsEntry(\"a\", singletonList(\"1\"))\n\t\t\t.containsEntry(\"b\", asList(\"2-a\", \"2-b\"))\n\t\t\t.containsEntry(\"c\", singletonList(\"3\"));\n\t}\n\n\t@Test\n\tvoid should_return_empty_headers() {\n\t\tHttpHeadersProvider provider = new CompositeHttpHeadersProvider(emptyList());\n\t\tHttpHeaders headers = provider.getHeaders(null);\n\t\tassertThat(headers.toSingleValueMap()).isEmpty();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/web/client/InstanceExchangeFilterFunctionsTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client;\n\nimport java.net.URI;\nimport java.nio.charset.StandardCharsets;\nimport java.time.Duration;\nimport java.util.concurrent.TimeoutException;\nimport java.util.concurrent.atomic.AtomicLong;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentCaptor;\nimport org.springframework.boot.actuate.endpoint.ApiVersion;\nimport org.springframework.core.io.buffer.DataBuffer;\nimport org.springframework.core.io.buffer.DefaultDataBufferFactory;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.MediaType;\nimport org.springframework.util.LinkedMultiValueMap;\nimport org.springframework.util.MultiValueMap;\nimport org.springframework.web.reactive.function.BodyExtractors;\nimport org.springframework.web.reactive.function.client.ClientRequest;\nimport org.springframework.web.reactive.function.client.ClientResponse;\nimport org.springframework.web.reactive.function.client.ExchangeFunction;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.values.Endpoint;\nimport de.codecentric.boot.admin.server.domain.values.Endpoints;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.web.client.cookies.PerInstanceCookieStore;\nimport de.codecentric.boot.admin.server.web.client.exception.ResolveEndpointException;\n\nimport static de.codecentric.boot.admin.server.web.client.InstanceExchangeFilterFunctions.ATTRIBUTE_ENDPOINT;\nimport static de.codecentric.boot.admin.server.web.client.InstanceWebClient.ATTRIBUTE_INSTANCE;\nimport static java.util.Collections.emptyMap;\nimport static java.util.Collections.singletonList;\nimport static java.util.Collections.singletonMap;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\nimport static org.springframework.http.HttpHeaders.ACCEPT;\nimport static org.springframework.http.HttpHeaders.CONTENT_LENGTH;\nimport static org.springframework.http.HttpHeaders.CONTENT_TYPE;\nimport static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;\nimport static org.springframework.http.MediaType.TEXT_PLAIN_VALUE;\n\nclass InstanceExchangeFilterFunctionsTest {\n\n\tprivate static final Instance INSTANCE = Instance.create(InstanceId.of(\"i\"));\n\n\t@Nested\n\tclass ConvertLegacyEndpoints {\n\n\t\tprivate final DefaultDataBufferFactory bufferFactory = new DefaultDataBufferFactory();\n\n\t\tprivate final DataBuffer original = this.bufferFactory.wrap(\"ORIGINAL\".getBytes(StandardCharsets.UTF_8));\n\n\t\tprivate final DataBuffer converted = this.bufferFactory.wrap(\"CONVERTED\".getBytes(StandardCharsets.UTF_8));\n\n\t\tprivate final InstanceExchangeFilterFunction filter = InstanceExchangeFilterFunctions.convertLegacyEndpoints(\n\t\t\t\tsingletonList(new LegacyEndpointConverter(\"test\", (from) -> Flux.just(this.converted)) {\n\t\t\t\t}));\n\n\t\t@Test\n\t\tvoid should_convert_v1_actuator() {\n\t\t\tClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create(\"/test\"))\n\t\t\t\t.attribute(ATTRIBUTE_ENDPOINT, \"test\")\n\t\t\t\t.build();\n\t\t\tClientResponse legacyResponse = ClientResponse.create(HttpStatus.OK)\n\t\t\t\t.header(CONTENT_TYPE, InstanceExchangeFilterFunctions.V1_ACTUATOR_JSON.toString())\n\t\t\t\t.header(CONTENT_LENGTH, Integer.toString(this.original.readableByteCount()))\n\t\t\t\t.body(Flux.just(this.original))\n\t\t\t\t.build();\n\n\t\t\tMono<ClientResponse> response = this.filter.filter(INSTANCE, request, (r) -> Mono.just(legacyResponse));\n\n\t\t\tStepVerifier.create(response).assertNext((r) -> {\n\t\t\t\tassertThat(r.headers().contentType()).hasValue(new MediaType(ApiVersion.LATEST.getProducedMimeType()));\n\t\t\t\tassertThat(r.headers().contentLength()).isEmpty();\n\t\t\t\tStepVerifier.create(r.body(BodyExtractors.toDataBuffers())).expectNext(this.converted).verifyComplete();\n\t\t\t}).verifyComplete();\n\t\t}\n\n\t\t@Test\n\t\tvoid should_convert_json() {\n\t\t\tClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create(\"/test\"))\n\t\t\t\t.attribute(ATTRIBUTE_ENDPOINT, \"test\")\n\t\t\t\t.build();\n\t\t\tClientResponse legacyResponse = ClientResponse.create(HttpStatus.OK)\n\t\t\t\t.header(CONTENT_TYPE, APPLICATION_JSON_VALUE)\n\t\t\t\t.header(CONTENT_LENGTH, Integer.toString(this.original.readableByteCount()))\n\t\t\t\t.body(Flux.just(this.original))\n\t\t\t\t.build();\n\n\t\t\tMono<ClientResponse> response = this.filter.filter(INSTANCE, request, (r) -> Mono.just(legacyResponse));\n\n\t\t\tStepVerifier.create(response).assertNext((r) -> {\n\t\t\t\tassertThat(r.headers().contentType()).hasValue(new MediaType(ApiVersion.LATEST.getProducedMimeType()));\n\t\t\t\tassertThat(r.headers().contentLength()).isEmpty();\n\t\t\t\tStepVerifier.create(r.body(BodyExtractors.toDataBuffers())).expectNext(this.converted).verifyComplete();\n\t\t\t}).verifyComplete();\n\t\t}\n\n\t\t@Test\n\t\tvoid should_not_convert_v2_actuator() {\n\t\t\tInstanceExchangeFilterFunction filter = InstanceExchangeFilterFunctions.convertLegacyEndpoints(\n\t\t\t\t\tsingletonList(new LegacyEndpointConverter(\"test\", (from) -> Flux.just(this.converted)) {\n\t\t\t\t\t}));\n\n\t\t\tClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create(\"/test\"))\n\t\t\t\t.attribute(ATTRIBUTE_ENDPOINT, \"test\")\n\t\t\t\t.build();\n\t\t\tClientResponse response = ClientResponse.create(HttpStatus.OK)\n\t\t\t\t.header(CONTENT_TYPE, ApiVersion.LATEST.getProducedMimeType().toString())\n\t\t\t\t.header(CONTENT_LENGTH, Integer.toString(this.original.readableByteCount()))\n\t\t\t\t.body(Flux.just(this.original))\n\t\t\t\t.build();\n\n\t\t\tMono<ClientResponse> convertedResponse = filter.filter(INSTANCE, request, (r) -> Mono.just(response));\n\n\t\t\tStepVerifier.create(convertedResponse).assertNext((r) -> {\n\t\t\t\tassertThat(r.headers().contentType()).hasValue(new MediaType(ApiVersion.LATEST.getProducedMimeType()));\n\t\t\t\tassertThat(r.headers().contentLength()).hasValue(this.original.readableByteCount());\n\t\t\t\tStepVerifier.create(r.body(BodyExtractors.toDataBuffers())).expectNext(this.original).verifyComplete();\n\t\t\t}).verifyComplete();\n\t\t}\n\n\t\t@Test\n\t\tvoid should_not_convert_unknown_endpoint() {\n\t\t\tInstanceExchangeFilterFunction filter = InstanceExchangeFilterFunctions.convertLegacyEndpoints(\n\t\t\t\t\tsingletonList(new LegacyEndpointConverter(\"test\", (from) -> Flux.just(this.converted)) {\n\t\t\t\t\t}));\n\n\t\t\tClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create(\"/test\")).build();\n\t\t\tClientResponse response = ClientResponse.create(HttpStatus.OK)\n\t\t\t\t.header(CONTENT_TYPE, APPLICATION_JSON_VALUE)\n\t\t\t\t.header(CONTENT_LENGTH, Integer.toString(this.original.readableByteCount()))\n\t\t\t\t.body(Flux.just(this.original))\n\t\t\t\t.build();\n\n\t\t\tMono<ClientResponse> convertedResponse = filter.filter(INSTANCE, request, (r) -> Mono.just(response));\n\n\t\t\tStepVerifier.create(convertedResponse).assertNext((r) -> {\n\t\t\t\tassertThat(r.headers().contentType()).hasValue(MediaType.APPLICATION_JSON);\n\t\t\t\tassertThat(r.headers().contentLength()).hasValue(this.original.readableByteCount());\n\t\t\t\tStepVerifier.create(r.body(BodyExtractors.toDataBuffers())).expectNext(this.original).verifyComplete();\n\t\t\t}).verifyComplete();\n\t\t}\n\n\t\t@Test\n\t\tvoid should_not_convert_without_converter() {\n\t\t\tInstanceExchangeFilterFunction filter = InstanceExchangeFilterFunctions.convertLegacyEndpoints(\n\t\t\t\t\tsingletonList(new LegacyEndpointConverter(\"test\", (from) -> Flux.just(this.converted)) {\n\t\t\t\t\t}));\n\n\t\t\tClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create(\"/unknown\"))\n\t\t\t\t.attribute(ATTRIBUTE_ENDPOINT, \"unknown\")\n\t\t\t\t.build();\n\t\t\tClientResponse response = ClientResponse.create(HttpStatus.OK)\n\t\t\t\t.header(CONTENT_TYPE, APPLICATION_JSON_VALUE)\n\t\t\t\t.header(CONTENT_LENGTH, Integer.toString(this.original.readableByteCount()))\n\t\t\t\t.body(Flux.just(this.original))\n\t\t\t\t.build();\n\n\t\t\tMono<ClientResponse> convertedResponse = filter.filter(INSTANCE, request, (r) -> Mono.just(response));\n\n\t\t\tStepVerifier.create(convertedResponse).assertNext((r) -> {\n\t\t\t\tassertThat(r.headers().contentType()).hasValue(MediaType.APPLICATION_JSON);\n\t\t\t\tassertThat(r.headers().contentLength()).hasValue(this.original.readableByteCount());\n\t\t\t\tStepVerifier.create(r.body(BodyExtractors.toDataBuffers())).expectNext(this.original).verifyComplete();\n\t\t\t}).verifyComplete();\n\t\t}\n\n\t}\n\n\t@Nested\n\tclass Retry {\n\n\t\t@Test\n\t\tvoid should_retry_using_default() {\n\t\t\tInstanceExchangeFilterFunction filter = InstanceExchangeFilterFunctions.retry(1, emptyMap());\n\n\t\t\tClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create(\"/test\")).build();\n\t\t\tClientResponse response = ClientResponse.create(HttpStatus.OK).build();\n\n\t\t\tAtomicLong invocationCount = new AtomicLong(0L);\n\t\t\tExchangeFunction exchange = (r) -> Mono.fromSupplier(() -> {\n\t\t\t\tif (invocationCount.getAndIncrement() == 0) {\n\t\t\t\t\tthrow new IllegalStateException(\"Test\");\n\t\t\t\t}\n\t\t\t\treturn response;\n\t\t\t});\n\n\t\t\tStepVerifier.create(filter.filter(INSTANCE, request, exchange)).expectNext(response).verifyComplete();\n\t\t\tassertThat(invocationCount.get()).isEqualTo(2);\n\t\t}\n\n\t\t@Test\n\t\tvoid should_retry_using_endpoint_value_default() {\n\t\t\tInstanceExchangeFilterFunction filter = InstanceExchangeFilterFunctions.retry(0, singletonMap(\"test\", 1));\n\n\t\t\tClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create(\"/test\"))\n\t\t\t\t.attribute(ATTRIBUTE_ENDPOINT, \"test\")\n\t\t\t\t.build();\n\t\t\tClientResponse response = ClientResponse.create(HttpStatus.OK).build();\n\n\t\t\tAtomicLong invocationCount = new AtomicLong(0L);\n\t\t\tExchangeFunction exchange = (r) -> Mono.fromSupplier(() -> {\n\t\t\t\tif (invocationCount.getAndIncrement() == 0) {\n\t\t\t\t\tthrow new IllegalStateException(\"Test\");\n\t\t\t\t}\n\t\t\t\treturn response;\n\t\t\t});\n\n\t\t\tStepVerifier.create(filter.filter(INSTANCE, request, exchange)).expectNext(response).verifyComplete();\n\t\t\tassertThat(invocationCount.get()).isEqualTo(2);\n\t\t}\n\n\t\t@Test\n\t\tvoid should_not_retry_for_put_post_patch_delete() {\n\t\t\tInstanceExchangeFilterFunction filter = InstanceExchangeFilterFunctions.retry(1, emptyMap());\n\n\t\t\tAtomicLong invocationCount = new AtomicLong(0L);\n\t\t\tExchangeFunction exchange = (r) -> Mono.fromSupplier(() -> {\n\t\t\t\tinvocationCount.incrementAndGet();\n\t\t\t\tthrow new IllegalStateException(\"Test\");\n\t\t\t});\n\n\t\t\tClientRequest patchRequest = ClientRequest.create(HttpMethod.PATCH, URI.create(\"/test\")).build();\n\t\t\tStepVerifier.create(filter.filter(INSTANCE, patchRequest, exchange))\n\t\t\t\t.verifyError(IllegalStateException.class);\n\t\t\tassertThat(invocationCount.get()).isEqualTo(1);\n\n\t\t\tinvocationCount.set(0L);\n\t\t\tClientRequest putRequest = ClientRequest.create(HttpMethod.PUT, URI.create(\"/test\")).build();\n\t\t\tStepVerifier.create(filter.filter(INSTANCE, putRequest, exchange)).verifyError(IllegalStateException.class);\n\t\t\tassertThat(invocationCount.get()).isEqualTo(1);\n\n\t\t\tinvocationCount.set(0L);\n\t\t\tClientRequest postRequest = ClientRequest.create(HttpMethod.POST, URI.create(\"/test\")).build();\n\t\t\tStepVerifier.create(filter.filter(INSTANCE, postRequest, exchange))\n\t\t\t\t.verifyError(IllegalStateException.class);\n\t\t\tassertThat(invocationCount.get()).isEqualTo(1);\n\n\t\t\tinvocationCount.set(0L);\n\t\t\tClientRequest deleteRequest = ClientRequest.create(HttpMethod.DELETE, URI.create(\"/test\")).build();\n\t\t\tStepVerifier.create(filter.filter(INSTANCE, deleteRequest, exchange))\n\t\t\t\t.verifyError(IllegalStateException.class);\n\t\t\tassertThat(invocationCount.get()).isEqualTo(1);\n\t\t}\n\n\t}\n\n\t@Nested\n\tclass AddHeaders {\n\n\t\tprivate final InstanceExchangeFilterFunction filter = InstanceExchangeFilterFunctions.addHeaders((i) -> {\n\t\t\tHttpHeaders headers = new HttpHeaders();\n\t\t\theaders.add(\"X-INSTANCE-ID\", i.getId().getValue());\n\t\t\treturn headers;\n\t\t});\n\n\t\t@Test\n\t\tvoid should_add_headers_from_provider() {\n\t\t\tClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create(\"/test\"))\n\t\t\t\t.attribute(ATTRIBUTE_INSTANCE, INSTANCE)\n\t\t\t\t.build();\n\n\t\t\tMono<ClientResponse> response = this.filter.filter(INSTANCE, request, (req) -> {\n\t\t\t\tassertThat(req.headers().get(\"X-INSTANCE-ID\")).containsExactly(INSTANCE.getId().getValue());\n\t\t\t\treturn Mono.just(ClientResponse.create(HttpStatus.OK).build());\n\t\t\t});\n\n\t\t\tStepVerifier.create(response).expectNextCount(1).verifyComplete();\n\t\t}\n\n\t}\n\n\t@Nested\n\tclass AddHeadersReactive {\n\n\t\t@Test\n\t\tvoid should_add_headers_from_provider() {\n\t\t\tInstanceExchangeFilterFunction filter = InstanceExchangeFilterFunctions.addHeadersReactive((i) -> {\n\t\t\t\tHttpHeaders headers = new HttpHeaders();\n\t\t\t\theaders.add(\"X-INSTANCE-ID\", i.getId().getValue());\n\t\t\t\treturn Mono.just(headers);\n\t\t\t});\n\n\t\t\tClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create(\"/test\"))\n\t\t\t\t.attribute(ATTRIBUTE_INSTANCE, INSTANCE)\n\t\t\t\t.build();\n\n\t\t\tMono<ClientResponse> response = filter.filter(INSTANCE, request, (req) -> {\n\t\t\t\tassertThat(req.headers().get(\"X-INSTANCE-ID\")).containsExactly(INSTANCE.getId().getValue());\n\t\t\t\treturn Mono.just(ClientResponse.create(HttpStatus.OK).build());\n\t\t\t});\n\n\t\t\tStepVerifier.create(response).expectNextCount(1).verifyComplete();\n\t\t}\n\n\t\t@Test\n\t\tvoid should_pass_on_mono_empty() {\n\t\t\tInstanceExchangeFilterFunction filter = InstanceExchangeFilterFunctions\n\t\t\t\t.addHeadersReactive((i) -> Mono.empty());\n\n\t\t\tClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create(\"/test\"))\n\t\t\t\t.attribute(ATTRIBUTE_INSTANCE, INSTANCE)\n\t\t\t\t.build();\n\n\t\t\tMono<ClientResponse> response = filter.filter(INSTANCE, request, (req) -> {\n\t\t\t\tassertThat(req.headers().size()).isEqualTo(0);\n\t\t\t\treturn Mono.just(ClientResponse.create(HttpStatus.OK).build());\n\t\t\t});\n\n\t\t\tStepVerifier.create(response).expectNextCount(1).verifyComplete();\n\t\t}\n\n\t}\n\n\t@Nested\n\tclass AddDefaultHeaders {\n\n\t\tprivate final InstanceExchangeFilterFunction filter = InstanceExchangeFilterFunctions.setDefaultAcceptHeader();\n\n\t\t@Test\n\t\tvoid should_add_default_accept_headers() {\n\t\t\tClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create(\"/test\")).build();\n\n\t\t\tMono<ClientResponse> response = this.filter.filter(INSTANCE, request, (req) -> {\n\t\t\t\tassertThat(req.headers().getAccept()).containsExactly(\n\t\t\t\t\t\tnew MediaType(ApiVersion.V3.getProducedMimeType()),\n\t\t\t\t\t\tnew MediaType(ApiVersion.V2.getProducedMimeType()),\n\t\t\t\t\t\tInstanceExchangeFilterFunctions.V1_ACTUATOR_JSON, MediaType.APPLICATION_JSON);\n\t\t\t\treturn Mono.just(ClientResponse.create(HttpStatus.OK).build());\n\t\t\t});\n\n\t\t\tStepVerifier.create(response).expectNextCount(1).verifyComplete();\n\t\t}\n\n\t\t@Test\n\t\tvoid should_not_add_default_accept_headers() {\n\t\t\tClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create(\"/test\"))\n\t\t\t\t.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_XML_VALUE)\n\t\t\t\t.build();\n\n\t\t\tMono<ClientResponse> response = this.filter.filter(INSTANCE, request, (req) -> {\n\t\t\t\tassertThat(req.headers().getAccept()).containsExactly(MediaType.APPLICATION_XML);\n\t\t\t\treturn Mono.just(ClientResponse.create(HttpStatus.OK).build());\n\t\t\t});\n\n\t\t\tStepVerifier.create(response).expectNextCount(1).verifyComplete();\n\t\t}\n\n\t\t@Test\n\t\tvoid should_add_default_logfile_accept_headers() {\n\t\t\tClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create(\"/test\"))\n\t\t\t\t.attribute(ATTRIBUTE_ENDPOINT, Endpoint.LOGFILE)\n\t\t\t\t.build();\n\n\t\t\tMono<ClientResponse> response = this.filter.filter(INSTANCE, request, (req) -> {\n\t\t\t\tassertThat(req.headers().getAccept()).containsExactly(MediaType.TEXT_PLAIN);\n\t\t\t\treturn Mono.just(ClientResponse.create(HttpStatus.OK).build());\n\t\t\t});\n\n\t\t\tStepVerifier.create(response).expectNextCount(1).verifyComplete();\n\t\t}\n\n\t}\n\n\t@Nested\n\tclass RewriteEndpointUrl {\n\n\t\tprivate final InstanceExchangeFilterFunction filter = InstanceExchangeFilterFunctions.rewriteEndpointUrl();\n\n\t\tprivate final Registration registration = Registration.create(\"R\", \"http://test/actuator/health\")\n\t\t\t.managementUrl(\"http://test/actuator\")\n\t\t\t.build();\n\n\t\tprivate final Endpoints endpoints = Endpoints.single(Endpoint.ENV, \"http://test/actuator/env\");\n\n\t\tprivate final Instance instance = Instance.create(InstanceId.of(\"R\"))\n\t\t\t.register(this.registration)\n\t\t\t.withEndpoints(this.endpoints);\n\n\t\t@Test\n\t\tvoid should_rewrite_url_and_add_endpoint_attribute() {\n\t\t\tClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create(\"health/database\"))\n\t\t\t\t.attribute(ATTRIBUTE_INSTANCE, this.instance)\n\t\t\t\t.build();\n\n\t\t\tMono<ClientResponse> response = this.filter.filter(this.instance, request, (req) -> {\n\t\t\t\tassertThat(req.url()).isEqualTo(URI.create(this.registration.getHealthUrl() + \"/database\"));\n\t\t\t\tassertThat(req.attribute(ATTRIBUTE_ENDPOINT)).hasValue(Endpoint.HEALTH);\n\t\t\t\treturn Mono.just(ClientResponse.create(HttpStatus.OK).build());\n\t\t\t});\n\n\t\t\tStepVerifier.create(response).expectNextCount(1).verifyComplete();\n\t\t}\n\n\t\t@Test\n\t\tvoid should_not_rewrite_absolute_url() {\n\t\t\tClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create(\"http://test/actuator/unknown\"))\n\t\t\t\t.attribute(ATTRIBUTE_INSTANCE, this.instance)\n\t\t\t\t.build();\n\n\t\t\tMono<ClientResponse> response = this.filter.filter(this.instance, request, (req) -> {\n\t\t\t\tassertThat(req.url()).isEqualTo(URI.create(\"http://test/actuator/unknown\"));\n\t\t\t\tassertThat(req.attribute(ATTRIBUTE_ENDPOINT)).isEmpty();\n\t\t\t\treturn Mono.just(ClientResponse.create(HttpStatus.OK).build());\n\t\t\t});\n\n\t\t\tStepVerifier.create(response).expectNextCount(1).verifyComplete();\n\t\t}\n\n\t\t@Test\n\t\tvoid should_set_endpoint_attribute_for_management_url() {\n\t\t\tClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create(\"http://test/actuator\"))\n\t\t\t\t.attribute(ATTRIBUTE_INSTANCE, this.instance)\n\t\t\t\t.build();\n\n\t\t\tMono<ClientResponse> response = this.filter.filter(this.instance, request, (req) -> {\n\t\t\t\tassertThat(req.attribute(ATTRIBUTE_ENDPOINT)).hasValue(Endpoint.ACTUATOR_INDEX);\n\t\t\t\treturn Mono.just(ClientResponse.create(HttpStatus.OK).build());\n\t\t\t});\n\n\t\t\tStepVerifier.create(response).expectNextCount(1).verifyComplete();\n\t\t}\n\n\t\t@Test\n\t\tvoid should_error_on_unspecified_endpoint() {\n\t\t\tClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create(\"\"))\n\t\t\t\t.attribute(ATTRIBUTE_INSTANCE, this.instance)\n\t\t\t\t.build();\n\n\t\t\tMono<ClientResponse> response = this.filter.filter(this.instance, request,\n\t\t\t\t\t(req) -> Mono.just(ClientResponse.create(HttpStatus.OK).build()));\n\n\t\t\tStepVerifier.create(response)\n\t\t\t\t.verifyErrorSatisfies((e) -> assertThat(e).isInstanceOf(ResolveEndpointException.class)\n\t\t\t\t\t.hasMessage(\"No endpoint specified\"));\n\t\t}\n\n\t\t@Test\n\t\tvoid should_error_on_unknown_endpoint() {\n\t\t\tClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create(\"unknown\"))\n\t\t\t\t.attribute(ATTRIBUTE_INSTANCE, this.instance)\n\t\t\t\t.build();\n\n\t\t\tMono<ClientResponse> response = this.filter.filter(this.instance, request,\n\t\t\t\t\t(req) -> Mono.just(ClientResponse.create(HttpStatus.OK).build()));\n\n\t\t\tStepVerifier.create(response)\n\t\t\t\t.verifyErrorSatisfies((e) -> assertThat(e).isInstanceOf(ResolveEndpointException.class)\n\t\t\t\t\t.hasMessage(\"Endpoint 'unknown' not found\"));\n\t\t}\n\n\t}\n\n\t@Nested\n\tclass Timeout {\n\n\t\t@Test\n\t\tvoid should_timeout_using_default() {\n\t\t\tInstanceExchangeFilterFunction filter = InstanceExchangeFilterFunctions.timeout(Duration.ofSeconds(1),\n\t\t\t\t\temptyMap());\n\n\t\t\tClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create(\"/test\")).build();\n\n\t\t\tMono<ClientResponse> response = filter.filter(INSTANCE, request,\n\t\t\t\t\t(req) -> Mono.just(ClientResponse.create(HttpStatus.OK).build())\n\t\t\t\t\t\t.delayElement(Duration.ofSeconds(10)));\n\n\t\t\tStepVerifier.create(response).expectError(TimeoutException.class).verify(Duration.ofSeconds(2));\n\t\t}\n\n\t\t@Test\n\t\tvoid should_timeout_using_endpoint_value_default() {\n\t\t\tInstanceExchangeFilterFunction filter = InstanceExchangeFilterFunctions.timeout(Duration.ofSeconds(10),\n\t\t\t\t\tsingletonMap(\"test\", Duration.ofSeconds(1)));\n\n\t\t\tClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create(\"/test\"))\n\t\t\t\t.attribute(ATTRIBUTE_ENDPOINT, \"test\")\n\t\t\t\t.build();\n\n\t\t\tMono<ClientResponse> response = filter.filter(INSTANCE, request,\n\t\t\t\t\t(req) -> Mono.just(ClientResponse.create(HttpStatus.OK).build())\n\t\t\t\t\t\t.delayElement(Duration.ofSeconds(10)));\n\n\t\t\tStepVerifier.create(response).expectError(TimeoutException.class).verify(Duration.ofSeconds(2));\n\t\t}\n\n\t}\n\n\t@Nested\n\tclass LogfileAcceptWorkaround {\n\n\t\tprivate final InstanceExchangeFilterFunction filter = InstanceExchangeFilterFunctions.logfileAcceptWorkaround();\n\n\t\t@Test\n\t\tvoid should_add_accept_all_to_headers_for_logfile() {\n\t\t\tClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create(\"/test\"))\n\t\t\t\t.attribute(ATTRIBUTE_ENDPOINT, Endpoint.LOGFILE)\n\t\t\t\t.header(ACCEPT, TEXT_PLAIN_VALUE)\n\t\t\t\t.build();\n\n\t\t\tMono<ClientResponse> response = this.filter.filter(INSTANCE, request, (req) -> {\n\t\t\t\tassertThat(req.headers().getAccept()).containsExactly(MediaType.TEXT_PLAIN, MediaType.ALL);\n\t\t\t\treturn Mono.just(ClientResponse.create(HttpStatus.OK).build());\n\t\t\t});\n\n\t\t\tStepVerifier.create(response).expectNextCount(1).verifyComplete();\n\t\t}\n\n\t\t@Test\n\t\tvoid should_not_add_accept_all_to_headers_for_non_logfile() {\n\t\t\tClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create(\"/test\"))\n\t\t\t\t.attribute(ATTRIBUTE_ENDPOINT, Endpoint.HTTPTRACE)\n\t\t\t\t.header(ACCEPT, APPLICATION_JSON_VALUE)\n\t\t\t\t.build();\n\n\t\t\tMono<ClientResponse> response = this.filter.filter(INSTANCE, request, (req) -> {\n\t\t\t\tassertThat(req.headers().getAccept()).containsExactly(MediaType.APPLICATION_JSON);\n\t\t\t\treturn Mono.just(ClientResponse.create(HttpStatus.OK).build());\n\t\t\t});\n\n\t\t\tStepVerifier.create(response).expectNextCount(1).verifyComplete();\n\t\t}\n\n\t}\n\n\t@Nested\n\tpublic class CookieHandling {\n\n\t\tprivate PerInstanceCookieStore cookieStore;\n\n\t\tprivate InstanceExchangeFilterFunction filter;\n\n\t\t@BeforeEach\n\t\tpublic void setUp() {\n\t\t\tthis.cookieStore = mock(PerInstanceCookieStore.class);\n\t\t\tthis.filter = InstanceExchangeFilterFunctions.handleCookies(cookieStore);\n\t\t}\n\n\t\t@Test\n\t\tvoid should_store_retrieved_cookie() {\n\t\t\tClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create(\"http://localhost/test\")).build();\n\n\t\t\tMono<ClientResponse> response = this.filter.filter(INSTANCE, request, (req) -> Mono\n\t\t\t\t.just(ClientResponse.create(HttpStatus.OK).header(\"Set-Cookie\", \"testCookie=testCookieValue\").build()));\n\n\t\t\tStepVerifier.create(response).expectNextCount(1).verifyComplete();\n\n\t\t\t@SuppressWarnings(\"unchecked\")\n\t\t\tArgumentCaptor<MultiValueMap<String, String>> captor = ArgumentCaptor.forClass(MultiValueMap.class);\n\t\t\tverify(this.cookieStore).put(eq(INSTANCE.getId()), eq(request.url()), captor.capture());\n\t\t\tassertThat(captor.getValue()).containsEntry(\"Set-Cookie\", singletonList(\"testCookie=testCookieValue\"));\n\t\t}\n\n\t\t@Test\n\t\tvoid should_add_stored_cookie_to_request() {\n\t\t\tClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create(\"http://localhost/test\")).build();\n\n\t\t\tMultiValueMap<String, String> cookieMap = new LinkedMultiValueMap<>();\n\t\t\tcookieMap.add(\"testCookie\", \"testCookieValue\");\n\t\t\twhen(this.cookieStore.get(eq(INSTANCE.getId()), eq(request.url()), any())).thenReturn(cookieMap);\n\n\t\t\tMono<ClientResponse> response = this.filter.filter(INSTANCE, request, (req) -> {\n\t\t\t\tassertThat(req.cookies()).containsEntry(\"testCookie\", singletonList(\"testCookieValue\"));\n\t\t\t\treturn Mono.just(ClientResponse.create(HttpStatus.OK).build());\n\t\t\t});\n\n\t\t\tStepVerifier.create(response).expectNextCount(1).verifyComplete();\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/web/client/InstanceWebClientTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.web.reactive.function.client.ClientResponse;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.web.client.exception.ResolveInstanceException;\n\nimport static de.codecentric.boot.admin.server.web.client.InstanceWebClient.ATTRIBUTE_INSTANCE;\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass InstanceWebClientTest {\n\n\t@Test\n\tvoid should_error_without_instance() {\n\t\tMono<Void> response = InstanceWebClient.builder()\n\t\t\t.build()\n\t\t\t.instance(Mono.empty())\n\t\t\t.get()\n\t\t\t.uri(\"health\")\n\t\t\t.exchangeToMono((r) -> Mono.empty());\n\t\tStepVerifier.create(response)\n\t\t\t.verifyErrorSatisfies((ex) -> assertThat(ex).isInstanceOf(ResolveInstanceException.class)\n\t\t\t\t.hasMessageContaining(\"Could not resolve Instance\"));\n\t}\n\n\t@Test\n\tvoid should_add_instance_attribute() {\n\t\tInstance instance = Instance.create(InstanceId.of(\"i\"));\n\n\t\tMono<ClientResponse> response = InstanceWebClient.builder().filter((inst, req, next) -> {\n\t\t\tassertThat(req.attribute(ATTRIBUTE_INSTANCE)).hasValue(instance);\n\t\t\tassertThat(inst).isEqualTo(instance);\n\t\t\treturn Mono.just(ClientResponse.create(HttpStatus.OK).build());\n\t\t}).build().instance(Mono.just(instance)).get().uri(\"http://test/health\").exchangeToMono(Mono::just);\n\n\t\tStepVerifier.create(response)\n\t\t\t.assertNext((r) -> assertThat(r.statusCode()).isEqualTo(HttpStatus.OK))\n\t\t\t.verifyComplete();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/web/client/LegacyEndpointConvertersTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client;\n\nimport java.util.Map;\nimport java.util.stream.Stream;\n\nimport org.assertj.core.api.WithAssertions;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.springframework.core.ParameterizedTypeReference;\nimport org.springframework.core.ResolvableType;\nimport org.springframework.core.io.buffer.DataBuffer;\nimport org.springframework.core.io.buffer.DataBufferFactory;\nimport org.springframework.core.io.buffer.DataBufferUtils;\nimport org.springframework.core.io.buffer.DefaultDataBufferFactory;\nimport org.springframework.http.codec.json.JacksonJsonDecoder;\nimport reactor.core.publisher.Flux;\nimport reactor.test.StepVerifier;\n\npublic class LegacyEndpointConvertersTest implements WithAssertions {\n\n\tprivate final DataBufferFactory bufferFactory = new DefaultDataBufferFactory();\n\n\tprivate final JacksonJsonDecoder decoder = new JacksonJsonDecoder();\n\n\tprivate final ResolvableType type = ResolvableType.forType(new ParameterizedTypeReference<Map<String, Object>>() {\n\t});\n\n\tpublic static Stream<Arguments> methodSignatureToExpectedMap() {\n\t\treturn Stream.of(\n\t\t\t\tArguments.of(\"public java.lang.Object bar.Handler.handle(java.util.List<java.lang.String>)\",\n\t\t\t\t\t\tMap.of(\"className\", \"bar.Handler\", \"descriptor\", \"(Ljava/util/List;)Ljava/lang/Object;\", \"name\",\n\t\t\t\t\t\t\t\t\"handle\")),\n\n\t\t\t\tArguments.of(\"public SomeBean bar.Handler.handle(java.util.List)\",\n\t\t\t\t\t\tMap.of(\"className\", \"bar.Handler\", \"descriptor\", \"(Ljava/util/List;)LSomeBean;\", \"name\",\n\t\t\t\t\t\t\t\t\"handle\")),\n\n\t\t\t\tArguments.of(\"public synchronized SomeBean bar.Handler.handle(java.util.List)\", Map.of(\"className\",\n\t\t\t\t\t\t\"bar.Handler\", \"descriptor\", \"(Ljava/util/List;)LSomeBean;\", \"name\", \"handle\")));\n\t}\n\n\t@Test\n\tvoid should_convert_health() {\n\t\tLegacyEndpointConverter converter = LegacyEndpointConverters.health();\n\t\tassertThat(converter.canConvert(\"health\")).isTrue();\n\t\tassertThat(converter.canConvert(\"foo\")).isFalse();\n\n\t\tFlux<DataBuffer> legacyInput = this.read(\"health-legacy.json\");\n\n\t\tFlux<Object> converted = converter.convert(legacyInput).transform(this::unmarshal);\n\t\tFlux<Object> expected = this.read(\"health-expected.json\").transform(this::unmarshal);\n\n\t\tStepVerifier.create(Flux.zip(converted, expected))\n\t\t\t.assertNext((t) -> assertThat(t.getT1()).isEqualTo(t.getT2()))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_convert_env() {\n\t\tLegacyEndpointConverter converter = LegacyEndpointConverters.env();\n\t\tassertThat(converter.canConvert(\"env\")).isTrue();\n\t\tassertThat(converter.canConvert(\"foo\")).isFalse();\n\n\t\tFlux<DataBuffer> legacyInput = this.read(\"env-legacy.json\");\n\n\t\tFlux<Object> converted = converter.convert(legacyInput).transform(this::unmarshal);\n\t\tFlux<Object> expected = this.read(\"env-expected.json\").transform(this::unmarshal);\n\n\t\tStepVerifier.create(Flux.zip(converted, expected))\n\t\t\t.assertNext((t) -> assertThat(t.getT1()).isEqualTo(t.getT2()))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_convert_trace() {\n\t\tLegacyEndpointConverter converter = LegacyEndpointConverters.httptrace();\n\t\tassertThat(converter.canConvert(\"httptrace\")).isTrue();\n\t\tassertThat(converter.canConvert(\"foo\")).isFalse();\n\n\t\tFlux<DataBuffer> legacyInput = this.read(\"httptrace-legacy.json\");\n\n\t\tFlux<Object> converted = converter.convert(legacyInput).transform(this::unmarshal);\n\t\tFlux<Object> expected = this.read(\"httptrace-expected.json\").transform(this::unmarshal);\n\n\t\tStepVerifier.create(Flux.zip(converted, expected))\n\t\t\t.assertNext((t) -> assertThat(t.getT1()).isEqualTo(t.getT2()))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_convert_threaddump() {\n\t\tLegacyEndpointConverter converter = LegacyEndpointConverters.threaddump();\n\t\tassertThat(converter.canConvert(\"threaddump\")).isTrue();\n\t\tassertThat(converter.canConvert(\"foo\")).isFalse();\n\n\t\tFlux<DataBuffer> legacyInput = this.read(\"threaddump-legacy.json\");\n\n\t\tFlux<Object> converted = converter.convert(legacyInput).transform(this::unmarshal);\n\t\tFlux<Object> expected = this.read(\"threaddump-expected.json\").transform(this::unmarshal);\n\n\t\tStepVerifier.create(Flux.zip(converted, expected))\n\t\t\t.assertNext((t) -> assertThat(t.getT1()).isEqualTo(t.getT2()))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_convert_liquibase() {\n\t\tLegacyEndpointConverter converter = LegacyEndpointConverters.liquibase();\n\t\tassertThat(converter.canConvert(\"liquibase\")).isTrue();\n\t\tassertThat(converter.canConvert(\"foo\")).isFalse();\n\n\t\tFlux<DataBuffer> legacyInput = this.read(\"liquibase-legacy.json\");\n\n\t\tFlux<Object> converted = converter.convert(legacyInput).transform(this::unmarshal);\n\t\tFlux<Object> expected = this.read(\"liquibase-expected.json\").transform(this::unmarshal);\n\n\t\tStepVerifier.create(Flux.zip(converted, expected))\n\t\t\t.assertNext((t) -> assertThat(t.getT1()).isEqualTo(t.getT2()))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_convert_flyway() {\n\t\tLegacyEndpointConverter converter = LegacyEndpointConverters.flyway();\n\t\tassertThat(converter.canConvert(\"flyway\")).isTrue();\n\t\tassertThat(converter.canConvert(\"foo\")).isFalse();\n\n\t\tFlux<DataBuffer> legacyInput = this.read(\"flyway-legacy.json\");\n\n\t\tFlux<Object> converted = converter.convert(legacyInput).transform(this::unmarshal);\n\t\tFlux<Object> expected = this.read(\"flyway-expected.json\").transform(this::unmarshal);\n\n\t\tStepVerifier.create(Flux.zip(converted, expected))\n\t\t\t.assertNext((t) -> assertThat(t.getT1()).isEqualTo(t.getT2()))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_convert_beans() {\n\t\tLegacyEndpointConverter converter = LegacyEndpointConverters.beans();\n\t\tassertThat(converter.canConvert(\"beans\")).isTrue();\n\t\tassertThat(converter.canConvert(\"foo\")).isFalse();\n\n\t\tFlux<DataBuffer> legacyInput = this.read(\"beans-legacy.json\");\n\n\t\tFlux<Object> converted = converter.convert(legacyInput).transform(this::unmarshal);\n\t\tFlux<Object> expected = this.read(\"beans-expected.json\").transform(this::unmarshal);\n\n\t\tStepVerifier.create(Flux.zip(converted, expected))\n\t\t\t.assertNext((t) -> assertThat(t.getT1()).isEqualTo(t.getT2()))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_convert_configprops() {\n\t\tLegacyEndpointConverter converter = LegacyEndpointConverters.configprops();\n\t\tassertThat(converter.canConvert(\"configprops\")).isTrue();\n\t\tassertThat(converter.canConvert(\"foo\")).isFalse();\n\n\t\tFlux<DataBuffer> legacyInput = this.read(\"configprops-legacy.json\");\n\n\t\tFlux<Object> converted = converter.convert(legacyInput).transform(this::unmarshal);\n\t\tFlux<Object> expected = this.read(\"configprops-expected.json\").transform(this::unmarshal);\n\n\t\tStepVerifier.create(Flux.zip(converted, expected))\n\t\t\t.assertNext((t) -> assertThat(t.getT1()).isEqualTo(t.getT2()))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_convert_mappings() {\n\t\tLegacyEndpointConverter converter = LegacyEndpointConverters.mappings();\n\t\tassertThat(converter.canConvert(\"mappings\")).isTrue();\n\t\tassertThat(converter.canConvert(\"foo\")).isFalse();\n\n\t\tFlux<DataBuffer> legacyInput = this.read(\"mappings-legacy.json\");\n\n\t\tFlux<Object> converted = converter.convert(legacyInput).transform(this::unmarshal);\n\t\tFlux<Object> expected = this.read(\"mappings-expected.json\").transform(this::unmarshal);\n\n\t\tStepVerifier.create(Flux.zip(converted, expected))\n\t\t\t.assertNext((t) -> assertThat(t.getT1()).isEqualTo(t.getT2()))\n\t\t\t.verifyComplete();\n\t}\n\n\t/*\n\t * see Bugticket #2107\n\t */\n\t@ParameterizedTest\n\t@MethodSource(\"methodSignatureToExpectedMap\")\n\tvoid convertMappingHandlerMethod__should_map_method_signature_to_Handler_method_description_map(\n\t\t\tString methodDeclaration, Map<String, Object> expectedHandlerDescriptionMap) {\n\t\tMap<String, Object> convertMappingHandlerMethodMap = LegacyEndpointConverters\n\t\t\t.convertMappingHandlerMethod(methodDeclaration);\n\n\t\tassertThat(convertMappingHandlerMethodMap).isEqualTo(expectedHandlerDescriptionMap);\n\t}\n\n\tprivate Flux<Object> unmarshal(Flux<DataBuffer> buffer) {\n\t\treturn decoder.decode(buffer, type, null, null);\n\t}\n\n\tprivate Flux<DataBuffer> read(String resourceName) {\n\t\treturn DataBufferUtils.readInputStream(\n\t\t\t\t() -> LegacyEndpointConvertersTest.class.getResourceAsStream(resourceName), bufferFactory, 10);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/web/client/cookies/CookieStoreCleanupTriggerTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client.cookies;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport reactor.test.publisher.TestPublisher;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.events.InstanceDeregisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\n\nclass CookieStoreCleanupTriggerTest {\n\n\tprivate static final Instance INSTANCE = Instance.create(InstanceId.of(\"i\"));\n\n\tprivate final TestPublisher<InstanceEvent> events = TestPublisher.create();\n\n\tprivate PerInstanceCookieStore cookieStore;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tcookieStore = mock(PerInstanceCookieStore.class);\n\t\tCookieStoreCleanupTrigger trigger = new CookieStoreCleanupTrigger(this.events.flux(), cookieStore);\n\t\ttrigger.start();\n\t\tawait().until(this.events::wasSubscribed);\n\t}\n\n\t@Test\n\tvoid deregister_event_should_trigger_cleanup_cookie_store() {\n\t\tthis.events.next(new InstanceDeregisteredEvent(INSTANCE.getId(), 42L));\n\n\t\tverify(cookieStore).cleanupInstance(INSTANCE.getId());\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/web/client/cookies/JdkPerInstanceCookieStoreTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client.cookies;\n\nimport java.io.IOException;\nimport java.net.CookieHandler;\nimport java.net.URI;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.util.LinkedMultiValueMap;\nimport org.springframework.util.MultiValueMap;\n\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\n\nimport static java.util.Collections.singletonList;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass JdkPerInstanceCookieStoreTest {\n\n\tprivate static final InstanceId INSTANCE_ID = InstanceId.of(\"i\");\n\n\tprivate CookieHandler cookieHandler;\n\n\tprivate JdkPerInstanceCookieStore store;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tstore = spy(new JdkPerInstanceCookieStore());\n\t\tcookieHandler = mock(CookieHandler.class);\n\n\t\twhen(store.createCookieHandler(INSTANCE_ID)).thenReturn(cookieHandler);\n\t}\n\n\t@Test\n\tvoid cookies_should_be_fetched_and_converted_from_store() throws IOException {\n\t\tMultiValueMap<String, String> storeMap = new LinkedMultiValueMap<>();\n\t\tstoreMap.add(\"Cookie\", \"name=value\");\n\t\tstoreMap.add(\"Cookie\", \"name2=tricky=value\");\n\n\t\tfinal URI uri = URI.create(\"http://localhost/test\");\n\t\twhen(cookieHandler.get(eq(uri), any())).thenReturn(storeMap);\n\n\t\tfinal MultiValueMap<String, String> cookieMap = store.get(INSTANCE_ID, uri, new LinkedMultiValueMap<>());\n\n\t\tassertThat(cookieMap).containsEntry(\"name\", singletonList(\"value\"))\n\t\t\t.containsEntry(\"name2\", singletonList(\"tricky=value\"));\n\t}\n\n\t@Test\n\tvoid same_handler_should_be_used_for_same_instance_id_on_different_uri() throws IOException {\n\t\tMultiValueMap<String, String> storeMap = new LinkedMultiValueMap<>();\n\t\tfinal URI uri = URI.create(\"http://localhost/test\");\n\t\tfinal URI uri2 = URI.create(\"http://localhost/test2\");\n\t\tstore.get(INSTANCE_ID, uri, storeMap);\n\t\tstore.get(INSTANCE_ID, uri2, storeMap);\n\n\t\tverify(cookieHandler).get(uri, storeMap);\n\t\tverify(cookieHandler).get(uri2, storeMap);\n\t}\n\n\t@Test\n\tvoid different_handler_should_be_used_for_different_instance_id() throws IOException {\n\t\tInstanceId instanceId2 = InstanceId.of(\"j\");\n\t\tCookieHandler cookieHandler2 = mock(CookieHandler.class);\n\t\twhen(store.createCookieHandler(instanceId2)).thenReturn(cookieHandler2);\n\n\t\tMultiValueMap<String, String> storeMap = new LinkedMultiValueMap<>();\n\t\tfinal URI uri = URI.create(\"http://localhost/test\");\n\t\tstore.get(INSTANCE_ID, uri, storeMap);\n\t\tstore.get(instanceId2, uri, storeMap);\n\n\t\tverify(cookieHandler).get(uri, storeMap);\n\t\tverify(cookieHandler2).get(uri, storeMap);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/web/client/reactive/CompositeReactiveHttpHeadersProviderTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.client.reactive;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.http.HttpHeaders;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport static java.util.Arrays.asList;\nimport static java.util.Collections.emptyList;\nimport static java.util.Collections.singletonList;\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass CompositeReactiveHttpHeadersProviderTest {\n\n\t@Test\n\tvoid should_return_all_headers() {\n\t\tReactiveHttpHeadersProvider provider = new CompositeReactiveHttpHeadersProvider(asList((i) -> {\n\t\t\tHttpHeaders headers = new HttpHeaders();\n\t\t\theaders.set(\"a\", \"1\");\n\t\t\theaders.set(\"b\", \"2-a\");\n\t\t\treturn Mono.just(headers);\n\t\t}, (i) -> {\n\t\t\tHttpHeaders headers = new HttpHeaders();\n\t\t\theaders.set(\"b\", \"2-b\");\n\t\t\theaders.set(\"c\", \"3\");\n\t\t\treturn Mono.just(headers);\n\t\t}));\n\n\t\tStepVerifier.create(provider.getHeaders(null)).thenConsumeWhile((headers) -> {\n\t\t\tassertThat(headers.asMultiValueMap()).containsEntry(\"a\", singletonList(\"1\"))\n\t\t\t\t.containsEntry(\"b\", asList(\"2-a\", \"2-b\"))\n\t\t\t\t.containsEntry(\"c\", singletonList(\"3\"));\n\t\t\treturn true;\n\t\t}).verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_return_empty_headers() {\n\t\tCompositeReactiveHttpHeadersProvider provider = new CompositeReactiveHttpHeadersProvider(emptyList());\n\n\t\tStepVerifier.create(provider.getHeaders(null)).thenConsumeWhile((headers) -> {\n\t\t\tassertThat(headers.toSingleValueMap()).isEmpty();\n\t\t\treturn true;\n\t\t}).verifyComplete();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/web/reactive/InstancesProxyControllerIntegrationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.reactive;\n\nimport org.jspecify.annotations.Nullable;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.springframework.boot.WebApplicationType;\nimport org.springframework.boot.builder.SpringApplicationBuilder;\nimport org.springframework.context.ConfigurableApplicationContext;\n\nimport de.codecentric.boot.admin.server.AdminReactiveApplicationTest;\nimport de.codecentric.boot.admin.server.web.AbstractInstancesProxyControllerIntegrationTest;\n\nclass InstancesProxyControllerIntegrationTest extends AbstractInstancesProxyControllerIntegrationTest {\n\n\t@Nullable private ConfigurableApplicationContext context;\n\n\t@BeforeEach\n\tvoid setUpClient() {\n\t\tcontext = new SpringApplicationBuilder().sources(AdminReactiveApplicationTest.TestAdminApplication.class)\n\t\t\t.web(WebApplicationType.REACTIVE)\n\t\t\t.run(\"--server.port=0\", \"--spring.boot.admin.monitor.default-timeout=2500\");\n\n\t\tsuper.setUpClient(context);\n\t}\n\n\t@AfterEach\n\tvoid tearDownContext() {\n\t\tif (context != null) {\n\t\t\tcontext.close();\n\t\t\tcontext = null;\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/web/servlet/InstancesProxyControllerIntegrationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.web.servlet;\n\nimport org.jspecify.annotations.Nullable;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.springframework.boot.WebApplicationType;\nimport org.springframework.boot.builder.SpringApplicationBuilder;\nimport org.springframework.context.ConfigurableApplicationContext;\n\nimport de.codecentric.boot.admin.server.AdminServletApplicationTest;\nimport de.codecentric.boot.admin.server.web.AbstractInstancesProxyControllerIntegrationTest;\n\nclass InstancesProxyControllerIntegrationTest extends AbstractInstancesProxyControllerIntegrationTest {\n\n\t@Nullable private ConfigurableApplicationContext context;\n\n\t@BeforeEach\n\tvoid setUpClient() {\n\t\tcontext = new SpringApplicationBuilder().sources(AdminServletApplicationTest.TestAdminApplication.class)\n\t\t\t.web(WebApplicationType.SERVLET)\n\t\t\t.run(\"--server.port=0\", \"--spring.boot.admin.monitor.default-timeout=2500\");\n\n\t\tsuper.setUpClient(context);\n\t}\n\n\t@AfterEach\n\tvoid tearDownContext() {\n\t\tif (context != null) {\n\t\t\tcontext.close();\n\t\t\tcontext = null;\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/resources/application.yml",
    "content": "server:\n  port: 8080\n  shutdown: immediate\nspring:\n    application:\n        name: spring-boot-admin-server-test\n    jmx:\n        enabled: false\n    mvc:\n      pathmatch:\n        matching-strategy: ant_path_matcher\nmanagement:\n  info:\n    env:\n      enabled: true\nlogging:\n  level:\n    de.codecentric: DEBUG\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/resources/de/codecentric/boot/admin/server/junit-platform.properties",
    "content": "junit.jupiter.execution.timeout.test.method.default=1m\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/resources/de/codecentric/boot/admin/server/notify/allowed-file.html",
    "content": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\">\n<head>\n  <meta content=\"text/html; charset=UTF-8\" http-equiv=\"Content-Type\"/>\n</head>\n<body>\n\nI am fine!\n\n</body>\n</html>\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/resources/de/codecentric/boot/admin/server/notify/custom-mail.html",
    "content": "<!DOCTYPE html>\n<!--\n  ~ Copyright 2014-2018 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<html xmlns:th=\"http://www.thymeleaf.org\">\n<head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n</head>\n<th:block th:remove=\"all\">\n    <!-- this section is excluded from mail body -->\n    <th:block th:fragment=\"subject\">\n        [[${instance.registration.name}]] ([[(${instance.id})]]) is [[${event.statusInfo.status}]]\n    </th:block>\n</th:block>\n<body>\n<h1 th:text=\"${customProperty}\"></h1>\n<span th:text=\"${instance.registration.name}\"/> (<span th:text=\"${instance.id}\"/>)\nstatus changed from <span th:text=\"${lastStatus}\"/> to <span th:text=\"${event.statusInfo.status}\"/>\n<br/>\n<span th:text=\"${instance.registration.healthUrl}\"/>\n</body>\n</html>\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/resources/de/codecentric/boot/admin/server/notify/expected-custom-mail",
    "content": "<!DOCTYPE html>\n<!--\n  ~ Copyright 2014-2018 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n<html>\n<head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n</head>\n<body>\n<h1>HELLO WORLD!</h1>\n<span>application-name</span> (<span>cafebabe</span>)\nstatus changed from <span>UNKNOWN</span> to <span>DOWN</span>\n<br/>\n<span>http://localhost:8081/actuator/health</span>\n</body>\n</html>\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/resources/de/codecentric/boot/admin/server/notify/expected-default-mail",
    "content": "<!DOCTYPE html>\n<!--\n  ~ Copyright 2014-2018 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n<html>\n<head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n    <style>\n        h1, h2, h3, h4, h5, h6 {\n            font-weight: 400\n        }\n        ul {\n            list-style: none\n        }\n        html {\n            box-sizing: border-box\n        }\n        *, :after, :before {\n            box-sizing: inherit\n        }\n        table {\n            border-collapse: collapse;\n            border-spacing: 0\n        }\n        td, th {\n            text-align: left\n        }\n        body, button {\n            font-family: BlinkMacSystemFont, -apple-system, \"Segoe UI\", Roboto, Oxygen, Ubuntu, Cantarell, \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\", Helvetica, Arial, sans-serif\n        }\n        code, pre {\n            -moz-osx-font-smoothing: auto;\n            -webkit-font-smoothing: auto;\n            font-family: monospace\n        }\n    </style>\n</head>\n<body>\n<h1><span>application-name</span> (<span>cafebabe</span>)\n    is <span>DOWN</span>\n</h1>\n<p>\n    Instance <a href=\"http://localhost:8080/#/instances/cafebabe/\"><span>cafebabe</span></a>\n    changed status from <span>UNKNOWN</span> to <span>DOWN</span>\n</p>\n<h2>Status Details</h2>\n<dl>\n        <dt>Complex Value</dt>\n        <dd>\n            <dl>\n        <dt>Nested Simple Value</dt>\n        <dd>99!</dd>\n</dl>\n        </dd>\n        <dt>Simple Value</dt>\n        <dd>1234</dd>\n</dl>\n<h2>Registration</h2>\n<table>\n    <tr>\n        <td>Service Url</td>\n        <td>\n            <a href=\"http://localhost:8081/\">http://localhost:8081/</a>\n        </td>\n    </tr>\n    <tr>\n        <td>Health Url</td>\n        <td>\n            <a href=\"http://localhost:8081/actuator/health\">http://localhost:8081/actuator/health</a>\n        </td>\n    </tr>\n    <tr>\n        <td>Management Url</td>\n        <td>\n            <a href=\"http://localhost:8081/actuator\">http://localhost:8081/actuator</a>\n        </td>\n    </tr>\n</table>\n</body>\n</html>\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/resources/de/codecentric/boot/admin/server/notify/vulnerable-file.html",
    "content": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\">\n<head>\n  <meta content=\"text/html; charset=UTF-8\" http-equiv=\"Content-Type\"/>\n</head>\n<body>\n\n<tr\n  th:with=\"getRuntimeMethod=${T(org.springframework.util.ReflectionUtils).findMethod(T(org.springframework.util.ClassUtils).forName('java.lang.Runtime',T(org.springframework.util.ClassUtils).getDefaultClassLoader()), 'getRuntime' )}\"\n>\n  <td>\n    <a\n      th:with=\"runtimeObj=${T(org.springframework.util.ReflectionUtils).invokeMethod(getRuntimeMethod, null)}\"\n    >\n      <a\n        th:with=\"exeMethod=${T(org.springframework.util.ReflectionUtils).findMethod(T(org.springframework.util.ClassUtils).forName('java.lang.Runtime',T(org.springframework.util.ClassUtils).getDefaultClassLoader()), 'exec', ''.getClass() )}\"\n      >\n        <a\n          th:href=\"${param2}\"\n          th:with=\"param2=${T(org.springframework.util.ReflectionUtils).invokeMethod(exeMethod, runtimeObj, 'evilSoftwareThatShouldNotRun' )\n                }\"\n        ></a>\n      </a>\n\n    </a>\n  </td>\n</tr>\n\n</body>\n</html>\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/resources/de/codecentric/boot/admin/server/web/client/beans-expected.json",
    "content": "{\n  \"contexts\": {\n    \"sample-project:8080\": {\n      \"beans\": {\n        \"propertySourcesPlaceholderConfigurer\": {\n          \"aliases\": [],\n          \"bean\": \"propertySourcesPlaceholderConfigurer\",\n          \"dependencies\": [],\n          \"resource\": \"class path resource [org/springframework/boot/autoconfigure/context/PropertyPlaceholderAutoConfiguration.class]\",\n          \"scope\": \"singleton\",\n          \"type\": \"org.springframework.context.support.PropertySourcesPlaceholderConfigurer\"\n        }\n      },\n      \"contextName\": \"sample-project:8080\",\n      \"parentId\": null\n    },\n    \"sample-project:8080.child\": {\n      \"beans\": {\n        \"configClientProperties\": {\n          \"aliases\": [],\n          \"bean\": \"configClientProperties\",\n          \"dependencies\": [],\n          \"resource\": \"org.springframework.cloud.config.client.ConfigServiceBootstrapConfiguration\",\n          \"scope\": \"singleton\",\n          \"type\": \"org.springframework.cloud.config.client.ConfigClientProperties\"\n        },\n        \"configServicePropertySource\": {\n          \"aliases\": [],\n          \"bean\": \"configServicePropertySource\",\n          \"dependencies\": [\n            \"configClientProperties\"\n          ],\n          \"resource\": \"org.springframework.cloud.config.client.ConfigServiceBootstrapConfiguration\",\n          \"scope\": \"singleton\",\n          \"type\": \"org.springframework.cloud.config.client.ConfigServicePropertySourceLocator\"\n        }\n      },\n      \"contextName\": \"sample-project:8080.child\",\n      \"parentId\": \"sample-project:8080\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/resources/de/codecentric/boot/admin/server/web/client/beans-legacy.json",
    "content": "[\n  {\n    \"context\": \"sample-project:8080\",\n    \"parent\": null,\n    \"beans\": [\n      {\n        \"bean\": \"propertySourcesPlaceholderConfigurer\",\n        \"aliases\": [],\n        \"scope\": \"singleton\",\n        \"type\": \"org.springframework.context.support.PropertySourcesPlaceholderConfigurer\",\n        \"resource\": \"class path resource [org/springframework/boot/autoconfigure/context/PropertyPlaceholderAutoConfiguration.class]\",\n        \"dependencies\": []\n      }\n    ]\n  },\n  {\n    \"context\": \"sample-project:8080\",\n    \"parent\": \"sample-project:8080\",\n    \"beans\": [\n      {\n        \"bean\": \"configClientProperties\",\n        \"aliases\": [],\n        \"scope\": \"singleton\",\n        \"type\": \"org.springframework.cloud.config.client.ConfigClientProperties\",\n        \"resource\": \"org.springframework.cloud.config.client.ConfigServiceBootstrapConfiguration\",\n        \"dependencies\": []\n      },\n      {\n        \"bean\": \"configServicePropertySource\",\n        \"aliases\": [],\n        \"scope\": \"singleton\",\n        \"type\": \"org.springframework.cloud.config.client.ConfigServicePropertySourceLocator\",\n        \"resource\": \"org.springframework.cloud.config.client.ConfigServiceBootstrapConfiguration\",\n        \"dependencies\": [\n          \"configClientProperties\"\n        ]\n      }\n    ]\n  }\n]\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/resources/de/codecentric/boot/admin/server/web/client/configprops-expected.json",
    "content": "{\n  \"contexts\": {\n    \"application\": {\n      \"beans\": {\n        \"spring.jpa-org.springframework.boot.autoconfigure.orm.jpa.JpaProperties\": {\n          \"prefix\": \"spring.jpa\",\n          \"properties\": {\n            \"error\": \"Cannot serialize 'spring.jpa'\"\n          }\n        },\n        \"endpoints-org.springframework.boot.actuate.endpoint.EndpointProperties\": {\n          \"prefix\": \"endpoints\",\n          \"properties\": {\n            \"enabled\": true,\n            \"sensitive\": null\n          }\n        }\n      }\n    },\n    \"parentContext\": {\n      \"beans\": {\n        \"spring.cloud.config-org.springframework.cloud.bootstrap.config.PropertySourceBootstrapProperties\": {\n          \"prefix\": \"spring.cloud.config\",\n          \"properties\": {\n            \"overrideSystemProperties\": true,\n            \"overrideNone\": false,\n            \"allowOverride\": true\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/resources/de/codecentric/boot/admin/server/web/client/configprops-legacy.json",
    "content": "{\n  \"spring.jpa-org.springframework.boot.autoconfigure.orm.jpa.JpaProperties\": {\n    \"prefix\": \"spring.jpa\",\n    \"properties\": {\n      \"error\": \"Cannot serialize 'spring.jpa'\"\n    }\n  },\n  \"endpoints-org.springframework.boot.actuate.endpoint.EndpointProperties\": {\n    \"prefix\": \"endpoints\",\n    \"properties\": {\n      \"enabled\": true,\n      \"sensitive\": null\n    }\n  },\n  \"parent\": {\n    \"spring.cloud.config-org.springframework.cloud.bootstrap.config.PropertySourceBootstrapProperties\": {\n      \"prefix\": \"spring.cloud.config\",\n      \"properties\": {\n        \"overrideSystemProperties\": true,\n        \"overrideNone\": false,\n        \"allowOverride\": true\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/resources/de/codecentric/boot/admin/server/web/client/env-expected.json",
    "content": "{\n  \"activeProfiles\": [\n    \"one\"\n  ],\n  \"propertySources\": [\n    {\n      \"name\": \"server.ports\",\n      \"properties\": {\n        \"local.server.port\": {\n          \"value\": 9000\n        }\n      }\n    },\n    {\n      \"name\": \"servletContextInitParams\",\n      \"properties\": {}\n    },\n    {\n      \"name\": \"systemProperties\",\n      \"properties\": {\n        \"java.runtime.name\": {\n          \"value\": \"OpenJDK Runtime Environment\"\n        },\n        \"java.protocol.handler.pkgs\": {\n          \"value\": \"org.springframework.boot.loader\"\n        },\n        \"sun.boot.library.path\": {\n          \"value\": \"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.121-0.b13.el7_3.x86_64/jre/lib/amd64\"\n        }\n      }\n    },\n    {\n      \"name\": \"class path resource [spring-boot-starter-batch-web.properties]\",\n      \"properties\": {\n        \"spring.batch.job.enabled\": {\n          \"value\": \"false\"\n        }\n      }\n    }\n  ]\n}"
  },
  {
    "path": "spring-boot-admin-server/src/test/resources/de/codecentric/boot/admin/server/web/client/env-legacy.json",
    "content": "{\n  \"profiles\": [\n    \"one\"\n  ],\n  \"server.ports\": {\n    \"local.server.port\": 9000\n  },\n  \"servletContextInitParams\": {},\n  \"systemProperties\": {\n    \"java.runtime.name\": \"OpenJDK Runtime Environment\",\n    \"java.protocol.handler.pkgs\": \"org.springframework.boot.loader\",\n    \"sun.boot.library.path\": \"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.121-0.b13.el7_3.x86_64/jre/lib/amd64\"\n  },\n  \"class path resource [spring-boot-starter-batch-web.properties]\": {\n    \"spring.batch.job.enabled\": \"false\"\n  }\n}"
  },
  {
    "path": "spring-boot-admin-server/src/test/resources/de/codecentric/boot/admin/server/web/client/flyway-expected.json",
    "content": "{\n  \"contexts\": {\n    \"application\": {\n      \"flywayBeans\": {\n        \"flyway\": {\n          \"migrations\": [\n            {\n              \"type\": \"SQL\",\n              \"checksum\": 710039845,\n              \"version\": \"1\",\n              \"description\": \"init\",\n              \"script\": \"V1__init.sql\",\n              \"state\": \"SUCCESS\",\n              \"installedOn\": \"2017-12-30T11:12:18.544Z\",\n              \"executionTime\": 10\n            }\n          ]\n        },\n        \"secondary\": {\n          \"migrations\": [\n            {\n              \"type\": \"SQL\",\n              \"checksum\": 710039845,\n              \"version\": \"1\",\n              \"description\": \"init\",\n              \"script\": \"V1__init.sql\",\n              \"state\": \"SUCCESS\",\n              \"installedOn\": \"2017-12-30T11:12:18.544Z\",\n              \"executionTime\": 10\n            }\n          ]\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/resources/de/codecentric/boot/admin/server/web/client/flyway-legacy.json",
    "content": "[\n  {\n    \"name\": \"flyway\",\n    \"migrations\": [\n      {\n        \"type\": \"SQL\",\n        \"checksum\": 710039845,\n        \"version\": \"1\",\n        \"description\": \"init\",\n        \"script\": \"V1__init.sql\",\n        \"state\": \"SUCCESS\",\n        \"installedOn\": 1514632338544,\n        \"executionTime\": 10\n      }\n    ]\n  },\n  {\n    \"name\": \"secondary\",\n    \"migrations\": [\n      {\n        \"type\": \"SQL\",\n        \"checksum\": 710039845,\n        \"version\": \"1\",\n        \"description\": \"init\",\n        \"script\": \"V1__init.sql\",\n        \"state\": \"SUCCESS\",\n        \"installedOn\": 1514632338544,\n        \"executionTime\": 10\n      }\n    ]\n  }\n]"
  },
  {
    "path": "spring-boot-admin-server/src/test/resources/de/codecentric/boot/admin/server/web/client/health-expected.json",
    "content": "{\n  \"status\": \"DOWN\",\n  \"details\": {\n    \"info\": \"Hello\",\n    \"sub-1\": {\n      \"status\": \"DOWN\",\n      \"details\": {\n        \"info-1\": false,\n        \"sub-1-1\": {\n          \"status\": \"UP\"\n        },\n        \"sub-1-2\": {\n          \"status\": \"DOWN\",\n          \"details\": {\n            \"info-1-2\": \"World\"\n          }\n        }\n      }\n    },\n    \"sub-2\": {\n      \"status\": \"UP\",\n      \"details\": {\n        \"info-2\": 1\n      }\n    }\n  }\n}"
  },
  {
    "path": "spring-boot-admin-server/src/test/resources/de/codecentric/boot/admin/server/web/client/health-legacy.json",
    "content": "{\n  \"status\": \"DOWN\",\n  \"info\": \"Hello\",\n  \"sub-1\": {\n    \"status\": \"DOWN\",\n    \"info-1\": false,\n    \"sub-1-1\": {\n      \"status\": \"UP\"\n    },\n    \"sub-1-2\": {\n      \"status\": \"DOWN\",\n      \"info-1-2\": \"World\"\n    }\n  },\n  \"sub-2\": {\n    \"status\": \"UP\",\n    \"info-2\": 1\n  }\n}"
  },
  {
    "path": "spring-boot-admin-server/src/test/resources/de/codecentric/boot/admin/server/web/client/httptrace-expected.json",
    "content": "{\n  \"traces\": [\n    {\n      \"timestamp\": \"2018-02-04T21:58:53.427Z\",\n      \"request\": {\n        \"method\": \"HEAD\",\n        \"uri\": \"/actuator/liquibase\",\n        \"headers\": {\n          \"pragma\": [\n            \"no-cache\"\n          ],\n          \"cache-control\": [\n            \"no-cache\"\n          ],\n          \"accept\": [\n            \"application/json, text/plain, */*\"\n          ],\n          \"user-agent\": [\n            \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36\"\n          ],\n          \"accept-language\": [\n            \"de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7\"\n          ],\n          \"accept-encoding\": [\n            \"gzip\"\n          ],\n          \"host\": [\n            \"172.17.0.17:8080\"\n          ],\n          \"connection\": [\n            \"Keep-Alive\"\n          ]\n        }\n      },\n      \"response\": {\n        \"status\": 404,\n        \"headers\": {\n          \"X-Application-Context\": [\n            \"spring-application\"\n          ]\n        }\n      },\n      \"timeTaken\": 2\n    },\n    {\n      \"timestamp\": \"2018-02-19T17:34:51.207Z\",\n      \"request\": {\n        \"method\": \"GET\",\n        \"uri\": \"\",\n        \"headers\": {\n          \"accept\": [\n            \"application/json, text/plain, */*\"\n          ],\n          \"user-agent\": [\n            \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36\"\n          ],\n          \"accept-language\": [\n            \"de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7\"\n          ],\n          \"accept-encoding\": [\n            \"gzip, deflate, br\"\n          ],\n          \"x-requested-with\": [\n            \"XMLHttpRequest\"\n          ]\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"X-Application-Context\": [\n            \"spring-application\"\n          ],\n          \"Content-Type\": [\n            \"application/json;charset=UTF-8\"\n          ],\n          \"Transfer-Encoding\": [\n            \"chunked\"\n          ],\n          \"Date\": [\n            \"Sun, 04 Feb 2018 21:58:44 GMT\"\n          ]\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/resources/de/codecentric/boot/admin/server/web/client/httptrace-legacy.json",
    "content": "[\n  {\n    \"timestamp\": 1517781533427,\n    \"info\": {\n      \"method\": \"HEAD\",\n      \"path\": \"/actuator/liquibase\",\n      \"headers\": {\n        \"request\": {\n          \"pragma\": \"no-cache\",\n          \"cache-control\": \"no-cache\",\n          \"accept\": \"application/json, text/plain, */*\",\n          \"user-agent\": \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36\",\n          \"accept-language\": \"de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7\",\n          \"accept-encoding\": \"gzip\",\n          \"host\": \"172.17.0.17:8080\",\n          \"connection\": \"Keep-Alive\"\n        },\n        \"response\": {\n          \"X-Application-Context\": \"spring-application\",\n          \"status\": \"404\"\n        }\n      },\n      \"timeTaken\": \"2\"\n    }\n  },\n  {\n    \"timestamp\": \"2018-02-19T17:34:51.207+0000\",\n    \"info\": {\n      \"method\": \"GET\",\n      \"path\": \"\",\n      \"headers\": {\n        \"request\": {\n          \"accept\": \"application/json, text/plain, */*\",\n          \"user-agent\": \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36\",\n          \"accept-language\": \"de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7\",\n          \"accept-encoding\": \"gzip, deflate, br\",\n          \"x-requested-with\": \"XMLHttpRequest\"\n        },\n        \"response\": {\n          \"X-Application-Context\": \"spring-application\",\n          \"Content-Type\": \"application/json;charset=UTF-8\",\n          \"Transfer-Encoding\": \"chunked\",\n          \"Date\": \"Sun, 04 Feb 2018 21:58:44 GMT\",\n          \"status\": \"200\"\n        }\n      }\n    }\n  }\n]\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/resources/de/codecentric/boot/admin/server/web/client/liquibase-expected.json",
    "content": "{\n  \"contexts\": {\n    \"application\": {\n      \"liquibaseBeans\": {\n        \"liquibase\": {\n          \"changeSets\": [\n            {\n              \"author\": \"marceloverdijk\",\n              \"changeLog\": \"classpath:/db/changelog/db.changelog-master.yaml\",\n              \"comments\": \"\",\n              \"contexts\": [],\n              \"dateExecuted\": \"2017-12-29T23:05:35.890Z\",\n              \"deploymentId\": \"4588735849\",\n              \"description\": \"createTable tableName=person\",\n              \"execType\": \"EXECUTED\",\n              \"id\": \"1\",\n              \"labels\": [],\n              \"checksum\": \"7:b8f2ae9c88deabd32666dff9bc5d7f5d\",\n              \"orderExecuted\": 1,\n              \"tag\": null\n            },\n            {\n              \"author\": \"marceloverdijk\",\n              \"changeLog\": \"classpath:/db/changelog/db.changelog-master.yaml\",\n              \"comments\": \"\",\n              \"contexts\": [\n                \"dev\",\n                \"db2\"\n              ],\n              \"dateExecuted\": \"2017-12-29T23:05:35.899Z\",\n              \"deploymentId\": \"4588735849\",\n              \"description\": \"insert tableName=person\",\n              \"execType\": \"EXECUTED\",\n              \"id\": \"2\",\n              \"labels\": [\n                \"hard\",\n                \"core\"\n              ],\n              \"checksum\": \"7:a8006415097ebb8b3334a23347847322\",\n              \"orderExecuted\": 2,\n              \"tag\": null\n            }\n          ]\n        },\n        \"secondary\": {\n          \"changeSets\": [\n            {\n              \"author\": \"marceloverdijk\",\n              \"changeLog\": \"classpath:/db/changelog/db.changelog-master.yaml\",\n              \"comments\": \"\",\n              \"contexts\": [],\n              \"dateExecuted\": \"2017-12-29T23:05:35.890Z\",\n              \"deploymentId\": \"4588735849\",\n              \"description\": \"createTable tableName=person\",\n              \"execType\": \"EXECUTED\",\n              \"id\": \"1\",\n              \"labels\": [],\n              \"checksum\": \"7:b8f2ae9c88deabd32666dff9bc5d7f5d\",\n              \"orderExecuted\": 1,\n              \"tag\": null\n            }\n          ]\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/resources/de/codecentric/boot/admin/server/web/client/liquibase-legacy.json",
    "content": "[\n  {\n    \"name\": \"liquibase\",\n    \"changeLogs\": [\n      {\n        \"ID\": \"1\",\n        \"AUTHOR\": \"marceloverdijk\",\n        \"FILENAME\": \"classpath:/db/changelog/db.changelog-master.yaml\",\n        \"DATEEXECUTED\": 1514588735890,\n        \"ORDEREXECUTED\": 1,\n        \"EXECTYPE\": \"EXECUTED\",\n        \"MD5SUM\": \"7:b8f2ae9c88deabd32666dff9bc5d7f5d\",\n        \"DESCRIPTION\": \"createTable tableName=person\",\n        \"COMMENTS\": \"\",\n        \"TAG\": null,\n        \"LIQUIBASE\": \"3.5.3\",\n        \"CONTEXTS\": null,\n        \"LABELS\": null,\n        \"DEPLOYMENT_ID\": \"4588735849\"\n      },\n      {\n        \"ID\": \"2\",\n        \"AUTHOR\": \"marceloverdijk\",\n        \"FILENAME\": \"classpath:/db/changelog/db.changelog-master.yaml\",\n        \"DATEEXECUTED\": 1514588735899,\n        \"ORDEREXECUTED\": 2,\n        \"EXECTYPE\": \"EXECUTED\",\n        \"MD5SUM\": \"7:a8006415097ebb8b3334a23347847322\",\n        \"DESCRIPTION\": \"insert tableName=person\",\n        \"COMMENTS\": \"\",\n        \"TAG\": null,\n        \"LIQUIBASE\": \"3.5.3\",\n        \"CONTEXTS\": \"dev,db2\",\n        \"LABELS\": \"hard,core\",\n        \"DEPLOYMENT_ID\": \"4588735849\"\n      }\n    ]\n  },\n  {\n    \"name\": \"secondary\",\n    \"changeLogs\": [\n      {\n        \"ID\": \"1\",\n        \"AUTHOR\": \"marceloverdijk\",\n        \"FILENAME\": \"classpath:/db/changelog/db.changelog-master.yaml\",\n        \"DATEEXECUTED\": 1514588735890,\n        \"ORDEREXECUTED\": 1,\n        \"EXECTYPE\": \"EXECUTED\",\n        \"MD5SUM\": \"7:b8f2ae9c88deabd32666dff9bc5d7f5d\",\n        \"DESCRIPTION\": \"createTable tableName=person\",\n        \"COMMENTS\": \"\",\n        \"TAG\": null,\n        \"LIQUIBASE\": \"3.5.3\",\n        \"CONTEXTS\": null,\n        \"LABELS\": null,\n        \"DEPLOYMENT_ID\": \"4588735849\"\n      }\n    ]\n  }\n]"
  },
  {
    "path": "spring-boot-admin-server/src/test/resources/de/codecentric/boot/admin/server/web/client/mappings-expected.json",
    "content": "{\n  \"contexts\": {\n    \"application\": {\n      \"mappings\": {\n        \"dispatcherServlets\": {\n          \"dispatcherServlet\": [\n            {\n              \"details\": {\n                \"requestMappingConditions\": {\n                  \"consumes\": [],\n                  \"headers\": [],\n                  \"methods\": [],\n                  \"params\": [],\n                  \"patterns\": [\n                    \"/**/favicon.ico\"\n                  ],\n                  \"produces\": []\n                }\n              },\n              \"predicate\": \"/**/favicon.ico\"\n            },\n            {\n              \"details\": {\n                \"handlerMethod\": {\n                  \"className\": \"org.springframework.boot.autoconfigure.web.BasicErrorController\",\n                  \"descriptor\": \"(Ljavax/servlet/http/HttpServletRequest;)Lorg/springframework/http/ResponseEntity;\",\n                  \"name\": \"error\"\n                },\n                \"requestMappingConditions\": {\n                  \"consumes\": [],\n                  \"headers\": [],\n                  \"methods\": [],\n                  \"params\": [],\n                  \"patterns\": [\n                    \"/error\"\n                  ],\n                  \"produces\": []\n                }\n              },\n              \"predicate\": \"{[/error]}\",\n              \"handler\": \"public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)\"\n            },\n            {\n              \"details\": {\n                \"handlerMethod\": {\n                  \"className\": \"org.springframework.boot.actuate.endpoint.mvc.LoggersMvcEndpoint\",\n                  \"descriptor\": \"(Ljava/lang/String;Ljava/util/Map;)Ljava/lang/Object;\",\n                  \"name\": \"set\"\n                },\n                \"requestMappingConditions\": {\n                  \"consumes\": [\n                    {\n                      \"mediaType\": \"application/vnd.spring-boot.actuator.v1+json\"\n                    },\n                    {\n                      \"mediaType\": \"application/json\"\n                    }\n                  ],\n                  \"headers\": [],\n                  \"methods\": [\n                    \"POST\"\n                  ],\n                  \"params\": [],\n                  \"patterns\": [\n                    \"/actuator/loggers/{name:.*}\"\n                  ],\n                  \"produces\": [\n                    {\n                      \"mediaType\": \"application/vnd.spring-boot.actuator.v1+json\"\n                    },\n                    {\n                      \"mediaType\": \"application/json\"\n                    }\n                  ]\n                }\n              },\n              \"predicate\": \"{[/actuator/loggers/{name:.*}],methods=[POST],consumes=[application/vnd.spring-boot.actuator.v1+json || application/json],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}\",\n              \"handler\": \"public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.LoggersMvcEndpoint.set(java.lang.String,java.util.Map<java.lang.String, java.lang.String>)\"\n            }\n          ]\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/resources/de/codecentric/boot/admin/server/web/client/mappings-legacy.json",
    "content": "{\n  \"/**/favicon.ico\": {\n    \"bean\": \"faviconHandlerMapping\"\n  },\n  \"{[/error]}\": {\n    \"bean\": \"requestMappingHandlerMapping\",\n    \"method\": \"public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)\"\n  },\n  \"{[/actuator/loggers/{name:.*}],methods=[POST],consumes=[application/vnd.spring-boot.actuator.v1+json || application/json],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}\": {\n    \"bean\": \"endpointHandlerMapping\",\n    \"method\": \"public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.LoggersMvcEndpoint.set(java.lang.String,java.util.Map<java.lang.String, java.lang.String>)\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/resources/de/codecentric/boot/admin/server/web/client/threaddump-expected.json",
    "content": "{\n  \"threads\": [\n    {\n      \"threadId\": \"1\",\n      \"threadName\": \"foo\"\n    },\n    {\n      \"threadId\": \"2\",\n      \"threadName\": \"bar\"\n    }\n  ]\n}"
  },
  {
    "path": "spring-boot-admin-server/src/test/resources/de/codecentric/boot/admin/server/web/client/threaddump-legacy.json",
    "content": "[\n  {\n    \"threadId\": \"1\",\n    \"threadName\": \"foo\"\n  },\n  {\n    \"threadId\": \"2\",\n    \"threadName\": \"bar\"\n  }\n]"
  },
  {
    "path": "spring-boot-admin-server/src/test/resources/logback-test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright 2014-2018 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<configuration>\n    <include resource=\"org/springframework/boot/logging/logback/base.xml\" />\n    <logger name=\"de.codecentric\" level=\"DEBUG\"/>\n</configuration>\n"
  },
  {
    "path": "spring-boot-admin-server/src/test/resources/server-config-test.properties",
    "content": "spring.boot.admin.contextPath=/admin\n\nspring.boot.admin.instance-auth.default-user-name=admin\nspring.boot.admin.instance-auth.default-password=topsecret\n\nspring.boot.admin.instance-auth.service-map.my-service.userName=me\nspring.boot.admin.instance-auth.service-map.my-service.userPassword=secret\n"
  },
  {
    "path": "spring-boot-admin-server-cloud/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright 2014-2019 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>spring-boot-admin-server-cloud</artifactId>\n\n    <name>Spring Boot Admin Server Cloud</name>\n    <description>Spring Boot Admin Server Cloud</description>\n\n    <parent>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-build</artifactId>\n        <version>${revision}</version>\n        <relativePath>../spring-boot-admin-build</relativePath>\n    </parent>\n\n    <dependencies>\n        <dependency>\n            <groupId>de.codecentric</groupId>\n            <artifactId>spring-boot-admin-server</artifactId>\n        </dependency>\n        <!-- Optional Discovery Client -->\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <!-- Optional Eureka Discovery Client -->\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>\n            <optional>true</optional>\n            <exclusions>\n                <exclusion>\n                    <groupId>org.springframework.boot</groupId>\n                    <artifactId>spring-boot-starter-webmvc</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n        <!-- Optional Kubernetes Discovery using Official Kubernetes Client -->\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-kubernetes-client</artifactId>\n            <optional>true</optional>\n            <exclusions>\n                <exclusion>\n                    <groupId>org.springframework.boot</groupId>\n                    <artifactId>spring-boot-starter-webmvc</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n        <!-- Optional Kubernetes Discovery using Fabric 8 Kubernetes Java Client -->\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-starter-kubernetes-fabric8</artifactId>\n            <optional>true</optional>\n            <exclusions>\n                <exclusion>\n                    <groupId>org.springframework.boot</groupId>\n                    <artifactId>spring-boot-starter-webmvc</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-autoconfigure-processor</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>com.google.code.findbugs</groupId>\n            <artifactId>jsr305</artifactId>\n        </dependency>\n        <!-- Optional Configuration Processor for metadata -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-configuration-processor</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <!-- Test -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-security</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>tools.jackson.datatype</groupId>\n            <artifactId>jackson-datatype-json-org</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>io.projectreactor</groupId>\n            <artifactId>reactor-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "spring-boot-admin-server-cloud/src/main/java/de/codecentric/boot/admin/server/cloud/config/AdminServerDiscoveryAutoConfiguration.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.cloud.config;\n\nimport com.netflix.discovery.EurekaClient;\nimport org.springframework.boot.autoconfigure.AutoConfigureAfter;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;\nimport org.springframework.boot.cloud.CloudPlatform;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.cloud.client.discovery.DiscoveryClient;\nimport org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport de.codecentric.boot.admin.server.cloud.discovery.DefaultServiceInstanceConverter;\nimport de.codecentric.boot.admin.server.cloud.discovery.EurekaServiceInstanceConverter;\nimport de.codecentric.boot.admin.server.cloud.discovery.InstanceDiscoveryListener;\nimport de.codecentric.boot.admin.server.cloud.discovery.KubernetesServiceInstanceConverter;\nimport de.codecentric.boot.admin.server.cloud.discovery.ServiceInstanceConverter;\nimport de.codecentric.boot.admin.server.config.AdminServerAutoConfiguration;\nimport de.codecentric.boot.admin.server.config.AdminServerMarkerConfiguration;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.services.InstanceRegistry;\n\n@Configuration(proxyBeanMethods = false)\n@ConditionalOnSingleCandidate(DiscoveryClient.class)\n@ConditionalOnBean(AdminServerMarkerConfiguration.Marker.class)\n@ConditionalOnProperty(prefix = \"spring.boot.admin.discovery\", name = \"enabled\", matchIfMissing = true)\n@AutoConfigureAfter(value = AdminServerAutoConfiguration.class,\n\t\tname = { \"org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration\",\n\t\t\t\t\"org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration\" })\npublic class AdminServerDiscoveryAutoConfiguration {\n\n\t@Bean\n\t@ConditionalOnMissingBean\n\t@ConfigurationProperties(prefix = \"spring.boot.admin.discovery\")\n\tpublic InstanceDiscoveryListener instanceDiscoveryListener(ServiceInstanceConverter serviceInstanceConverter,\n\t\t\tDiscoveryClient discoveryClient, InstanceRegistry registry, InstanceRepository repository) {\n\t\tInstanceDiscoveryListener listener = new InstanceDiscoveryListener(discoveryClient, registry, repository);\n\t\tlistener.setConverter(serviceInstanceConverter);\n\t\treturn listener;\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean({ ServiceInstanceConverter.class })\n\t@ConfigurationProperties(prefix = \"spring.boot.admin.discovery.converter\")\n\tpublic DefaultServiceInstanceConverter serviceInstanceConverter() {\n\t\treturn new DefaultServiceInstanceConverter();\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnMissingBean({ ServiceInstanceConverter.class })\n\t@ConditionalOnBean(EurekaClient.class)\n\tpublic static class EurekaConverterConfiguration {\n\n\t\t@Bean\n\t\t@ConfigurationProperties(prefix = \"spring.boot.admin.discovery.converter\")\n\t\tpublic EurekaServiceInstanceConverter serviceInstanceConverter() {\n\t\t\treturn new EurekaServiceInstanceConverter();\n\t\t}\n\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnBean(KubernetesDiscoveryProperties.class)\n\t@ConditionalOnMissingBean({ ServiceInstanceConverter.class })\n\t@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)\n\tpublic static class KubernetesConverterConfiguration {\n\n\t\t@Bean\n\t\t@ConfigurationProperties(prefix = \"spring.boot.admin.discovery.converter\")\n\t\tpublic KubernetesServiceInstanceConverter serviceInstanceConverter(\n\t\t\t\tKubernetesDiscoveryProperties discoveryProperties) {\n\t\t\treturn new KubernetesServiceInstanceConverter(discoveryProperties);\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-cloud/src/main/java/de/codecentric/boot/admin/server/cloud/config/package-info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n@NullMarked\npackage de.codecentric.boot.admin.server.cloud.config;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "spring-boot-admin-server-cloud/src/main/java/de/codecentric/boot/admin/server/cloud/discovery/DefaultServiceInstanceConverter.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.cloud.discovery;\n\nimport java.net.URI;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\nimport org.jspecify.annotations.Nullable;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.cloud.client.ServiceInstance;\nimport org.springframework.web.util.UriComponentsBuilder;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\n\nimport static java.util.Collections.emptyMap;\nimport static org.springframework.util.StringUtils.hasText;\n\n/**\n * Converts any {@link ServiceInstance}s to {@link Instance}s. To customize the health- or\n * management-url for all instances you can set healthEndpointPath or\n * managementContextPath respectively. If you want to influence the url per service you\n * can add <code>management.scheme</code>, <code>management.address</code>,\n * <code>management.port</code>, <code>management.context-path</code> or\n * <code>health.path</code> to the instances metadata.\n *\n * @author Johannes Edmeier\n */\npublic class DefaultServiceInstanceConverter implements ServiceInstanceConverter {\n\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(DefaultServiceInstanceConverter.class);\n\n\tprivate static final String[] KEYS_MANAGEMENT_SCHEME = { \"management.scheme\", \"management-scheme\" };\n\n\tprivate static final String[] KEYS_MANAGEMENT_ADDRESS = { \"management.address\", \"management-address\" };\n\n\tprivate static final String[] KEYS_MANAGEMENT_PORT = { \"management.port\", \"management-port\", \"port.management\" };\n\n\tprivate static final String[] KEYS_MANAGEMENT_PATH = { \"management.context-path\", \"management-context-path\" };\n\n\tprivate static final String[] KEYS_HEALTH_PATH = { \"health.path\", \"health-path\" };\n\n\t/**\n\t * Default context-path to be appended to the url of the discovered service for the\n\t * management-url.\n\t */\n\tprivate String managementContextPath = \"/actuator\";\n\n\t/**\n\t * Default path of the health-endpoint to be used for the health-url of the discovered\n\t * service.\n\t */\n\tprivate String healthEndpointPath = \"health\";\n\n\tprotected static @Nullable String getMetadataValue(ServiceInstance instance, String... keys) {\n\t\tMap<String, String> metadata = instance.getMetadata();\n\t\tfor (String key : keys) {\n\t\t\tString value = metadata.get(key);\n\t\t\tif (value != null) {\n\t\t\t\treturn value;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic Registration convert(ServiceInstance instance) {\n\t\tLOGGER.debug(\"Converting service '{}' running at '{}' with metadata {}\", instance.getServiceId(),\n\t\t\t\tinstance.getUri(), instance.getMetadata());\n\n\t\tString healthUrl = getHealthUrl(instance).toString();\n\t\tString managementUrl = getManagementUrl(instance).toString();\n\t\tString serviceUrl = getServiceUrl(instance).toString();\n\n\t\treturn Registration.create(instance.getServiceId(), healthUrl)\n\t\t\t.managementUrl(managementUrl)\n\t\t\t.serviceUrl(serviceUrl)\n\t\t\t.metadata(getMetadata(instance))\n\t\t\t.build();\n\t}\n\n\tprotected URI getHealthUrl(ServiceInstance instance) {\n\t\treturn UriComponentsBuilder.fromUri(getManagementUrl(instance))\n\t\t\t.path(\"/\")\n\t\t\t.path(getHealthPath(instance))\n\t\t\t.build()\n\t\t\t.toUri();\n\t}\n\n\tprotected String getHealthPath(ServiceInstance instance) {\n\t\tString healthPath = getMetadataValue(instance, KEYS_HEALTH_PATH);\n\t\tif (hasText(healthPath)) {\n\t\t\treturn healthPath;\n\t\t}\n\t\treturn this.healthEndpointPath;\n\t}\n\n\tprotected URI getManagementUrl(ServiceInstance instance) {\n\t\tURI serviceUrl = this.getServiceUrl(instance);\n\t\tString managementScheme = this.getManagementScheme(instance);\n\t\tString managementHost = this.getManagementHost(instance);\n\t\tint managementPort = this.getManagementPort(instance);\n\n\t\tUriComponentsBuilder builder;\n\t\tif (serviceUrl.getHost().equals(managementHost) && serviceUrl.getScheme().equals(managementScheme)\n\t\t\t\t&& serviceUrl.getPort() == managementPort) {\n\t\t\tbuilder = UriComponentsBuilder.fromUri(serviceUrl);\n\t\t}\n\t\telse {\n\t\t\tbuilder = UriComponentsBuilder.newInstance().scheme(managementScheme).host(managementHost);\n\t\t\tif (managementPort != -1) {\n\t\t\t\tbuilder.port(managementPort);\n\t\t\t}\n\t\t}\n\n\t\treturn builder.path(\"/\").path(getManagementPath(instance)).build().toUri();\n\t}\n\n\tprivate String getManagementScheme(ServiceInstance instance) {\n\t\tString managementServerScheme = getMetadataValue(instance, KEYS_MANAGEMENT_SCHEME);\n\t\tif (hasText(managementServerScheme)) {\n\t\t\treturn managementServerScheme;\n\t\t}\n\t\treturn getServiceUrl(instance).getScheme();\n\t}\n\n\tprotected String getManagementHost(ServiceInstance instance) {\n\t\tString managementServerHost = getMetadataValue(instance, KEYS_MANAGEMENT_ADDRESS);\n\t\tif (hasText(managementServerHost)) {\n\t\t\treturn managementServerHost;\n\t\t}\n\t\treturn getServiceUrl(instance).getHost();\n\t}\n\n\tprotected int getManagementPort(ServiceInstance instance) {\n\t\tString managementPort = getMetadataValue(instance, KEYS_MANAGEMENT_PORT);\n\t\tif (hasText(managementPort)) {\n\t\t\treturn Integer.parseInt(managementPort);\n\t\t}\n\t\treturn getServiceUrl(instance).getPort();\n\t}\n\n\tprotected String getManagementPath(ServiceInstance instance) {\n\t\tString managementPath = getMetadataValue(instance, KEYS_MANAGEMENT_PATH);\n\t\tif (hasText(managementPath)) {\n\t\t\treturn managementPath;\n\t\t}\n\t\treturn this.managementContextPath;\n\t}\n\n\tprotected URI getServiceUrl(ServiceInstance instance) {\n\t\treturn instance.getUri();\n\t}\n\n\tprotected Map<String, String> getMetadata(ServiceInstance instance) {\n\t\treturn (instance.getMetadata() != null) ? instance.getMetadata()\n\t\t\t.entrySet()\n\t\t\t.stream()\n\t\t\t.filter((e) -> e.getKey() != null && e.getValue() != null)\n\t\t\t.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)) : emptyMap();\n\t}\n\n\tpublic String getManagementContextPath() {\n\t\treturn this.managementContextPath;\n\t}\n\n\tpublic void setManagementContextPath(String managementContextPath) {\n\t\tthis.managementContextPath = managementContextPath;\n\t}\n\n\tpublic String getHealthEndpointPath() {\n\t\treturn this.healthEndpointPath;\n\t}\n\n\tpublic void setHealthEndpointPath(String healthEndpointPath) {\n\t\tthis.healthEndpointPath = healthEndpointPath;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-cloud/src/main/java/de/codecentric/boot/admin/server/cloud/discovery/EurekaServiceInstanceConverter.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.cloud.discovery;\n\nimport java.net.URI;\n\nimport com.netflix.appinfo.InstanceInfo;\nimport org.springframework.cloud.client.ServiceInstance;\nimport org.springframework.cloud.netflix.eureka.EurekaServiceInstance;\nimport org.springframework.util.StringUtils;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\n\n/**\n * Converts {@link EurekaServiceInstance}s to {@link Instance}s\n *\n * @author Johannes Edmeier\n */\npublic class EurekaServiceInstanceConverter extends DefaultServiceInstanceConverter {\n\n\t@Override\n\tprotected URI getHealthUrl(ServiceInstance instance) {\n\t\tif (!(instance instanceof EurekaServiceInstance)) {\n\t\t\treturn super.getHealthUrl(instance);\n\t\t}\n\n\t\tInstanceInfo instanceInfo = ((EurekaServiceInstance) instance).getInstanceInfo();\n\t\tString healthUrl = instanceInfo.getSecureHealthCheckUrl();\n\t\tif (!StringUtils.hasText(healthUrl)) {\n\t\t\thealthUrl = instanceInfo.getHealthCheckUrl();\n\t\t}\n\t\treturn URI.create(healthUrl);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-cloud/src/main/java/de/codecentric/boot/admin/server/cloud/discovery/InstanceDiscoveryListener.java",
    "content": "/*\n * Copyright 2014-2024 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.cloud.discovery;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.boot.context.event.ApplicationReadyEvent;\nimport org.springframework.cloud.client.ServiceInstance;\nimport org.springframework.cloud.client.discovery.DiscoveryClient;\nimport org.springframework.cloud.client.discovery.event.HeartbeatEvent;\nimport org.springframework.cloud.client.discovery.event.HeartbeatMonitor;\nimport org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent;\nimport org.springframework.cloud.client.discovery.event.ParentHeartbeatEvent;\nimport org.springframework.context.event.EventListener;\nimport org.springframework.util.PatternMatchUtils;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.services.InstanceRegistry;\nimport de.codecentric.boot.admin.server.web.client.RefreshInstancesEvent;\n\n/**\n * Listener for Heartbeats events to publish all services to the instance registry.\n *\n * @author Johannes Edmeier\n */\npublic class InstanceDiscoveryListener {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(InstanceDiscoveryListener.class);\n\n\tprivate static final String SOURCE = \"discovery\";\n\n\tprivate final DiscoveryClient discoveryClient;\n\n\tprivate final InstanceRegistry registry;\n\n\tprivate final InstanceRepository repository;\n\n\tprivate final HeartbeatMonitor monitor = new HeartbeatMonitor();\n\n\tprivate ServiceInstanceConverter converter = new DefaultServiceInstanceConverter();\n\n\t/**\n\t * Set of serviceIds to be ignored and not to be registered as application. Supports\n\t * simple patterns (e.g. \"foo*\", \"*foo\", \"foo*bar\").\n\t */\n\tprivate Set<String> ignoredServices = new HashSet<>();\n\n\t/**\n\t * Set of serviceIds that has to match to be registered as application. Supports\n\t * simple patterns (e.g. \"foo*\", \"*foo\", \"foo*bar\"). Default value is everything\n\t */\n\tprivate Set<String> services = new HashSet<>(Collections.singletonList(\"*\"));\n\n\t/**\n\t * Map of metadata that has to be matched by service instance that is to be\n\t * registered. (e.g. \"discoverable=true\")\n\t */\n\tprivate Map<String, String> instancesMetadata = new HashMap<>();\n\n\t/**\n\t * Map of metadata that has to be matched by service instance that is to be ignored.\n\t * (e.g. \"discoverable=false\")\n\t */\n\tprivate Map<String, String> ignoredInstancesMetadata = new HashMap<>();\n\n\tpublic InstanceDiscoveryListener(DiscoveryClient discoveryClient, InstanceRegistry registry,\n\t\t\tInstanceRepository repository) {\n\t\tthis.discoveryClient = discoveryClient;\n\t\tthis.registry = registry;\n\t\tthis.repository = repository;\n\t}\n\n\t@EventListener\n\tpublic void onApplicationReady(ApplicationReadyEvent event) {\n\t\tdiscover();\n\t}\n\n\t@EventListener\n\tpublic void onInstanceRegistered(InstanceRegisteredEvent<?> event) {\n\t\tdiscover();\n\t}\n\n\t@EventListener\n\tpublic void onRefreshInstances(RefreshInstancesEvent event) {\n\t\tdiscover();\n\t}\n\n\t@EventListener\n\tpublic void onParentHeartbeat(ParentHeartbeatEvent event) {\n\t\tdiscoverIfNeeded(event.getValue());\n\t}\n\n\t@EventListener\n\tpublic void onApplicationEvent(HeartbeatEvent event) {\n\t\tdiscoverIfNeeded(event.getValue());\n\t}\n\n\tprivate void discoverIfNeeded(Object value) {\n\t\tif (this.monitor.update(value)) {\n\t\t\tdiscover();\n\t\t}\n\t}\n\n\tprotected void discover() {\n\t\tlog.debug(\"Discovering new instances from DiscoveryClient\");\n\t\tFlux.fromIterable(discoveryClient.getServices())\n\t\t\t.filter(this::shouldRegisterService)\n\t\t\t.flatMapIterable(discoveryClient::getInstances)\n\t\t\t.filter(this::shouldRegisterInstanceBasedOnMetadata)\n\t\t\t.flatMap(this::registerInstance)\n\t\t\t.collect(Collectors.toSet())\n\t\t\t.flatMap(this::removeStaleInstances)\n\t\t\t.subscribe((v) -> {\n\t\t\t}, (ex) -> log.error(\"Unexpected error.\", ex));\n\t}\n\n\tprotected Mono<Void> removeStaleInstances(Set<InstanceId> registeredInstanceIds) {\n\t\treturn repository.findAll()\n\t\t\t.filter(Instance::isRegistered)\n\t\t\t.filter((instance) -> SOURCE.equals(instance.getRegistration().getSource()))\n\t\t\t.map(Instance::getId)\n\t\t\t.filter((id) -> !registeredInstanceIds.contains(id))\n\t\t\t.doOnNext((id) -> log.info(\"Instance '{}' missing in DiscoveryClient services and will be removed.\", id))\n\t\t\t.flatMap(registry::deregister)\n\t\t\t.then();\n\t}\n\n\tprotected boolean shouldRegisterService(final String serviceId) {\n\t\tboolean shouldRegister = matchesPattern(serviceId, services) && !matchesPattern(serviceId, ignoredServices);\n\t\tif (!shouldRegister) {\n\t\t\tlog.debug(\"Ignoring service '{}' from discovery.\", serviceId);\n\t\t}\n\t\treturn shouldRegister;\n\t}\n\n\tprotected boolean matchesPattern(String serviceId, Set<String> patterns) {\n\t\treturn patterns.stream()\n\t\t\t.anyMatch((pattern) -> PatternMatchUtils.simpleMatch(pattern.toLowerCase(), serviceId.toLowerCase()));\n\t}\n\n\tprotected boolean shouldRegisterInstanceBasedOnMetadata(ServiceInstance instance) {\n\t\tboolean shouldRegister = isInstanceAllowedBasedOnMetadata(instance)\n\t\t\t\t&& !isInstanceIgnoredBasedOnMetadata(instance);\n\t\tif (!shouldRegister) {\n\t\t\tlog.debug(\"Ignoring instance '{}' of '{}' service from discovery based on metadata.\",\n\t\t\t\t\tinstance.getInstanceId(), instance.getServiceId());\n\t\t}\n\t\treturn shouldRegister;\n\t}\n\n\tprotected Mono<InstanceId> registerInstance(ServiceInstance instance) {\n\t\ttry {\n\t\t\tRegistration registration = converter.convert(instance).toBuilder().source(SOURCE).build();\n\t\t\tlog.debug(\"Registering discovered instance {}\", registration);\n\t\t\treturn registry.register(registration);\n\t\t}\n\t\tcatch (Exception ex) {\n\t\t\tlog.error(\"Couldn't register instance for discovered instance ({})\", toString(instance), ex);\n\t\t\treturn Mono.empty();\n\t\t}\n\t}\n\n\tprotected String toString(ServiceInstance instance) {\n\t\tString httpScheme = instance.isSecure() ? \"https\" : \"http\";\n\t\treturn String.format(\"serviceId=%s, instanceId=%s, url= %s://%s:%d\", instance.getServiceId(),\n\t\t\t\tinstance.getInstanceId(), (instance.getScheme() != null) ? instance.getScheme() : httpScheme,\n\t\t\t\tinstance.getHost(), instance.getPort());\n\t}\n\n\tpublic void setConverter(ServiceInstanceConverter converter) {\n\t\tthis.converter = converter;\n\t}\n\n\tpublic void setIgnoredServices(Set<String> ignoredServices) {\n\t\tthis.ignoredServices = ignoredServices;\n\t}\n\n\tpublic Set<String> getIgnoredServices() {\n\t\treturn ignoredServices;\n\t}\n\n\tpublic Set<String> getServices() {\n\t\treturn services;\n\t}\n\n\tpublic void setServices(Set<String> services) {\n\t\tthis.services = services;\n\t}\n\n\tpublic Map<String, String> getInstancesMetadata() {\n\t\treturn instancesMetadata;\n\t}\n\n\tpublic void setInstancesMetadata(Map<String, String> instancesMetadata) {\n\t\tthis.instancesMetadata = instancesMetadata;\n\t}\n\n\tpublic Map<String, String> getIgnoredInstancesMetadata() {\n\t\treturn ignoredInstancesMetadata;\n\t}\n\n\tpublic void setIgnoredInstancesMetadata(Map<String, String> ignoredInstancesMetadata) {\n\t\tthis.ignoredInstancesMetadata = ignoredInstancesMetadata;\n\t}\n\n\tprivate boolean isInstanceAllowedBasedOnMetadata(ServiceInstance instance) {\n\t\tif (instancesMetadata.isEmpty()) {\n\t\t\treturn true;\n\t\t}\n\n\t\tfor (Map.Entry<String, String> metadata : instance.getMetadata().entrySet()) {\n\t\t\tif (isMapContainsEntry(instancesMetadata, metadata)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tprivate boolean isInstanceIgnoredBasedOnMetadata(ServiceInstance instance) {\n\t\tif (ignoredInstancesMetadata.isEmpty()) {\n\t\t\treturn false;\n\t\t}\n\n\t\tfor (Map.Entry<String, String> metadata : instance.getMetadata().entrySet()) {\n\t\t\tif (isMapContainsEntry(ignoredInstancesMetadata, metadata)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tprivate boolean isMapContainsEntry(Map<String, String> map, Map.Entry<String, String> entry) {\n\t\tString value = map.get(entry.getKey());\n\t\treturn value != null && value.equals(entry.getValue());\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-cloud/src/main/java/de/codecentric/boot/admin/server/cloud/discovery/KubernetesServiceInstanceConverter.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.cloud.discovery;\n\nimport org.springframework.cloud.client.ServiceInstance;\nimport org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;\n\nimport static org.springframework.util.StringUtils.hasText;\n\npublic class KubernetesServiceInstanceConverter extends DefaultServiceInstanceConverter {\n\n\tpublic static final String MANAGEMENT_PORT_NAME = \"management\";\n\n\tprivate final String portsPrefix;\n\n\tpublic KubernetesServiceInstanceConverter(KubernetesDiscoveryProperties discoveryProperties) {\n\t\tif (discoveryProperties.metadata() != null && discoveryProperties.metadata().portsPrefix() != null) {\n\t\t\tthis.portsPrefix = discoveryProperties.metadata().portsPrefix();\n\t\t}\n\t\telse {\n\t\t\tthis.portsPrefix = \"\";\n\t\t}\n\t}\n\n\t@Override\n\tprotected int getManagementPort(ServiceInstance instance) {\n\t\t// the DiscoveryClient implementation using Kubernetes Client\n\t\t// (KubernetesInformerDiscoveryClient) currently ignores\n\t\t// the portsPrefix from KubernetesDiscoveryProperties\n\t\tString managementPort = getMetadataValue(instance, portsPrefix + MANAGEMENT_PORT_NAME, MANAGEMENT_PORT_NAME);\n\t\tif (hasText(managementPort)) {\n\t\t\treturn Integer.parseInt(managementPort);\n\t\t}\n\t\treturn super.getManagementPort(instance);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-cloud/src/main/java/de/codecentric/boot/admin/server/cloud/discovery/ServiceInstanceConverter.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.cloud.discovery;\n\nimport org.springframework.cloud.client.ServiceInstance;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\n\n/**\n * Converts {@link ServiceInstance}s to {@link Instance}s.\n *\n * @author Johannes Edmeier\n */\npublic interface ServiceInstanceConverter {\n\n\t/**\n\t * Converts a service instance to an application instance to be registered.\n\t * @param instance the service instance.\n\t * @return the Registration\n\t */\n\tRegistration convert(ServiceInstance instance);\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-cloud/src/main/java/de/codecentric/boot/admin/server/cloud/discovery/package-info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n@NullMarked\npackage de.codecentric.boot.admin.server.cloud.discovery;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "spring-boot-admin-server-cloud/src/main/resources/META-INF/additional-spring-configuration-metadata.json",
    "content": "{\n  \"groups\": [\n  ],\n  \"properties\": [\n    {\n      \"name\": \"spring.boot.admin.discovery.enabled\",\n      \"type\": \"java.lang.Boolean\",\n      \"description\": \"Enable Spring Cloud Discovery support.\",\n      \"defaultValue\": \"true\"\n    }\n  ]\n}\n"
  },
  {
    "path": "spring-boot-admin-server-cloud/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "de.codecentric.boot.admin.server.cloud.config.AdminServerDiscoveryAutoConfiguration\n"
  },
  {
    "path": "spring-boot-admin-server-cloud/src/test/java/de/codecentric/boot/admin/server/cloud/AdminApplicationDiscoveryTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.cloud;\n\nimport java.net.URI;\nimport java.time.Duration;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport org.json.JSONObject;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.SpringBootConfiguration;\nimport org.springframework.boot.WebApplicationType;\nimport org.springframework.boot.autoconfigure.EnableAutoConfiguration;\nimport org.springframework.boot.builder.SpringApplicationBuilder;\nimport org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent;\nimport org.springframework.cloud.client.discovery.simple.InstanceProperties;\nimport org.springframework.cloud.client.discovery.simple.SimpleDiscoveryProperties;\nimport org.springframework.context.ConfigurableApplicationContext;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.http.MediaType;\nimport org.springframework.http.codec.json.JacksonJsonDecoder;\nimport org.springframework.http.codec.json.JacksonJsonEncoder;\nimport org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;\nimport org.springframework.security.config.web.server.ServerHttpSecurity;\nimport org.springframework.security.web.server.SecurityWebFilterChain;\nimport org.springframework.test.web.reactive.server.WebTestClient;\nimport org.springframework.web.reactive.function.client.ExchangeStrategies;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\nimport tools.jackson.databind.json.JsonMapper;\nimport tools.jackson.datatype.jsonorg.JsonOrgModule;\n\nimport de.codecentric.boot.admin.server.config.EnableAdminServer;\n\nimport static java.util.Collections.singletonList;\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass AdminApplicationDiscoveryTest {\n\n\tprivate ConfigurableApplicationContext instance;\n\n\tprivate SimpleDiscoveryProperties simpleDiscovery;\n\n\tprivate WebTestClient webClient;\n\n\tprivate int port;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tthis.instance = new SpringApplicationBuilder().sources(TestAdminApplication.class)\n\t\t\t.web(WebApplicationType.REACTIVE)\n\t\t\t.run(\"--server.port=0\", \"--management.endpoints.web.base-path=/mgmt\",\n\t\t\t\t\t\"--management.endpoints.web.exposure.include=info,health\", \"--info.test=foobar\",\n\t\t\t\t\t\"--eureka.client.enabled=false\", \"--spring.cloud.kubernetes.enabled=false\",\n\t\t\t\t\t\"--spring.cloud.kubernetes.discovery.enabled=false\", \"--management.info.env.enabled=true\");\n\n\t\tthis.simpleDiscovery = this.instance.getBean(SimpleDiscoveryProperties.class);\n\n\t\tthis.port = this.instance.getEnvironment().getProperty(\"local.server.port\", Integer.class, 0);\n\t\tthis.webClient = createWebClient(this.port);\n\t}\n\n\t@Test\n\tvoid lifecycle() {\n\t\tAtomicReference<URI> location = new AtomicReference<>();\n\n\t\tStepVerifier.create(getEventStream().log()).expectSubscription().then(() -> {\n\t\t\tStepVerifier.create(listEmptyInstances()).expectNext(true).verifyComplete();\n\t\t\tStepVerifier.create(registerInstance()).consumeNextWith(location::set).verifyComplete();\n\t\t})\n\t\t\t.assertNext((event) -> assertThat(event.opt(\"type\")).isEqualTo(\"REGISTERED\"))\n\t\t\t.assertNext((event) -> assertThat(event.opt(\"type\")).isEqualTo(\"STATUS_CHANGED\"))\n\t\t\t.assertNext((event) -> assertThat(event.opt(\"type\")).isEqualTo(\"ENDPOINTS_DETECTED\"))\n\t\t\t.assertNext((event) -> assertThat(event.opt(\"type\")).isEqualTo(\"INFO_CHANGED\"))\n\t\t\t.then(() -> {\n\t\t\t\tStepVerifier.create(getInstance(location.get())).expectNext(true).verifyComplete();\n\t\t\t\tStepVerifier.create(listInstances()).expectNext(true).verifyComplete();\n\t\t\t\tderegisterInstance();\n\t\t\t})\n\t\t\t.assertNext((event) -> assertThat(event.opt(\"type\")).isEqualTo(\"DEREGISTERED\"))\n\t\t\t.then(() -> StepVerifier.create(listEmptyInstances()).expectNext(true).verifyComplete())\n\t\t\t.thenCancel()\n\t\t\t.verify(Duration.ofSeconds(60));\n\t}\n\n\tprivate Mono<URI> registerInstance() {\n\t\t// We register the instance by setting static values for the SimpleDiscoveryClient\n\t\t// and issuing a\n\t\t// InstanceRegisteredEvent that makes sure the instance gets registered.\n\t\tInstanceProperties instanceProps = new InstanceProperties();\n\t\tinstanceProps.setServiceId(\"Test-Instance\");\n\t\tinstanceProps.setUri(URI.create(\"http://localhost:\" + this.port));\n\t\tinstanceProps.getMetadata().put(\"management.context-path\", \"/mgmt\");\n\t\tthis.simpleDiscovery.getInstances().put(\"Test-Instance\", singletonList(instanceProps));\n\n\t\tthis.instance.publishEvent(new InstanceRegisteredEvent<>(new Object(), null));\n\n\t\t// To get the location of the registered instances we fetch the instance with the\n\t\t// name.\n\t\t//@formatter:off\n\t\treturn this.webClient.get()\n\t\t\t.uri(\"/instances?name=Test-Instance\")\n\t\t\t.accept(MediaType.APPLICATION_JSON)\n\t\t\t.exchange()\n\t\t\t.returnResult(JSONObject.class).getResponseBody()\n\t\t\t.collectList()\n\t\t\t.map((applications) -> {\n\t\t\t\tassertThat(applications).hasSize(1);\n\t\t\t\treturn URI\n\t\t\t\t\t.create(\"http://localhost:\" + this.port + \"/instances/\" + applications.get(0).optString(\"id\"));\n\t\t\t});\n\t\t//@formatter:on\n\t}\n\n\tprivate void deregisterInstance() {\n\t\tthis.simpleDiscovery.getInstances().clear();\n\t\tthis.instance.publishEvent(new InstanceRegisteredEvent<>(new Object(), null));\n\t}\n\n\tprivate Flux<JSONObject> getEventStream() {\n\t\t//@formatter:off\n\t\treturn this.webClient.get().uri(\"/instances/events\").accept(MediaType.TEXT_EVENT_STREAM)\n\t\t\t\t\t\t.exchange()\n\t\t\t\t\t\t.expectStatus().isOk()\n\t\t\t\t\t\t.expectHeader().contentTypeCompatibleWith(MediaType.TEXT_EVENT_STREAM)\n\t\t\t\t\t\t.returnResult(JSONObject.class).getResponseBody();\n\t\t//@formatter:on\n\t}\n\n\tprivate Mono<Boolean> getInstance(URI uri) {\n\t\t//@formatter:off\n\t\treturn this.webClient.get().uri(uri).accept(MediaType.APPLICATION_JSON)\n\t\t\t\t.exchange()\n\t\t\t\t.returnResult(String.class).getResponseBody().single()\n\t\t\t\t.map((body) -> {\n\t\t\t\t\tassertThat(body).contains(\"\\\"name\\\":\\\"Test-Instance\\\"\");\n\t\t\t\t\tassertThat(body).contains(\"\\\"status\\\":\\\"UP\\\"\");\n\t\t\t\t\tassertThat(body).contains(\"\\\"test\\\":\\\"foobar\\\"\");\n\t\t\t\t\treturn true;\n\t\t\t\t});\n\t\t//@formatter:on\n\t}\n\n\tprivate Mono<Boolean> listInstances() {\n\t\t//@formatter:off\n\t\treturn this.webClient.get().uri(\"/instances\").accept(MediaType.APPLICATION_JSON)\n\t\t\t\t.exchange()\n\t\t\t\t.returnResult(String.class).getResponseBody().single()\n\t\t\t\t.map((body) -> {\n\t\t\t\t\tassertThat(body).contains(\"\\\"name\\\":\\\"Test-Instance\\\"\");\n\t\t\t\t\tassertThat(body).contains(\"\\\"status\\\":\\\"UP\\\"\");\n\t\t\t\t\tassertThat(body).contains(\"\\\"test\\\":\\\"foobar\\\"\");\n\t\t\t\t\treturn true;\n\t\t\t\t});\n\t\t//@formatter:on\n\t}\n\n\tprivate Mono<Boolean> listEmptyInstances() {\n\t\t//@formatter:off\n\t\treturn this.webClient.get().uri(\"/instances\").accept(MediaType.APPLICATION_JSON)\n\t\t\t\t.exchange()\n\t\t\t\t.returnResult(String.class).getResponseBody()\n\t\t\t\t.collectList()\n\t\t\t\t.map((list) -> {\n\t\t\t\t\tassertThat(list).hasSize(1);\n\t\t\t\t\tassertThat(list.get(0)).isEqualTo(\"[]\");\n\t\t\t\t\treturn true;\n\t\t\t\t});\n\t\t//@formatter:on\n\t}\n\n\tprivate WebTestClient createWebClient(int port) {\n\t\tJsonMapper mapper = JsonMapper.builder().addModule(new JsonOrgModule()).build();\n\t\treturn WebTestClient.bindToServer()\n\t\t\t.baseUrl(\"http://localhost:\" + port)\n\t\t\t.exchangeStrategies(ExchangeStrategies.builder().codecs((configurer) -> {\n\t\t\t\tconfigurer.defaultCodecs().jacksonJsonDecoder(new JacksonJsonDecoder(mapper));\n\t\t\t\tconfigurer.defaultCodecs().jacksonJsonEncoder(new JacksonJsonEncoder(mapper));\n\t\t\t}).build())\n\t\t\t.build();\n\t}\n\n\t@AfterEach\n\tvoid shutdown() {\n\t\tthis.instance.close();\n\t}\n\n\t@EnableAdminServer\n\t@EnableAutoConfiguration\n\t@SpringBootConfiguration\n\t@EnableWebFluxSecurity\n\tpublic static class TestAdminApplication {\n\n\t\t@Bean\n\t\tSecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {\n\t\t\treturn http.authorizeExchange((authorizeExchange) -> authorizeExchange.anyExchange().permitAll())\n\t\t\t\t.csrf(ServerHttpSecurity.CsrfSpec::disable)\n\t\t\t\t.build();\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-cloud/src/test/java/de/codecentric/boot/admin/server/cloud/config/AdminServerDiscoveryAutoConfigurationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.cloud.config;\n\nimport com.netflix.discovery.EurekaClient;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.autoconfigure.AutoConfigurations;\nimport org.springframework.boot.http.client.autoconfigure.reactive.ReactiveHttpClientAutoConfiguration;\nimport org.springframework.boot.test.context.runner.ApplicationContextRunner;\nimport org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration;\nimport org.springframework.cloud.client.ServiceInstance;\nimport org.springframework.cloud.client.discovery.DiscoveryClient;\nimport org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration;\nimport org.springframework.cloud.commons.util.UtilAutoConfiguration;\nimport org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;\n\nimport de.codecentric.boot.admin.server.cloud.discovery.DefaultServiceInstanceConverter;\nimport de.codecentric.boot.admin.server.cloud.discovery.EurekaServiceInstanceConverter;\nimport de.codecentric.boot.admin.server.cloud.discovery.KubernetesServiceInstanceConverter;\nimport de.codecentric.boot.admin.server.cloud.discovery.ServiceInstanceConverter;\nimport de.codecentric.boot.admin.server.config.AdminServerAutoConfiguration;\nimport de.codecentric.boot.admin.server.config.AdminServerMarkerConfiguration;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\n\nclass AdminServerDiscoveryAutoConfigurationTest {\n\n\tprivate final ApplicationContextRunner contextRunner = new ApplicationContextRunner()\n\t\t.withConfiguration(AutoConfigurations.of(UtilAutoConfiguration.class, ReactiveHttpClientAutoConfiguration.class,\n\t\t\t\tWebClientAutoConfiguration.class, AdminServerAutoConfiguration.class,\n\t\t\t\tAdminServerDiscoveryAutoConfiguration.class))\n\t\t.withUserConfiguration(AdminServerMarkerConfiguration.class);\n\n\t@Test\n\tvoid defaultServiceInstanceConverter() {\n\t\tthis.contextRunner.withUserConfiguration(SimpleDiscoveryClientAutoConfiguration.class)\n\t\t\t.run((context) -> assertThat(context.getBean(ServiceInstanceConverter.class))\n\t\t\t\t.isInstanceOf(DefaultServiceInstanceConverter.class));\n\t}\n\n\t@Test\n\tvoid eurekaServiceInstanceConverter() {\n\t\tthis.contextRunner.withBean(EurekaClient.class, () -> mock(EurekaClient.class))\n\t\t\t.withBean(DiscoveryClient.class, () -> mock(DiscoveryClient.class))\n\t\t\t.run((context) -> assertThat(context.getBean(ServiceInstanceConverter.class))\n\t\t\t\t.isInstanceOf(EurekaServiceInstanceConverter.class));\n\t}\n\n\t@Test\n\tvoid kubernetesServiceInstanceConverter() {\n\t\tthis.contextRunner.withBean(DiscoveryClient.class, () -> mock(DiscoveryClient.class))\n\t\t\t.withBean(KubernetesDiscoveryProperties.class, () -> mock(KubernetesDiscoveryProperties.class))\n\t\t\t.withPropertyValues(\"spring.main.cloud-platform=KUBERNETES\")\n\t\t\t.run((context) -> assertThat(context.getBean(ServiceInstanceConverter.class))\n\t\t\t\t.isInstanceOf(KubernetesServiceInstanceConverter.class));\n\t}\n\n\t@Test\n\tvoid customServiceInstanceConverter() {\n\t\tthis.contextRunner.withUserConfiguration(SimpleDiscoveryClientAutoConfiguration.class)\n\t\t\t.withBean(CustomServiceInstanceConverter.class)\n\t\t\t.run((context) -> assertThat(context.getBean(ServiceInstanceConverter.class))\n\t\t\t\t.isInstanceOf(CustomServiceInstanceConverter.class));\n\t}\n\n\tpublic static class CustomServiceInstanceConverter implements ServiceInstanceConverter {\n\n\t\t@Override\n\t\tpublic Registration convert(ServiceInstance instance) {\n\t\t\treturn null;\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-cloud/src/test/java/de/codecentric/boot/admin/server/cloud/discovery/DefaultServiceInstanceConverterTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.cloud.discovery;\n\nimport java.net.URI;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.cloud.client.DefaultServiceInstance;\nimport org.springframework.cloud.client.ServiceInstance;\n\nimport de.codecentric.boot.admin.server.domain.values.Registration;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass DefaultServiceInstanceConverterTest {\n\n\t@Test\n\tvoid should_convert_with_defaults() {\n\t\tServiceInstance service = new DefaultServiceInstance(\"test-1\", \"test\", \"localhost\", 80, false);\n\t\tRegistration registration = new DefaultServiceInstanceConverter().convert(service);\n\n\t\tassertThat(registration.getName()).isEqualTo(\"test\");\n\t\tassertThat(registration.getServiceUrl()).isEqualTo(\"http://localhost:80\");\n\t\tassertThat(registration.getManagementUrl()).isEqualTo(\"http://localhost:80/actuator\");\n\t\tassertThat(registration.getHealthUrl()).isEqualTo(\"http://localhost:80/actuator/health\");\n\t}\n\n\t@Test\n\tvoid should_convert_with_custom_defaults() {\n\t\tDefaultServiceInstanceConverter converter = new DefaultServiceInstanceConverter();\n\t\tconverter.setHealthEndpointPath(\"ping\");\n\t\tconverter.setManagementContextPath(\"mgmt\");\n\n\t\tServiceInstance service = new DefaultServiceInstance(\"test-1\", \"test\", \"localhost\", 80, false);\n\t\tRegistration registration = converter.convert(service);\n\n\t\tassertThat(registration.getName()).isEqualTo(\"test\");\n\t\tassertThat(registration.getServiceUrl()).isEqualTo(\"http://localhost:80\");\n\t\tassertThat(registration.getManagementUrl()).isEqualTo(\"http://localhost:80/mgmt\");\n\t\tassertThat(registration.getHealthUrl()).isEqualTo(\"http://localhost:80/mgmt/ping\");\n\t}\n\n\t@Test\n\tvoid should_convert_with_metadata() {\n\t\tServiceInstance service = new DefaultServiceInstance(\"test-1\", \"test\", \"localhost\", 80, false);\n\t\tMap<String, String> metadata = new HashMap<>();\n\t\tmetadata.put(\"health.path\", \"ping\");\n\t\tmetadata.put(\"management.scheme\", \"https\");\n\t\tmetadata.put(\"management.address\", \"127.0.0.1\");\n\t\tmetadata.put(\"management.port\", \"1234\");\n\t\tmetadata.put(\"management.context-path\", \"mgmt\");\n\t\tservice.getMetadata().putAll(metadata);\n\n\t\tRegistration registration = new DefaultServiceInstanceConverter().convert(service);\n\n\t\tassertThat(registration.getName()).isEqualTo(\"test\");\n\t\tassertThat(registration.getServiceUrl()).isEqualTo(\"http://localhost:80\");\n\t\tassertThat(registration.getManagementUrl()).isEqualTo(\"https://127.0.0.1:1234/mgmt\");\n\t\tassertThat(registration.getHealthUrl()).isEqualTo(\"https://127.0.0.1:1234/mgmt/ping\");\n\t\tassertThat(registration.getMetadata()).isEqualTo(metadata);\n\t}\n\n\t// Fix for Issue #2076, #1737\n\t@Test\n\tvoid should_convert_with_metadata_without_dots() {\n\t\tServiceInstance service = new DefaultServiceInstance(\"test-1\", \"test\", \"localhost\", 80, false);\n\t\tMap<String, String> metadata = new HashMap<>();\n\t\tmetadata.put(\"health-path\", \"ping\");\n\t\tmetadata.put(\"management-scheme\", \"https\");\n\t\tmetadata.put(\"management-address\", \"127.0.0.1\");\n\t\tmetadata.put(\"management-port\", \"1234\");\n\t\tmetadata.put(\"management-context-path\", \"mgmt\");\n\t\tservice.getMetadata().putAll(metadata);\n\n\t\tRegistration registration = new DefaultServiceInstanceConverter().convert(service);\n\n\t\tassertThat(registration.getName()).isEqualTo(\"test\");\n\t\tassertThat(registration.getServiceUrl()).isEqualTo(\"http://localhost:80\");\n\t\tassertThat(registration.getManagementUrl()).isEqualTo(\"https://127.0.0.1:1234/mgmt\");\n\t\tassertThat(registration.getHealthUrl()).isEqualTo(\"https://127.0.0.1:1234/mgmt/ping\");\n\t\tassertThat(registration.getMetadata()).isEqualTo(metadata);\n\t}\n\n\t@Test\n\tvoid should_convert_with_metadata_having_null_value() {\n\t\tServiceInstance service = new DefaultServiceInstance(\"test-1\", \"test\", \"localhost\", 80, false);\n\t\tMap<String, String> metadata = new HashMap<>();\n\t\tmetadata.put(\"health.path\", \"ping\");\n\t\tmetadata.put(\"management.scheme\", \"https\");\n\t\tmetadata.put(\"management.address\", \"127.0.0.1\");\n\t\tmetadata.put(\"management.port\", \"1234\");\n\t\tmetadata.put(\"null.value\", null);\n\t\tmetadata.put(null, \"null.key\");\n\t\tservice.getMetadata().putAll(metadata);\n\n\t\tRegistration registration = new DefaultServiceInstanceConverter().convert(service);\n\n\t\tassertThat(registration.getHealthUrl()).isEqualTo(\"https://127.0.0.1:1234/actuator/ping\");\n\t}\n\n\t@Test\n\tvoid should_convert_service_with_uri() {\n\t\tServiceInstance service = new TestServiceInstance(\"test\", URI.create(\"http://localhost/test\"),\n\t\t\t\tCollections.emptyMap());\n\t\tRegistration registration = new DefaultServiceInstanceConverter().convert(service);\n\n\t\tassertThat(registration.getName()).isEqualTo(\"test\");\n\t\tassertThat(registration.getServiceUrl()).isEqualTo(\"http://localhost/test\");\n\t\tassertThat(registration.getManagementUrl()).isEqualTo(\"http://localhost/test/actuator\");\n\t\tassertThat(registration.getHealthUrl()).isEqualTo(\"http://localhost/test/actuator/health\");\n\t\tassertThat(registration.getMetadata()).isEmpty();\n\t}\n\n\t@Test\n\tvoid should_convert_service_with_uri_and_custom_defaults() {\n\t\tDefaultServiceInstanceConverter converter = new DefaultServiceInstanceConverter();\n\t\tconverter.setHealthEndpointPath(\"ping\");\n\t\tconverter.setManagementContextPath(\"mgmt\");\n\n\t\tServiceInstance service = new TestServiceInstance(\"test\", URI.create(\"http://localhost/test\"),\n\t\t\t\tCollections.emptyMap());\n\t\tRegistration registration = converter.convert(service);\n\n\t\tassertThat(registration.getName()).isEqualTo(\"test\");\n\t\tassertThat(registration.getServiceUrl()).isEqualTo(\"http://localhost/test\");\n\t\tassertThat(registration.getManagementUrl()).isEqualTo(\"http://localhost/test/mgmt\");\n\t\tassertThat(registration.getHealthUrl()).isEqualTo(\"http://localhost/test/mgmt/ping\");\n\t}\n\n\t@Test\n\tvoid should_convert_service_with_uri_and_metadata_different_port() {\n\t\tMap<String, String> metadata = new HashMap<>();\n\t\tmetadata.put(\"health.path\", \"ping\");\n\t\tmetadata.put(\"management.context-path\", \"mgmt\");\n\t\tmetadata.put(\"management.port\", \"1234\");\n\t\tmetadata.put(\"management.address\", \"127.0.0.1\");\n\t\tServiceInstance service = new TestServiceInstance(\"test\", URI.create(\"http://localhost/test\"), metadata);\n\n\t\tRegistration registration = new DefaultServiceInstanceConverter().convert(service);\n\t\tassertThat(registration.getName()).isEqualTo(\"test\");\n\t\tassertThat(registration.getServiceUrl()).isEqualTo(\"http://localhost/test\");\n\t\tassertThat(registration.getManagementUrl()).isEqualTo(\"http://127.0.0.1:1234/mgmt\");\n\t\tassertThat(registration.getHealthUrl()).isEqualTo(\"http://127.0.0.1:1234/mgmt/ping\");\n\t\tassertThat(registration.getMetadata()).isEqualTo(metadata);\n\t}\n\n\t@Test\n\tvoid should_convert_service_with_uri_and_metadata() {\n\t\tMap<String, String> metadata = new HashMap<>();\n\t\tmetadata.put(\"health.path\", \"ping\");\n\t\tmetadata.put(\"management.context-path\", \"mgmt\");\n\t\tServiceInstance service = new TestServiceInstance(\"test\", URI.create(\"http://localhost/test\"), metadata);\n\n\t\tRegistration registration = new DefaultServiceInstanceConverter().convert(service);\n\t\tassertThat(registration.getName()).isEqualTo(\"test\");\n\t\tassertThat(registration.getServiceUrl()).isEqualTo(\"http://localhost/test\");\n\t\tassertThat(registration.getManagementUrl()).isEqualTo(\"http://localhost/test/mgmt\");\n\t\tassertThat(registration.getHealthUrl()).isEqualTo(\"http://localhost/test/mgmt/ping\");\n\t\tassertThat(registration.getMetadata()).isEqualTo(metadata);\n\t}\n\n\tprivate record TestServiceInstance(String serviceId, URI uri,\n\t\t\tMap<String, String> metadata) implements ServiceInstance {\n\n\t\t@Override\n\t\tpublic String getServiceId() {\n\t\t\treturn this.serviceId;\n\t\t}\n\n\t\t@Override\n\t\tpublic String getHost() {\n\t\t\treturn this.uri.getHost();\n\t\t}\n\n\t\t@Override\n\t\tpublic int getPort() {\n\t\t\tif (this.uri.getPort() != -1) {\n\t\t\t\treturn this.uri.getPort();\n\t\t\t}\n\t\t\treturn this.isSecure() ? 443 : 80;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean isSecure() {\n\t\t\treturn this.uri.getScheme().equals(\"https\");\n\t\t}\n\n\t\t@Override\n\t\tpublic URI getUri() {\n\t\t\treturn this.uri;\n\t\t}\n\n\t\t@Override\n\t\tpublic Map<String, String> getMetadata() {\n\t\t\treturn this.metadata;\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-cloud/src/test/java/de/codecentric/boot/admin/server/cloud/discovery/EurekaServiceInstanceConverterTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.cloud.discovery;\n\nimport java.net.URI;\n\nimport com.netflix.appinfo.InstanceInfo;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.cloud.netflix.eureka.EurekaServiceInstance;\n\nimport de.codecentric.boot.admin.server.domain.values.Registration;\n\nimport static java.util.Collections.singletonMap;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass EurekaServiceInstanceConverterTest {\n\n\t@Test\n\tvoid convert_secure() {\n\t\tInstanceInfo instanceInfo = mock(InstanceInfo.class);\n\t\twhen(instanceInfo.getSecureHealthCheckUrl()).thenReturn(\"\");\n\t\twhen(instanceInfo.getHealthCheckUrl()).thenReturn(\"http://localhost:80/mgmt/ping\");\n\t\tEurekaServiceInstance service = mock(EurekaServiceInstance.class);\n\t\twhen(service.getInstanceInfo()).thenReturn(instanceInfo);\n\t\twhen(service.getUri()).thenReturn(URI.create(\"http://localhost:80\"));\n\t\twhen(service.getServiceId()).thenReturn(\"test\");\n\t\twhen(service.getMetadata()).thenReturn(singletonMap(\"management.context-path\", \"/mgmt\"));\n\n\t\tRegistration registration = new EurekaServiceInstanceConverter().convert(service);\n\n\t\tassertThat(registration.getName()).isEqualTo(\"test\");\n\t\tassertThat(registration.getServiceUrl()).isEqualTo(\"http://localhost:80\");\n\t\tassertThat(registration.getManagementUrl()).isEqualTo(\"http://localhost:80/mgmt\");\n\t\tassertThat(registration.getHealthUrl()).isEqualTo(\"http://localhost:80/mgmt/ping\");\n\t}\n\n\t@Test\n\tvoid convert_missing_mgmt_path() {\n\t\tInstanceInfo instanceInfo = mock(InstanceInfo.class);\n\t\twhen(instanceInfo.getHealthCheckUrl()).thenReturn(\"http://localhost:80/mgmt/ping\");\n\t\tEurekaServiceInstance service = mock(EurekaServiceInstance.class);\n\t\twhen(service.getInstanceInfo()).thenReturn(instanceInfo);\n\t\twhen(service.getUri()).thenReturn(URI.create(\"http://localhost:80\"));\n\t\twhen(service.getServiceId()).thenReturn(\"test\");\n\n\t\tRegistration registration = new EurekaServiceInstanceConverter().convert(service);\n\n\t\tassertThat(registration.getManagementUrl()).isEqualTo(\"http://localhost:80/actuator\");\n\t}\n\n\t@Test\n\tvoid convert_secure_healthUrl() {\n\t\tInstanceInfo instanceInfo = mock(InstanceInfo.class);\n\t\twhen(instanceInfo.getSecureHealthCheckUrl()).thenReturn(\"https://localhost:80/health\");\n\t\tEurekaServiceInstance service = mock(EurekaServiceInstance.class);\n\t\twhen(service.getInstanceInfo()).thenReturn(instanceInfo);\n\t\twhen(service.getUri()).thenReturn(URI.create(\"http://localhost:80\"));\n\t\twhen(service.getServiceId()).thenReturn(\"test\");\n\n\t\tRegistration registration = new EurekaServiceInstanceConverter().convert(service);\n\n\t\tassertThat(registration.getHealthUrl()).isEqualTo(\"https://localhost:80/health\");\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-cloud/src/test/java/de/codecentric/boot/admin/server/cloud/discovery/InstanceDiscoveryListenerTest.java",
    "content": "/*\n * Copyright 2014-2024 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.cloud.discovery;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.cloud.client.DefaultServiceInstance;\nimport org.springframework.cloud.client.ServiceInstance;\nimport org.springframework.cloud.client.discovery.DiscoveryClient;\nimport org.springframework.cloud.client.discovery.event.HeartbeatEvent;\nimport org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent;\nimport org.springframework.cloud.client.discovery.event.ParentHeartbeatEvent;\nimport reactor.test.StepVerifier;\n\nimport de.codecentric.boot.admin.server.domain.entities.EventsourcingInstanceRepository;\nimport de.codecentric.boot.admin.server.domain.entities.InstanceRepository;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.eventstore.InMemoryEventStore;\nimport de.codecentric.boot.admin.server.services.HashingInstanceUrlIdGenerator;\nimport de.codecentric.boot.admin.server.services.InstanceRegistry;\n\nimport static java.util.Arrays.asList;\nimport static java.util.Collections.singleton;\nimport static java.util.Collections.singletonList;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass InstanceDiscoveryListenerTest {\n\n\tprivate InstanceDiscoveryListener listener;\n\n\tprivate DiscoveryClient discovery;\n\n\tprivate InstanceRegistry registry;\n\n\t@BeforeEach\n\tvoid setup() {\n\t\tthis.discovery = mock(DiscoveryClient.class);\n\t\tInstanceRepository repository = new EventsourcingInstanceRepository(new InMemoryEventStore());\n\t\tthis.registry = spy(new InstanceRegistry(repository, new HashingInstanceUrlIdGenerator(), (instance) -> true));\n\t\tthis.listener = new InstanceDiscoveryListener(this.discovery, this.registry, repository);\n\t}\n\n\t@Test\n\tvoid should_discover_instances_when_application_is_ready() {\n\t\twhen(this.discovery.getServices()).thenReturn(Collections.singletonList(\"service\"));\n\t\twhen(this.discovery.getInstances(\"service\")).thenReturn(\n\t\t\t\tCollections.singletonList(new DefaultServiceInstance(\"test-1\", \"service\", \"localhost\", 80, false)));\n\n\t\tthis.listener.onApplicationReady(null);\n\n\t\tStepVerifier.create(this.registry.getInstances())\n\t\t\t.assertNext((a) -> assertThat(a.getRegistration().getName()).isEqualTo(\"service\"))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_not_register_instance_when_serviceId_is_ignored() {\n\t\twhen(this.discovery.getServices()).thenReturn(singletonList(\"service\"));\n\t\twhen(this.discovery.getInstances(\"service\"))\n\t\t\t.thenReturn(singletonList(new DefaultServiceInstance(\"test-1\", \"service\", \"localhost\", 80, false)));\n\n\t\tthis.listener.setIgnoredServices(singleton(\"service\"));\n\t\tthis.listener.onInstanceRegistered(new InstanceRegisteredEvent<>(new Object(), null));\n\n\t\tStepVerifier.create(this.registry.getInstances()).verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_not_register_instance_when_serviceId_is_ignored_caseInsensitive() {\n\t\twhen(this.discovery.getServices()).thenReturn(singletonList(\"service\"));\n\t\twhen(this.discovery.getInstances(\"service\"))\n\t\t\t.thenReturn(singletonList(new DefaultServiceInstance(\"test-1\", \"service\", \"localhost\", 80, false)));\n\n\t\tthis.listener.setIgnoredServices(singleton(\"SERVICE\"));\n\t\tthis.listener.onInstanceRegistered(new InstanceRegisteredEvent<>(new Object(), null));\n\n\t\tStepVerifier.create(this.registry.getInstances()).verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_not_register_instance_when_instanceMetadata_is_ignored() {\n\t\twhen(this.discovery.getServices()).thenReturn(singletonList(\"service\"));\n\t\twhen(this.discovery.getInstances(\"service\")).thenReturn(singletonList(new DefaultServiceInstance(\"test-1\",\n\t\t\t\t\"service\", \"localhost\", 80, false, Collections.singletonMap(\"monitoring\", \"false\"))));\n\n\t\tthis.listener.setIgnoredInstancesMetadata(Collections.singletonMap(\"monitoring\", \"false\"));\n\t\tthis.listener.onInstanceRegistered(new InstanceRegisteredEvent<>(new Object(), null));\n\n\t\tStepVerifier.create(this.registry.getInstances()).verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_register_instance_when_serviceId_is_not_ignored() {\n\t\twhen(this.discovery.getServices()).thenReturn(singletonList(\"service\"));\n\t\twhen(this.discovery.getInstances(\"service\"))\n\t\t\t.thenReturn(singletonList(new DefaultServiceInstance(\"test-1\", \"service\", \"localhost\", 80, false)));\n\n\t\tthis.listener.setServices(singleton(\"notService2\"));\n\t\tthis.listener.onInstanceRegistered(new InstanceRegisteredEvent<>(new Object(), null));\n\n\t\tStepVerifier.create(this.registry.getInstances()).verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_register_instance_when_instanceMetadata_is_not_ignored() {\n\t\twhen(this.discovery.getServices()).thenReturn(singletonList(\"service\"));\n\t\twhen(this.discovery.getInstances(\"service\")).thenReturn(singletonList(new DefaultServiceInstance(\"test-1\",\n\t\t\t\t\"service\", \"localhost\", 80, false, Collections.singletonMap(\"monitoring\", \"true\"))));\n\n\t\tthis.listener.setInstancesMetadata(Collections.singletonMap(\"monitoring\", \"false\"));\n\t\tthis.listener.onInstanceRegistered(new InstanceRegisteredEvent<>(new Object(), null));\n\n\t\tStepVerifier.create(this.registry.getInstances()).verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_not_register_instance_when_serviceId_matches_ignored_pattern() {\n\t\twhen(this.discovery.getServices()).thenReturn(asList(\"service\", \"rabbit-1\", \"rabbit-2\"));\n\t\twhen(this.discovery.getInstances(\"service\"))\n\t\t\t.thenReturn(singletonList(new DefaultServiceInstance(\"test-1\", \"service\", \"localhost\", 80, false)));\n\n\t\tthis.listener.setIgnoredServices(singleton(\"rabbit-*\"));\n\t\tthis.listener.onInstanceRegistered(new InstanceRegisteredEvent<>(new Object(), null));\n\n\t\tStepVerifier.create(this.registry.getInstances())\n\t\t\t.assertNext((a) -> assertThat(a.getRegistration().getName()).isEqualTo(\"service\"))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_not_register_instance_when_instanceMetadata_matches_ignored_metadata() {\n\t\twhen(this.discovery.getServices()).thenReturn(asList(\"service\", \"rabbit-1\", \"rabbit-2\"));\n\t\twhen(this.discovery.getInstances(\"service\")).thenReturn(singletonList(new DefaultServiceInstance(\"test-1\",\n\t\t\t\t\"service\", \"localhost\", 80, false, Collections.singletonMap(\"monitoring\", \"true\"))));\n\t\twhen(this.discovery.getInstances(\"rabbit-1\"))\n\t\t\t.thenReturn(singletonList(new DefaultServiceInstance(\"rabbit-test-1\", \"rabbit-1\", \"localhost\", 80, false,\n\t\t\t\t\tCollections.singletonMap(\"monitoring\", \"false\"))));\n\t\twhen(this.discovery.getInstances(\"rabbit-2\"))\n\t\t\t.thenReturn(singletonList(new DefaultServiceInstance(\"rabbit-test-1\", \"rabbit-2\", \"localhost\", 80, false,\n\t\t\t\t\tCollections.singletonMap(\"monitoring\", \"false\"))));\n\n\t\tthis.listener.setIgnoredInstancesMetadata(Collections.singletonMap(\"monitoring\", \"false\"));\n\t\tthis.listener.onInstanceRegistered(new InstanceRegisteredEvent<>(new Object(), null));\n\n\t\tStepVerifier.create(this.registry.getInstances())\n\t\t\t.assertNext((a) -> assertThat(a.getRegistration().getName()).isEqualTo(\"service\"))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_register_instances_when_serviceId_matches_wanted_pattern() {\n\t\twhen(this.discovery.getServices()).thenReturn(asList(\"service\", \"rabbit-1\", \"rabbit-2\"));\n\t\twhen(this.discovery.getInstances(\"service\"))\n\t\t\t.thenReturn(singletonList(new DefaultServiceInstance(\"test-1\", \"service\", \"localhost\", 80, false)));\n\n\t\tthis.listener.setServices(singleton(\"ser*\"));\n\t\tthis.listener.onInstanceRegistered(new InstanceRegisteredEvent<>(new Object(), null));\n\n\t\tStepVerifier.create(this.registry.getInstances())\n\t\t\t.assertNext((a) -> assertThat(a.getRegistration().getName()).isEqualTo(\"service\"))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_register_instances_when_instanceMetadata_matches_wanted_metadata() {\n\t\twhen(this.discovery.getServices()).thenReturn(asList(\"service\", \"rabbit-1\", \"rabbit-2\"));\n\t\twhen(this.discovery.getInstances(\"service\")).thenReturn(singletonList(new DefaultServiceInstance(\"test-1\",\n\t\t\t\t\"service\", \"localhost\", 80, false, Collections.singletonMap(\"monitoring\", \"true\"))));\n\t\twhen(this.discovery.getInstances(\"rabbit-1\"))\n\t\t\t.thenReturn(singletonList(new DefaultServiceInstance(\"rabbit-test-1\", \"rabbit-1\", \"localhost\", 80, false,\n\t\t\t\t\tCollections.singletonMap(\"monitoring\", \"false\"))));\n\t\twhen(this.discovery.getInstances(\"rabbit-2\"))\n\t\t\t.thenReturn(singletonList(new DefaultServiceInstance(\"rabbit-test-1\", \"rabbit-2\", \"localhost\", 80, false,\n\t\t\t\t\tCollections.singletonMap(\"monitoring\", \"false\"))));\n\n\t\tthis.listener.setInstancesMetadata(Collections.singletonMap(\"monitoring\", \"true\"));\n\t\tthis.listener.onInstanceRegistered(new InstanceRegisteredEvent<>(new Object(), null));\n\n\t\tStepVerifier.create(this.registry.getInstances())\n\t\t\t.assertNext((a) -> assertThat(a.getRegistration().getName()).isEqualTo(\"service\"))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_register_instances_when_serviceId_matches_wanted_pattern_and_ignored_pattern() {\n\t\twhen(this.discovery.getServices()).thenReturn(asList(\"service-1\", \"service\", \"rabbit-1\", \"rabbit-2\"));\n\t\twhen(this.discovery.getInstances(\"service\"))\n\t\t\t.thenReturn(singletonList(new DefaultServiceInstance(\"test-1\", \"service\", \"localhost\", 80, false)));\n\t\twhen(this.discovery.getInstances(\"service-1\"))\n\t\t\t.thenReturn(singletonList(new DefaultServiceInstance(\"test-1\", \"service-1\", \"localhost\", 80, false)));\n\n\t\tthis.listener.setServices(singleton(\"ser*\"));\n\t\tthis.listener.setIgnoredServices(singleton(\"service-*\"));\n\t\tthis.listener.onInstanceRegistered(new InstanceRegisteredEvent<>(new Object(), null));\n\n\t\tStepVerifier.create(this.registry.getInstances())\n\t\t\t.assertNext((a) -> assertThat(a.getRegistration().getName()).isEqualTo(\"service\"))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_not_register_instances_when_instanceMetadata_matches_wanted_metadata_and_ignored_metadata() {\n\t\twhen(this.discovery.getServices()).thenReturn(asList(\"service\", \"service-1\"));\n\t\twhen(this.discovery.getInstances(\"service\")).thenReturn(\n\t\t\t\tsingletonList(new DefaultServiceInstance(\"test-1\", \"service\", \"localhost\", 80, false, new HashMap<>() {\n\t\t\t\t\t{\n\t\t\t\t\t\tput(\"monitoring\", \"true\");\n\t\t\t\t\t\tput(\"management\", \"true\");\n\t\t\t\t\t}\n\t\t\t\t})));\n\t\twhen(this.discovery.getInstances(\"service-1\")).thenReturn(singletonList(\n\t\t\t\tnew DefaultServiceInstance(\"test-1\", \"service-1\", \"localhost\", 80, false, new HashMap<>() {\n\t\t\t\t\t{\n\t\t\t\t\t\tput(\"monitoring\", \"true\");\n\t\t\t\t\t\tput(\"management\", \"false\");\n\t\t\t\t\t}\n\t\t\t\t})));\n\n\t\tthis.listener.setInstancesMetadata(Collections.singletonMap(\"monitoring\", \"true\"));\n\t\tthis.listener.setIgnoredInstancesMetadata(Collections.singletonMap(\"management\", \"true\"));\n\t\tthis.listener.onInstanceRegistered(new InstanceRegisteredEvent<>(new Object(), null));\n\n\t\tStepVerifier.create(this.registry.getInstances())\n\t\t\t.assertNext((a) -> assertThat(a.getRegistration().getName()).isEqualTo(\"service-1\"))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_register_instance_when_new_service_instance_is_discovered() {\n\t\twhen(this.discovery.getServices()).thenReturn(singletonList(\"service\"));\n\t\twhen(this.discovery.getInstances(\"service\"))\n\t\t\t.thenReturn(singletonList(new DefaultServiceInstance(\"test-1\", \"service\", \"localhost\", 80, false)));\n\n\t\tthis.listener.onInstanceRegistered(new InstanceRegisteredEvent<>(new Object(), null));\n\n\t\tStepVerifier.create(this.registry.getInstances()).assertNext((application) -> {\n\t\t\tRegistration registration = application.getRegistration();\n\t\t\tassertThat(registration.getHealthUrl()).isEqualTo(\"http://localhost:80/actuator/health\");\n\t\t\tassertThat(registration.getManagementUrl()).isEqualTo(\"http://localhost:80/actuator\");\n\t\t\tassertThat(registration.getServiceUrl()).isEqualTo(\"http://localhost:80\");\n\t\t\tassertThat(registration.getName()).isEqualTo(\"service\");\n\t\t}).verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_only_discover_new_instances_when_new_heartbeat_is_emitted() {\n\t\tObject heartbeat = new Object();\n\t\tthis.listener.onParentHeartbeat(new ParentHeartbeatEvent(new Object(), heartbeat));\n\n\t\twhen(this.discovery.getServices()).thenReturn(singletonList(\"service\"));\n\t\twhen(this.discovery.getInstances(\"service\"))\n\t\t\t.thenReturn(singletonList(new DefaultServiceInstance(\"test-1\", \"service\", \"localhost\", 80, false)));\n\n\t\tthis.listener.onApplicationEvent(new HeartbeatEvent(new Object(), heartbeat));\n\t\tStepVerifier.create(this.registry.getInstances()).verifyComplete();\n\n\t\tthis.listener.onApplicationEvent(new HeartbeatEvent(new Object(), new Object()));\n\t\tStepVerifier.create(this.registry.getInstances())\n\t\t\t.assertNext((a) -> assertThat(a.getRegistration().getName()).isEqualTo(\"service\"))\n\t\t\t.verifyComplete();\n\t}\n\n\t@Test\n\tvoid should_remove_instances_when_they_are_no_longer_available_in_discovery() {\n\t\tStepVerifier.create(this.registry.register(Registration.create(\"ignored\", \"https://health\").build()))\n\t\t\t.consumeNextWith((id) -> {\n\t\t\t})\n\t\t\t.verifyComplete();\n\t\tStepVerifier\n\t\t\t.create(this.registry\n\t\t\t\t.register(Registration.create(\"different-source\", \"https://health2\").source(\"http-api\").build()))\n\t\t\t.consumeNextWith((id) -> {\n\t\t\t})\n\t\t\t.verifyComplete();\n\t\tthis.listener.setIgnoredServices(singleton(\"ignored\"));\n\n\t\tList<ServiceInstance> instances = new ArrayList<>();\n\t\tinstances.add(new DefaultServiceInstance(\"test-1\", \"service\", \"localhost\", 80, false));\n\t\tinstances.add(new DefaultServiceInstance(\"test-1\", \"service\", \"example.net\", 80, false));\n\n\t\twhen(this.discovery.getServices()).thenReturn(singletonList(\"service\"));\n\t\twhen(this.discovery.getInstances(\"service\")).thenReturn(instances);\n\n\t\tthis.listener.onApplicationEvent(new HeartbeatEvent(new Object(), new Object()));\n\n\t\tStepVerifier.create(this.registry.getInstances(\"service\"))\n\t\t\t.assertNext((a) -> assertThat(a.getRegistration().getName()).isEqualTo(\"service\"))\n\t\t\t.assertNext((a) -> assertThat(a.getRegistration().getName()).isEqualTo(\"service\"))\n\t\t\t.verifyComplete();\n\n\t\tStepVerifier.create(this.registry.getInstances(\"ignored\"))\n\t\t\t.assertNext((a) -> assertThat(a.getRegistration().getName()).isEqualTo(\"ignored\"))\n\t\t\t.verifyComplete();\n\n\t\tStepVerifier.create(this.registry.getInstances(\"different-source\"))\n\t\t\t.assertNext((a) -> assertThat(a.getRegistration().getName()).isEqualTo(\"different-source\"))\n\t\t\t.verifyComplete();\n\n\t\tinstances.remove(0);\n\n\t\tthis.listener.onApplicationEvent(new HeartbeatEvent(new Object(), new Object()));\n\n\t\tStepVerifier.create(this.registry.getInstances(\"service\"))\n\t\t\t.assertNext((a) -> assertThat(a.getRegistration().getName()).isEqualTo(\"service\"))\n\t\t\t.verifyComplete();\n\n\t\tStepVerifier.create(this.registry.getInstances(\"ignored\"))\n\t\t\t.assertNext((a) -> assertThat(a.getRegistration().getName()).isEqualTo(\"ignored\"))\n\t\t\t.verifyComplete();\n\n\t\tStepVerifier.create(this.registry.getInstances(\"different-source\"))\n\t\t\t.assertNext((a) -> assertThat(a.getRegistration().getName()).isEqualTo(\"different-source\"))\n\t\t\t.verifyComplete();\n\n\t\t// shouldn't deregister a second time\n\t\tthis.listener.onApplicationEvent(new HeartbeatEvent(new Object(), new Object()));\n\t\tverify(this.registry, times(1)).deregister(any(InstanceId.class));\n\t}\n\n\t@Test\n\tvoid should_not_throw_error_when_conversion_fails_and_proceed_with_next_instance() {\n\t\twhen(this.discovery.getServices()).thenReturn(singletonList(\"service\"));\n\t\twhen(this.discovery.getInstances(\"service\"))\n\t\t\t.thenReturn(asList(new DefaultServiceInstance(\"test-1\", \"service\", \"localhost\", 80, false),\n\t\t\t\t\tnew DefaultServiceInstance(\"error-1\", \"error\", \"localhost\", 80, false)));\n\t\tthis.listener.setConverter((instance) -> {\n\t\t\tif (instance.getServiceId().equals(\"error\")) {\n\t\t\t\tthrow new IllegalStateException(\"Test-Error\");\n\t\t\t}\n\t\t\telse {\n\t\t\t\treturn new DefaultServiceInstanceConverter().convert(instance);\n\t\t\t}\n\t\t});\n\n\t\tthis.listener.onInstanceRegistered(new InstanceRegisteredEvent<>(new Object(), null));\n\n\t\tStepVerifier.create(this.registry.getInstances())\n\t\t\t.assertNext((a) -> assertThat(a.getRegistration().getName()).isEqualTo(\"service\"))\n\t\t\t.verifyComplete();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-cloud/src/test/java/de/codecentric/boot/admin/server/cloud/discovery/KubernetesServiceInstanceConverterTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.cloud.discovery;\n\nimport java.net.URI;\nimport java.util.Collections;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.cloud.client.ServiceInstance;\nimport org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;\n\nimport de.codecentric.boot.admin.server.domain.values.Registration;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass KubernetesServiceInstanceConverterTest {\n\n\t@Test\n\tvoid convert_using_port_mgmt() {\n\t\tKubernetesDiscoveryProperties discoveryProperties = KubernetesDiscoveryProperties.DEFAULT;\n\t\tServiceInstance service = mockServiceInstanceWithManagementPort(\n\t\t\t\tdiscoveryProperties.metadata().portsPrefix() + \"management\");\n\n\t\tRegistration registration = new KubernetesServiceInstanceConverter(discoveryProperties).convert(service);\n\n\t\tassertThat(registration.getManagementUrl()).isEqualTo(\"http://localhost:9080/actuator\");\n\t\tassertThat(registration.getHealthUrl()).isEqualTo(\"http://localhost:9080/actuator/health\");\n\t}\n\n\t@Test\n\tvoid fallback_for_port_mgmt() {\n\t\tKubernetesDiscoveryProperties discoveryProperties = KubernetesDiscoveryProperties.DEFAULT;\n\t\tServiceInstance service = mockServiceInstanceWithManagementPort(\"management\");\n\n\t\tRegistration registration = new KubernetesServiceInstanceConverter(discoveryProperties).convert(service);\n\n\t\tassertThat(registration.getManagementUrl()).isEqualTo(\"http://localhost:9080/actuator\");\n\t\tassertThat(registration.getHealthUrl()).isEqualTo(\"http://localhost:9080/actuator/health\");\n\t}\n\n\tprivate static ServiceInstance mockServiceInstanceWithManagementPort(String managementPortName) {\n\t\tServiceInstance service = mock(ServiceInstance.class);\n\t\twhen(service.getUri()).thenReturn(URI.create(\"http://localhost:80\"));\n\t\twhen(service.getServiceId()).thenReturn(\"test\");\n\t\twhen(service.getMetadata()).thenReturn(Collections.singletonMap(managementPortName, \"9080\"));\n\t\treturn service;\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-cloud/src/test/resources/application.yml",
    "content": "server:\n  shutdown: immediate\n"
  },
  {
    "path": "spring-boot-admin-server-cloud/src/test/resources/logback-test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright 2014-2018 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<configuration>\n    <include resource=\"org/springframework/boot/logging/logback/base.xml\" />\n    <logger name=\"de.codecentric\" level=\"DEBUG\"/>\n</configuration>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/.gitignore",
    "content": "/coverage/\n"
  },
  {
    "path": "spring-boot-admin-server-ui/.npmrc",
    "content": "legacy-peer-deps=true\n"
  },
  {
    "path": "spring-boot-admin-server-ui/.nvmrc",
    "content": "24.14.0\n"
  },
  {
    "path": "spring-boot-admin-server-ui/.prettierrc.json",
    "content": "{\n  \"singleQuote\": true,\n  \"plugins\": [\"@trivago/prettier-plugin-sort-imports\"],\n  \"importOrder\": [\"(.*).css$\",\"^[./]\" ,\"^@/components/(.*)$\",\"^@/(.*)$\"],\n  \"importOrderSeparation\": true,\n  \"importOrderSortSpecifiers\": true\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/.storybook/main.js",
    "content": "const { mergeConfig } = require('vite');\nconst path = require('path');\nconst frontend = path.resolve(__dirname, '../src/main/frontend/');\nmodule.exports = {\n  stories: ['../src/main/frontend/**/*.stories.@(js|jsx|ts|tsx|mdx)'],\n  addons: ['@storybook/addon-links', '@storybook/addon-docs'],\n\n  framework: {\n    name: '@storybook/vue3-vite',\n    options: {},\n  },\n\n  async viteFinal(config) {\n    config.plugins = config.plugins.filter((p) => p.name !== 'vue-docgen');\n    return mergeConfig(config, {\n      resolve: {\n        alias: {\n          '@': frontend,\n        },\n        extensions: ['.vue', '.js', '.json'],\n      },\n    });\n  }\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/.storybook/preview-head.html",
    "content": "<script>\n  window.global = window;\n  window.SBA = {\n    uiSettings: {\n      brand: '<img src=\".storybook/img/icon-spring-boot-admin.svg\"><span>Spring Boot Admin</span>',\n    }\n  };\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/.storybook/preview.js",
    "content": "import { setup } from '@storybook/vue3-vite';\nimport { initialize, mswLoader } from 'msw-storybook-addon';\nimport { createRouter, createWebHistory } from 'vue-router';\n\nimport './storybook.css';\nimport '@/index.css';\n\nimport components from '@/components';\nimport { createApplicationStore } from '@/composables/useApplicationStore';\nimport i18n from '@/i18n';\nimport applicationsEndpoint from '@/mocks/applications';\nimport mappingsEndpoint from '@/mocks/instance/mappings';\n\ninitialize();\nconst router = createRouter({\n  history: createWebHistory(),\n  routes: [],\n});\n\nconst applicationStore = createApplicationStore();\nsetup((app) => {\n  app.use(components);\n  app.use(i18n);\n  app.use(router);\n  app.use(applicationStore);\n});\n\nexport const parameters = {\n  actions: { argTypesRegex: '^on[A-Z].*' },\n  controls: {\n    matchers: {\n      color: /(background|color)$/i,\n      date: /Date$/,\n    },\n  },\n  msw: {\n    handlers: {\n      auth: null,\n      others: [...mappingsEndpoint, ...applicationsEndpoint],\n    },\n  },\n  loader: { '.js': 'jsx' },\n};\n\nexport const preview = {\n  parameters,\n  loaders: [mswLoader],\n};\n\nexport const tags = ['autodocs'];\n"
  },
  {
    "path": "spring-boot-admin-server-ui/.storybook/storybook.css",
    "content": ":root {\n    --main-50: 238, 252, 250;\n    --main-100: 217, 247, 244;\n    --main-200: 183, 240, 234;\n    --main-300: 145, 232, 224;\n    --main-400: 107, 224, 213;\n    --main-500: 71, 217, 203;\n    --main-600: 39, 190, 175;\n    --main-700: 30, 144, 132;\n    --main-800: 20, 97, 90;\n    --main-900: 10, 47, 43;\n    --bg-color-start: #91E8E0;\n    --bg-color-stop: #1E9084;\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/README.md",
    "content": "spring-boot-admin-server-ui\n================================\n\n### Building this module\nThe jar **can be built with Maven** with the maven-exec-plugin. To do this node.js and npm must be installed on your machine and be on your `$PATH`.\nIf you don't want to use the maven exec run the following commands:\n\n### Running Spring Boot Admin Server for development\nTo develop the ui on a running server the best to do is\n\n1. Running the ui build in watch mode so the resources get updated:\n```shell\nnpm run build:watch\n```\n2. Run a Spring Boot Admin Server instances with the template-location and resource-location pointing to the build output and disable caching:\n```\nspring.boot.admin.ui.cache.no-cache: true\nspring.boot.admin.ui.resource-locations: file:../../spring-boot-admin-server-ui/target/dist/\nspring.boot.admin.ui.template-location: file:../../spring-boot-admin-server-ui/target/dist/\nspring.boot.admin.ui.cache-templates: false\n```\nOr just start the [spring-boot-admin-sample-servlet](../spring-boot-admin-samples/spring-boot-admin-sample-servlet)\nproject using the `dev` profile. You also might want to use the `insecure` profile so you don't need to login.\n\nIf you are using hierarchical projects (like the samples here), you have to point \"Working directory\" in your run config to the Project you are running.\nIn IntelliJ IDEA you can simply use \"$MODULE_DIR$\".\n\n### Build\n```shell\nnpm install\nnpm run build\n```\n\nRepeated build with watching the files:\n```shell\nnpm run watch\n```\n\n## Run tests\n\n```shell\nnpm run test\n```\n\nRepeated tests with watching the files:\n\n```shell\nnpm run test:watch\n```\n\n## Run Storybook\n\n```shell\nnpm run storybook\n```\n\nFor some recurring UI elements we have created components that should be reused. To see how they work and which\noptions (props) they provide, use Storybook (see https://storybook.js.org/).\n"
  },
  {
    "path": "spring-boot-admin-server-ui/components.d.ts",
    "content": "/* eslint-disable */\n// @ts-nocheck\n// Generated by unplugin-vue-components\n// Read more: https://github.com/vuejs/core/pull/3399\n// biome-ignore lint: disable\nexport {}\n\n/* prettier-ignore */\ndeclare module 'vue' {\n  export interface GlobalComponents {\n    Button: typeof import('primevue/button')['default']\n    Column: typeof import('primevue/column')['default']\n    DataTable: typeof import('primevue/datatable')['default']\n    DatePicker: typeof import('primevue/datepicker')['default']\n    Dialog: typeof import('primevue/dialog')['default']\n    IconField: typeof import('primevue/iconfield')['default']\n    InputIcon: typeof import('primevue/inputicon')['default']\n    InputText: typeof import('primevue/inputtext')['default']\n    MultiSelect: typeof import('primevue/multiselect')['default']\n    RouterLink: typeof import('vue-router')['RouterLink']\n    RouterView: typeof import('vue-router')['RouterView']\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport js from '@eslint/js';\nimport typescriptEslint from '@typescript-eslint/eslint-plugin';\nimport prettier from 'eslint-plugin-prettier';\nimport pluginVue from 'eslint-plugin-vue';\nimport { defineConfig, globalIgnores } from 'eslint/config';\nimport globals from 'globals';\nimport parser from 'vue-eslint-parser';\n\nconst compat = new FlatCompat({\n  recommendedConfig: js.configs.recommended,\n  allConfig: js.configs.all,\n});\n\nexport default defineConfig([\n  ...pluginVue.configs['flat/recommended'],\n  {\n    languageOptions: {\n      sourceType: 'module',\n      globals: {\n        ...globals.browser,\n        ...globals.node,\n        globalThis: false,\n        window: false,\n      },\n\n      parser: parser,\n      ecmaVersion: 2020,\n\n      parserOptions: {\n        parser: '@typescript-eslint/parser',\n      },\n    },\n\n    extends: compat.extends(\n      'plugin:@typescript-eslint/recommended',\n      'plugin:prettier/recommended',\n    ),\n\n    plugins: {\n      prettier,\n      '@typescript-eslint': typescriptEslint,\n    },\n\n    rules: {\n      'no-console': [\n        'error',\n        {\n          allow: ['warn', 'error'],\n        },\n      ],\n\n      'vue/multi-word-component-names': 'off',\n      'vue/no-v-html': 'off',\n      'vue/no-reserved-component-names': 'off',\n      'vue/no-v-text-v-html-on-component': 'off',\n\n      'no-restricted-syntax': [\n        'error',\n        {\n          message: 'Please do not commit this.',\n          selector:\n            'MemberExpression > Identifier[name=\"logTestingPlaygroundURL\"]',\n        },\n      ],\n\n      quotes: [\n        2,\n        'single',\n        {\n          avoidEscape: true,\n        },\n      ],\n    },\n  },\n  globalIgnores(['**/dist']),\n  {\n    rules: {\n      'no-undef': 'off',\n      '@typescript-eslint/no-explicit-any': 'off',\n    },\n  },\n]);\n"
  },
  {
    "path": "spring-boot-admin-server-ui/package.json",
    "content": "{\n  \"name\": \"spring-boot-admin-server-ui\",\n  \"private\": true,\n  \"description\": \"Spring Boot Admin UI\",\n  \"scripts\": {\n    \"build\": \"vite build --emptyOutDir --sourcemap\",\n    \"build:dev\": \"NODE_ENV=development vite build --emptyOutDir --sourcemap --mode development\",\n    \"build:watch\": \"NODE_ENV=development vite build --emptyOutDir --sourcemap --watch --mode development\",\n    \"dev\": \"vite\",\n    \"test\": \"vitest run --silent\",\n    \"test:watch\": \"vitest\",\n    \"storybook\": \"storybook dev -p 6006\",\n    \"build-storybook\": \"storybook build\",\n    \"lint\": \"eslint --ext .js,.ts,.vue src/main/frontend\",\n    \"lint:fix\": \"eslint --ext .js,.ts,.vue --fix src/main/frontend\",\n    \"format\": \"prettier src/main/frontend --check\",\n    \"format:fix\": \"prettier src/main/frontend --write\"\n  },\n  \"dependencies\": {\n    \"@fortawesome/fontawesome-svg-core\": \"7.2.0\",\n    \"@fortawesome/free-brands-svg-icons\": \"7.2.0\",\n    \"@fortawesome/free-regular-svg-icons\": \"7.2.0\",\n    \"@fortawesome/free-solid-svg-icons\": \"7.2.0\",\n    \"@fortawesome/vue-fontawesome\": \"3.1.3\",\n    \"@headlessui/vue\": \"1.7.23\",\n    \"@primeuix/themes\": \"^2.0.0\",\n    \"@primevue/core\": \"^4.4.1\",\n    \"@primevue/forms\": \"4.5.4\",\n    \"@stekoe/vue-toast-notificationcenter\": \"https://github.com/SteKoe/vue-toast-notificationcenter/archive/refs/tags/1.0.0-RC5.tar.gz\",\n    \"@tailwindcss/forms\": \"0.5.11\",\n    \"@tailwindcss/typography\": \"0.5.19\",\n    \"@types/sanitize-html\": \"^2.16.0\",\n    \"ansi_up\": \"6.0.6\",\n    \"autolinker\": \"4.1.5\",\n    \"axios\": \"1.13.6\",\n    \"chart.js\": \"4.5.1\",\n    \"chartjs-adapter-moment\": \"1.0.1\",\n    \"classnames\": \"2.5.1\",\n    \"cronstrue\": \"^3.0.0\",\n    \"d3\": \"^7.9.0\",\n    \"d3-array\": \"3.2.4\",\n    \"d3-axis\": \"3.0.0\",\n    \"d3-brush\": \"3.0.0\",\n    \"d3-scale\": \"4.0.2\",\n    \"d3-selection\": \"3.0.0\",\n    \"d3-shape\": \"3.2.0\",\n    \"d3-time\": \"3.1.0\",\n    \"event-source-polyfill\": \"1.0.31\",\n    \"file-saver\": \"2.0.5\",\n    \"fuse.js\": \"^7.0.0\",\n    \"iso8601-duration\": \"2.1.3\",\n    \"js-yaml\": \"^4.1.0\",\n    \"lodash-es\": \"4.17.23\",\n    \"mitt\": \"^3.0.0\",\n    \"moment\": \"2.30.1\",\n    \"popper.js\": \"1.16.1\",\n    \"pretty-bytes\": \"7.1.0\",\n    \"primelocale\": \"^2.1.7\",\n    \"primevue\": \"^4.4.1\",\n    \"qs\": \"^6.13.0\",\n    \"random-string\": \"0.2.0\",\n    \"react\": \"19.2.4\",\n    \"react-dom\": \"19.2.4\",\n    \"resize-observer-polyfill\": \"1.5.1\",\n    \"rxjs\": \"7.8.2\",\n    \"sanitize-html\": \"^2.17.0\",\n    \"uuid\": \"13.0.0\",\n    \"v3-infinite-loading\": \"1.3.2\",\n    \"vue\": \"3.5.30\",\n    \"vue-i18n\": \"11.3.0\",\n    \"vue-router\": \"5.0.3\",\n    \"vue3-click-away\": \"1.2.4\"\n  },\n  \"devDependencies\": {\n    \"@eslint/eslintrc\": \"^3.3.1\",\n    \"@eslint/js\": \"^10.0.0\",\n    \"@storybook/addon-docs\": \"^10.0.0\",\n    \"@storybook/addon-links\": \"10.2.19\",\n    \"@storybook/vue3-vite\": \"10.2.19\",\n    \"@testing-library/jest-dom\": \"6.9.1\",\n    \"@testing-library/user-event\": \"14.6.1\",\n    \"@testing-library/vue\": \"8.1.0\",\n    \"@trivago/prettier-plugin-sort-imports\": \"^6.0.0\",\n    \"@types/d3\": \"^7.4.3\",\n    \"@types/lodash-es\": \"^4.17.7\",\n    \"@typescript-eslint/eslint-plugin\": \"^8.0.0\",\n    \"@typescript-eslint/parser\": \"^8.0.0\",\n    \"@vitejs/plugin-vue\": \"6.0.5\",\n    \"@vue/eslint-config-typescript\": \"^14.0.0\",\n    \"@vue/test-utils\": \"2.4.6\",\n    \"autoprefixer\": \"10.4.27\",\n    \"babel-loader\": \"10.1.1\",\n    \"eslint\": \"^10.0.0\",\n    \"eslint-config-prettier\": \"^10.0.0\",\n    \"eslint-plugin-prettier\": \"^5.0.0\",\n    \"eslint-plugin-storybook\": \"10.2.19\",\n    \"eslint-plugin-vue\": \"^10.0.0\",\n    \"globals\": \"^17.0.0\",\n    \"happy-dom\": \"^20.0.0\",\n    \"jsdom\": \"^28.0.0\",\n    \"msw\": \"2.12.10\",\n    \"msw-storybook-addon\": \"2.0.6\",\n    \"postcss\": \"8.5.8\",\n    \"prettier\": \"^3.0.3\",\n    \"rollup-plugin-visualizer\": \"7.0.1\",\n    \"sass\": \"^1.57.1\",\n    \"storybook\": \"10.2.19\",\n    \"storybook-vue3-router\": \"^7.0.0\",\n    \"tailwindcss\": \"3.4.19\",\n    \"ts-node-dev\": \"^2.0.0\",\n    \"typescript\": \"^5.0.3\",\n    \"unplugin-vue-components\": \"^31.0.0\",\n    \"vite\": \"7.3.1\",\n    \"vite-plugin-static-copy\": \"3.3.0\",\n    \"vitest\": \"4.1.0\",\n    \"vue-eslint-parser\": \"^10.0.0\",\n    \"vue-loader\": \"17.4.2\"\n  },\n  \"browserslist\": [\n    \"> .5%, last 2 versions\"\n  ],\n  \"engines\": {\n    \"node\": \"24.14.0\"\n  },\n  \"msw\": {\n    \"workerDirectory\": [\n      \"./src/main/frontend/public\",\n      \"src/main/frontend/public\",\n      \"public\"\n    ]\n  },\n  \"overrides\": {\n    \"esbuild\": \"0.27.4\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright 2014-2019 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>spring-boot-admin-server-ui</artifactId>\n\n    <name>Spring Boot Admin Server UI</name>\n    <description>Spring Boot Admin Server UI</description>\n\n    <parent>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-build</artifactId>\n        <version>${revision}</version>\n        <relativePath>../spring-boot-admin-build</relativePath>\n    </parent>\n\n    <dependencies>\n        <dependency>\n            <groupId>de.codecentric</groupId>\n            <artifactId>spring-boot-admin-server</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-webmvc</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-autoconfigure-processor</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-configuration-processor</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>com.google.code.findbugs</groupId>\n            <artifactId>jsr305</artifactId>\n        </dependency>\n        <!-- Test -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-security</artifactId>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>com.github.eirslett</groupId>\n                <artifactId>frontend-maven-plugin</artifactId>\n                <executions>\n                    <execution>\n                        <id>install-node-and-npm</id>\n                        <goals>\n                            <goal>install-node-and-npm</goal>\n                        </goals>\n                        <configuration>\n                            <nodeVersion>${node.version}</nodeVersion>\n                        </configuration>\n                    </execution>\n                    <execution>\n                        <id>npm-install</id>\n                        <goals>\n                            <goal>npm</goal>\n                        </goals>\n                        <configuration>\n                            <arguments>ci --prefer-offline --no-progress --no-audit --silent</arguments>\n                        </configuration>\n                    </execution>\n                    <execution>\n                        <id>npm-build</id>\n                        <goals>\n                            <goal>npm</goal>\n                        </goals>\n                        <configuration>\n                            <arguments>run build</arguments>\n                            <environmentVariables>\n                                <PROJECT_VERSION>${project.version}</PROJECT_VERSION>\n                            </environmentVariables>\n                        </configuration>\n                    </execution>\n                    <execution>\n                        <id>npm-lint</id>\n                        <goals>\n                            <goal>npm</goal>\n                        </goals>\n                        <phase>verify</phase>\n                        <configuration>\n                            <arguments>run lint</arguments>\n                        </configuration>\n                    </execution>\n                    <execution>\n                        <id>npm-test</id>\n                        <goals>\n                            <goal>npm</goal>\n                        </goals>\n                        <phase>test</phase>\n                        <configuration>\n                            <arguments>run test</arguments>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-resources-plugin</artifactId>\n                <configuration>\n                    <nonFilteredFileExtensions>\n                        <nonFilteredFileExtension>woff</nonFilteredFileExtension>\n                        <nonFilteredFileExtension>ttf</nonFilteredFileExtension>\n                        <nonFilteredFileExtension>woff2</nonFilteredFileExtension>\n                        <nonFilteredFileExtension>eot</nonFilteredFileExtension>\n                        <nonFilteredFileExtension>swf</nonFilteredFileExtension>\n                        <nonFilteredFileExtension>ico</nonFilteredFileExtension>\n                        <nonFilteredFileExtension>png</nonFilteredFileExtension>\n                    </nonFilteredFileExtensions>\n                    <includeEmptyDirs>true</includeEmptyDirs>\n                </configuration>\n                <executions>\n                    <execution>\n                        <id>copy-resources</id>\n                        <phase>process-resources</phase>\n                        <goals>\n                            <goal>copy-resources</goal>\n                        </goals>\n                        <configuration>\n                            <outputDirectory>\n                                ${project.build.outputDirectory}/META-INF/spring-boot-admin-server-ui\n                            </outputDirectory>\n                            <resources>\n                                <resource>\n                                    <directory>${project.build.directory}/dist</directory>\n                                    <filtering>false</filtering>\n                                </resource>\n                            </resources>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/postcss.config.js",
    "content": "// Must be JavaScript as Storybook does not work otherwise.\nimport autoprefixer from 'autoprefixer';\n\nimport tailwindcss from 'tailwindcss';\n\nmodule.exports = {\n  plugins: [tailwindcss, autoprefixer],\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/HealthStatus.ts",
    "content": "class HealthStatusEnum {\n  static DOWN = 'DOWN';\n  static UP = 'UP';\n  static RESTRICTED = 'RESTRICTED';\n  static UNKNOWN = 'UNKNOWN';\n  static OUT_OF_SERVICE = 'OUT_OF_SERVICE';\n  static OFFLINE = 'OFFLINE';\n}\n\nexport const HealthStatus = Object.freeze(HealthStatusEnum);\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/ActionScope.ts",
    "content": "class ActionScopeEnum {\n  static INSTANCE = 'instance';\n  static APPLICATION = 'application';\n}\n\nexport const ActionScope = Object.freeze(ActionScopeEnum);\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/__snapshots__/sba-formatted-obj.spec.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`SbaFormattedObject > processes json > autolinks urls 1`] = `\n\"<div><pre data-v-4fb4748a=\"\" class=\"formatted\">a:\n  nested:\n    object:\n      key: This is a text with a link <a href=\"https://www.codecentric.de\" target=\"_blank\" rel=\"noopener noreferrer\">https://www.codecentric.de</a> that is automagically created.\n</pre></div>\"\n`;\n\nexports[`SbaFormattedObject > processes json > does not render unsafe <script> in html 1`] = `\n\"<div><pre data-v-4fb4748a=\"\" class=\"formatted\">a:\n  nested:\n    object:\n      key: \n</pre></div>\"\n`;\n\nexports[`SbaFormattedObject > processes simple text > autolinks urls 1`] = `\"<div><pre data-v-4fb4748a=\"\" class=\"formatted\">This is a text with a link <a href=\"https://www.codecentric.de\" target=\"_blank\" rel=\"noopener noreferrer\">https://www.codecentric.de</a> that is automagically created.</pre></div>\"`;\n\nexports[`SbaFormattedObject > processes simple text > does not render unsafe <img> in html 1`] = `\"<div><pre data-v-4fb4748a=\"\" class=\"formatted\"></pre></div>\"`;\n\nexports[`SbaFormattedObject > processes simple text > does not render unsafe <script> in html 1`] = `\"<div><pre data-v-4fb4748a=\"\" class=\"formatted\"></pre></div>\"`;\n\nexports[`SbaFormattedObject > processes simple text > does render (safe) html 1`] = `\"<div><pre data-v-4fb4748a=\"\" class=\"formatted\">This is a text with <a href=\"https://www.codecentric.de\" target=\"_blank\">a link</a> and a bold text <b>bold</b>.</pre></div>\"`;\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/font-awesome-icon.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { library } from '@fortawesome/fontawesome-svg-core';\nimport { faGithub } from '@fortawesome/free-brands-svg-icons/faGithub';\nimport { faGitter } from '@fortawesome/free-brands-svg-icons/faGitter';\nimport { faStackOverflow } from '@fortawesome/free-brands-svg-icons/faStackOverflow';\nimport { faStopCircle as farStopCircle } from '@fortawesome/free-regular-svg-icons/faStopCircle';\nimport { faTimesCircle as farTimesCircle } from '@fortawesome/free-regular-svg-icons/faTimesCircle';\nimport {\n  faAngleDoubleLeft,\n  faCogs,\n  faExpand,\n  faEye,\n  faList,\n  faPowerOff,\n  faUndoAlt,\n} from '@fortawesome/free-solid-svg-icons';\nimport { faAngleDoubleRight } from '@fortawesome/free-solid-svg-icons/faAngleDoubleRight';\nimport { faBan } from '@fortawesome/free-solid-svg-icons/faBan';\nimport { faBars } from '@fortawesome/free-solid-svg-icons/faBars';\nimport { faBell } from '@fortawesome/free-solid-svg-icons/faBell';\nimport { faBellSlash } from '@fortawesome/free-solid-svg-icons/faBellSlash';\nimport { faBook } from '@fortawesome/free-solid-svg-icons/faBook';\nimport { faCheck } from '@fortawesome/free-solid-svg-icons/faCheck';\nimport { faCheckCircle } from '@fortawesome/free-solid-svg-icons/faCheckCircle';\nimport { faChevronDown } from '@fortawesome/free-solid-svg-icons/faChevronDown';\nimport { faChevronRight } from '@fortawesome/free-solid-svg-icons/faChevronRight';\nimport { faCube } from '@fortawesome/free-solid-svg-icons/faCube';\nimport { faCubes } from '@fortawesome/free-solid-svg-icons/faCubes';\nimport { faDownload } from '@fortawesome/free-solid-svg-icons/faDownload';\nimport { faExclamation } from '@fortawesome/free-solid-svg-icons/faExclamation';\nimport { faExclamationCircle } from '@fortawesome/free-solid-svg-icons/faExclamationCircle';\nimport { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons/faExclamationTriangle';\nimport { faFilter } from '@fortawesome/free-solid-svg-icons/faFilter';\nimport { faFrownOpen } from '@fortawesome/free-solid-svg-icons/faFrownOpen';\nimport { faHeartbeat } from '@fortawesome/free-solid-svg-icons/faHeartbeat';\nimport { faHistory } from '@fortawesome/free-solid-svg-icons/faHistory';\nimport { faHome } from '@fortawesome/free-solid-svg-icons/faHome';\nimport { faInfoCircle } from '@fortawesome/free-solid-svg-icons/faInfoCircle';\nimport { faMapMarkerAlt } from '@fortawesome/free-solid-svg-icons/faMapMarkerAlt';\nimport { faMinusCircle } from '@fortawesome/free-solid-svg-icons/faMinusCircle';\nimport { faPencilAlt } from '@fortawesome/free-solid-svg-icons/faPencilAlt';\nimport { faQuestionCircle } from '@fortawesome/free-solid-svg-icons/faQuestionCircle';\nimport { faRedo } from '@fortawesome/free-solid-svg-icons/faRedo';\nimport { faSearch } from '@fortawesome/free-solid-svg-icons/faSearch';\nimport { faSignOutAlt } from '@fortawesome/free-solid-svg-icons/faSignOutAlt';\nimport { faStepBackward } from '@fortawesome/free-solid-svg-icons/faStepBackward';\nimport { faStepForward } from '@fortawesome/free-solid-svg-icons/faStepForward';\nimport { faSyncAlt } from '@fortawesome/free-solid-svg-icons/faSyncAlt';\nimport { faTimesCircle } from '@fortawesome/free-solid-svg-icons/faTimesCircle';\nimport { faTrash } from '@fortawesome/free-solid-svg-icons/faTrash';\nimport { faUserCircle } from '@fortawesome/free-solid-svg-icons/faUserCircle';\nimport { faWrench } from '@fortawesome/free-solid-svg-icons/faWrench';\nimport { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';\n\nexport default FontAwesomeIcon;\n\nlibrary.add(\n  //solid\n  faAngleDoubleRight,\n  faAngleDoubleLeft,\n  faBan,\n  faBell,\n  faBellSlash,\n  faBook,\n  faBars,\n  faCogs,\n  faEye,\n  faCheck,\n  faCube,\n  faCubes,\n  faDownload,\n  faExclamation,\n  faExclamationTriangle,\n  faExclamationCircle,\n  faFilter,\n  faFrownOpen,\n  faHeartbeat,\n  faHistory,\n  faInfoCircle,\n  faExclamationCircle,\n  faHome,\n  faList,\n  faExpand,\n  faMapMarkerAlt,\n  faCheckCircle,\n  faMinusCircle,\n  faChevronRight,\n  faChevronDown,\n  faPencilAlt,\n  faPowerOff,\n  faQuestionCircle,\n  faSearch,\n  faSignOutAlt,\n  faStepBackward,\n  faStepForward,\n  faTimesCircle,\n  faTrash,\n  faRedo,\n  faSyncAlt,\n  faUndoAlt,\n  faUserCircle,\n  faWrench,\n  //regular\n  farStopCircle,\n  farTimesCircle,\n  //brands\n  faGithub,\n  faGitter,\n  faStackOverflow,\n);\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/index.ts",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport FontAwesomeIcon from './font-awesome-icon';\nimport SbaActionButtonScoped from './sba-action-button-scoped.vue';\nimport SbaAlert from './sba-alert.vue';\nimport SbaButtonGroup from './sba-button-group.vue';\nimport SbaButton from './sba-button.vue';\nimport SbaCheckbox from './sba-checkbox.vue';\nimport SbaConfirmButton from './sba-confirm-button.vue';\nimport SbaFormattedObj from './sba-formatted-obj.vue';\nimport SbaIconButton from './sba-icon-button.vue';\nimport SbaInput from './sba-input.vue';\nimport SbaKeyValueTable from './sba-key-value-table.vue';\nimport SbaLoadingSpinner from './sba-loading-spinner.vue';\nimport SbaModal from './sba-modal.vue';\nimport SbaPaginationNav from './sba-pagination-nav.vue';\nimport SbaPanel from './sba-panel.vue';\nimport SbaSelect from './sba-select.vue';\nimport SbaStatusBadge from './sba-status-badge.vue';\nimport SbaStatus from './sba-status.vue';\nimport SbaStickySubnav from './sba-sticky-subnav.vue';\nimport SbaTag from './sba-tag.vue';\nimport SbaTags from './sba-tags.vue';\nimport SbaTimeAgo from './sba-time-ago.vue';\nimport SbaToggleScopeButton from './sba-toggle-scope-button.vue';\nimport SbaWave from './sba-wave.vue';\n\nexport const components = {\n  'sba-action-button-scoped': SbaActionButtonScoped,\n  'sba-alert': SbaAlert,\n  'sba-button-group': SbaButtonGroup,\n  'sba-button': SbaButton,\n  'sba-checkbox': SbaCheckbox,\n  'sba-confirm-button': SbaConfirmButton,\n  'sba-formatted-obj': SbaFormattedObj,\n  'sba-icon-button': SbaIconButton,\n  'sba-input': SbaInput,\n  'sba-select': SbaSelect,\n  'sba-key-value-table': SbaKeyValueTable,\n  'sba-loading-spinner': SbaLoadingSpinner,\n  'sba-modal': SbaModal,\n  'sba-pagination-nav': SbaPaginationNav,\n  'sba-panel': SbaPanel,\n  'sba-status-badge': SbaStatusBadge,\n  'sba-status': SbaStatus,\n  'sba-sticky-subnav': SbaStickySubnav,\n  'sba-tag': SbaTag,\n  'sba-tags': SbaTags,\n  'sba-time-ago': SbaTimeAgo,\n  'sba-toggle-scope-button': SbaToggleScopeButton,\n  'font-awesome-icon': FontAwesomeIcon,\n  'sba-wave': SbaWave,\n};\n\nexport default {\n  install(app) {\n    for (const [name, component] of Object.entries(components)) {\n      app.component(name, component);\n    }\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-accordion.spec.ts",
    "content": "/*\n * Copyright 2014-2025 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport userEvent from '@testing-library/user-event';\nimport { screen } from '@testing-library/vue';\nimport { beforeEach, describe, expect, it } from 'vitest';\n\nimport { render } from '../test-utils';\nimport SbaAccordion from './sba-accordion.vue';\n\ndescribe('sba-accordion.vue', () => {\n  beforeEach(() => {\n    localStorage.clear();\n  });\n\n  it('should load open state from localStorage when id is provided', () => {\n    const accordionId = 'test-accordion';\n    localStorage.setItem(\n      `de.codecentric.spring-boot-admin.accordion.${accordionId}.open`,\n      'false',\n    );\n\n    const { emitted } = render(SbaAccordion, {\n      props: {\n        id: accordionId,\n        modelValue: true,\n      },\n      slots: {\n        title: 'Test Title',\n        default: 'Test Content',\n      },\n    });\n\n    expect(emitted()['update:modelValue'][0]).toContain(false);\n  });\n\n  it('should save open state to localStorage when title is clicked', async () => {\n    const accordionId = 'test-accordion';\n\n    render(SbaAccordion, {\n      props: {\n        id: accordionId,\n        modelValue: true,\n      },\n      slots: {\n        title: 'Test Title',\n        default: 'Test Content',\n      },\n    });\n\n    const title = screen.getByText('Test Title');\n    await userEvent.click(title);\n\n    const storedValue = localStorage.getItem(\n      `de.codecentric.spring-boot-admin.accordion.${accordionId}.open`,\n    );\n    expect(storedValue).toBe('false');\n  });\n\n  it('should not interact with localStorage when id is not provided', async () => {\n    render(SbaAccordion, {\n      props: {\n        modelValue: true,\n      },\n      slots: {\n        title: 'Test Title',\n        default: 'Test Content',\n      },\n    });\n\n    const title = screen.getByText('Test Title');\n    await userEvent.click(title);\n\n    expect(localStorage.length).toBe(0);\n  });\n\n  it('should use default open state when no localStorage value exists', () => {\n    const accordionId = 'test-accordion';\n\n    const { emitted } = render(SbaAccordion, {\n      props: {\n        id: accordionId,\n        modelValue: false,\n      },\n      slots: {\n        title: 'Test Title',\n        default: 'Test Content',\n      },\n    });\n\n    expect(emitted()['update:modelValue']).toBeUndefined();\n  });\n\n  it('should toggle open state and save to localStorage', async () => {\n    const accordionId = 'test-accordion';\n    localStorage.setItem(\n      `de.codecentric.spring-boot-admin.accordion.${accordionId}.open`,\n      'true',\n    );\n\n    render(SbaAccordion, {\n      props: {\n        id: accordionId,\n        modelValue: false,\n      },\n      slots: {\n        title: 'Test Title',\n        default: 'Test Content',\n      },\n    });\n\n    const title = screen.getByText('Test Title');\n    await userEvent.click(title);\n\n    const storedValue = localStorage.getItem(\n      `de.codecentric.spring-boot-admin.accordion.${accordionId}.open`,\n    );\n    expect(storedValue).toBe('false');\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-accordion.stories.ts",
    "content": "/*\n * Copyright 2014-2025 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { ref } from 'vue';\n\nimport SbaAccordion from './sba-accordion.vue';\n\nexport default {\n  component: SbaAccordion,\n  title: 'Components/Accordion',\n};\n\nconst Template = (args) => {\n  return {\n    components: { SbaAccordion },\n    setup() {\n      const isOpen = ref(args.modelValue ?? true);\n      return { args, isOpen };\n    },\n    template: `\n      <sba-accordion v-bind=\"args\" v-model=\"isOpen\">\n        <template #title>\n          ${args.title || 'Accordion Title'}\n        </template>\n        <template #actions v-if=\"${'actions' in args}\">\n          ${args.actions}\n        </template>\n        <template #default>\n          ${args.slot}\n        </template>\n      </sba-accordion>\n      <div class=\"mt-4 text-sm text-gray-600\">\n        Current state: <strong>{{ isOpen ? 'Open' : 'Closed' }}</strong>\n      </div>\n    `,\n  };\n};\n\nexport const DefaultOpen = {\n  render: Template,\n\n  args: {\n    modelValue: true,\n    title: 'Application Information',\n    slot: `<article class=\"prose max-w-none\">\n      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vitae dolor ac ante ornare pharetra.\n      Proin laoreet ex et lacinia hendrerit. Fusce sed justo at nulla pellentesque maximus sed at diam.</p>\n      <p>Suspendisse sem lorem, lobortis vel orci quis, efficitur porta massa. In vel neque justo.\n      Maecenas dapibus quam ut nisl porta, molestie egestas felis maximus.</p>\n    </article>`,\n  },\n};\n\nexport const DefaultClosed = {\n  render: Template,\n\n  args: {\n    modelValue: false,\n    title: 'System Properties',\n    slot: `<article class=\"prose max-w-none\">\n      <p>This content is initially hidden. Click the title to expand the accordion.</p>\n    </article>`,\n  },\n};\n\nexport const WithKeyValueTable = {\n  render: Template,\n\n  args: {\n    modelValue: true,\n    title: 'Health Details',\n    slot: `\n      <div class=\"-mx-4 -my-3\">\n        <div class=\"bg-white px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6\">\n          <dt class=\"text-sm font-medium text-gray-500\">Status</dt>\n          <dd class=\"mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2\">UP</dd>\n        </div>\n        <div class=\"bg-gray-50 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6\">\n          <dt class=\"text-sm font-medium text-gray-500\">Disk Space</dt>\n          <dd class=\"mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2\">10.5 GB free</dd>\n        </div>\n        <div class=\"bg-white px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6\">\n          <dt class=\"text-sm font-medium text-gray-500\">Database</dt>\n          <dd class=\"mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2\">Connected</dd>\n        </div>\n        <div class=\"bg-gray-50 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6\">\n          <dt class=\"text-sm font-medium text-gray-500\">Memory</dt>\n          <dd class=\"mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2\">512 MB / 2 GB</dd>\n        </div>\n      </div>`,\n  },\n};\n\nexport const WithActions = {\n  render: Template,\n\n  args: {\n    modelValue: true,\n    title: 'Configuration Settings',\n    actions:\n      '<button class=\"text-sm text-blue-600 hover:text-blue-800\">Edit</button>',\n    slot: `<article class=\"prose max-w-none\">\n      <p>Configuration content with action buttons in the header.</p>\n    </article>`,\n  },\n};\n\nexport const WithId = {\n  render: (args) => {\n    return {\n      components: { SbaAccordion },\n      setup() {\n        const isOpen = ref(args.modelValue ?? true);\n        return { args, isOpen };\n      },\n      template: `\n        <div>\n          <p class=\"mb-4 text-sm text-gray-600\">\n            This accordion has an ID and will persist its state in localStorage.\n            Try toggling it and refreshing the page.\n          </p>\n          <sba-accordion v-bind=\"args\" v-model=\"isOpen\">\n            <template #title>\n              ${args.title || 'Persisted Accordion'}\n            </template>\n            <template #default>\n              ${args.slot}\n            </template>\n          </sba-accordion>\n          <div class=\"mt-4 text-sm text-gray-600\">\n            Current state: <strong>{{ isOpen ? 'Open' : 'Closed' }}</strong>\n            <br />\n            LocalStorage key: <code class=\"text-xs bg-gray-100 px-1 py-0.5 rounded\">de.codecentric.spring-boot-admin.accordion.${args.id}.open</code>\n          </div>\n        </div>\n      `,\n    };\n  },\n\n  args: {\n    id: 'storybook-example',\n    modelValue: true,\n    title: 'Persisted State Example',\n    slot: `<article class=\"prose max-w-none\">\n      <p>This accordion's open/closed state is stored in localStorage using the ID \"storybook-example\".</p>\n      <p>Try toggling it and refreshing the browser to see the state persist.</p>\n    </article>`,\n  },\n};\n\nexport const MultipleAccordions = {\n  render: (args) => {\n    return {\n      components: { SbaAccordion },\n      setup() {\n        const accordion1Open = ref(true);\n        const accordion2Open = ref(false);\n        const accordion3Open = ref(true);\n        return { args, accordion1Open, accordion2Open, accordion3Open };\n      },\n      template: `\n        <div class=\"space-y-4\">\n          <sba-accordion v-model=\"accordion1Open\">\n            <template #title>First Accordion</template>\n            <template #default>\n              <p>Content of the first accordion.</p>\n            </template>\n          </sba-accordion>\n\n          <sba-accordion v-model=\"accordion2Open\">\n            <template #title>Second Accordion</template>\n            <template #default>\n              <p>Content of the second accordion (initially closed).</p>\n            </template>\n          </sba-accordion>\n\n          <sba-accordion v-model=\"accordion3Open\">\n            <template #title>Third Accordion</template>\n            <template #actions>\n              <span class=\"text-xs text-gray-500\">With Actions</span>\n            </template>\n            <template #default>\n              <p>Content of the third accordion with actions.</p>\n            </template>\n          </sba-accordion>\n\n          <div class=\"mt-4 text-sm text-gray-600\">\n            States:\n            <strong>1: {{ accordion1Open ? 'Open' : 'Closed' }}</strong> |\n            <strong>2: {{ accordion2Open ? 'Open' : 'Closed' }}</strong> |\n            <strong>3: {{ accordion3Open ? 'Open' : 'Closed' }}</strong>\n          </div>\n        </div>\n      `,\n    };\n  },\n\n  args: {},\n};\n\nexport const NestedContent = {\n  render: Template,\n\n  args: {\n    modelValue: true,\n    title: 'Advanced Configuration',\n    slot: `\n      <div class=\"space-y-4\">\n        <div>\n          <h4 class=\"text-sm font-semibold mb-2\">Server Settings</h4>\n          <ul class=\"list-disc list-inside text-sm space-y-1\">\n            <li>Port: 8080</li>\n            <li>Context Path: /admin</li>\n            <li>SSL Enabled: false</li>\n          </ul>\n        </div>\n        <div>\n          <h4 class=\"text-sm font-semibold mb-2\">Monitoring</h4>\n          <ul class=\"list-disc list-inside text-sm space-y-1\">\n            <li>Interval: 10000ms</li>\n            <li>Timeout: 5000ms</li>\n            <li>Retries: 3</li>\n          </ul>\n        </div>\n      </div>`,\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-accordion.vue",
    "content": "<!--\n  - Copyright 2014-2025 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-panel\n    v-bind=\"$attrs\"\n    :aria-expanded=\"open\"\n    :class=\"\n      classNames(\n        {\n          '!p-0 !h-0 overflow-hidden': !open,\n        },\n        'transition-[height] h-auto',\n      )\n    \"\n    @title-click=\"handleTitleClick\"\n  >\n    <template #title>\n      <slot name=\"title\" />\n    </template>\n\n    <template #prefix>\n      <font-awesome-icon\n        icon=\"chevron-down\"\n        :class=\"\n          classNames(\n            {\n              '-rotate-90': !open,\n            },\n            'mr-2 transition-[transform]',\n          )\n        \"\n      />\n    </template>\n    <template #actions>\n      <slot name=\"actions\" />\n    </template>\n\n    <template #default>\n      <slot />\n    </template>\n  </sba-panel>\n</template>\n\n<script setup lang=\"ts\">\nimport { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';\nimport classNames from 'classnames';\nimport { onMounted, watch } from 'vue';\n\nconst { id = null } = defineProps<{\n  id?: string;\n}>();\n\nconst open = defineModel({ default: true, type: Boolean });\n\nonMounted(() => {\n  if (id) {\n    const storedValue = localStorage.getItem(\n      `de.codecentric.spring-boot-admin.accordion.${id}.open`,\n    );\n    if (storedValue !== null) {\n      open.value = storedValue === 'true';\n    }\n  }\n});\n\nwatch(open, (newValue) => {\n  if (id) {\n    localStorage.setItem(\n      `de.codecentric.spring-boot-admin.accordion.${id}.open`,\n      newValue.toString(),\n    );\n  }\n});\n\nconst handleTitleClick = () => {\n  open.value = !open.value;\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-action-button-scoped.spec.tsx",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport userEvent from '@testing-library/user-event';\nimport { screen } from '@testing-library/vue';\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport SbaActionButtonScoped from './sba-action-button-scoped.vue';\n\nimport { render } from '@/test-utils';\n\ndescribe('SbaActionButtonScoped', function () {\n  const actionFn = vi.fn().mockResolvedValue([]);\n\n  beforeEach(() => {\n    render(SbaActionButtonScoped, {\n      props: {\n        instanceCount: 10,\n        actionFn,\n      },\n      slots: {\n        default: 'Execute',\n      },\n    });\n  });\n\n  it('should cal actionFn when confirmed', async () => {\n    await userEvent.click(\n      await screen.findByRole('button', { name: 'Execute' }),\n    );\n    await userEvent.click(\n      await screen.findByRole('button', { name: 'Confirm' }),\n    );\n\n    expect(actionFn).toHaveBeenCalledTimes(1);\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-action-button-scoped.stories.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport SbaActionButtonScoped from './sba-action-button-scoped.vue';\n\nimport i18n from '@/i18n';\n\nexport default {\n  component: SbaActionButtonScoped,\n  title: 'Components/Buttons/Action Button Scoped',\n};\n\nconst TemplateWithProps = (args) => ({\n  components: { SbaActionButtonScoped },\n  setup() {\n    return { args };\n  },\n  template: '<sba-action-button-scoped v-bind=\"args\" />',\n  i18n,\n});\n\nexport const OneInstanceSuccessful = {\n  render: TemplateWithProps,\n\n  args: {\n    instanceCount: 1,\n    label: 'Push me!',\n    actionFn() {\n      return new Promise((resolve) => {\n        setTimeout(() => resolve(), 2000);\n      });\n    },\n  },\n};\n\nexport const MultipleInstancesSuccessful = {\n  render: TemplateWithProps,\n\n  args: {\n    ...OneInstanceSuccessful.args,\n    instanceCount: 10,\n  },\n};\n\nexport const OneInstanceFailing = {\n  render: TemplateWithProps,\n\n  args: {\n    ...OneInstanceSuccessful.args,\n    instanceCount: 1,\n    actionFn() {\n      return new Promise((resolve, reject) => {\n        setTimeout(() => {\n          reject();\n        }, 2000);\n      });\n    },\n  },\n};\n\nconst TemplateWithSlot = (args) => ({\n  components: { SbaActionButtonScoped },\n  setup() {\n    return { args };\n  },\n  template: `\n    <sba-action-button-scoped v-bind=\"args\">\n      <template v-slot=\"slotProps\">\n        <span v-if=\"slotProps.refreshStatus === 'executing'\">Working...</span>\n        <span v-else-if=\"slotProps.refreshStatus === 'completed'\">Status Is Completed</span>\n        <span v-else-if=\"slotProps.refreshStatus === 'failed'\">Status Is Failed</span>\n        <span v-else>Default Label</span>\n      </template>\n    </sba-action-button-scoped>`,\n  i18n,\n});\n\nexport const SlottedOneInstanceSuccessful = {\n  render: TemplateWithSlot,\n\n  args: {\n    instanceCount: 1,\n    actionFn() {\n      return new Promise((resolve) => {\n        setTimeout(() => {\n          resolve();\n        }, 2000);\n      });\n    },\n  },\n};\n\nexport const SlottedOneInstanceFailing = {\n  render: TemplateWithSlot,\n\n  args: {\n    instanceCount: 1,\n    actionFn() {\n      return new Promise((resolve, reject) => {\n        setTimeout(() => {\n          reject();\n        }, 2000);\n      });\n    },\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-action-button-scoped.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div class=\"inline-flex rounded items-start -space-x-px\">\n    <sba-toggle-scope-button\n      v-if=\"instanceCount > 1\"\n      v-model=\"currentScope\"\n      class=\"!rounded-r-none relative focus:z-10\"\n      :instance-count=\"instanceCount\"\n      :show-info=\"showInfo\"\n      @update:model-value=\"emitScopeChanged\"\n    />\n    <sba-confirm-button\n      class=\"inline-flex focus:z-10\"\n      :class=\"{ '!rounded-l-none': instanceCount > 1 }\"\n      :disabled=\"\n        disabled ||\n        refreshStatus === 'executing' ||\n        refreshStatus === 'completed'\n      \"\n      @click=\"click\"\n    >\n      <slot v-if=\"!refreshStatus\" name=\"default\" />\n\n      <slot v-if=\"refreshStatus === 'completed'\" name=\"completed\" />\n      <slot v-if=\"refreshStatus === 'failed'\" name=\"failed\" />\n      <slot v-if=\"refreshStatus === 'executing'\" name=\"executing\">\n        <svg\n          class=\"inline w-5 h-5 text-gray-900 animate-spin\"\n          viewBox=\"0 0 100 101\"\n          fill=\"none\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n        >\n          <path\n            d=\"M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z\"\n            fill=\"#E5E7EB\"\n          />\n          <path\n            d=\"M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z\"\n            fill=\"currentColor\"\n          />\n        </svg>\n      </slot>\n    </sba-confirm-button>\n  </div>\n</template>\n\n<script>\nimport { finalize } from 'rxjs';\n\nimport { ActionScope } from '@/components/ActionScope';\n\nimport { from, listen } from '@/utils/rxjs';\n\nexport default {\n  props: {\n    instanceCount: {\n      type: Number,\n      required: true,\n    },\n    actionFn: {\n      type: Function,\n      required: true,\n    },\n    disabled: { type: Boolean, default: false },\n    showInfo: {\n      type: Boolean,\n      default: true,\n    },\n  },\n  emits: ['scopeChanged'],\n  data() {\n    return {\n      status: null,\n      refreshStatus: '',\n      currentScope: ActionScope.INSTANCE,\n      refreshTimeout: null,\n    };\n  },\n  beforeUnmount() {\n    if (this.refreshTimeout) {\n      clearTimeout(this.refreshTimeout);\n      this.refreshTimeout = null;\n    }\n  },\n  methods: {\n    resetRefreshState() {\n      this.refreshTimeout = setTimeout(() => {\n        this.refreshStatus = null;\n      }, 2000);\n    },\n    click() {\n      this.refreshStatus = 'executing';\n\n      from(this.actionFn(this.currentScope))\n        .pipe(\n          listen((status) => (this.refreshStatus = status)),\n          finalize(() => this.resetRefreshState()),\n        )\n        .subscribe();\n    },\n    emitScopeChanged(scope) {\n      this.$emit('scopeChanged', scope);\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-alert.stories.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport SbaAlert, { Severity } from './sba-alert.vue';\n\nexport default {\n  component: SbaAlert,\n  title: 'Components/Alert',\n};\n\nconst Template = (args) => ({\n  components: { SbaAlert },\n  setup() {\n    return { args };\n  },\n  template: '<sba-alert v-bind=\"args\" />',\n});\n\nexport const AlertError = {\n  render: Template,\n\n  args: {\n    title: 'Server error',\n    error: new Error('Error reading from endpoint /applications'),\n    severity: Severity.ERROR,\n  },\n};\n\nexport const AlertErrorWithoutTitle = {\n  render: Template,\n\n  args: {\n    error: new Error('Error reading from endpoint /applications'),\n    severity: Severity.ERROR,\n  },\n};\n\nexport const AlertWarning = {\n  render: Template,\n\n  args: {\n    ...AlertError.args,\n    title: 'Warning',\n    error: new Error('The response took longer than expected.'),\n    severity: 'WARN',\n  },\n};\n\nexport const AlertInfo = {\n  render: Template,\n\n  args: {\n    ...AlertError.args,\n    title: 'Hint',\n    error: new Error('Check GC information as well!'),\n    severity: 'INFO',\n  },\n};\n\nexport const AlertSuccess = {\n  render: Template,\n\n  args: {\n    ...AlertError.args,\n    title: 'Successful',\n    error: new Error('Changes have been applied.'),\n    severity: 'SUCCESS',\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-alert.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div\n    v-if=\"hasError\"\n    :class=\"classNames(alertClass, borderClassNames)\"\n    class=\"rounded-b px-4 py-3 shadow-sm backdrop-filter backdrop-blur-xs bg-opacity-80 my-3\"\n    role=\"alert\"\n  >\n    <div class=\"flex\">\n      <div class=\"py-1\">\n        <font-awesome-icon :icon=\"icon\" class=\"mr-4\" prefix=\"fa\" size=\"1x\" />\n      </div>\n      <div class=\"grid grid-cols-1 place-content-center\">\n        <p v-if=\"title\" class=\"font-bold\" v-text=\"title\" />\n        <p class=\"text-sm\" v-html=\"message\" />\n      </div>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { faCheckCircle } from '@fortawesome/free-solid-svg-icons/faCheckCircle';\nimport { faExclamationCircle } from '@fortawesome/free-solid-svg-icons/faExclamationCircle';\nimport { faInfoCircle } from '@fortawesome/free-solid-svg-icons/faInfoCircle';\nimport classNames from 'classnames';\nimport { defineComponent } from 'vue';\n\nimport FontAwesomeIcon from '@/components/font-awesome-icon';\n\nexport const Severity = {\n  ERROR: 'ERROR',\n  WARN: 'WARN',\n  INFO: 'INFO',\n  SUCCESS: 'SUCCESS',\n};\n\nexport default defineComponent({\n  name: 'SbaAlert',\n  components: { FontAwesomeIcon },\n  props: {\n    title: {\n      type: String,\n      default: undefined,\n    },\n    error: {\n      type: [Error, String, Event],\n      default: null,\n    },\n    severity: {\n      type: String,\n      default: Severity.ERROR,\n    },\n  },\n  data() {\n    return {\n      classNames,\n      alertClass: {\n        'bg-red-100 border-red-400 text-red-700':\n          this.severity.toUpperCase() === Severity.ERROR,\n        'bg-orange-100 border-orange-500 text-orange-700':\n          this.severity.toUpperCase() === Severity.WARN,\n        'bg-blue-100 border-blue-500 text-blue-900':\n          this.severity.toUpperCase() === Severity.INFO,\n        'bg-teal-100 border-teal-500 text-teal-900':\n          this.severity.toUpperCase() === Severity.SUCCESS,\n      },\n      textColor: {\n        'text-red-700': this.severity.toUpperCase() === Severity.ERROR,\n        'text-orange-700': this.severity.toUpperCase() === Severity.WARN,\n        'text-blue-900': this.severity.toUpperCase() === Severity.INFO,\n        'text-teal-900': this.severity.toUpperCase() === Severity.SUCCESS,\n      },\n    };\n  },\n  computed: {\n    message() {\n      if (this.error instanceof Error) {\n        return this.error.message;\n      }\n      if (typeof this.error === 'string') {\n        return this.error;\n      }\n\n      return null;\n    },\n    borderClassNames() {\n      if (this.$attrs.class?.indexOf('border-') >= 0) {\n        return [];\n      } else {\n        return ['border-t-4'];\n      }\n    },\n    hasError() {\n      return this.error !== undefined && this.error !== null;\n    },\n    icon() {\n      switch (this.severity.toUpperCase()) {\n        case Severity.ERROR:\n        case Severity.WARN:\n          return faExclamationCircle;\n        case Severity.SUCCESS:\n          return faCheckCircle;\n        case Severity.INFO:\n        default:\n          return faInfoCircle;\n      }\n    },\n  },\n});\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-button-group.stories.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport SbaButtonGroup from './sba-button-group.vue';\nimport SbaButton from './sba-button.vue';\n\nexport default {\n  component: SbaButtonGroup,\n  title: 'Components/Buttons/Button Group',\n};\n\nconst Template = (args) => {\n  return {\n    components: { SbaButtonGroup, SbaButton },\n    setup() {\n      return { args };\n    },\n    template: `<sba-button-group v-bind=\"args\">${args.slot}</sba-button-group>`,\n  };\n};\n\nexport const OneButton = {\n  render: Template,\n\n  args: {\n    slot: '<sba-button>First</sba-button>',\n  },\n};\n\nexport const TwoButtons = {\n  render: Template,\n\n  args: {\n    slot: `\n      <sba-button>First</sba-button>\n      <sba-button>Second</sba-button>\n    `,\n  },\n};\n\nexport const MoreButtons = {\n  render: Template,\n\n  args: {\n    slot: `\n      <sba-button>First</sba-button>\n      <sba-button>Second</sba-button>\n      <sba-button>Third</sba-button>\n    `,\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-button-group.vue",
    "content": "<template>\n  <div class=\"btn-group\">\n    <slot />\n  </div>\n</template>\n\n<style scoped>\n.btn-group {\n  @apply inline-flex z-0 relative -space-x-px;\n}\n\n.btn-group .btn,\n.btn-group button {\n  @apply items-center relative inline-flex rounded-none border focus:z-10;\n}\n\n.btn-group:deep(:first-child:not(:last-child)) {\n  @apply rounded-r-none !important;\n}\n\n.btn-group:deep(:not(:first-child):not(:last-child)) {\n  @apply rounded-none !important;\n}\n\n.btn-group:deep(:last-child:not(:first-child)) {\n  @apply rounded-l-none !important;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-button.stories.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport SbaButton from './sba-button.vue';\n\nexport default {\n  component: SbaButton,\n  title: 'Components/Buttons/Button',\n};\n\nconst Template = (args) => {\n  return {\n    components: { SbaButton },\n    setup() {\n      return { args };\n    },\n    template: `<sba-button v-bind=\"args\">${args.label}</sba-button>`,\n  };\n};\n\nexport const DefaultButton = {\n  render: Template,\n\n  args: {\n    label: 'Default button',\n  },\n};\n\nexport const PrimaryButton = {\n  render: Template,\n\n  args: {\n    label: 'Primary button',\n    primary: 'primary',\n  },\n};\n\nexport const DangerButton = {\n  render: Template,\n\n  args: {\n    label: 'Danger button',\n    class: 'is-danger',\n  },\n};\n\nexport const SuccessButton = {\n  render: Template,\n\n  args: {\n    label: 'Danger button',\n    class: 'is-success',\n  },\n};\n\nconst SizeTemplate = () => {\n  return {\n    components: { SbaButton },\n    template: `\n      <sba-button size=\"xs\">button xs</sba-button>\n      <sba-button size=\"sm\">button sm</sba-button>\n      <sba-button size=\"base\">button base</sba-button>\n    `,\n  };\n};\n\nexport const ButtonSizes = {\n  render: SizeTemplate,\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-button.vue",
    "content": "<template>\n  <component\n    :is=\"as\"\n    class=\"btn relative items-center\"\n    v-bind=\"componentAttrs\"\n    @click=\"handleClick\"\n  >\n    <slot />\n  </component>\n</template>\n\n<script setup>\nimport classNames from 'classnames';\nimport { computed, useAttrs } from 'vue';\n\nconst props = defineProps({\n  title: {\n    type: String,\n    default: '',\n  },\n  as: {\n    type: String,\n    default: 'button',\n    validator(value) {\n      return ['a', 'button'].includes(value);\n    },\n  },\n  href: {\n    type: String,\n    default: null,\n  },\n  size: {\n    type: String,\n    default: 'sm',\n    validator(value) {\n      return ['2xs', 'xs', 'sm', 'base'].includes(value);\n    },\n  },\n  disabled: {\n    type: Boolean,\n    default: false,\n  },\n  primary: {\n    type: Boolean,\n    default: false,\n  },\n});\nconst attrs = useAttrs();\n\nconst cssClasses = computed(() => {\n  return {\n    'px-1 py-0 text-xs': props.size === '2xs',\n    'px-2 py-2 text-xs': props.size === 'xs',\n    'px-3 py-2': props.size === 'sm',\n    'px-4 py-3': props.size === 'base',\n    'px-5 py-2.5': props.size === '',\n    // Types\n    'is-primary': props.primary === true,\n  };\n});\n\nconst componentAttrs = computed(() => {\n  const common = {\n    ...attrs,\n    title: props.title,\n    class: classNames(attrs.class, cssClasses.value),\n  };\n\n  if (props.as === 'a') {\n    return {\n      ...common,\n      href: props.href,\n    };\n  }\n  if (props.as === 'button') {\n    return {\n      ...common,\n      disabled: props.disabled === true,\n      type: props.type,\n    };\n  }\n  return {};\n});\n\nconst emit = defineEmits(['click']);\nconst handleClick = (event) => {\n  if (props.as === 'button') {\n    emit('click', event);\n  }\n  if (props.as === 'a') {\n    event.stopPropagation();\n  }\n};\n</script>\n\n<style scoped>\n.btn {\n  @apply rounded-l rounded-r font-medium text-sm text-center text-black border-gray-300 border bg-white;\n  @apply focus:ring-2 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500;\n  @apply hover:bg-gray-100;\n}\n\n.btn:disabled {\n  @apply text-gray-500 cursor-not-allowed bg-gray-100 hover:bg-gray-100;\n}\n\n.btn.is-danger {\n  @apply text-white bg-red-600 hover:bg-red-700 focus:ring-red-300;\n}\n\n.btn.is-warning {\n  @apply focus:outline-none text-gray-700 bg-yellow-400 hover:bg-yellow-500 focus:ring-yellow-300 font-medium text-sm;\n}\n\n.btn.is-info {\n  @apply text-white bg-blue-700 hover:bg-blue-800  focus:ring-blue-300 font-medium text-sm focus:outline-none;\n}\n\n.btn.is-success {\n  @apply text-white bg-green-700 hover:bg-green-800  focus:ring-green-300 font-medium text-sm focus:outline-none;\n}\n\n.btn.is-light {\n  @apply text-gray-900 bg-white border border-gray-300 focus:outline-none hover:bg-gray-100  focus:ring-gray-200 font-medium  text-sm;\n\n  &.is-active {\n    @apply bg-gray-300;\n  }\n}\n\n.btn.is-extra-light {\n  @apply text-white bg-gray-500 hover:bg-gray-400 focus:outline-none focus:ring-gray-300 font-medium  text-sm;\n\n  &.is-active {\n    @apply bg-gray-400;\n  }\n}\n\n.btn.is-black {\n  @apply text-white bg-gray-800 hover:bg-gray-900 focus:outline-none focus:ring-gray-300 font-medium  text-sm;\n}\n\n.btn.is-primary {\n  @apply text-white bg-blue-600 hover:bg-blue-700 focus:ring-blue-300;\n}\n\n@supports (-moz-appearance: none) {\n  .backdrop-filter.bg-opacity-40 {\n    --tw-bg-opacity: 1 !important;\n  }\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-checkbox.stories.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport SbaCheckbox from './sba-checkbox.vue';\n\nexport default {\n  component: SbaCheckbox,\n  title: 'Components/Checkbox',\n};\n\nconst Template = (args) => {\n  return {\n    components: { SbaCheckbox },\n    setup() {\n      return { args };\n    },\n    template: '<sba-checkbox v-bind=\"args\" />{{args}}',\n  };\n};\n\nexport const OneButton = {\n  render: Template,\n\n  args: {\n    modelValue: true,\n    label: 'I am a label',\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-checkbox.vue",
    "content": "<template>\n  <div class=\"flex items-center\">\n    <input\n      :id=\"id\"\n      :checked=\"modelValue\"\n      type=\"checkbox\"\n      class=\"focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded\"\n      :name=\"name\"\n      @change=\"$emit('update:modelValue', $event.target.checked)\"\n    />\n    <label :for=\"id\" class=\"ml-2 font-medium text-gray-700\" v-text=\"label\" />\n  </div>\n</template>\n\n<script>\nexport default {\n  props: {\n    name: {\n      type: String,\n      required: false,\n      default: null,\n    },\n    modelValue: {\n      type: Boolean,\n      required: true,\n    },\n    label: {\n      type: String,\n      required: true,\n    },\n  },\n  emits: ['update:modelValue'],\n  computed: {\n    id() {\n      return 'checkbox-' + this._.uid;\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-confirm-button.spec.ts",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport userEvent from '@testing-library/user-event';\nimport { screen } from '@testing-library/vue';\nimport { beforeEach, describe, expect, it } from 'vitest';\n\nimport SbaConfirmButton from './sba-confirm-button.vue';\n\nimport { render } from '@/test-utils';\n\ndescribe('SbaConfirmButton', function () {\n  let emitted;\n\n  beforeEach(() => {\n    const vm = render(SbaConfirmButton, { slots: { default: 'Execute' } });\n    emitted = vm.emitted;\n  });\n\n  it('should not emit when clicked once', async () => {\n    await userEvent.click(\n      await screen.findByRole('button', { name: 'Execute' }),\n    );\n\n    expect(emitted().click).toBeUndefined();\n  });\n\n  it('should emit when click confirmed', async () => {\n    await userEvent.click(\n      await screen.findByRole('button', { name: 'Execute' }),\n    );\n    await userEvent.click(\n      await screen.findByRole('button', { name: 'Confirm' }),\n    );\n\n    expect(emitted().click).toBeDefined();\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-confirm-button.stories.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport SbaConfirmButton from './sba-confirm-button.vue';\n\nexport default {\n  component: SbaConfirmButton,\n  title: 'Components/Buttons/Confirm Button',\n};\n\nconst Template = (args) => {\n  return {\n    components: { SbaConfirmButton },\n    setup() {\n      return { args };\n    },\n    template: `<sba-confirm-button v-bind=\"args\">${args.label}</sba-confirm-button>`,\n  };\n};\n\nexport const DefaultButton = {\n  render: Template,\n\n  args: {\n    label: 'Default confirm button',\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-confirm-button.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-button\n    v-on-clickaway=\"abort\"\n    :class=\"{ 'is-success': confirm }\"\n    :title=\"title\"\n    @click=\"click\"\n  >\n    <slot v-if=\"confirm\" name=\"confirm\">\n      {{ $t('term.confirm') }}\n    </slot>\n    <slot v-else />\n  </sba-button>\n</template>\n\n<script>\nimport { directive as onClickaway } from 'vue3-click-away';\n\nexport default {\n  directives: { onClickaway },\n  props: {\n    title: {\n      type: String,\n      default: '',\n    },\n  },\n  emits: ['click'],\n  data() {\n    return {\n      confirm: false,\n    };\n  },\n  methods: {\n    abort() {\n      this.confirm = false;\n    },\n    click(event) {\n      if (this.confirm) {\n        this.$emit('click', event);\n      } else {\n        event.stopPropagation();\n      }\n      this.confirm = !this.confirm;\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-dropdown/sba-dropdown-divider.vue",
    "content": "<template>\n  <hr class=\"sba-dropdown-divider\" />\n</template>\n\n<style scoped>\n.sba-dropdown-divider {\n  height: 0;\n  margin: 0.25rem 0;\n  overflow: hidden;\n  border-top: 1px solid #e9ecef;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-dropdown/sba-dropdown-item.vue",
    "content": "<template>\n  <MenuItem\n    v-slot=\"{ close }\"\n    :active=\"active\"\n    :as=\"as\"\n    :disabled=\"disabled\"\n    class=\"block w-full\"\n    type=\"submit\"\n    v-bind=\"$attrs\"\n  >\n    <a\n      v-if=\"href\"\n      :aria-disabled=\"disabled\"\n      :href=\"href\"\n      class=\"sba-dropdown-item\"\n      rel=\"noopener noreferrer\"\n      target=\"_blank\"\n      @click=\"(event) => onClick(event, close)\"\n    >\n      <slot :active=\"active\" />\n    </a>\n\n    <router-link\n      v-else-if=\"to\"\n      :aria-disabled=\"disabled\"\n      :to=\"to\"\n      class=\"sba-dropdown-item\"\n      @click=\"(event) => onClick(event, close)\"\n    >\n      <slot :active=\"active\" />\n    </router-link>\n\n    <div\n      v-else\n      class=\"sba-dropdown-item\"\n      @click=\"(event) => onClick(event, close)\"\n    >\n      <slot />\n    </div>\n  </MenuItem>\n</template>\n\n<script setup>\nimport { MenuItem } from '@headlessui/vue';\n\nconst props = defineProps({\n  disabled: {\n    type: Boolean,\n    default: false,\n  },\n  active: {\n    type: Boolean,\n    default: false,\n  },\n  as: {\n    type: String,\n    default: 'div',\n  },\n  href: {\n    type: String,\n    default: null,\n  },\n  to: {\n    type: Object,\n    default: null,\n  },\n});\n\nconst emit = defineEmits(['click']);\n\nconst onClick = ($event, close) => {\n  if (!props.href && !props.to) {\n    emit('click', $event);\n  }\n\n  close();\n};\n</script>\n\n<style scoped>\n.sba-dropdown-item {\n  @apply flex w-full items-center rounded-md px-2 py-2 hover:bg-sba-700 hover:text-white;\n}\n\n.sba-dropdown-item[aria-disabled='true'] {\n  @apply text-gray-400 hover:text-gray-400 hover:bg-transparent cursor-not-allowed;\n}\n\n.sba-dropdown-item[active='true'] {\n  @apply bg-sba-700 text-white;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-dropdown/sba-dropdown.stories.ts",
    "content": "import SbaDropdownDivider from '@/components/sba-dropdown/sba-dropdown-divider.vue';\nimport SbaDropdownItem from '@/components/sba-dropdown/sba-dropdown-item.vue';\nimport SbaDropdown from '@/components/sba-dropdown/sba-dropdown.vue';\n\nexport default {\n  component: SbaDropdown,\n  title: 'Components/Dropdown',\n};\n\nconst Template = (args) => {\n  return {\n    components: {\n      SbaDropdown,\n      SbaDropdownItem,\n      SbaDropdownDivider,\n    },\n    setup() {\n      const click = () => alert('Clicked button!');\n      return { ...args, click };\n    },\n    template: `\n      <div class='flex p-2 justify-center'>\n        <sba-dropdown text='Simple Dropdown'>\n          <sba-dropdown-item @click='click'>On Click</sba-dropdown-item>\n          <sba-dropdown-item href='#'>A link!</sba-dropdown-item>\n          <sba-dropdown-item to='name'>router-link</sba-dropdown-item>\n          <sba-dropdown-divider></sba-dropdown-divider>\n          <sba-dropdown-item active>Active action</sba-dropdown-item>\n          <sba-dropdown-item disabled>Disabled action</sba-dropdown-item>\n          <sba-dropdown-divider></sba-dropdown-divider>\n          <sba-dropdown-item as='button'>Button</sba-dropdown-item>\n        </sba-dropdown>\n      </div>\n\n    `,\n  };\n};\n\nexport const Default = Template.bind({});\nDefault.args = {};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-dropdown/sba-dropdown.vue",
    "content": "<template>\n  <Menu as=\"div\" class=\"submenu\">\n    <div class=\"submenu-opener\">\n      <MenuButton class=\"submenu-opener-button\">\n        <span class=\"submenu-opener-label\">\n          <template v-if=\"text\">\n            <span v-text=\"text\" />\n          </template>\n          <component :is=\"is\" v-else />\n        </span>\n        <font-awesome-icon\n          :icon=\"['fas', 'chevron-down']\"\n          class=\"submenu-opener-icon\"\n        />\n      </MenuButton>\n    </div>\n\n    <transition\n      enter-active-class=\"transition duration-100 ease-out\"\n      enter-from-class=\"transform scale-95 opacity-0\"\n      enter-to-class=\"transform scale-100 opacity-100\"\n      leave-active-class=\"transition duration-75 ease-in\"\n      leave-from-class=\"transform scale-100 opacity-100\"\n      leave-to-class=\"transform scale-95 opacity-0\"\n    >\n      <MenuItems class=\"submenu-items\">\n        <div class=\"px-1 py-1\">\n          <slot />\n        </div>\n      </MenuItems>\n    </transition>\n  </Menu>\n</template>\n\n<script setup>\nimport { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';\nimport { Menu, MenuButton, MenuItems } from '@headlessui/vue';\n\ndefineProps({\n  text: {\n    type: String,\n    default: null,\n  },\n  is: {\n    type: Object,\n    default: null,\n  },\n});\n</script>\n\n<style scoped>\n.submenu {\n  @apply inline-block text-left text-white cursor-pointer lg:relative;\n}\n\n.submenu-opener {\n  @apply flex items-center px-3 py-2 rounded bg-sba-700;\n}\n\n.submenu-opener--active {\n  @apply bg-sba-700;\n}\n\n.submenu-opener-label {\n  @apply pr-3 items-center truncate;\n}\n\n.submenu-opener-icon {\n  @apply ml-auto;\n}\n\n.submenu-opener-button {\n  @apply flex flex-row items-center w-full;\n}\n\n.submenu-items {\n  @apply absolute right-0 text-black w-56 origin-top-right divide-y divide-gray-100 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none;\n}\n\n.submenu-opener--link {\n  @apply py-0 pr-0;\n}\n\n.submenu-opener--link .submenu-opener-button {\n  @apply px-3 py-3 hover:bg-sba-800 rounded-r border-l border-black ml-auto w-auto;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-formatted-obj.spec.ts",
    "content": "import { describe } from 'vitest';\n\nimport SbaFormattedObj from '@/components/sba-formatted-obj.vue';\n\nimport { render } from '@/test-utils';\n\ndescribe('SbaFormattedObject', () => {\n  describe('processes simple text', () => {\n    it('autolinks urls', async () => {\n      render(SbaFormattedObj, {\n        props: {\n          value:\n            'This is a text with a link https://www.codecentric.de that is automagically created.',\n        },\n      });\n\n      expect(document.body.innerHTML).toMatchSnapshot();\n    });\n\n    it('does render (safe) html', async () => {\n      render(SbaFormattedObj, {\n        props: {\n          value:\n            \"This is a text with <a href='https://www.codecentric.de'>a link</a> and a bold text <b>bold</b>.\",\n        },\n      });\n\n      expect(document.body.innerHTML).toMatchSnapshot();\n    });\n\n    it('does not render unsafe <script> in html', async () => {\n      render(SbaFormattedObj, {\n        props: {\n          value: \"<script>(function() { console.log('pwned'); )})();</script>\",\n        },\n      });\n\n      expect(document.body.innerHTML).toMatchSnapshot();\n    });\n\n    it('does not render unsafe <img> in html', async () => {\n      render(SbaFormattedObj, {\n        props: {\n          value:\n            \"<img onload='javascript:alert(1)' src='https://www.codecentric.de' />\",\n        },\n      });\n\n      expect(document.body.innerHTML).toMatchSnapshot();\n    });\n  });\n\n  describe('processes json', () => {\n    it('autolinks urls', async () => {\n      render(SbaFormattedObj, {\n        props: {\n          value: {\n            a: {\n              nested: {\n                object: {\n                  key: 'This is a text with a link https://www.codecentric.de that is automagically created.',\n                },\n              },\n            },\n          },\n        },\n      });\n\n      expect(document.body.innerHTML).toMatchSnapshot();\n    });\n\n    it('does not render unsafe <script> in html', async () => {\n      render(SbaFormattedObj, {\n        props: {\n          value: {\n            a: {\n              nested: {\n                object: {\n                  key: \"<script>(function() { console.log('pwned'); )})();</script>\",\n                },\n              },\n            },\n          },\n        },\n      });\n\n      expect(document.body.innerHTML).toMatchSnapshot();\n    });\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-formatted-obj.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <pre class=\"formatted\" v-html=\"formatted\" />\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue';\n\nimport { createAutolink } from '@/utils/autolink';\nimport { objToYaml } from '@/utils/objToYaml';\nimport { sanitizeHtml } from '@/utils/sanitizeHtml';\n\nconst props = defineProps<{\n  value: unknown;\n}>();\n\nconst autolinkFn = createAutolink({\n  truncate: {\n    length: 50,\n    location: 'smart',\n  },\n});\n\nconst formatted = computed(() => {\n  const yaml = objToYaml(props.value);\n  const sanitized = sanitizeHtml(yaml);\n  return autolinkFn(sanitized);\n});\n</script>\n\n<style scoped>\n.formatted {\n  @apply whitespace-break-spaces break-words;\n\n  &:deep(a[href]) {\n    @apply underline;\n  }\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-icon-button.stories.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport SbaIconButton from './sba-icon-button.vue';\n\nexport default {\n  component: SbaIconButton,\n  title: 'Components/Buttons/Icon Button',\n};\n\nconst Template = (args) => {\n  return {\n    components: { SbaIconButton },\n    setup() {\n      return { args };\n    },\n    template: `\n      <sba-icon-button v-bind=\"args\">${args.label}</sba-icon-button>`,\n  };\n};\n\nexport const DefaultButton = {\n  render: Template,\n\n  args: {\n    icon: 'trash',\n    title: 'unregister',\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-icon-button.vue",
    "content": "<!--\n  - Copyright 2014-2019 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-button\n    class=\"border-none sm:m-1 !bg-transparent\"\n    :title=\"title\"\n    size=\"xs\"\n    v-on=\"$attrs\"\n  >\n    <font-awesome-icon :icon=\"icon\" :size=\"size\" :class=\"iconClass\" />\n  </sba-button>\n</template>\n\n<script>\nimport SbaButton from '@/components/sba-button';\n\nexport default {\n  components: { SbaButton },\n  props: {\n    title: {\n      type: String,\n      required: false,\n      default: null,\n    },\n    icon: {\n      type: [String, Array],\n      required: true,\n    },\n    size: {\n      type: String,\n      default: null,\n    },\n    iconClass: {\n      type: String,\n      default: null,\n    },\n  },\n};\n</script>\n\n<style></style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-input.stories.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport SbaInput from './sba-input.vue';\n\nexport default {\n  component: SbaInput,\n  title: 'Components/Form/Input',\n};\n\nconst Template = (args) => {\n  return {\n    components: { SbaInput },\n    setup() {\n      return { args };\n    },\n    template: `\n      <sba-input v-bind=\"args\">${args.slots}</sba-input>\n    `,\n  };\n};\n\nexport const SimpleInput = {\n  render: Template,\n\n  args: {\n    modelValue: 'Hello there!',\n  },\n};\n\nexport const InputWithError = {\n  render: Template,\n\n  args: {\n    ...SimpleInput.args,\n    error: 'Please provide a value.',\n  },\n};\n\nexport const InputWithHint = {\n  render: Template,\n\n  args: {\n    ...SimpleInput.args,\n    hint: 'This is a hint',\n  },\n};\n\nexport const InputWithPrepend = {\n  render: Template,\n\n  args: {\n    ...SimpleInput.args,\n    slots: `\n      <template #prepend>Prepend</template>\n    `,\n  },\n};\n\nexport const InputWithAppend = {\n  render: Template,\n\n  args: {\n    ...SimpleInput.args,\n    inputClass: 'text-right',\n    slots: `\n      <template #append>EUR</template>\n    `,\n  },\n};\n\nexport const Complex = {\n  render: Template,\n\n  args: {\n    ...SimpleInput.args,\n    label: 'Label',\n    error: 'Please provide a value.',\n    hint: 'I am a hint!',\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-input.vue",
    "content": "<!--\n  - Copyright 2014-2020 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div>\n    <div>\n      <label\n        v-if=\"hasLabel\"\n        :for=\"id\"\n        class=\"block text-sm font-medium text-gray-700\"\n        v-text=\"label\"\n      />\n      <div :class=\"{ 'mt-1': hasLabel }\" class=\"flex rounded shadow-sm\">\n        <!-- PREPEND -->\n        <label\n          v-if=\"$slots.prepend\"\n          :for=\"id\"\n          class=\"inline-flex items-center px-3 rounded-l border border-r-0 border-gray-300 bg-gray-50 text-gray-500 text-sm\"\n        >\n          <slot name=\"prepend\" />\n        </label>\n\n        <!-- INPUT -->\n        <datalist :id=\"datalistId\">\n          <option\n            v-for=\"optionName in list\"\n            :key=\"optionName\"\n            v-text=\"optionName\"\n          />\n        </datalist>\n        <input\n          :id=\"id\"\n          :autocomplete=\"autocomplete\"\n          :class=\"classNames(inputFieldClassNames, inputClass)\"\n          :disabled=\"disabled\"\n          :list=\"datalistId\"\n          :min=\"min\"\n          :name=\"name\"\n          :placeholder=\"placeholder\"\n          :readonly=\"readonly\"\n          :type=\"type\"\n          :value=\"modelValue\"\n          :aria-label=\"label || placeholder\"\n          :autofocus=\"autofocus\"\n          class=\"focus:z-10 px-2 py-1.5 relative flex-1 block w-full rounded-none bg-opacity-40 backdrop-blur-sm\"\n          @input=\"handleInput\"\n        />\n        <!-- APPEND -->\n        <span\n          v-if=\"$slots.append\"\n          class=\"inline-flex items-center px-3 rounded-r border border-l-0 border-gray-300 bg-gray-50 text-gray-500 text-sm\"\n        >\n          <slot name=\"append\" />\n        </span>\n      </div>\n      <span v-if=\"$slots.info\" class=\"mt-2 text-sm text-gray-500\">\n        <slot name=\"info\" />\n      </span>\n    </div>\n    <div v-if=\"error || hint\" class=\"py-2\">\n      <div v-if=\"hint && !error\" class=\"text-xs text-gray-500\" v-text=\"hint\" />\n      <div v-if=\"error\" class=\"text-xs text-red-500\" v-text=\"error\" />\n    </div>\n  </div>\n</template>\n\n<script>\nimport classNames from 'classnames';\n\nexport default {\n  props: {\n    name: {\n      type: String,\n      required: true,\n    },\n    label: {\n      type: String,\n      default: null,\n    },\n    placeholder: {\n      type: String,\n      default: null,\n    },\n    type: {\n      type: String,\n      default: 'text',\n    },\n    modelValue: {\n      type: [String, Number],\n      default: null,\n    },\n    min: {\n      type: Number,\n      default: undefined,\n    },\n    disabled: {\n      type: Boolean,\n      default: false,\n    },\n    readonly: {\n      type: Boolean,\n      default: false,\n    },\n    list: {\n      type: Array,\n      default: undefined,\n    },\n    inputClass: {\n      type: [String, Array, Object],\n      default: undefined,\n    },\n    error: {\n      type: String,\n      default: undefined,\n    },\n    hint: {\n      type: String,\n      default: undefined,\n    },\n    autocomplete: {\n      type: String,\n      default: undefined,\n    },\n    autofocus: {\n      type: Boolean,\n      default: undefined,\n    },\n  },\n  emits: ['update:modelValue', 'input'],\n  computed: {\n    hasLabel() {\n      return this.label !== null && this.label.trim() !== '';\n    },\n    id() {\n      let number = 'input-' + Math.floor(Math.random() * Date.now());\n      return (this.name || number).replace(/[^\\w]/gi, '');\n    },\n    datalistId() {\n      return 'listId-' + this._.uid;\n    },\n    inputFieldClassNames() {\n      const hasAppend = this.hasSlot('append');\n      const hasPrepend = this.hasSlot('prepend');\n\n      const classNames = [];\n\n      if (this.error !== null && this.error !== undefined) {\n        classNames.push(\n          'focus:ring-red-500 focus:border-red-500 border-red-400',\n        );\n      } else {\n        classNames.push(\n          'focus:ring-indigo-500 focus:border-indigo-500 border-gray-300',\n        );\n      }\n\n      if (!hasAppend) {\n        classNames.push('rounded-r');\n      }\n      if (!hasPrepend) {\n        classNames.push('rounded-l');\n      }\n\n      if (this.disabled === true) {\n        classNames.push('cursor-not-allowed');\n      }\n\n      return classNames;\n    },\n  },\n  methods: {\n    classNames,\n    handleInput($event) {\n      this.$emit('update:modelValue', $event.target.value);\n      this.$emit('input', $event.target.value);\n    },\n    hasSlot(slot) {\n      return !!this.$slots[slot] && Object.keys(this.$slots[slot]).length > 0;\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-key-value-table.stories.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport SbaKeyValueTable from './sba-key-value-table.vue';\n\nexport default {\n  component: SbaKeyValueTable,\n  title: 'Components/Key-Value Table',\n};\n\nconst Template = (args) => {\n  return {\n    components: { SbaKeyValueTable },\n    setup() {\n      return { args };\n    },\n    template: `<sba-key-value-table v-bind=\"args\">\n      <template #customContent=\"slotProps\">\n        {{slotProps}}\n      </template>\n      <template #customId=\"slotProps\">\n        {{slotProps}}\n      </template>\n    </sba-key-value-table>`,\n  };\n};\n\nexport const Default = {\n  render: Template,\n\n  args: {\n    map: {\n      key1: 'value 1',\n      key2: 'value 2',\n      key3: 'value 3',\n    },\n  },\n};\n\nexport const UsingSlots = {\n  render: Template,\n\n  args: {\n    map: {\n      ...Default.args.map,\n      slotProps: 'Special Value',\n      'a key with sapces': {\n        id: 'customId',\n        value: 'Custom value',\n      },\n    },\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-key-value-table.vue",
    "content": "<template>\n  <dl\n    v-for=\"(value, key, index) in filteredMap\"\n    :key=\"key\"\n    class=\"px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6\"\n    :class=\"{ 'bg-white': index % 2 === 0, 'bg-gray-50': index % 2 !== 0 }\"\n  >\n    <dt\n      class=\"text-sm font-medium text-gray-500 break-all\"\n      v-text=\"getLabel(key, value)\"\n    />\n    <dd class=\"mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2\">\n      <slot :name=\"getSlotName(key, value)\" :value=\"getValue(value)\">\n        <sba-formatted-obj :value=\"getValue(value)\" />\n      </slot>\n    </dd>\n  </dl>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue';\n\nconst { map, skipNullValues = false } = defineProps<{\n  map: Record<string, any>;\n  skipNullValues?: boolean;\n}>();\n\nconst filteredMap = computed(() => {\n  return Object.entries(map)\n    .filter(([, value]) => {\n      if (skipNullValues) {\n        return value && value.value !== null;\n      }\n      return true;\n    })\n    .reduce(\n      (acc, [key, value]) => {\n        acc[key] = value;\n        return acc;\n      },\n      {} as Record<string, any>,\n    );\n});\n\nconst getSlotName = (key: string, value: any) => {\n  return value?.id || key.replace(/[^a-zA-Z]/gi, '').toLowerCase();\n};\n\nconst getLabel = (key: string, value: any) => {\n  return value?.label || key;\n};\n\nconst getValue = (value: any) => {\n  return value?.value || value;\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-loading-spinner.vue",
    "content": "<template>\n  <div class=\"flex text-center h-full items-center\">\n    <svg\n      :class=\"classNames(sizeClassNames)\"\n      class=\"inline text-gray-900 animate-spin\"\n      fill=\"none\"\n      role=\"status\"\n      viewBox=\"0 0 100 101\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <path\n        d=\"M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z\"\n        fill=\"#E5E7EB\"\n      />\n      <path\n        d=\"M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  </div>\n</template>\n<script>\nimport classNames from 'classnames';\n\nexport default {\n  props: {\n    size: {\n      type: String,\n      default: 'md',\n      validate(value) {\n        return ['sm', 'md'].includes(value);\n      },\n    },\n  },\n  computed: {\n    sizeClassNames() {\n      switch (this.size) {\n        case 'sm':\n          return 'w-8 h-8';\n        case 'md':\n        default:\n          return 'w-10 h-10';\n      }\n    },\n  },\n  methods: {\n    classNames,\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-modal.spec.ts",
    "content": "/*\n * Copyright 2014-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport userEvent from '@testing-library/user-event';\nimport { screen, waitFor } from '@testing-library/vue';\nimport { describe, expect, it } from 'vitest';\n\nimport { render } from '../test-utils';\nimport SbaModal from './sba-modal.vue';\n\ndescribe('sba-modal.vue', () => {\n  it('modal is closed when close button is clicked', async () => {\n    const { emitted } = render(SbaModal, {\n      props: { modelValue: true },\n      slots: { body: '<div>test</div>' },\n    });\n\n    await waitFor(() => {\n      expect(screen.queryByLabelText('Close')).toBeInTheDocument();\n    });\n    await userEvent.click(screen.getByLabelText('Close'));\n\n    expect(emitted()['update:modelValue'][0]).toContain(false);\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-modal.stories.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport SbaModal from './sba-modal.vue';\n\nimport i18n from '@/i18n';\n\nexport default {\n  component: SbaModal,\n  title: 'Components/Modal',\n};\n\nconst Template = (args) => ({\n  components: { SbaModal },\n  setup() {\n    return {\n      args,\n    };\n  },\n  template: `\n    <sba-modal v-bind=\"args\">\n      <template v-if=\"${'header' in args}\" #header>${args.header}</template>\n      <template v-if=\"${'body' in args}\" #body>${args.body}</template>\n      <template v-if=\"${'footer' in args}\" #footer>${args.footer}</template>\n    </sba-modal>\n  `,\n  i18n,\n});\n\nexport const ModalWithBody = {\n  render: Template,\n\n  args: {\n    modelValue: true,\n    body: 'I am a body',\n  },\n};\n\nexport const ModalWithHeaderAndFooter = {\n  render: Template,\n\n  args: {\n    modelValue: true,\n    header: 'You are awesome!',\n    body: '<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla accumsan, metus ultrices eleifend gravida, nulla nunc varius lectus, nec rutrum justo nibh eu lectus.</p>',\n    footer: '<sba-button class=\"button\">Close me!</sba-button>',\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-modal.vue",
    "content": "<template>\n  <TransitionRoot :show=\"modelValue\" appear as=\"template\">\n    <Dialog as=\"div\" class=\"relative z-10 modal\" @close=\"close\">\n      <TransitionChild\n        as=\"template\"\n        enter=\"duration-300 ease-out\"\n        enter-from=\"opacity-0\"\n        enter-to=\"opacity-100\"\n        leave=\"duration-200 ease-in\"\n        leave-from=\"opacity-100\"\n        leave-to=\"opacity-0\"\n      >\n        <div class=\"fixed inset-0 bg-black bg-opacity-25\" />\n      </TransitionChild>\n\n      <div class=\"fixed inset-0 overflow-y-auto\">\n        <div\n          class=\"flex min-h-full items-center justify-center p-4 text-center\"\n        >\n          <TransitionChild\n            as=\"template\"\n            enter=\"duration-300 ease-out\"\n            enter-from=\"opacity-0 scale-95\"\n            enter-to=\"opacity-100 scale-100\"\n            leave=\"duration-200 ease-in\"\n            leave-from=\"opacity-100 scale-100\"\n            leave-to=\"opacity-0 scale-95\"\n          >\n            <DialogPanel\n              class=\"w-full max-w-xl transform overflow-hidden rounded bg-white p-6 text-left align-middle shadow-xl transition-all\"\n            >\n              <DialogTitle\n                as=\"h3\"\n                class=\"text-lg font-medium leading-6 text-gray-900 flex justify-between\"\n              >\n                <div>\n                  <slot name=\"header\" />\n                </div>\n                <button :aria-label=\"$t('term.close')\" @click=\"close\">\n                  <span aria-hidden=\"true\">&times;</span>\n                </button>\n              </DialogTitle>\n              <div class=\"mt-2 mb-2 max-h-96 overflow-auto\">\n                <slot name=\"body\" />\n              </div>\n\n              <div v-if=\"'footer' in slots\" class=\"mt-4 gap-1 flex justify-end\">\n                <slot name=\"footer\" />\n                <sba-button\n                  v-if=\"!('footer' in slots)\"\n                  aria-label=\"close\"\n                  @click=\"close\"\n                >\n                  {{ $t('term.close') }}\n                </sba-button>\n              </div>\n            </DialogPanel>\n          </TransitionChild>\n        </div>\n      </div>\n    </Dialog>\n  </TransitionRoot>\n</template>\n<script>\nimport {\n  Dialog,\n  DialogPanel,\n  DialogTitle,\n  TransitionChild,\n  TransitionRoot,\n} from '@headlessui/vue';\nimport { useSlots } from 'vue';\n\nexport default {\n  name: 'SbaModal',\n  components: {\n    Dialog,\n    DialogPanel,\n    DialogTitle,\n    TransitionRoot,\n    TransitionChild,\n  },\n  props: {\n    modelValue: { type: Boolean, default: false },\n  },\n  emits: ['update:modelValue'],\n  setup() {\n    const slots = useSlots();\n\n    return {\n      slots,\n    };\n  },\n  methods: {\n    close() {\n      this.$emit('update:modelValue', false);\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-nav/sba-nav-dropdown.stories.ts",
    "content": "import SbaDropdownDivider from '@/components/sba-dropdown/sba-dropdown-divider.vue';\nimport SbaDropdownItem from '@/components/sba-dropdown/sba-dropdown-item.vue';\nimport SbaNavDropdown from '@/components/sba-nav/sba-nav-dropdown.vue';\nimport SbaNavItem from '@/components/sba-nav/sba-nav-item.vue';\n\nexport default {\n  component: SbaNavDropdown,\n  title: 'Components/Navbar/Nav/Dropdown',\n};\n\nconst Template = (args) => {\n  return {\n    components: {\n      SbaDropdownItem,\n      SbaNavDropdown,\n      SbaNavItem,\n      SbaDropdownDivider,\n    },\n    setup() {\n      return { ...args };\n    },\n    template: `\n      <div class='gap-2 flex flex-col p-2 bg-black'>\n      <sba-nav-dropdown text='Simple Dropdown'>\n        <sba-dropdown-item>A Menu Item</sba-dropdown-item>\n        <sba-dropdown-item>A Menu Item</sba-dropdown-item>\n        <sba-dropdown-item>A Menu Item</sba-dropdown-item>\n        <sba-dropdown-item>A Menu Item</sba-dropdown-item>\n        <sba-dropdown-item>A Menu Item</sba-dropdown-item>\n      </sba-nav-dropdown>\n      <sba-nav-dropdown text='Dropdown Is Link' href='#'>\n        <sba-dropdown-item>A Menu Item</sba-dropdown-item>\n        <sba-dropdown-item>A Menu Item</sba-dropdown-item>\n        <sba-dropdown-item>A Menu Item</sba-dropdown-item>\n        <sba-dropdown-item>A Menu Item</sba-dropdown-item>\n        <sba-dropdown-item>A Menu Item</sba-dropdown-item>\n      </sba-nav-dropdown>\n      <sba-nav-dropdown>\n        <template #label>\n          <font-awesome-icon\n            color='white'\n            class='w-10 rounded-full white mr-2'\n            icon='user-circle'\n          />\n          <strong v-text='username' />\n        </template>\n        <sba-dropdown-item>Signed in as: <strong v-text='username' /></sba-dropdown-item>\n        <sba-dropdown-divider />\n        <sba-dropdown-item>\n          <font-awesome-icon icon='sign-out-alt' />\n          &nbsp;\n          Logout\n        </sba-dropdown-item>\n      </sba-nav-dropdown>\n      </div>\n    `,\n  };\n};\n\nexport const Default = Template.bind({});\nDefault.args = {};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-nav/sba-nav-dropdown.vue",
    "content": "<template>\n  <Menu as=\"div\" class=\"submenu\" v-bind=\"$attrs\">\n    <div\n      :class=\"{\n        'submenu-opener--link': href,\n        'submenu-opener--active': active,\n      }\"\n      class=\"submenu-opener\"\n    >\n      <a v-if=\"href\" :href=\"href\" class=\"submenu-opener-label\" target=\"_blank\">\n        <span v-if=\"text\" v-text=\"text\" />\n        <slot v-else name=\"label\" />\n      </a>\n\n      <MenuButton class=\"submenu-opener-button\">\n        <span v-if=\"!href\" class=\"submenu-opener-label\">\n          <span v-if=\"text\" v-text=\"text\" />\n          <slot v-else-if=\"!text\" name=\"label\" />\n        </span>\n        <font-awesome-icon\n          :icon=\"['fas', 'chevron-down']\"\n          class=\"submenu-opener-icon\"\n        />\n      </MenuButton>\n    </div>\n\n    <transition\n      enter-active-class=\"transition duration-100 ease-out\"\n      enter-from-class=\"transform scale-95 opacity-0\"\n      enter-to-class=\"transform scale-100 opacity-100\"\n      leave-active-class=\"transition duration-75 ease-in\"\n      leave-from-class=\"transform scale-100 opacity-100\"\n      leave-to-class=\"transform scale-95 opacity-0\"\n    >\n      <MenuItems class=\"submenu-items\">\n        <div class=\"px-1 py-1\">\n          <slot />\n        </div>\n      </MenuItems>\n    </transition>\n  </Menu>\n</template>\n\n<script setup>\nimport { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';\nimport { Menu, MenuButton, MenuItems } from '@headlessui/vue';\n\ndefineProps({\n  text: {\n    type: String,\n    default: null,\n  },\n  href: {\n    type: String,\n    default: null,\n  },\n  active: {\n    type: Boolean,\n    default: false,\n  },\n});\n</script>\n\n<style scoped>\n.submenu {\n  @apply inline-block text-left text-white cursor-pointer lg:relative;\n}\n\n.submenu-opener {\n  @apply flex items-center;\n}\n\n.submenu-opener > button {\n  @apply flex items-center px-3 py-2 rounded hover:bg-sba-700;\n}\n\n.submenu-opener--active {\n  @apply bg-sba-700;\n}\n\n.submenu-opener-label {\n  @apply pr-3 items-center truncate;\n}\n\n.submenu-opener-icon {\n  @apply ml-auto;\n}\n\n.submenu-opener-button {\n  @apply flex flex-row items-center w-full;\n}\n\n.submenu-items {\n  @apply absolute right-0 text-black w-56 origin-top-right divide-y divide-gray-100 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none;\n}\n\n.submenu-opener--link {\n  @apply hover:bg-sba-700 rounded;\n}\n.submenu-opener--link > a {\n  @apply px-3 py-2;\n}\n\n.submenu-opener--link .submenu-opener-button {\n  @apply px-3 py-3 hover:bg-sba-800 rounded-r rounded-l-none border-l border-black ml-auto w-auto;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-nav/sba-nav-item.stories.ts",
    "content": "import SbaNavItem from '@/components/sba-nav/sba-nav-item.vue';\n\nexport default {\n  component: SbaNavItem,\n  title: 'Components/Navbar/Nav/Item',\n};\n\nconst Template = (args) => {\n  return {\n    components: {\n      SbaNavItem,\n    },\n    setup() {\n      return { ...args };\n    },\n    template: `\n      <sba-nav-item>Just an item</sba-nav-item>\n      <sba-nav-item href='https://www.codecentric.de'>Link</sba-nav-item>\n      <sba-nav-item to='routeName'>Router Link</sba-nav-item>\n    `,\n  };\n};\n\nexport const Default = Template.bind({});\nDefault.args = {};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-nav/sba-nav-item.vue",
    "content": "<template>\n  <a\n    v-if=\"href\"\n    :href=\"href\"\n    :target=\"target\"\n    class=\"sba-nav-item\"\n    rel=\"noopener noreferrer\"\n  >\n    <slot />\n  </a>\n  <router-link v-else-if=\"to\" :to=\"to\" class=\"sba-nav-item\">\n    <slot />\n  </router-link>\n\n  <div v-else class=\"sba-nav-item\">\n    <slot />\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { PropType } from 'vue';\nimport { RouteLocationRaw } from 'vue-router';\n\ndefineProps({\n  href: {\n    type: String,\n    default: null,\n  },\n  target: {\n    type: String,\n    default: '_blank',\n  },\n  to: {\n    type: Object as PropType<RouteLocationRaw>,\n    default: null,\n  },\n});\n</script>\n\n<style scoped>\n.sba-nav-item {\n  @apply px-3 py-2 rounded lg:self-center hover:bg-sba-700;\n}\n</style>\n<style>\n.sba-nav-item.is-active {\n  @apply bg-sba-700;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-navbar/sba-navbar-brand.vue",
    "content": "<template>\n  <router-link class=\"brand\" to=\"/\" v-html=\"brand\" />\n</template>\n\n<script lang=\"ts\" setup>\ndefineProps({\n  brand: {\n    type: String,\n    required: true,\n  },\n});\n</script>\n\n<style>\n.brand {\n  @apply flex items-center text-white mr-4;\n}\n\n.brand img {\n  @apply h-8 w-8 mr-2;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-navbar/sba-navbar-nav.vue",
    "content": "<template>\n  <div class=\"gap-2 flex lg:items-center flex-col lg:flex-row mb-2 lg:mb-0\">\n    <slot />\n  </div>\n</template>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-navbar/sba-navbar-toggle.stories.ts",
    "content": "import { ref } from 'vue';\n\nimport SbaNavbarToggle from '@/components/sba-navbar/sba-navbar-toggle.vue';\n\nexport default {\n  component: SbaNavbarToggle,\n  title: 'Components/Navbar/Toggle',\n};\n\nconst Template = (args) => {\n  return {\n    components: {\n      SbaNavbarToggle,\n    },\n    setup() {\n      const showMenu = ref(false);\n      // eslint-disable-next-line no-console\n      const handleClick = (e) => console.log(e);\n      return { ...args, showMenu, onClick: handleClick };\n    },\n    template: `\n      <sba-navbar-toggle :show-menu='showMenu' @click='handleClick' />\n    `,\n  };\n};\n\nexport const Default = Template.bind({});\nDefault.args = {};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-navbar/sba-navbar-toggle.vue",
    "content": "<template>\n  <button\n    :aria-expanded=\"isMenuShown\"\n    aria-controls=\"mobile-menu\"\n    class=\"sba-navbar-toggle\"\n    type=\"button\"\n    @click=\"handleClick\"\n  >\n    <span class=\"sr-only\">Open main menu</span>\n    <svg\n      :class=\"{ block: !isMenuShown, hidden: isMenuShown }\"\n      aria-hidden=\"true\"\n      class=\"h-6 w-6\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      viewBox=\"0 0 24 24\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <path\n        d=\"M4 6h16M4 12h16M4 18h16\"\n        stroke-linecap=\"round\"\n        stroke-linejoin=\"round\"\n        stroke-width=\"2\"\n      />\n    </svg>\n    <svg\n      :class=\"{ block: isMenuShown, hidden: !isMenuShown }\"\n      aria-hidden=\"true\"\n      class=\"h-6 w-6\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      viewBox=\"0 0 24 24\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <path\n        d=\"M6 18L18 6M6 6l12 12\"\n        stroke-linecap=\"round\"\n        stroke-linejoin=\"round\"\n        stroke-width=\"2\"\n      />\n    </svg>\n  </button>\n</template>\n\n<script lang=\"ts\" setup>\nimport { ref } from 'vue';\n\nconst props = defineProps({\n  showMenu: {\n    type: Boolean,\n    default: () => false,\n  },\n});\n\nconst emit = defineEmits(['click']);\n\nconst isMenuShown = ref(props.showMenu);\n\nconst handleClick = () => {\n  isMenuShown.value = !isMenuShown.value;\n  emit('click', isMenuShown.value);\n};\n</script>\n\n<style>\n.sba-navbar-toggle {\n  @apply ml-auto inline-flex bg-gray-800 items-center justify-center p-2 rounded-md text-gray-400;\n  @apply xl:hidden;\n  @apply hover:text-white hover:bg-gray-700;\n  @apply focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-navbar/sba-navbar.stories.ts",
    "content": "import { onUnmounted, ref } from 'vue';\n\nimport SbaNavbar from './sba-navbar.vue';\n\nimport SbaDropdownDivider from '@/components/sba-dropdown/sba-dropdown-divider.vue';\nimport SbaDropdownItem from '@/components/sba-dropdown/sba-dropdown-item.vue';\nimport SbaNavDropdown from '@/components/sba-nav/sba-nav-dropdown.vue';\nimport SbaNavItem from '@/components/sba-nav/sba-nav-item.vue';\nimport SbaNavbarNav from '@/components/sba-navbar/sba-navbar-nav.vue';\n\nexport default {\n  component: SbaNavbar,\n  title: 'Components/Navbar',\n};\n\nconst Template = (args) => {\n  return {\n    components: {\n      SbaNavbar,\n      SbaDropdownItem,\n      SbaNavDropdown,\n      SbaNavItem,\n      SbaNavbarNav,\n      SbaDropdownDivider,\n    },\n    setup() {\n      const random = ref(0);\n      const intervalId = setInterval(() => {\n        random.value = Math.round(Math.random() * 10);\n      }, 1000);\n\n      onUnmounted(() => clearInterval(intervalId)); // ✅ Cleanup\n\n      return { ...args, random, username: 'Admin' };\n    },\n    template: `\n      <sba-navbar :brand='brand'>\n      <sba-navbar-nav>\n        <sba-nav-item>Wallboard</sba-nav-item>\n        <sba-nav-item>Applications</sba-nav-item>\n        <sba-nav-item>About</sba-nav-item>\n        <sba-nav-item href='#'>UP: {{ random }}</sba-nav-item>\n        <sba-nav-dropdown text='Simple Dropdown'>\n          <sba-dropdown-item>A Menu Item</sba-dropdown-item>\n          <sba-dropdown-item>A Menu Item</sba-dropdown-item>\n          <sba-dropdown-item>A Menu Item</sba-dropdown-item>\n          <sba-dropdown-item>A Menu Item</sba-dropdown-item>\n          <sba-dropdown-item>A Menu Item</sba-dropdown-item>\n        </sba-nav-dropdown>\n        <sba-nav-dropdown text='Dropdown Is Link' href='#'>\n          <sba-dropdown-item>A Menu Item</sba-dropdown-item>\n          <sba-dropdown-item>A Menu Item</sba-dropdown-item>\n          <sba-dropdown-item>A Menu Item</sba-dropdown-item>\n          <sba-dropdown-item>A Menu Item</sba-dropdown-item>\n          <sba-dropdown-item>A Menu Item</sba-dropdown-item>\n        </sba-nav-dropdown>\n      </sba-navbar-nav>\n\n      <sba-navbar-nav class='ml-auto'>\n        <sba-nav-dropdown text='English'>\n          <sba-dropdown-item>한국어</sba-dropdown-item>\n          <sba-dropdown-item>简体中文</sba-dropdown-item>\n          <sba-dropdown-item>繁體中文</sba-dropdown-item>\n          <sba-dropdown-item>português (Brasil)</sba-dropdown-item>\n          <sba-dropdown-item>A Menu Item</sba-dropdown-item>\n        </sba-nav-dropdown>\n\n        <sba-nav-dropdown>\n          <template #label>\n            <font-awesome-icon\n              color='white'\n              class='w-10 rounded-full white mr-2'\n              icon='user-circle'\n            />\n            <strong v-text='username' />\n          </template>\n          <sba-dropdown-item>Signed in as: <strong v-text='username' /></sba-dropdown-item>\n          <sba-dropdown-divider />\n          <sba-dropdown-item>\n            <font-awesome-icon icon='sign-out-alt' />\n            &nbsp;\n            Logout\n          </sba-dropdown-item>\n        </sba-nav-dropdown>\n      </sba-navbar-nav>\n      </sba-navbar>\n      <article class='mt-16 p-4 prose max-w-full '>\n      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur mollis vulputate elit eget hendrerit.\n        Suspendisse pretium odio sit amet nibh rhoncus, non cursus magna mollis. Mauris blandit aliquet consequat. Donec\n        et volutpat mauris, in volutpat tortor. Ut sed nisl sed est sodales interdum eget ac sem. Mauris neque mauris,\n        fringilla quis dictum quis, aliquam ac ipsum. Phasellus congue eu massa pharetra finibus. Aenean massa libero,\n        egestas sit amet sem consequat, vestibulum porta sem. Duis nisi nibh, gravida a molestie a, gravida a turpis.\n        Quisque sagittis nisl eu ultricies mollis. Etiam rutrum faucibus dolor faucibus hendrerit. Curabitur volutpat\n        fermentum dolor, nec consectetur urna blandit non. Proin sed eleifend tellus, aliquam semper mauris.</p>\n      <p>Duis sapien diam, tempus vel quam rhoncus, commodo pulvinar magna. Suspendisse condimentum, lectus quis varius\n        iaculis, nibh lectus mattis neque, aliquam ornare mauris diam quis leo. Nunc efficitur mollis accumsan. Fusce\n        eget lacinia nisl. Sed congue leo sem, non fermentum mauris blandit ut. Nulla aliquam mattis erat at venenatis.\n        Donec nec nisl nec tortor convallis rhoncus. Class aptent taciti sociosqu ad litora torquent per conubia nostra,\n        per inceptos himenaeos. Maecenas id tempor orci. Quisque sit amet nisi quis neque maximus facilisis quis rutrum\n        mi. Aenean vel blandit mauris. In quis lobortis diam. Quisque fringilla mauris sit amet magna aliquet, vulputate\n        hendrerit sapien blandit.</p>\n      </article>\n    `,\n  };\n};\n\nexport const Default = Template.bind({});\nDefault.args = {\n  brand:\n    '<img src=\".storybook/img/icon-spring-boot-admin.svg\">Spring Boot Admin',\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-navbar/sba-navbar.vue",
    "content": "<!--\n  - Copyright 2014-2019 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <nav id=\"navigation\" class=\"sba-navbar\">\n    <sba-navbar-brand :brand=\"brand\" />\n\n    <sba-navbar-toggle @click=\"toggleNavigation\" />\n\n    <div\n      :class=\"{ 'nav-container--hidden': !showNavigation }\"\n      class=\"nav-container\"\n    >\n      <slot />\n    </div>\n  </nav>\n</template>\n\n<script lang=\"ts\" setup>\nimport { ref } from 'vue';\n\nimport SbaNavbarBrand from '@/components/sba-navbar/sba-navbar-brand.vue';\nimport SbaNavbarToggle from '@/components/sba-navbar/sba-navbar-toggle.vue';\n\ndefineProps({\n  brand: {\n    type: String,\n    required: true,\n  },\n});\n\nconst showNavigation = ref(false);\n\nfunction toggleNavigation() {\n  showNavigation.value = !showNavigation.value;\n}\n</script>\n\n<style scoped>\n.sba-navbar {\n  @apply bg-black fixed flex flex-wrap justify-start mx-auto top-0 w-full z-50 items-center px-4 py-2 text-white;\n  @apply sm:px-6;\n  @apply xl:px-8;\n}\n.nav-container {\n  @apply flex-grow block w-full flex-row mt-4;\n  @apply lg:w-auto lg:flex xl:mt-0;\n}\n.nav-container--hidden {\n  @apply hidden;\n  @apply xl:flex;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-pagination-nav.spec.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { screen } from '@testing-library/vue';\nimport { describe, expect, it } from 'vitest';\n\nimport SbaPaginationNav from '@/components/sba-pagination-nav.vue';\n\nimport { render } from '@/test-utils';\n\ndescribe('sba-pagination-nav.vue', () => {\n  it('should show a button when page count is 0', async () => {\n    render(SbaPaginationNav, {\n      props: {\n        pageCount: 0,\n      },\n    });\n\n    const prevPage = screen.getByRole('button', {\n      name: 'Go to previous page',\n    });\n    expect(prevPage).toBeDisabled();\n\n    const nextPage = screen.getByRole('button', { name: 'Go to next page' });\n    expect(nextPage).toBeDisabled();\n  });\n\n  it('should show a button when page count is 1', async () => {\n    render(SbaPaginationNav, {\n      props: {\n        pageCount: 1,\n      },\n    });\n\n    const prevPage = screen.getByRole('button', {\n      name: 'Go to previous page',\n    });\n    expect(prevPage).toBeDisabled();\n\n    const nextPage = screen.getByRole('button', { name: 'Go to next page' });\n    expect(nextPage).toBeDisabled();\n\n    screen.getByRole('button', { name: 'Page 1, current page' });\n  });\n\n  it('should show first and last page when page count is 12 including intermediate pages', async () => {\n    render(SbaPaginationNav, {\n      props: {\n        pageCount: 11,\n        modelValue: 6,\n      },\n    });\n\n    expect(\n      screen.queryByRole('button', { name: 'Go to previous page' }),\n    ).not.toBeDisabled();\n\n    expect(\n      screen.queryByRole('button', { name: 'Go to page 1' }),\n    ).toBeVisible();\n    expect(\n      screen.queryByRole('button', { name: 'Go to page 2' }),\n    ).not.toBeInTheDocument();\n    expect(\n      screen.queryByRole('button', { name: 'Go to page 3' }),\n    ).not.toBeInTheDocument();\n    expect(\n      screen.queryByRole('button', { name: 'Go to page 4' }),\n    ).toBeVisible();\n    expect(\n      screen.queryByRole('button', { name: 'Go to page 5' }),\n    ).toBeVisible();\n\n    const selectedButton = screen.getByRole('button', {\n      name: 'Page 6, current page',\n    });\n    expect(selectedButton).toBeVisible();\n    expect(selectedButton).toBeDisabled();\n    expect(selectedButton).toHaveClass('is-active');\n\n    expect(\n      screen.queryByRole('button', { name: 'Go to page 7' }),\n    ).toBeVisible();\n    expect(\n      screen.queryByRole('button', { name: 'Go to page 8' }),\n    ).toBeVisible();\n    expect(\n      screen.queryByRole('button', { name: 'Go to page 9' }),\n    ).not.toBeInTheDocument();\n    expect(\n      screen.queryByRole('button', { name: 'Go to page 10' }),\n    ).not.toBeInTheDocument();\n    expect(\n      screen.queryByRole('button', { name: 'Go to page 11' }),\n    ).toBeVisible();\n\n    expect(\n      screen.queryByRole('button', { name: 'Go to previous page' }),\n    ).not.toBeDisabled();\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-pagination-nav.stories.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport SbaPaginationNav from './sba-pagination-nav.vue';\n\nimport i18n from '@/i18n';\n\nexport default {\n  component: SbaPaginationNav,\n  title: 'Components/Pagination',\n};\n\nconst Template = (args) => {\n  return {\n    components: { SbaPaginationNav },\n    setup() {\n      return { args };\n    },\n    methods: {\n      change($event) {\n        this.current = $event;\n      },\n    },\n    data() {\n      return {\n        current: 1,\n      };\n    },\n    template: `\n      <sba-pagination-nav v-bind=\"args\" @update=\"change\"/>\n    `,\n    i18n,\n  };\n};\n\nexport const NoPages = {\n  render: Template,\n\n  args: {\n    pageCount: 0,\n    modelValue: 1,\n  },\n};\n\nexport const OnePage = {\n  render: Template,\n\n  args: {\n    pageCount: 1,\n  },\n};\n\nexport const ManyPages = {\n  render: Template,\n\n  args: {\n    modelValue: 1,\n    pageCount: 12,\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-pagination-nav.vue",
    "content": "<!--\n  - Copyright 2014-2019 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div>\n    <sba-button-group\n      class=\"relative z-0 btn-group rounded shadow-sm -space-x-px\"\n      aria-label=\"Pagination\"\n    >\n      <sba-button :disabled=\"modelValue <= 1\" @click=\"goPrev()\">\n        <span class=\"sr-only\" v-text=\"$t('term.go_to_previous_page')\" />\n        <font-awesome-icon\n          class=\"h-5 w-5\"\n          :icon=\"['fas', 'angle-double-left']\"\n        />\n      </sba-button>\n      <sba-button\n        v-for=\"(page, idx) in pageRange\"\n        :key=\"'page_' + idx\"\n        :aria-hidden=\"page === skipPageString\"\n        :aria-current=\"page === modelValue\"\n        :disabled=\"page === skipPageString || page === modelValue\"\n        :class=\"{\n          'is-active': page === modelValue,\n          'cursor-not-allowed': page === skipPageString,\n        }\"\n        @click=\"() => changePage(page)\"\n      >\n        <span class=\"sr-only\">\n          <span\n            v-if=\"page !== modelValue\"\n            v-text=\"$t('term.go_to_page_n', { page })\"\n          />\n          <span v-else v-text=\"$t('term.current_page', { page })\" />\n        </span>\n\n        <span aria-hidden=\"true\" v-text=\"page\" />\n      </sba-button>\n\n      <sba-button :disabled=\"modelValue >= pageCount\" @click=\"goNext\">\n        <span class=\"sr-only\" v-text=\"$t('term.go_to_next_page')\" />\n        <font-awesome-icon\n          class=\"h-5 w-5\"\n          :icon=\"['fas', 'angle-double-right']\"\n        />\n      </sba-button>\n    </sba-button-group>\n  </div>\n</template>\n\n<script>\nimport SbaButton from '@/components/sba-button';\nimport SbaButtonGroup from '@/components/sba-button-group';\n\nexport default {\n  name: 'SbaPaginationNav',\n  components: { SbaButton, SbaButtonGroup },\n  props: {\n    modelValue: { type: Number, default: 1 },\n    pageCount: { type: Number, required: true },\n    // Define amount of pages shown before and after current page.\n    delta: { type: Number, default: 2 },\n  },\n  emits: ['update:modelValue'],\n  data() {\n    return {\n      skipPageString: '...',\n    };\n  },\n  computed: {\n    pageRange() {\n      const pageCount = this.pageCount <= 0 ? 1 : this.pageCount;\n      const current = this.modelValue;\n      const delta = this.delta;\n      const left = current - delta;\n      const right = current + delta + 1;\n\n      let prevPageNum;\n\n      return Array(pageCount)\n        .fill(0)\n        .reduce((pageNumsRemaining, cur, idx) => {\n          const pageNum = idx + 1;\n          if (\n            pageNum === 1 ||\n            pageNum === pageCount ||\n            (pageNum >= left && pageNum < right)\n          ) {\n            pageNumsRemaining.push(pageNum);\n          }\n          return pageNumsRemaining;\n        }, [])\n        .reduce((paginationNavEntries, pageNum) => {\n          if (prevPageNum) {\n            if (pageNum - prevPageNum === 2) {\n              paginationNavEntries.push(prevPageNum + 1);\n            } else if (pageNum - prevPageNum !== 1) {\n              paginationNavEntries.push(this.skipPageString);\n            }\n          }\n          paginationNavEntries.push(pageNum);\n          prevPageNum = pageNum;\n          return paginationNavEntries;\n        }, []);\n    },\n  },\n  methods: {\n    goPrev() {\n      const newPage = this.modelValue - 1;\n      if (newPage > 0) {\n        this.$emit('update:modelValue', newPage);\n      }\n    },\n    goNext() {\n      const newPage = this.modelValue + 1;\n      if (newPage <= this.pageCount) {\n        this.$emit('update:modelValue', newPage);\n      }\n    },\n    changePage(page) {\n      if (page !== this.skipPageString) {\n        this.$emit('update:modelValue', page);\n      }\n    },\n  },\n};\n</script>\n\n<style scoped>\n.is-active {\n  @apply bg-indigo-50 border border-indigo-500 z-10 !important;\n  @apply font-extrabold;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-panel.stories.ts",
    "content": "import SbaPanel from './sba-panel.vue';\n\nexport default {\n  component: SbaPanel,\n  title: 'Components/Panel',\n};\n\nconst Template = (args) => {\n  return {\n    components: { SbaPanel },\n    setup() {\n      return { args };\n    },\n    methods: {\n      onClose(event) {\n        alert('Close clicked! ' + JSON.stringify(event));\n      },\n    },\n    template: `\n      <sba-panel @close=\"onClose\" v-bind=\"args\">\n      <template #actions v-if=\"${'actions' in args}\">\n        ${args.actions}\n      </template>\n      <template #default>\n        ${args.slot}\n      </template>\n      <template #footer v-if=\"${'footer' in args}\">\n        ${args.footer}\n      </template>\n      </sba-panel>\n    `,\n  };\n};\n\nexport const WithTitle = {\n  render: Template,\n\n  args: {\n    title: 'Title',\n    slot: `<article class=\"prose max-w-none\">\n  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vitae dolor ac ante ornare pharetra. Proin\n              laoreet ex et lacinia hendrerit. Fusce sed justo at nulla pellentesque maximus sed at diam. Suspendisse sem\n              lorem,\n              lobortis vel orci quis, efficitur porta massa. In vel neque justo. Maecenas dapibus quam ut nisl porta,\n              molestie\n              egestas felis maximus. Proin vehicula, lacus vehicula lacinia tristique, dui turpis sodales orci, ac pretium\n              nibh\n              nisl sed est. Vivamus pharetra tristique mi. Nam libero lorem, pharetra eu sagittis ac, elementum quis quam.\n              Integer\n              sed feugiat dui. In euismod, ante id lobortis vehicula, libero leo pellentesque orci, ac consectetur leo sem\n              nec\n              erat. Nunc dapibus eu est at pretium. Curabitur eget elementum risus.</p>\n\n            <p>Aenean convallis tempus dolor. Mauris eget ipsum tortor. Mauris congue facilisis eros. Phasellus tortor urna,\n              semper\n              congue nisl maximus, pulvinar luctus justo. Vestibulum dignissim malesuada magna, imperdiet blandit est\n              commodo\n              vitae. Sed a suscipit nisi, non imperdiet orci. Nulla rutrum ligula ut velit ultrices, non tincidunt lacus\n              elementum. Etiam vitae blandit arcu, nec congue felis. Praesent fermentum vehicula risus, vitae finibus felis\n              vestibulum ac. In ullamcorper tellus vitae ante euismod, eget consectetur nibh efficitur. Donec iaculis\n              placerat\n              erat a rutrum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.\n              Donec\n              semper erat nec ipsum molestie, eu commodo dui lobortis.</p>\n\n            <p>Aenean convallis tempus dolor. Mauris eget ipsum tortor. Mauris congue facilisis eros. Phasellus tortor urna,\n              semper\n              congue nisl maximus, pulvinar luctus justo. Vestibulum dignissim malesuada magna, imperdiet blandit est\n              commodo\n              vitae. Sed a suscipit nisi, non imperdiet orci. Nulla rutrum ligula ut velit ultrices, non tincidunt lacus\n              elementum. Etiam vitae blandit arcu, nec congue felis. Praesent fermentum vehicula risus, vitae finibus felis\n              vestibulum ac. In ullamcorper tellus vitae ante euismod, eget consectetur nibh efficitur. Donec iaculis\n              placerat\n              erat a rutrum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.\n              Donec\n              semper erat nec ipsum molestie, eu commodo dui lobortis.</p>\n  </article>`,\n  },\n};\n\nexport const WithTable = {\n  render: Template,\n\n  args: {\n    ...WithTitle.args,\n    slot: `\n      <div class=\"-mx-4 -my-3\">\n        <div class=\"bg-white px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6\">\n            <dt class=\"text-sm font-medium text-gray-500\">Count</dt>\n            <dd class=\"mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2\">97</dd>\n        </div>\n        <div class=\"bg-gray-50 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6\">\n            <dt class=\"text-sm font-medium text-gray-500\">Time total</dt>\n            <dd class=\"mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2\">0.4890s</dd>\n        </div>\n        <div class=\"bg-white px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6\">\n            <dt class=\"text-sm font-medium text-gray-500\">Max duration</dt>\n            <dd class=\"mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2\">0.0040s</dd>\n        </div>\n      </div>`,\n  },\n};\n\nexport const Closable = {\n  render: Template,\n\n  args: {\n    ...WithTitle.args,\n    closeable: true,\n  },\n};\n\nexport const ClosableWithActions = {\n  render: Template,\n\n  args: {\n    ...WithTitle.args,\n    closeable: true,\n    actions: '<i>Action Slot</i>',\n  },\n};\n\nexport const StickyHeader = {\n  render: Template,\n\n  args: {\n    ...WithTitle.args,\n    closeable: true,\n  },\n};\n\nexport const NoTitle = {\n  render: Template,\n\n  args: {\n    ...WithTitle.args,\n    title: undefined,\n  },\n};\n\nexport const WithTitleAndFooter = {\n  render: Template,\n\n  args: {\n    ...WithTitle.args,\n    footer: 'Hello from the footer!',\n  },\n};\n\nexport const LoadingContent = {\n  render: Template,\n\n  args: {\n    ...WithTitle.args,\n    loading: true,\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-panel.vue",
    "content": "<!--\n  - Copyright 2014-2019 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div\n    :id=\"$attrs.id\"\n    class=\"shadow-sm border rounded break-inside-avoid mb-4\"\n    :aria-expanded=\"$attrs.ariaExpanded\"\n  >\n    <header\n      v-if=\"hasTitle\"\n      ref=\"header\"\n      v-sticks-below=\"headerSticksBelow\"\n      :class=\"\n        classNames(\n          'rounded-t flex justify-between px-4 py-2.5 border-b items-center bg-white transition-all',\n        )\n      \"\n    >\n      <h3 class=\"font-medium text-gray-900 flex-1 order border-blue-300\">\n        <button class=\"flex items-center w-full\" @click=\"$emit('title-click')\">\n          <slot v-if=\"'prefix' in $slots\" name=\"prefix\" />\n          <span v-text=\"title\" />\n          <span\n            v-if=\"subtitle\"\n            class=\"ml-2 text-sm text-gray-500 self-end\"\n            v-text=\"subtitle\"\n          />\n          <slot v-if=\"'title' in $slots\" name=\"title\" />\n        </button>\n      </h3>\n\n      <div>\n        <slot v-if=\"'actions' in $slots\" name=\"actions\" />\n        <sba-icon-button\n          v-if=\"closeable\"\n          :icon=\"['far', 'times-circle']\"\n          @click.stop=\"$emit('close', $event)\"\n        />\n      </div>\n    </header>\n    <div\n      v-if=\"'default' in $slots\"\n      :class=\"[\n        $attrs.class,\n        {\n          'rounded-t': !hasTitle,\n          'rounded-b': !('footer' in $slots),\n        },\n      ]\"\n      class=\"px-4 py-3 bg-white\"\n    >\n      <div :class=\"{ '-mx-4 -my-3': seamless }\">\n        <sba-loading-spinner v-if=\"loading\" class=\"\" size=\"sm\" />\n        <slot v-if=\"!loading\" />\n      </div>\n    </div>\n    <footer v-if=\"'footer' in $slots\">\n      <div class=\"px-4 py-3 border-t bg-gray-50\">\n        <slot name=\"footer\" />\n      </div>\n    </footer>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport classNames from 'classnames';\nimport { throttle } from 'lodash-es';\n\nimport SbaIconButton from '@/components/sba-icon-button';\nimport SbaLoadingSpinner from '@/components/sba-loading-spinner';\n\nimport sticksBelow from '@/directives/sticks-below';\n\nexport default {\n  components: { SbaLoadingSpinner, SbaIconButton },\n  directives: { sticksBelow },\n  inheritAttrs: false,\n  props: {\n    title: {\n      type: String,\n      default: undefined,\n    },\n    subtitle: {\n      type: String,\n      default: undefined,\n    },\n    closeable: {\n      type: Boolean,\n      default: false,\n    },\n    headerSticksBelow: {\n      type: String,\n      default: undefined,\n    },\n    loading: {\n      type: Boolean,\n      default: false,\n    },\n    seamless: {\n      type: Boolean,\n      default: false,\n    },\n  },\n  emits: ['close', 'title-click'],\n  data() {\n    return {\n      classNames,\n      headerTopValue: 0,\n      onScrollFn: undefined,\n    };\n  },\n  computed: {\n    hasTitle() {\n      return !!this.title || 'title' in this.$slots || 'actions' in this.$slots;\n    },\n  },\n  mounted() {\n    if (this.headerSticksBelow) {\n      const header = this.$refs.header;\n      this.headerTopValue = +header.style.top.substr(\n        0,\n        header.style.top.length - 2,\n      );\n\n      this.onScrollFn = throttle(this.onScroll, 150);\n      document.addEventListener('scroll', this.onScrollFn);\n    }\n  },\n  beforeUnmount() {\n    if (this.headerSticksBelow) {\n      document.removeEventListener('scroll', this.onScrollFn);\n    }\n  },\n  methods: {\n    onScroll() {\n      const header = this.$refs.header;\n      const boundingClientRect = header.getBoundingClientRect();\n      if (boundingClientRect.top <= this.headerTopValue) {\n        header.classList.add('!rounded-none', '!py-2');\n      } else {\n        header.classList.remove('!rounded-none', '!py-2');\n      }\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-select.stories.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport SbaSelect from './sba-select.vue';\n\nexport default {\n  component: SbaSelect,\n  title: 'Components/Form/Select',\n};\n\nconst Template = (args) => {\n  return {\n    components: { SbaSelect },\n    setup() {\n      return { args };\n    },\n    template: `\n      <sba-select v-bind=\"args\">\n        <template #prepend>Prepend</template>\n        <template #append>Append</template>\n      </sba-select>\n    `,\n  };\n};\n\nexport const SimpleSelect = {\n  render: Template,\n\n  args: {\n    modelValue: 'berlin',\n    options: [\n      { value: 'beer', label: 'Beer' },\n      { value: 'water', label: 'Water' },\n      { value: 'wine', label: 'Wine' },\n    ],\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-select.vue",
    "content": "<!--\n  - Copyright 2014-2020 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div>\n    <div>\n      <label\n        v-if=\"hasLabel\"\n        :for=\"id\"\n        class=\"block text-sm font-medium text-gray-700\"\n        v-text=\"label\"\n      />\n      <div class=\"flex rounded shadow-sm\" :class=\"{ 'mt-1': hasLabel }\">\n        <span\n          v-if=\"$slots.prepend\"\n          class=\"inline-flex items-center px-3 rounded-l border border-r-0 border-gray-300 bg-gray-50 text-gray-500 text-sm\"\n        >\n          <slot name=\"prepend\" />\n        </span>\n\n        <select\n          :id=\"id\"\n          :name=\"name\"\n          :value=\"modelValue\"\n          :autocomplete=\"autocomplete\"\n          class=\"focus:z-10 p-2 relative flex-1 block w-full rounded-none sm:text-sm bg-opacity-40 backdrop-blur-sm\"\n          :class=\"classNames(inputFieldClassNames, inputClass)\"\n          @input=\"handleInput\"\n        >\n          <option v-for=\"(option, idx) in options\" :key=\"idx\" v-bind=\"option\">\n            {{ option.label }}\n          </option>\n        </select>\n\n        <span\n          v-if=\"$slots.append\"\n          class=\"inline-flex items-center px-3 rounded-r border border-l-0 border-gray-300 bg-gray-50 text-gray-500 text-sm\"\n        >\n          <slot name=\"append\" />\n        </span>\n      </div>\n      <span v-if=\"$slots.info\" class=\"mt-2 text-sm text-gray-500\">\n        <slot name=\"info\" />\n      </span>\n    </div>\n    <div v-if=\"error || hint\" class=\"py-2\">\n      <div v-if=\"hint && !error\" class=\"text-xs text-gray-500\" v-text=\"hint\" />\n      <div v-if=\"error\" class=\"text-xs text-red-500\" v-text=\"error\" />\n    </div>\n  </div>\n</template>\n\n<script>\nimport classNames from 'classnames';\n\nexport default {\n  props: {\n    name: {\n      type: String,\n      required: true,\n    },\n    label: {\n      type: String,\n      default: null,\n    },\n    placeholder: {\n      type: String,\n      default: null,\n    },\n    type: {\n      type: String,\n      default: 'text',\n    },\n    modelValue: {\n      type: [String, Number],\n      default: null,\n    },\n    min: {\n      type: Number,\n      default: undefined,\n    },\n    list: {\n      type: Array,\n      default: undefined,\n    },\n    inputClass: {\n      type: [String, Array, Object],\n      default: undefined,\n    },\n    error: {\n      type: String,\n      default: undefined,\n    },\n    hint: {\n      type: String,\n      default: undefined,\n    },\n    autocomplete: {\n      type: String,\n      default: undefined,\n    },\n    options: {\n      type: Array,\n      required: true,\n    },\n  },\n  emits: ['update:modelValue', 'input'],\n  computed: {\n    hasLabel() {\n      return this.label !== null && this.label.trim() !== '';\n    },\n    id() {\n      return (this.name || '').replace(/[^\\w]/gi, '');\n    },\n    inputFieldClassNames() {\n      const hasAppend = this.hasSlot('append');\n      const hasPrepend = this.hasSlot('prepend');\n\n      const classNames = [];\n\n      if (this.error !== undefined) {\n        classNames.push(\n          'focus:ring-red-500 focus:border-red-500 border-red-400',\n        );\n      } else {\n        classNames.push(\n          'focus:ring-indigo-500 focus:border-indigo-500 border-gray-300',\n        );\n      }\n\n      if (!hasAppend) {\n        classNames.push('rounded-r');\n      }\n      if (!hasPrepend) {\n        classNames.push('rounded-l');\n      }\n\n      return classNames;\n    },\n  },\n  methods: {\n    classNames,\n\n    handleInput($event) {\n      this.$emit('update:modelValue', $event.target.value);\n      this.$emit('input', $event.target.value);\n    },\n    hasSlot(slot) {\n      return !!this.$slots[slot] && Object.keys(this.$slots[slot]).length > 0;\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-status-badge.spec.ts",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { render } from '@testing-library/vue';\nimport { describe, it } from 'vitest';\n\nimport { HealthStatus } from '../HealthStatus';\nimport sbaStatusBadge from './sba-status-badge.vue';\n\ndescribe('sba-status-badge.vue', () => {\n  it('should accept valid HealthStatus', () => {\n    render(sbaStatusBadge, {\n      props: {\n        status: HealthStatus.DOWN,\n      },\n    });\n  });\n\n  it('should accept String', () => {\n    render(sbaStatusBadge, {\n      props: {\n        status: 'down',\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-status-badge.vue",
    "content": "<template>\n  <span\n    role=\"status\"\n    :class=\"classNames('status-badge', healthStatus)\"\n    v-text=\"status\"\n  />\n</template>\n\n<script>\nimport classNames from 'classnames';\n\nimport { HealthStatus } from '@/HealthStatus';\n\nexport default {\n  props: {\n    status: {\n      type: [HealthStatus, String, Number],\n      required: true,\n    },\n  },\n  computed: {\n    healthStatus() {\n      return `${this.status}`.toLowerCase();\n    },\n  },\n  methods: {\n    classNames,\n  },\n};\n</script>\n\n<style scoped>\n.status-badge {\n  @apply bg-gray-200 text-black text-xs inline-flex items-center uppercase  rounded overflow-hidden px-3 py-1;\n}\n.up {\n  @apply bg-green-200 text-green-700;\n}\n.down,\n.out_of_service {\n  @apply bg-red-200 text-red-700;\n}\n.restricted {\n  @apply bg-yellow-200 text-yellow-700;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-status.spec.ts",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { screen } from '@testing-library/vue';\nimport moment from 'moment';\nimport { describe, expect, it } from 'vitest';\n\nimport sbaStatus from './sba-status.vue';\n\nimport { render } from '@/test-utils';\n\nmoment.now = () => +new Date(1318781879406);\n\ndescribe('application-status', () => {\n  function testSnapshotForStatus(status: string, date?: number) {\n    render(sbaStatus, {\n      propsData: {\n        status,\n        date,\n      },\n    });\n  }\n\n  it('should match the snapshot with status UP with Timestamp', async () => {\n    const status = 'UP';\n    testSnapshotForStatus(status, 1318781000000);\n\n    expect(await screen.findByLabelText(status)).toBeDefined();\n  });\n\n  it('should match the snapshot with status RESTRICTED', async () => {\n    const status = 'RESTRICTED';\n    testSnapshotForStatus(status);\n\n    expect(await screen.findByLabelText(status)).toBeDefined();\n  });\n\n  it('should match the snapshot with status OUT_OF_SERVICE', async () => {\n    const status = 'OUT_OF_SERVICE';\n    testSnapshotForStatus(status);\n\n    expect(await screen.findByLabelText(status)).toBeDefined();\n  });\n\n  it('should match the snapshot with status DOWN', async () => {\n    const status = 'DOWN';\n    testSnapshotForStatus(status);\n\n    expect(await screen.findByLabelText(status)).toBeDefined();\n  });\n\n  it('should match the snapshot with status UNKNOWN', async () => {\n    const status = 'UNKNOWN';\n    testSnapshotForStatus(status);\n\n    expect(await screen.findByLabelText(status)).toBeDefined();\n  });\n\n  it('should match the snapshot with status OFFLINE', async () => {\n    const status = 'OFFLINE';\n    testSnapshotForStatus(status);\n\n    expect(await screen.findByLabelText(status)).toBeDefined();\n  });\n\n  it('should match the snapshot with custom status', async () => {\n    const status = '?';\n    testSnapshotForStatus(status);\n\n    expect(await screen.findByLabelText(status)).toBeDefined();\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-status.stories.ts",
    "content": "import SbaStatus from './sba-status.vue';\n\nexport default {\n  component: SbaStatus,\n  title: 'Components/Status',\n};\n\nconst Template = (args) => ({\n  components: { SbaStatus },\n  setup() {\n    return { args };\n  },\n  template: '<sba-status v-bind=\"args\" />',\n});\n\nexport const Status = {\n  render: Template,\n};\n\nexport const StatusUp = {\n  render: Template,\n\n  args: {\n    date: Date.now(),\n    status: 'UP',\n  },\n};\n\nexport const StatusRestricted = {\n  render: Template,\n\n  args: {\n    ...StatusUp.args,\n    status: 'RESTRICTED',\n  },\n};\n\nexport const StatusOos = {\n  render: Template,\n\n  args: {\n    ...StatusUp.args,\n    status: 'OUT_OF_SERVICE',\n  },\n};\n\nexport const StatusDown = {\n  render: Template,\n\n  args: {\n    ...StatusUp.args,\n    status: 'DOWN',\n  },\n};\n\nexport const StatusOffline = {\n  render: Template,\n\n  args: {\n    ...StatusUp.args,\n    status: 'OFFLINE',\n  },\n};\n\nexport const StatusUnknown = {\n  render: Template,\n\n  args: {\n    ...StatusUp.args,\n    status: 'UNKNOWN',\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-status.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div :aria-label=\"status\" class=\"application-status\">\n    <font-awesome-icon\n      :class=\"`application-status__icon--${status}`\"\n      :icon=\"icon\"\n      class=\"application-status__icon\"\n    />\n    <small v-if=\"date\" class=\"hidden md:block\">\n      <sba-time-ago :date=\"date\" />\n    </small>\n  </div>\n</template>\n\n<script>\nimport moment from 'moment';\n\nimport sbaTimeAgo from '@/components/sba-time-ago';\n\nconst icons = {\n  UP: 'check-circle',\n  RESTRICTED: 'exclamation',\n  OUT_OF_SERVICE: 'ban',\n  DOWN: 'times-circle',\n  OFFLINE: 'minus-circle',\n  UNKNOWN: 'question-circle',\n};\n\nexport default {\n  components: { sbaTimeAgo },\n  props: {\n    status: {\n      type: String,\n      default: 'UNKNOWN',\n    },\n    date: {\n      type: [String, Date, Number, moment],\n      default: null,\n    },\n  },\n  computed: {\n    icon() {\n      return icons[this.status];\n    },\n  },\n};\n</script>\n\n<style>\n.application-status {\n  @apply text-center inline-flex flex-col;\n}\n.application-status__icon {\n  @apply text-gray-500 mx-auto;\n}\n.application-status__icon--UP {\n  color: #48c78e;\n}\n.application-status__icon--RESTRICTED {\n  color: #ffe08a;\n}\n.application-status__icon--OUT_OF_SERVICE,\n.application-status__icon--DOWN {\n  color: #f14668;\n}\n.application-status__icon--UNKNOWN,\n.application-status__icon--OFFLINE {\n  color: #7a7a7a;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-sticky-subnav.vue",
    "content": "<template>\n  <div\n    id=\"subnavigation\"\n    class=\"sticky shadow-sm top-14 w-full bg-white py-1 px-2 md:px-6 backdrop-filter backdrop-blur bg-opacity-40 z-20 drop-shadow-lg\"\n  >\n    <slot />\n  </div>\n</template>\n\n<script>\nexport default {\n  name: 'SbaStickySubnav',\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-tag.stories.ts",
    "content": "import SbaTag from './sba-tag.vue';\n\nexport default {\n  component: SbaTag,\n  title: 'Components/Tag',\n  argTypes: {\n    value: {\n      description: 'A value to show',\n      example: 'asd',\n      control: 'text',\n    },\n    label: {\n      description: 'An optional label',\n      example: 'asd',\n      control: 'text',\n    },\n    small: {\n      description: 'A single value to show',\n      control: 'boolean',\n    },\n  },\n};\n\nconst Template = (args) => ({\n  components: { SbaTag },\n  setup() {\n    return { args };\n  },\n\n  template: '<sba-tag v-bind=\"args\" />',\n});\n\nexport const Tag = {\n  render: Template,\n\n  args: {\n    value: 'I am a tag',\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-tag.vue",
    "content": "<template>\n  <span\n    :class=\"{\n      'py-1 border border-gray-300 bg-white': !small,\n      'py-1 border text-xs bg-white': small,\n    }\"\n    class=\"inline-flex leading-none rounded text-sm overflow-hidden\"\n  >\n    <span\n      v-if=\"label\"\n      :class=\"{\n        'py-1.5 px-2 ml-1': !small,\n        'py-1.5 px-1.5 ml-1': small,\n      }\"\n      class=\"inline-flex bg-sba-200 rounded text-sba-900 justify-center items-center transition-all whitespace-nowrap\"\n      v-text=\"label\"\n    />\n    <span\n      :class=\"{\n        'pl-2': !!label,\n        'bg-white pl-1.5': small,\n      }\"\n      class=\"inline-flex px-2 justify-center items-center whitespace-nowrap\"\n      v-text=\"value\"\n    />\n  </span>\n</template>\n\n<script setup>\ndefineProps({\n  label: {\n    type: [String, Number],\n    default: null,\n  },\n  small: {\n    type: Boolean,\n    default: false,\n  },\n  value: {\n    type: [String, Number],\n    required: true,\n  },\n});\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-tags.stories.ts",
    "content": "import SbaTags from './sba-tags.vue';\n\nexport default {\n  component: SbaTags,\n  title: 'Components/Tags',\n};\n\nconst Template = (args) => ({\n  components: { SbaTags },\n  setup() {\n    return { args };\n  },\n  template: '<SbaTags v-bind=\"args\" />',\n});\n\nexport const SingleTag = {\n  render: Template,\n\n  args: {\n    tags: {\n      'This is a key': 'This a value',\n    },\n  },\n};\n\nexport const SingleTagSmall = {\n  render: Template,\n\n  args: {\n    small: true,\n    tags: {\n      'This is a key': 'This a value',\n    },\n  },\n};\n\nexport const MultipleTags = {\n  render: Template,\n\n  args: {\n    tags: {\n      'This is a key': 'This a value',\n      simpleKey: 'value',\n    },\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-tags.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div :class=\"classNames('inline-flex gap-1', { 'flex-wrap': wrap })\">\n    <template v-for=\"(value, key) in tags\" :key=\"key\">\n      <sba-tag :label=\"key\" :small=\"small\" :value=\"value\" />\n    </template>\n  </div>\n</template>\n\n<script>\nimport classNames from 'classnames';\n\nexport default {\n  props: {\n    tags: {\n      type: Object,\n      required: true,\n    },\n    small: {\n      type: Boolean,\n      default: false,\n    },\n    wrap: {\n      type: Boolean,\n      default: true,\n    },\n  },\n  methods: {\n    classNames: classNames,\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-time-ago.spec.ts",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { screen } from '@testing-library/vue';\nimport moment from 'moment';\nimport { describe, expect, it } from 'vitest';\n\nimport sbaTimeAgo from './sba-time-ago.vue';\n\nimport { render } from '@/test-utils';\n\n// Sun Oct 16 2011 18:00:00 GMT+0200 (Mitteleuropäische Sommerzeit)\nmoment.now = () => Date.now();\n\ndescribe('time-ago', () => {\n  it('should show short period', async () => {\n    render(sbaTimeAgo, {\n      propsData: {\n        date: moment().subtract(15, 'minutes').utc(),\n      },\n    });\n\n    expect(await screen.findByText('15m'));\n  });\n\n  it('multiple minutes', async () => {\n    render(sbaTimeAgo, {\n      propsData: {\n        date: moment().subtract(800, 'minutes').utc(),\n      },\n    });\n\n    expect(await screen.findByText('13h'));\n  });\n\n  it('multiple days', async () => {\n    render(sbaTimeAgo, {\n      propsData: {\n        date: moment().subtract(5, 'days'),\n      },\n    });\n\n    expect(await screen.findByText('5d'));\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-time-ago.vue",
    "content": "/* * Copyright 2014-2018 the original author or authors. * * Licensed under the\nApache License, Version 2.0 (the \"License\"); * you may not use this file except\nin compliance with the License. * You may obtain a copy of the License at * *\nhttp://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law\nor agreed to in writing, software * distributed under the License is distributed\non an \"AS IS\" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either\nexpress or implied. * See the License for the specific language governing\npermissions and * limitations under the License. */\n<template>\n  {{ timeAgo }}\n</template>\n\n<script>\nimport moment from 'moment';\n\nconst minute = 60 * 1000;\nconst hour = 60 * minute;\nconst day = 24 * hour;\nconst week = 7 * day;\n\nfunction shortFormat(\n  aMoment,\n  withoutPreOrSuffix,\n  now = moment(),\n  withPrecision = false,\n) {\n  let diff = Math.abs(aMoment.diff(now));\n  let unit;\n  let secondaryUnit;\n\n  if (diff < minute) {\n    unit = 'seconds';\n  } else if (diff < hour) {\n    unit = 'minutes';\n    secondaryUnit = 'seconds';\n  } else if (diff < day) {\n    unit = 'hours';\n    secondaryUnit = 'minutes';\n  } else if (diff < week) {\n    unit = 'days';\n    secondaryUnit = 'hours';\n  } else if (aMoment.year() !== now.year()) {\n    return aMoment.format('MMM D, YYYY');\n  } else {\n    unit = 'weeks';\n    secondaryUnit = 'days';\n  }\n\n  let num = Math.max(1, moment.duration(diff)[unit]());\n  let result = num + unit.charAt(0);\n\n  if (withPrecision && secondaryUnit) {\n    let secondaryNum =\n      moment.duration(diff)[secondaryUnit]() %\n      (unit === 'weeks' ? 7 : unit === 'days' ? 24 : 60);\n    if (secondaryNum > 0) {\n      result += ' ' + secondaryNum + secondaryUnit.charAt(0);\n    }\n  }\n\n  if (!withoutPreOrSuffix) {\n    result = moment.localeData().pastFuture(aMoment.diff(now), result);\n  }\n  return result;\n}\n\nexport default {\n  props: {\n    date: {\n      type: [String, Date, Number, moment],\n      default: null,\n    },\n    precision: {\n      type: Boolean,\n      default: false,\n    },\n  },\n  data: () => ({\n    now: moment(),\n    timer: null,\n  }),\n  computed: {\n    timeAgo() {\n      return shortFormat(moment(this.date), true, this.now, this.precision);\n    },\n  },\n  created() {\n    this.timer = window.setInterval(() => {\n      this.now = moment();\n    }, 1000);\n  },\n  beforeUnmount() {\n    if (this.timer) {\n      window.clearInterval(this.timer);\n    }\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-toggle-scope-button.spec.ts",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport userEvent from '@testing-library/user-event';\nimport { screen } from '@testing-library/vue';\nimport { beforeEach, describe, expect, it } from 'vitest';\n\nimport { render } from '../test-utils';\nimport { ActionScope } from './ActionScope';\nimport SbaToggleScopeButton from './sba-toggle-scope-button.vue';\n\ndescribe('SbaToggleScopeButton', function () {\n  let wrapper;\n\n  beforeEach(() => {\n    wrapper = render(SbaToggleScopeButton, {\n      props: { instanceCount: 2, modelValue: ActionScope.INSTANCE },\n    });\n  });\n\n  it('should emit changed scope when clicked', async () => {\n    await userEvent.click(\n      await screen.findByRole('button', { name: /instance/i }),\n    );\n\n    expect(wrapper.emitted()['update:modelValue'][0]).toEqual(['application']);\n  });\n\n  it('should toggle the scope when clicked twice', async () => {\n    await userEvent.click(\n      await screen.findByRole('button', { name: /instance/i }),\n    );\n    expect(wrapper.emitted()['update:modelValue'][0]).toEqual(['application']);\n\n    await wrapper.rerender({ modelValue: ActionScope.APPLICATION });\n\n    await userEvent.click(\n      await screen.findByRole('button', { name: /application/i }),\n    );\n    expect(wrapper.emitted()['update:modelValue'][1]).toEqual(['instance']);\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-toggle-scope-button.stories.ts",
    "content": "import { reactive } from 'vue';\n\nimport { ActionScope } from './ActionScope';\nimport SbaToggleScopeButton from './sba-toggle-scope-button.vue';\n\nexport default {\n  component: SbaToggleScopeButton,\n  title: 'Components/Buttons/Toggle Scope Button',\n};\n\nconst Template = (args) => ({\n  components: { SbaToggleScopeButton },\n  setup() {\n    return reactive({ args });\n  },\n  template:\n    '<sba-toggle-scope-button v-bind=\"args\" v-model:model-value=\"args.modelValue\" />',\n});\n\nexport const Default = {\n  render: Template,\n\n  args: {\n    modelValue: ActionScope.INSTANCE,\n    instanceCount: 2,\n    showInfo: true,\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-toggle-scope-button.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div class=\"flex\">\n    <sba-button\n      v-if=\"instanceCount <= 1 || modelValue === ActionScope.APPLICATION\"\n      :class=\"classNames\"\n      :title=\"$t('term.affects_all_instances', { count: instanceCount })\"\n      class=\"w-full\"\n      size=\"sm\"\n      @click=\"() => (modelValue = ActionScope.INSTANCE)\"\n    >\n      <span v-text=\"$t('term.application')\" />\n    </sba-button>\n    <sba-button\n      v-else\n      :class=\"classNames\"\n      :title=\"$t('term.affects_this_instance_only')\"\n      class=\"w-full\"\n      size=\"sm\"\n      @click=\"() => (modelValue = ActionScope.APPLICATION)\"\n    >\n      <span v-text=\"$t('term.instance')\" />\n    </sba-button>\n\n    <p v-if=\"showInfo\" class=\"text-center text-xs pt-1 truncate\">\n      <span\n        v-if=\"modelValue === ActionScope.APPLICATION\"\n        v-text=\"$t('term.affects_all_instances', { count: instanceCount })\"\n      />\n      <span v-else v-text=\"$t('term.affects_this_instance_only')\" />\n    </p>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { ActionScope } from '@/components/ActionScope';\nimport SbaButton from '@/components/sba-button.vue';\n\nconst modelValue = defineModel({\n  type: String,\n  default: ActionScope.APPLICATION,\n});\n\nconst {\n  instanceCount,\n  showInfo = true,\n  classNames = '',\n} = defineProps<{\n  instanceCount: number;\n  showInfo?: boolean;\n  classNames?: string | string[] | Record<string, boolean>;\n}>();\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/sba-wave.vue",
    "content": "<template>\n  <div v-if=\"backgroundEnabled\" class=\"bg-wave\">\n    <svg\n      height=\"100%\"\n      preserveAspectRatio=\"none\"\n      viewBox=\"0 0 2000 240\"\n      width=\"100%\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n    >\n      <defs>\n        <linearGradient\n          :id=\"'bg-wave-a-' + id\"\n          gradientTransform=\"matrix(1680, 0, 0, -916.53, -3571233, 453859.52)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"2125.74\"\n          x2=\"2126.93\"\n          y1=\"495.08\"\n          y2=\"495.08\"\n        >\n          <stop class=\"bg-color-start\" offset=\"0\" />\n          <stop class=\"bg-color-stop\" offset=\"1\" />\n        </linearGradient>\n        <linearGradient\n          :id=\"'bg-wave-b-' + id\"\n          gradientTransform=\"matrix(1680, 0, 0, -925.87, -3571233, 458484.7)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"2125.74\"\n          x2=\"2126.93\"\n          y1=\"495.08\"\n          y2=\"495.08\"\n        >\n          <stop class=\"bg-color-start\" offset=\"0\" />\n          <stop class=\"bg-color-stop\" offset=\"1\" />\n        </linearGradient>\n        <linearGradient\n          :id=\"'bg-wave-c-' + id\"\n          gradientTransform=\"matrix(1680, 0, 0, -956.3, -3571233, 473553.87)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"2125.74\"\n          x2=\"2126.93\"\n          y1=\"495.07\"\n          y2=\"495.07\"\n        >\n          <stop class=\"bg-color-start\" offset=\"0\" />\n          <stop class=\"bg-color-stop\" offset=\"1\" />\n        </linearGradient>\n      </defs>\n      <path\n        :style=\"`fill:url('#bg-wave-a-${id}')`\"\n        d=\"M0,0V142.67q200,73.8,400,42.5T800,139q200-15,400,36.8t400-5.6q200-57.45,400-18.9V0Z\"\n        style=\"opacity: 0.2\"\n      />\n      <path\n        :style=\"`fill:url('#bg-wave-b-${id}')`\"\n        d=\"M0,0V152.67q200,64.5,800,29.5a1115.6,1115.6,0,0,1,400,2.4q200,37.35,400,19.1a3681.66,3681.66,0,0,1,400-14.6q200,3.6,400-45.5V0Z\"\n        style=\"opacity: 0.2\"\n      />\n      <path\n        :style=\"`fill:url('#bg-wave-c-${id}')`\"\n        d=\"M0,0V180.77q200,7.35,400-34.1T800,190q200,84.75,400-8.4t400,11.9q200,105,400-26.9V0Z\"\n        style=\"opacity: 0.2\"\n      />\n    </svg>\n  </div>\n</template>\n\n<script>\nimport sbaConfig from '@/sba-config';\n\nexport default {\n  name: 'SbaWave',\n  data() {\n    return {\n      id: this._.uid,\n      backgroundEnabled: sbaConfig.uiSettings.theme.backgroundEnabled,\n    };\n  },\n};\n</script>\n\n<style scoped>\n.bg-wave {\n  @apply w-full h-40 fixed;\n  z-index: -10;\n  pointer-events: none;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components/table.stories.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport default {\n  title: 'Components/Table',\n};\n\nconst TemplateWithProps = () => ({\n  template: `\n    <table class=\"table table-full\">\n        <thead>\n        <tr>\n            <th>Anwendung</th>\n            <th>Instanzen</th>\n            <th>Zeit</th>\n            <th>Ereignis</th>\n        </tr>\n        </thead>\n        <tbody>\n        <tr>\n            <td>spring-boot-admin-sample-servlet</td>\n            <td>25b07dc98984</td>\n            <td>04/29/2022 16:03:45.778</td>\n            <td><span>INFO_CHANGED</span> <!--v-if--></td>\n        </tr>\n        <tr>\n            <td>spring-boot-admin-sample-servlet</td>\n            <td>25b07dc98984</td>\n            <td>04/29/2022 16:03:45.776</td>\n            <td><span>ENDPOINTS_DETECTED</span> <!--v-if--></td>\n        </tr>\n        <tr>\n            <td>spring-boot-admin-sample-servlet</td>\n            <td>25b07dc98984</td>\n            <td>04/29/2022 16:03:45.768</td>\n            <td><span>STATUS_CHANGED</span> <span>(UP)</span></td>\n        </tr>\n        <tr>\n            <td>spring-boot-admin-sample-servlet</td>\n            <td>25b07dc98984</td>\n            <td>04/29/2022 16:03:45.755</td>\n            <td><span>REGISTERED</span> <!--v-if--></td>\n        </tr>\n        <tr>\n            <td>spring-boot-admin-sample-servlet</td>\n            <td>af578c480d41</td>\n            <td>04/29/2022 16:03:44.666</td>\n            <td><span>INFO_CHANGED</span> <!--v-if--></td>\n        </tr>\n        <tr>\n            <td>spring-boot-admin-sample-servlet</td>\n            <td>af578c480d41</td>\n            <td>04/29/2022 16:03:44.662</td>\n            <td><span>ENDPOINTS_DETECTED</span> <!--v-if--></td>\n        </tr>\n        <tr>\n            <td>spring-boot-admin-sample-servlet</td>\n            <td>af578c480d41</td>\n            <td>04/29/2022 16:03:44.657</td>\n            <td><span>STATUS_CHANGED</span> <span>(UP)</span></td>\n        </tr>\n        <tr>\n            <td>spring-boot-admin-sample-servlet</td>\n            <td>af578c480d41</td>\n            <td>04/29/2022 16:03:44.639</td>\n            <td><span>REGISTERED</span> <!--v-if--></td>\n        </tr>\n        <tr>\n            <td>spring-boot-admin-sample-servlet</td>\n            <td>af578c480d41</td>\n            <td>04/29/2022 16:03:39.801</td>\n            <td><span>DEREGISTERED</span> <!--v-if--></td>\n        </tr>\n        <tr>\n            <td>spring-boot-admin-sample-servlet</td>\n            <td>25b07dc98984</td>\n            <td>04/29/2022 16:03:39.799</td>\n            <td><span>DEREGISTERED</span> <!--v-if--></td>\n        </tr>\n        <tr>\n            <td>spring-boot-admin-sample-servlet</td>\n            <td>25b07dc98984</td>\n            <td>04/29/2022 15:51:45.716</td>\n            <td><span>INFO_CHANGED</span> <!--v-if--></td>\n        </tr>\n        <tr>\n            <td>spring-boot-admin-sample-servlet</td>\n            <td>25b07dc98984</td>\n            <td>04/29/2022 15:51:45.712</td>\n            <td><span>ENDPOINTS_DETECTED</span> <!--v-if--></td>\n        </tr>\n        <tr>\n            <td>spring-boot-admin-sample-servlet</td>\n            <td>25b07dc98984</td>\n            <td>04/29/2022 15:51:45.702</td>\n            <td><span>STATUS_CHANGED</span> <span>(UP)</span></td>\n        </tr>\n        <tr>\n            <td>spring-boot-admin-sample-servlet</td>\n            <td>25b07dc98984</td>\n            <td>04/29/2022 15:51:45.690</td>\n            <td><span>REGISTERED</span> <!--v-if--></td>\n        </tr>\n        <tr>\n            <td>spring-boot-admin-sample-servlet</td>\n            <td>af578c480d41</td>\n            <td>04/29/2022 15:51:44.591</td>\n            <td><span>INFO_CHANGED</span> <!--v-if--></td>\n        </tr>\n        <tr>\n            <td>spring-boot-admin-sample-servlet</td>\n            <td>af578c480d41</td>\n            <td>04/29/2022 15:51:44.586</td>\n            <td><span>ENDPOINTS_DETECTED</span> <!--v-if--></td>\n        </tr>\n        <tr>\n            <td>spring-boot-admin-sample-servlet</td>\n            <td>af578c480d41</td>\n            <td>04/29/2022 15:51:44.578</td>\n            <td><span>STATUS_CHANGED</span> <span>(UP)</span></td>\n        </tr>\n        <tr>\n            <td>spring-boot-admin-sample-servlet</td>\n            <td>af578c480d41</td>\n            <td>04/29/2022 15:51:44.564</td>\n            <td><span>REGISTERED</span> <!--v-if--></td>\n        </tr>\n        <tr>\n            <td>spring-boot-admin-sample-servlet</td>\n            <td>25b07dc98984</td>\n            <td>04/29/2022 15:51:40.611</td>\n            <td><span>DEREGISTERED</span> <!--v-if--></td>\n        </tr>\n        <tr>\n            <td>spring-boot-admin-sample-servlet</td>\n            <td>af578c480d41</td>\n            <td>04/29/2022 15:51:39.600</td>\n            <td><span>DEREGISTERED</span> <!--v-if--></td>\n        </tr>\n        <tr>\n            <td>spring-boot-admin-sample-servlet</td>\n            <td>af578c480d41</td>\n            <td>04/29/2022 15:46:54.948</td>\n            <td><span>INFO_CHANGED</span> <!--v-if--></td>\n        </tr>\n        <tr>\n            <td>spring-boot-admin-sample-servlet</td>\n            <td>af578c480d41</td>\n            <td>04/29/2022 15:46:54.943</td>\n            <td><span>ENDPOINTS_DETECTED</span> <!--v-if--></td>\n        </tr>\n        <tr>\n            <td>spring-boot-admin-sample-servlet</td>\n            <td>af578c480d41</td>\n            <td>04/29/2022 15:46:54.935</td>\n            <td><span>STATUS_CHANGED</span> <span>(UP)</span></td>\n        </tr>\n        <tr>\n            <td>spring-boot-admin-sample-servlet</td>\n            <td>af578c480d41</td>\n            <td>04/29/2022 15:46:54.606</td>\n            <td><span>REGISTERED</span> <!--v-if--></td>\n        </tr>\n        <tr>\n            <td>spring-boot-1.5</td>\n            <td>5914e4fcb78b</td>\n            <td>04/29/2022 14:14:56.321</td>\n            <td><span>ENDPOINTS_DETECTED</span> <!--v-if--></td>\n        </tr>\n        </tbody>\n    </table>\n  `,\n});\n\nexport const Default = {\n  render: TemplateWithProps,\n\n  args: {\n    startColor: '#ff0000',\n    stopColor: '#00fa73',\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/components.d.ts",
    "content": "/* eslint-disable */\n// @ts-nocheck\n// Generated by unplugin-vue-components\n// Read more: https://github.com/vuejs/core/pull/3399\n// biome-ignore lint: disable\nexport {};\n\n/* prettier-ignore */\ndeclare module 'vue' {\n  export interface GlobalComponents {\n    Button: typeof import('primevue/button')['default']\n    Column: typeof import('primevue/column')['default']\n    DataTable: typeof import('primevue/datatable')['default']\n    DatePicker: typeof import('primevue/datepicker')['default']\n    Dialog: typeof import('primevue/dialog')['default']\n    IconField: typeof import('primevue/iconfield')['default']\n    InputIcon: typeof import('primevue/inputicon')['default']\n    InputText: typeof import('primevue/inputtext')['default']\n    MultiSelect: typeof import('primevue/multiselect')['default']\n    RouterLink: typeof import('vue-router')['RouterLink']\n    RouterView: typeof import('vue-router')['RouterView']\n    Sidebar: typeof import('primevue/sidebar')['default']\n    TreeTable: typeof import('primevue/treetable')['default']\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/composables/ViewRegistry.ts",
    "content": "import { debounce } from 'lodash-es';\n\nimport ViewRegistry from '../viewRegistry';\n\nimport eventBus from '@/services/bus';\n\nexport const CUSTOM_ROUTES_ADDED_EVENT = 'custom-routes-added';\n\nlet viewRegistry: ViewRegistry;\n\nexport function createViewRegistry() {\n  if (viewRegistry) throw new Error('ViewRegistry already created!');\n\n  viewRegistry = new ViewRegistry();\n  return viewRegistry;\n}\n\nconst emitCustomRouteAddedEvent = debounce(() => {\n  eventBus.emit(CUSTOM_ROUTES_ADDED_EVENT);\n});\n\nexport function useViewRegistry() {\n  return {\n    views: viewRegistry.views,\n    setGroupIcon(name, icon) {\n      viewRegistry.setGroupIcon(name, icon);\n    },\n    addView(viewToAdd) {\n      const view = viewRegistry.addView(viewToAdd)[0];\n\n      if (view.parent) {\n        viewRegistry.router.addRoute(view.parent, {\n          path: view.path,\n          name: view.name,\n          component: view.component,\n          props: view.props,\n          meta: { view: view },\n        });\n      } else {\n        viewRegistry.router.addRoute({\n          path: view.path,\n          name: view.name,\n          component: view.component,\n          props: view.props,\n          meta: { view: view },\n        });\n      }\n\n      emitCustomRouteAddedEvent();\n    },\n    getViewByName(name: string) {\n      return viewRegistry.views.find((view) => view.name === name);\n    },\n  };\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/composables/useApplicationStore.ts",
    "content": "import { Ref, ref } from 'vue';\n\nimport ApplicationStore from '../store';\n\nimport Application from '@/services/application';\n\nlet applicationStore: ApplicationStore | null = null;\nconst applications: Ref<Application[]> = ref([]);\nconst applicationsInitialized = ref(false);\nconst error = ref(null);\nlet listenersRegistered = false;\n\nexport function createApplicationStore() {\n  if (applicationStore) throw new Error('ApplicationStore already created!');\n\n  applicationStore = new ApplicationStore();\n  return applicationStore;\n}\n\ntype ApplicationStoreValue = {\n  applications: Ref<Application[]>;\n  applicationsInitialized: Ref<boolean>;\n  error: Ref<any>;\n  applicationStore: ApplicationStore;\n  findApplicationByInstanceId: (instanceId: string) => Ref<Application | null>;\n};\n\nexport function useApplicationStore(): ApplicationStoreValue {\n  if (!applicationStore) {\n    throw new Error(\n      'ApplicationStore not created yet! Call createApplicationStore() first.',\n    );\n  }\n\n  if (!listenersRegistered) {\n    applicationStore.addEventListener('connected', () => {\n      applicationsInitialized.value = true;\n      error.value = null;\n    });\n\n    applicationStore.addEventListener('changed', (newApplications) => {\n      applicationsInitialized.value = true;\n      applications.value = newApplications;\n      error.value = null;\n    });\n\n    applicationStore.addEventListener('error', (errorResponse) => {\n      applicationsInitialized.value = true;\n      error.value = errorResponse;\n    });\n\n    applicationStore.addEventListener('removed', () => {\n      applicationsInitialized.value = false;\n    });\n\n    listenersRegistered = true;\n  }\n\n  return {\n    applications,\n    applicationsInitialized,\n    error,\n    applicationStore,\n  };\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/composables/useClassnameShortener.spec.ts",
    "content": "import { describe, expect, it } from 'vitest';\n\nimport { useClassnameShortener } from '@/composables/useClassnameShortener';\n\ndescribe('useClassnameShortener', () => {\n  const { truncateClassname } = useClassnameShortener();\n\n  it.each`\n    given                                                             | expected\n    ${'com.example.MyService'}                                        | ${'com.example.MyService'}\n    ${'MyTopLevelClass'}                                              | ${'MyTopLevelClass'}\n    ${'com.example.Outer$Inner'}                                      | ${'com.example.Outer$Inner'}\n    ${'org.springframework.boot.web.embedded.tomcat.TomcatWebServer'} | ${'o.s.b.w.embedded.tomcat.TomcatWebServer'}\n  `('$given => $expected', ({ given, expected }) => {\n    expect(truncateClassname(given)).toEqual(expected);\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/composables/useClassnameShortener.ts",
    "content": "type Options = {\n  maxLen?: number;\n};\n\nexport const useClassnameShortener = (options?: Options) => {\n  return {\n    truncateClassname: (fqcn: string) =>\n      abbreviateLoggerName(fqcn, options?.maxLen),\n  };\n};\n\n/**\n * Abbreviate a fully qualified Java class name like Spring Boot logging does.\n *\n * Examples:\n *  - org.springframework.boot.web.embedded.tomcat.TomcatWebServer\n *    -> o.s.b.w.e.tomcat.TomcatWebServer\n *\n *  - com.example.very.long.package.name.MyService with maxLen=30\n *    -> c.e.v.l.p.name.MyService  (then drops leftmost segments if needed)\n *\n * @param fqcn   Fully-qualified class name (e.g., \"org.example.FooBar\")\n * @param maxLen Maximum length of the resulting string\n * @returns Abbreviated name\n */\nfunction abbreviateLoggerName(fqcn: string, maxLen = 40): string {\n  const input = (fqcn || '').trim();\n  if (!input) return '';\n\n  if (input.length <= maxLen) return input; // already fits\n\n  const parts = input.split('.');\n  if (parts.length === 1) {\n    // No package, just crop if needed\n    return input.slice(input.length - maxLen);\n  }\n\n  const simpleName = parts[parts.length - 1];\n  let packages = parts.slice(0, -1);\n\n  // 1. Start with full name\n  let out = packages.join('.') + '.' + simpleName;\n\n  // 2. Abbreviate left-to-right\n  for (let i = 0; i < packages.length && out.length > maxLen; i++) {\n    packages[i] = packages[i].charAt(0); // abbreviate one package\n    out = packages.join('.') + '.' + simpleName;\n    if (out.length <= maxLen) return out;\n  }\n\n  // 3. If still too long, drop leftmost package segments\n  while (packages.length > 0 && out.length > maxLen) {\n    packages = packages.slice(1);\n    out = packages.join('.') + (packages.length ? '.' : '') + simpleName;\n    if (out.length <= maxLen) return out;\n  }\n\n  // 4. Fallback: left-crop\n  return out.slice(out.length - maxLen);\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/composables/useDateTimeFormatter.ts",
    "content": "type DateFormatterOptions = {\n  dateTimeFormat?: Intl.DateTimeFormatOptions;\n  timeFormat?: Intl.DateTimeFormatOptions;\n};\n\nexport const useDateTimeFormatter = (options?: DateFormatterOptions) => {\n  const userLocale = navigator.languages\n    ? navigator.languages[0]\n    : navigator.language;\n\n  const dateTimeFormat = new Intl.DateTimeFormat(userLocale, {\n    ...{ dateStyle: 'medium', timeStyle: 'medium' },\n    ...(options?.dateTimeFormat ?? {}),\n  });\n\n  return {\n    formatDateTime: (date: Date) => {\n      return dateTimeFormat.format(date);\n    },\n  };\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/directives/on-resize.ts",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport ResizeObserver from 'resize-observer-polyfill';\n\nconst observers = new WeakMap();\n\nconst mounted = (el, binding) => {\n  beforeUnmount(el);\n  const observer = new ResizeObserver(binding.value);\n  observer.observe(el);\n  observers.set(el, observer);\n};\n\nconst beforeUnmount = (el) => {\n  const observer = observers.get(el);\n  if (observer) {\n    observer.disconnect();\n    observers.delete(el);\n  }\n};\n\nexport default {\n  mounted,\n  update(el, binding) {\n    if (binding.value === binding.oldValue) {\n      return;\n    }\n    mounted(el, binding);\n  },\n  beforeUnmount,\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/directives/popper.ts",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport Popper from 'popper.js';\n\nconst poppers = new WeakMap();\n\nconst mounted = (el, binding) => {\n  const reference =\n    typeof binding.value === 'string'\n      ? document.getElementById(binding.value)\n      : binding.value;\n  if (reference) {\n    const popper = new Popper(reference, el);\n    poppers.set(el, popper);\n  }\n};\n\nconst beforeUnmount = (el) => {\n  const popper = poppers.get(el);\n  if (popper) {\n    popper.destroy(el);\n  }\n};\n\nexport default {\n  mounted,\n  update(el, binding) {\n    if (binding.value === binding.oldValue) {\n      return;\n    }\n    mounted(el, binding);\n  },\n  beforeUnmount,\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/directives/sticks-below.ts",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst mounted = (el, binding) => {\n  if (!binding.value) {\n    return;\n  }\n\n  const targetElement = document.querySelector(binding.value);\n  if (targetElement) {\n    const clientRect = targetElement.getBoundingClientRect();\n    const top = clientRect.height + clientRect.top;\n\n    el.style.top = `${top}px`;\n    el.style.position = 'sticky';\n  }\n};\n\nexport default {\n  mounted,\n  update(el, binding) {\n    if (binding.value === binding.oldValue) {\n      return;\n    }\n    mounted(el, binding);\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/global.d.ts",
    "content": "import { Component, Raw, RenderFunction } from 'vue';\n\nimport ViewRegistry from '@/viewRegistry';\n\nexport {};\n\ndeclare global {\n  type ApplicationStream = {\n    data: any;\n  } & MessageEvent;\n\n  interface Window {\n    SBA: SBASettings;\n  }\n\n  type Extension = {\n    resourcePath: string;\n    resourceLocation: string;\n  };\n\n  type UITheme = {\n    color: string;\n    backgroundEnabled: boolean;\n    palette?: {\n      shade50: string;\n      shade100: string;\n      shade200: string;\n      shade300: string;\n      shade400: string;\n      shade500: string;\n      shade600: string;\n      shade700: string;\n      shade800: string;\n      shade900: string;\n    };\n  };\n\n  type PollTimer = {\n    cache: number;\n    datasource: number;\n    gc: number;\n    process: number;\n    memory: number;\n    threads: number;\n    logfile: number;\n  };\n\n  type ViewSettings = {\n    name: string;\n    enabled: boolean;\n  };\n\n  type ExternalView = {\n    label: string;\n    url: string;\n    order: number;\n    iframe: boolean;\n    children: ExternalView[];\n  };\n\n  type UISettings = {\n    title: string;\n    brand: string;\n    favicon: string;\n    faviconDanger: string;\n    pollTimer: PollTimer;\n    theme: UITheme;\n    notificationFilterEnabled: boolean;\n    rememberMeEnabled: boolean;\n    availableLanguages: string[];\n    routes: string[];\n    externalViews: ExternalView[];\n    viewSettings: ViewSettings[];\n    enableToasts: boolean;\n    hideInstanceUrl: boolean;\n    disableInstanceUrl: boolean;\n    allowUnsafeHtml: boolean;\n  };\n\n  type SBASettings = {\n    uiSettings: UISettings;\n    user: {\n      name: string;\n      [key: string]: any;\n    };\n    extensions: {\n      js?: Extension[];\n      css?: Extension[];\n    };\n    csrf: {\n      headerName: string;\n      parameterName: string;\n    };\n    [key: string]: any;\n  };\n\n  type ViewInstallFunctionParams = {\n    viewRegistry: ViewRegistry;\n  };\n\n  type SbaView = {\n    id: string;\n    name?: string;\n    parent: string;\n    handle: string | Component | RenderFunction;\n    path?: string;\n    href?: string;\n    order: number;\n    isEnabled: () => boolean;\n    component: Raw<any>;\n    group: string;\n    hasChildren: boolean;\n    props: any;\n  };\n\n  type View = ComponentView | LinkView;\n\n  interface ComponentView {\n    name: string;\n    path: string;\n    label?: string;\n    handle?: Component | RenderFunction;\n    order?: number;\n    group?: string;\n    component: Component;\n    isEnabled?: () => boolean;\n  }\n\n  interface LinkView {\n    name: string;\n    href?: string;\n    label: string;\n    order?: number;\n  }\n\n  type HealthStatus =\n    | 'DOWN'\n    | 'UP'\n    | 'RESTRICTED'\n    | 'UNKNOWN'\n    | 'OUT_OF_SERVICE'\n    | 'OFFLINE';\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/i18n/PrimeLocale.ts",
    "content": "import { PrimeVueConfiguration } from '@primevue/core/config';\nimport { all } from 'primelocale';\n\nexport const PrimeLocale = {\n  setLocale(primevue: PrimeVueConfiguration, locale: string) {\n    const primeLocale = all[locale];\n    primevue.config.locale = primeLocale ?? all.en;\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/i18n/i18n.de.json",
    "content": "{\n  \"error\": {\n    \"server_connection_failed\": \"Verbindung zum Server fehlgeschlagen.\"\n  },\n  \"term\": {\n    \"actuator_endpoint\": \"Actuator-Endpunkt\",\n    \"affects_all_instances\": \"Betrifft alle {count} Instanzen\",\n    \"affects_this_instance_only\": \"Betrifft nur diese Instanz\",\n    \"all\": \"Alle\",\n    \"application\": \"Anwendung\",\n    \"attributes\": \"Attribute\",\n    \"bytes\": \"Bytes\",\n    \"cancel\": \"Abbrechen\",\n    \"clear\": \"Leeren\",\n    \"cleared\": \"Geleert\",\n    \"close\": \"Schließen\",\n    \"confirm\": \"Bestätigen\",\n    \"delete\": \"Löschen\",\n    \"deleted\": \"Gelöscht\",\n    \"duration\": \"Dauer\",\n    \"epoch_time\": \"Epoch Zeit\",\n    \"event\": \"Ereignis\",\n    \"ever\": \"immer\",\n    \"executing\": \"Wird ausgeführt...\",\n    \"execution_failed\": \"Ausführung fehlgeschlagen.\",\n    \"execution_successful\": \"Ausführung erfolgreich.\",\n    \"failed\": \"Fehlgeschlagen\",\n    \"fetching_data\": \"Lade Daten...\",\n    \"fetch_failed\": \"Abruf der Daten fehlgeschlagen.\",\n    \"filter_action\": {\n      \"reset\": \"Filter zurücksetzen\"\n    },\n    \"float\": \"Float\",\n    \"homepage\": \"Startseite\",\n    \"hours\": \"{count} Stunde | {count} Stunden\",\n    \"instance\": \"Instanz\",\n    \"instances\": \"Instanzen\",\n    \"instances_tc\": \"{count} Instanz | {count} Instanzen\",\n    \"integer\": \"Integer\",\n    \"keyword_search\": \"Schlagwortsuche\",\n    \"menu\": {\n      \"open\": \"Menü öffnen\"\n    },\n    \"milliseconds\": \"Millisekunden\",\n    \"minutes\": \"{count} Minute | {count} Minuten\",\n    \"name\": \"Name\",\n    \"operations\": \"Operationen\",\n    \"save\": \"Speichern\",\n    \"stacktrace\": \"Stacktrace\",\n    \"suppress\": \"Unterdrücken\",\n    \"time\": \"Zeit\",\n    \"today\": \"Heute\",\n    \"unsuppress\": \"Reaktivieren\",\n    \"no_group\": \"Keine Gruppe\",\n    \"username\": \"Username\",\n    \"go_to_previous_page\": \"Gehe zur vorherigen Seite\",\n    \"go_to_page_n\": \"Gehe zu Seite {page}\",\n    \"current_page\": \"Seite {page}, aktuelle Seite\",\n    \"go_to_next_page\": \"Gehe zur nächsten Seite\",\n    \"no_results_for_term\": \"Keine Ergebnisse für \\\"{term}\\\".\"\n  },\n  \"health\": {\n    \"label\": \"Zustand\",\n    \"status\": {\n      \"DOWN\": \"down\",\n      \"UP\": \"up\",\n      \"RESTRICTED\": \"eingeschränkt\",\n      \"UNKNOWN\": \"unbekannt\",\n      \"OUT_OF_SERVICE\": \"außer Betrieb\",\n      \"OFFLINE\": \"offline\"\n    }\n  },\n  \"time_short\": {\n    \"unknown\": \"unbekannt\",\n    \"milliseconds\": \"{count}ms\",\n    \"seconds\": \"{count}s\",\n    \"minutes\": \"{count}min\",\n    \"hours\": \"{count}h\",\n    \"days\": \"{count}d\",\n    \"weeks\": \"{count}Wo\",\n    \"months\": \"{count}Mo\",\n    \"years\": \"{count}a\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/i18n/i18n.en.json",
    "content": "{\n  \"error\": {\n    \"server_connection_failed\": \"Server connection failed.  \"\n  },\n  \"term\": {\n    \"actuator_endpoint\": \"Actuator-Endpoint\",\n    \"affects_all_instances\": \"Affects all {count} instances\",\n    \"affects_this_instance_only\": \"Affects this instance only\",\n    \"all\": \"all\",\n    \"application\": \"Application\",\n    \"applications_tc\": \"{n} application | {n} applications\",\n    \"attributes\": \"Attributes\",\n    \"bytes\": \"Bytes\",\n    \"ok\": \"OK\",\n    \"cancel\": \"Cancel\",\n    \"clear\": \"Clear\",\n    \"cleared\": \"Cleared\",\n    \"close\": \"Close\",\n    \"confirm\": \"Confirm\",\n    \"context_refresh\": \"Refresh context\",\n    \"context_refresh_failed\": \"Failed\",\n    \"context_refreshed\": \"Context refreshed\",\n    \"delete\": \"Delete\",\n    \"deleted\": \"Deleted\",\n    \"duration\": \"Duration\",\n    \"epoch_time\": \"Epoch Time\",\n    \"event\": \"Event\",\n    \"ever\": \"ever\",\n    \"execute\": \"Execute\",\n    \"executing\": \"Executing...\",\n    \"execution_failed\": \"Execution failed\",\n    \"execution_successful\": \"Execution successful\",\n    \"failed\": \"Failed\",\n    \"fetching_data\": \"Fetching data...\",\n    \"fetch_failed\": \"Fetching of data failed.\",\n    \"float\": \"Float\",\n    \"homepage\": \"Homepage\",\n    \"filter\": \"Filter\",\n    \"filter_action\": {\n      \"reset\": \"Reset filters\"\n    },\n    \"hours\": \"{count} hour | {count} hours\",\n    \"instance\": \"Instance\",\n    \"instances\": \"Instances\",\n    \"instances_tc\": \"{count} instance | {count} instances\",\n    \"integer\": \"Integer\",\n    \"keyword_search\": \"Keyword search\",\n    \"menu\": {\n      \"open\": \"Open menu\"\n    },\n    \"milliseconds\": \"Milliseconds\",\n    \"minutes\": \"{count} minute | {count} minutes\",\n    \"name\": \"Name\",\n    \"operations\": \"Operations\",\n    \"save\": \"Save\",\n    \"stacktrace\": \"Stacktrace\",\n    \"suppress\": \"Suppress\",\n    \"time\": \"Time\",\n    \"today\": \"Today\",\n    \"unsuppress\": \"Unsuppress\",\n    \"no_group\": \"No Group\",\n    \"username\": \"Username\",\n    \"go_to_previous_page\": \"Go to previous page\",\n    \"go_to_page_n\": \"Go to page {page}\",\n    \"current_page\": \"Page {page}, current page\",\n    \"go_to_next_page\": \"Go to next page\",\n    \"no_results_for_term\": \"No results for \\\"{term}\\\".\"\n  },\n  \"health\": {\n    \"label\": \"Health status\",\n    \"status\": {\n      \"DOWN\": \"down\",\n      \"UP\": \"up\",\n      \"RESTRICTED\": \"restricted\",\n      \"UNKNOWN\": \"unknown\",\n      \"OUT_OF_SERVICE\": \"out of service\",\n      \"OFFLINE\": \"offline\"\n    }\n  },\n  \"time_short\": {\n    \"unknown\": \"unknown\",\n    \"milliseconds\": \"{count}ms\",\n    \"seconds\": \"{count}s\",\n    \"minutes\": \"{count}min\",\n    \"hours\": \"{count}h\",\n    \"days\": \"{count}d\",\n    \"weeks\": \"{count}wk\",\n    \"months\": \"{count}mo\",\n    \"years\": \"{count}y\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/i18n/i18n.es.json",
    "content": "{\n  \"error\": {\n    \"server_connection_failed\": \"Falló la conexión al servidor.  \"\n  },\n  \"term\": {\n    \"affects_all_instances\": \"Afecta a {count} instancias\",\n    \"affects_this_instance_only\": \"Afectar sólo a esta instancia\",\n    \"application\": \"Aplicación\",\n    \"attributes\": \"Atributos\",\n    \"bytes\": \"Bytes\",\n    \"cancel\": \"Cancelar\",\n    \"clear\": \"Borrar\",\n    \"cleared\": \"Borrado\",\n    \"close\": \"Cerrado\",\n    \"confirm\": \"Confirmar\",\n    \"context_refresh\": \"Actualizar contexto\",\n    \"context_refresh_failed\": \"Fallido\",\n    \"context_refreshed\": \"Contexto actualizado\",\n    \"delete\": \"Eliminar\",\n    \"deleted\": \"Eliminado\",\n    \"duration\": \"Duración\",\n    \"event\": \"Evento\",\n    \"ever\": \"Siempre\",\n    \"execute\": \"Ejecutar\",\n    \"executing\": \"Ejecutando...\",\n    \"execution_failed\": \"Ejecución fallida\",\n    \"execution_successful\": \"Ejecución exitosa\",\n    \"failed\": \"Fallido\",\n    \"float\": \"Float\",\n    \"hours\": \"{count} hora | {count} horas\",\n    \"instance\": \"Instancia\",\n    \"instances\": \"Instancias\",\n    \"integer\": \"Entero\",\n    \"milliseconds\": \"Milisegundos\",\n    \"minutes\": \"{count} minuto | {count} minutos\",\n    \"name\": \"Nombre\",\n    \"operations\": \"Operaciones\",\n    \"save\": \"Guardar\",\n    \"stacktrace\": \"Stacktrace\",\n    \"suppress\": \"Suprimir\",\n    \"time\": \"Horario\",\n    \"unsuppress\": \"Anular\",\n    \"username\": \"Usuario\",\n    \"go_to_previous_page\": \"Página anterior\",\n    \"go_to_page_n\": \"Ir a página {page}\",\n    \"go_to_next_page\": \"Página siguiente\"\n  },\n  \"time_short\": {\n    \"unknown\": \"unknown\",\n    \"milliseconds\": \"{count}ms\",\n    \"seconds\": \"{count}s\",\n    \"minutes\": \"{count}min\",\n    \"hours\": \"{count}h\",\n    \"days\": \"{count}d\",\n    \"weeks\": \"{count}sem\",\n    \"months\": \"{count}mes\",\n    \"years\": \"{count}a\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/i18n/i18n.fr.json",
    "content": "{\n  \"error\": {\n    \"server_connection_failed\": \"Erreur de connexion au serveur.  \"\n  },\n  \"term\": {\n    \"affects_all_instances\": \"Affects all {count} instances\",\n    \"affects_this_instance_only\": \"Affects only this instance\",\n    \"application\": \"Application\",\n    \"attributes\": \"Attributs\",\n    \"bytes\": \"Octets\",\n    \"cancel\": \"Annuler\",\n    \"clear\": \"Effacer\",\n    \"cleared\": \"Effacé\",\n    \"close\": \"Fermer\",\n    \"confirm\": \"Confirmer\",\n    \"delete\": \"Supprimer\",\n    \"deleted\": \"Supprimé\",\n    \"duration\": \"Durée\",\n    \"event\": \"Evènement\",\n    \"ever\": \"Toujours\",\n    \"executing\": \"Exécution...\",\n    \"execution_failed\": \"Exécution en échec.\",\n    \"execution_successful\": \"Exécution en succès.\",\n    \"failed\": \"Echec\",\n    \"float\": \"Décimal\",\n    \"hours\": \"{count} heure | {count} heures\",\n    \"instances\": \"Instances\",\n    \"integer\": \"Entier\",\n    \"milliseconds\": \"Millisecondes\",\n    \"minutes\": \"{count} minute | {count} minutes\",\n    \"name\": \"Nom\",\n    \"operations\": \"Opérations\",\n    \"save\": \"Sauvegarder\",\n    \"stacktrace\": \"Stacktrace\",\n    \"suppress\": \"Supprimer\",\n    \"time\": \"Temps\",\n    \"unsuppress\": \"Désinscription\",\n    \"username\": \"Identifiant\"\n  },\n  \"time_short\": {\n    \"unknown\": \"unknown\",\n    \"milliseconds\": \"{count}ms\",\n    \"seconds\": \"{count}s\",\n    \"minutes\": \"{count}min\",\n    \"hours\": \"{count}h\",\n    \"days\": \"{count}j\",\n    \"weeks\": \"{count}sem\",\n    \"months\": \"{count}m\",\n    \"years\": \"{count}a\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/i18n/i18n.is.json",
    "content": "{\n  \"error\": {\n    \"server_connection_failed\": \"Mistókst sambandið við þjón.\"\n  },\n  \"term\": {\n    \"affects_all_instances\": \"Affects all {count} instances\",\n    \"affects_this_instance_only\": \"Affects only this instance\",\n    \"application\": \"Forrit\",\n    \"attributes\": \"Einkenni\",\n    \"bytes\": \"Bæti\",\n    \"cancel\": \"Hætta við\",\n    \"clear\": \"Tæma\",\n    \"cleared\": \"Tæmt\",\n    \"close\": \"Ljúka\",\n    \"confirm\": \"Staðfesta\",\n    \"delete\": \"Eyđa\",\n    \"deleted\": \"Eytt\",\n    \"duration\": \"Tímalengd\",\n    \"event\": \"Atburður\",\n    \"ever\": \"alltaf\",\n    \"executing\": \"Í vinnslu…\",\n    \"execution_failed\": \"Framkvæmd mistekist.\",\n    \"execution_successful\": \"Framkvæmd farsæl.\",\n    \"failed\": \"Mistekist\",\n    \"float\": \"Float\",\n    \"hours\": \"{count} klst. | {count} klst.\",\n    \"instances\": \"Eintök\",\n    \"integer\": \"Integer\",\n    \"milliseconds\": \"Millisekúndar\",\n    \"minutes\": \"{count} Mínúta | {count} Mínútur\",\n    \"name\": \"Nafn\",\n    \"operations\": \"Rökaðgerðir\",\n    \"save\": \"Vista\",\n    \"stacktrace\": \"Stacktrace\",\n    \"suppress\": \"Hunsa\",\n    \"time\": \"Tími\",\n    \"unsuppress\": \"Endurvekja\",\n    \"username\": \"Notandanafn\"\n  },\n  \"time_short\": {\n    \"unknown\": \"óþekkt\",\n    \"milliseconds\": \"{count}ms\",\n    \"seconds\": \"{count}sek\",\n    \"minutes\": \"{count}mín\",\n    \"hours\": \"{count}klst\",\n    \"days\": \"{count}dag\",\n    \"weeks\": \"{count}vika\",\n    \"months\": \"{count}mán\",\n    \"years\": \"{count}ár\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/i18n/i18n.ko.json",
    "content": "{\n  \"error\": {\n    \"server_connection_failed\": \"서버 연결에 실패했습니다.  \"\n  },\n  \"term\": {\n    \"affects_all_instances\": \"{count} 개의 인스턴스에 대해 영향을 미칩니다.\",\n    \"affects_this_instance_only\": \"이 인스턴스에만 영향을 미칩니다.\",\n    \"all\": \"전체\",\n    \"application\": \"애플리케이션\",\n    \"applications_tc\": \"{n} 애플리케이션 | {n} 애플리케이션\",\n    \"attributes\": \"속성\",\n    \"bytes\": \"Bytes\",\n    \"ok\": \"예\",\n    \"cancel\": \"취소\",\n    \"clear\": \"초기화\",\n    \"cleared\": \"초기화됨\",\n    \"close\": \"닫기\",\n    \"confirm\": \"확인\",\n    \"context_refresh\": \"컨텍스트 갱신\",\n    \"context_refresh_failed\": \"실패\",\n    \"context_refreshed\": \"컨텍스트 갱신됨\",\n    \"delete\": \"삭제\",\n    \"deleted\": \"삭제됨\",\n    \"duration\": \"기간\",\n    \"event\": \"이벤트\",\n    \"ever\": \"ever\",\n    \"execute\": \"실행\",\n    \"executing\": \"실행 중...\",\n    \"execution_failed\": \"실행 실패\",\n    \"execution_successful\": \"실행 성공.\",\n    \"failed\": \"실패했습니다\",\n    \"fetching_data\": \"데이터 불러오는 중...\",\n    \"fetch_failed\": \"데이터를 불러오지 못하였습니다.\",\n    \"float\": \"Float\",\n    \"filter\": \"필터\",\n    \"hours\": \"{count} 시간 | {count} 시간\",\n    \"instance\": \"인스턴스\",\n    \"instances\": \"인스턴스\",\n    \"instances_tc\": \"{count} 인스턴스 | {count} 인스턴스\",\n    \"integer\": \"Integer\",\n    \"menu\": {\n      \"open\": \"메뉴 열기\"\n    },\n    \"milliseconds\": \"Milliseconds\",\n    \"minutes\": \"{count} 분 | {count} 분\",\n    \"name\": \"이름\",\n    \"operations\": \"Operations\",\n    \"save\": \"저장\",\n    \"stacktrace\": \"Stacktrace\",\n    \"suppress\": \"비활성화\",\n    \"time\": \"시간\",\n    \"unsuppress\": \"활성화\",\n    \"no_group\": \"그룹 없음\",\n    \"username\": \"사용자명\",\n    \"go_to_previous_page\": \"이전 페이지로 이동\",\n    \"go_to_page_n\": \"{page} 페이지로 이동\",\n    \"current_page\": \"페이지 {page}, 현재 페이지\",\n    \"go_to_next_page\": \"다음 페이지로 이동\",\n    \"no_results_for_term\": \"\\\"{term}\\\" 에 대한 결과가 없습니다.\"\n  },\n  \"health\": {\n    \"label\": \"Health status\",\n    \"status\": {\n      \"DOWN\": \"down\",\n      \"UP\": \"up\",\n      \"RESTRICTED\": \"restricted\",\n      \"UNKNOWN\": \"unknown\",\n      \"OUT_OF_SERVICE\": \"out of service\",\n      \"OFFLINE\": \"offline\"\n    }\n  },\n  \"time_short\": {\n    \"unknown\": \"알 수 없음\",\n    \"milliseconds\": \"{count}ms\",\n    \"seconds\": \"{count}초\",\n    \"minutes\": \"{count}분\",\n    \"hours\": \"{count}시간\",\n    \"days\": \"{count}일\",\n    \"weeks\": \"{count}주\",\n    \"months\": \"{count}개월\",\n    \"years\": \"{count}년\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/i18n/i18n.pt-BR.json",
    "content": "{\n  \"error\": {\n    \"server_connection_failed\": \"Falha na conexão com o servidor.  \"\n  },\n  \"term\": {\n    \"affects_all_instances\": \"Affects all {count} instances\",\n    \"affects_this_instance_only\": \"Affects only this instance\",\n    \"application\": \"Aplicação\",\n    \"attributes\": \"Atributos\",\n    \"bytes\": \"Bytes\",\n    \"cancel\": \"Cancelar\",\n    \"clear\": \"Clear\",\n    \"cleared\": \"Limpo\",\n    \"close\": \"Fechar\",\n    \"confirm\": \"Confirmar\",\n    \"delete\": \"Remover\",\n    \"deleted\": \"Removido\",\n    \"duration\": \"Duração\",\n    \"event\": \"Evento\",\n    \"ever\": \"nunca\",\n    \"executing\": \"Executando...\",\n    \"execution_failed\": \"Falha na execução.\",\n    \"execution_successful\": \"Executado com sucesso.\",\n    \"failed\": \"Falha\",\n    \"float\": \"Float\",\n    \"hours\": \"{count} hora | {count} horas\",\n    \"instances\": \"Instâncias\",\n    \"integer\": \"Integer\",\n    \"milliseconds\": \"Milissegundos\",\n    \"minutes\": \"{count} minuto | {count} minutos\",\n    \"name\": \"Nome\",\n    \"operations\": \"Operações\",\n    \"save\": \"Salvar\",\n    \"stacktrace\": \"Stacktrace\",\n    \"suppress\": \"Esconder\",\n    \"time\": \"Tempo\",\n    \"unsuppress\": \"Exibir\",\n    \"username\": \"Nome do usuário\"\n  },\n  \"time_short\": {\n    \"unknown\": \"desconhecido\",\n    \"milliseconds\": \"{count}ms\",\n    \"seconds\": \"{count}s\",\n    \"minutes\": \"{count}min\",\n    \"hours\": \"{count}h\",\n    \"days\": \"{count}d\",\n    \"weeks\": \"{count}sem\",\n    \"months\": \"{count}mês\",\n    \"years\": \"{count}a\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/i18n/i18n.ru.json",
    "content": "{\n  \"error\": {\n    \"server_connection_failed\": \"Ошибка подключения к серверу.  \"\n  },\n  \"term\": {\n    \"affects_all_instances\": \"Affects all {count} instances\",\n    \"affects_this_instance_only\": \"Affects only this instance\",\n    \"application\": \"Приложение\",\n    \"attributes\": \"Атрибуты\",\n    \"bytes\": \"Байты\",\n    \"cancel\": \"Отменить\",\n    \"clear\": \"Очистить\",\n    \"cleared\": \"Очищено\",\n    \"close\": \"Закрыть\",\n    \"confirm\": \"Подтвердить\",\n    \"delete\": \"Удалить\",\n    \"deleted\": \"Удален\",\n    \"duration\": \"Задержка\",\n    \"event\": \"Событие\",\n    \"ever\": \"всегда\",\n    \"executing\": \"Выполнение...\",\n    \"execution_failed\": \"Ошибка при выполнении.\",\n    \"execution_successful\": \"Успешно выполнено.\",\n    \"failed\": \"Ошибка\",\n    \"float\": \"Float\",\n    \"hours\": \"{count} ч. | {count} ч.\",\n    \"instances\": \"Экземпляры\",\n    \"integer\": \"Integer\",\n    \"milliseconds\": \"Мс.\",\n    \"minutes\": \"{count} мин. | {count} мин.\",\n    \"name\": \"Имя\",\n    \"operations\": \"Операции\",\n    \"save\": \"Сохранить\",\n    \"stacktrace\": \"Stacktrace\",\n    \"suppress\": \"Скрыть\",\n    \"time\": \"Время\",\n    \"unsuppress\": \"Включить\",\n    \"username\": \"Имя пользователя\"\n  },\n  \"time_short\": {\n    \"unknown\": \"неизвестно\",\n    \"milliseconds\": \"{count}мс\",\n    \"seconds\": \"{count}с\",\n    \"minutes\": \"{count}мин\",\n    \"hours\": \"{count}ч\",\n    \"days\": \"{count}д\",\n    \"weeks\": \"{count}нед\",\n    \"months\": \"{count}мес\",\n    \"years\": \"{count}г\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/i18n/i18n.zh-CN.json",
    "content": "{\n  \"error\": {\n    \"server_connection_failed\": \"服务连接失败。\"\n  },\n  \"term\": {\n    \"affects_all_instances\": \"Affects all {count} instances\",\n    \"affects_this_instance_only\": \"Affects only this instance\",\n    \"application\": \"应用\",\n    \"attributes\": \"属性\",\n    \"bytes\": \"字节\",\n    \"cancel\": \"取消\",\n    \"clear\": \"清除\",\n    \"cleared\": \"已清除\",\n    \"close\": \"关闭\",\n    \"confirm\": \"确认\",\n    \"delete\": \"删除\",\n    \"deleted\": \"已删除\",\n    \"duration\": \"持续时间\",\n    \"event\": \"事件\",\n    \"ever\": \"ever\",\n    \"executing\": \"执行中...\",\n    \"execution_failed\": \"执行失败。\",\n    \"execution_successful\": \"执行成功。\",\n    \"failed\": \"失败\",\n    \"float\": \"浮点型\",\n    \"hours\": \"{count} 小时 | {count} 小时\",\n    \"instances\": \"实例\",\n    \"integer\": \"整型\",\n    \"milliseconds\": \"毫秒\",\n    \"minutes\": \"{count} 分钟 | {count} 分钟\",\n    \"name\": \"名称\",\n    \"operations\": \"操作\",\n    \"save\": \"保存\",\n    \"stacktrace\": \"堆栈信息\",\n    \"suppress\": \"Suppress\",\n    \"time\": \"时间\",\n    \"unsuppress\": \"Unsuppress\",\n    \"username\": \"用户名\"\n  },\n  \"time_short\": {\n    \"unknown\": \"未知\",\n    \"milliseconds\": \"{count}毫秒\",\n    \"seconds\": \"{count}秒\",\n    \"minutes\": \"{count}分\",\n    \"hours\": \"{count}小时\",\n    \"days\": \"{count}天\",\n    \"weeks\": \"{count}周\",\n    \"months\": \"{count}月\",\n    \"years\": \"{count}年\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/i18n/i18n.zh-TW.json",
    "content": "{\n  \"error\": {\n    \"server_connection_failed\": \"伺服器連線失敗。\"\n  },\n  \"term\": {\n    \"actuator_endpoint\": \"Actuator 端點\",\n    \"affects_all_instances\": \"影響全部 {count} 個執行個體\",\n    \"affects_this_instance_only\": \"僅影響此執行個體\",\n    \"all\": \"全部\",\n    \"application\": \"應用程式\",\n    \"applications_tc\": \"{n} 個應用程式 | {n} 個應用程式\",\n    \"attributes\": \"屬性\",\n    \"bytes\": \"位元組\",\n    \"ok\": \"確定\",\n    \"cancel\": \"取消\",\n    \"clear\": \"清除\",\n    \"cleared\": \"已清除\",\n    \"close\": \"關閉\",\n    \"confirm\": \"確認\",\n    \"context_refresh\": \"重新整理內容\",\n    \"context_refresh_failed\": \"失敗\",\n    \"context_refreshed\": \"內容已重新整理\",\n    \"delete\": \"刪除\",\n    \"deleted\": \"已刪除\",\n    \"duration\": \"持續時間\",\n    \"epoch_time\": \"Epoch 時間\",\n    \"event\": \"事件\",\n    \"ever\": \"任何時候\",\n    \"execute\": \"執行\",\n    \"executing\": \"執行中...\",\n    \"execution_failed\": \"執行失敗。\",\n    \"execution_successful\": \"執行成功。\",\n    \"failed\": \"失敗\",\n    \"fetching_data\": \"正在取得資料...\",\n    \"fetch_failed\": \"取得資料失敗。\",\n    \"float\": \"浮點數\",\n    \"homepage\": \"首頁\",\n    \"filter\": \"篩選\",\n    \"filter_action\": {\n      \"reset\": \"重設篩選條件\"\n    },\n    \"hours\": \"{count} 小時 | {count} 小時\",\n    \"instance\": \"執行個體\",\n    \"instances\": \"執行個體\",\n    \"instances_tc\": \"{count} 個執行個體 | {count} 個執行個體\",\n    \"integer\": \"整數\",\n    \"keyword_search\": \"關鍵字搜尋\",\n    \"menu\": {\n      \"open\": \"開啟選單\"\n    },\n    \"milliseconds\": \"毫秒\",\n    \"minutes\": \"{count} 分鐘 | {count} 分鐘\",\n    \"name\": \"名稱\",\n    \"operations\": \"作業\",\n    \"save\": \"儲存\",\n    \"stacktrace\": \"堆疊追蹤\",\n    \"suppress\": \"隱藏通知\",\n    \"time\": \"時間\",\n    \"today\": \"今天\",\n    \"unsuppress\": \"取消隱藏\",\n    \"no_group\": \"無群組\",\n    \"username\": \"使用者名稱\",\n    \"go_to_previous_page\": \"前往上一頁\",\n    \"go_to_page_n\": \"前往第 {page} 頁\",\n    \"current_page\": \"第 {page} 頁，目前頁面\",\n    \"go_to_next_page\": \"前往下一頁\",\n    \"no_results_for_term\": \"找不到「{term}」的結果。\"\n  },\n  \"health\": {\n    \"label\": \"健康狀態\",\n    \"status\": {\n      \"DOWN\": \"停止\",\n      \"UP\": \"正常\",\n      \"RESTRICTED\": \"受限\",\n      \"UNKNOWN\": \"未知\",\n      \"OUT_OF_SERVICE\": \"停止服務\",\n      \"OFFLINE\": \"離線\"\n    }\n  },\n  \"time_short\": {\n    \"unknown\": \"未知\",\n    \"milliseconds\": \"{count} 毫秒\",\n    \"seconds\": \"{count} 秒\",\n    \"minutes\": \"{count} 分\",\n    \"hours\": \"{count} 小時\",\n    \"days\": \"{count} 天\",\n    \"weeks\": \"{count} 週\",\n    \"months\": \"{count} 個月\",\n    \"years\": \"{count} 年\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/i18n/index.ts",
    "content": "import { isEmpty, merge } from 'lodash-es';\nimport { createI18n } from 'vue-i18n';\n\nimport sbaConfig from '@/sba-config';\n\nconst context = import.meta.glob('../**/(*.)?i18n.*.json', { eager: true });\nconst messages = Object.keys(context)\n  .map((key) => {\n    const localeFromFile = /\\.*i18n\\.?([^/]*)\\.json$/.exec(key);\n    const messages = (context[key] as { default: never }).default;\n    if (localeFromFile[1]) {\n      return {\n        [localeFromFile[1]]: messages,\n      };\n    } else {\n      return messages;\n    }\n  })\n  .reduce((prev, cur) => merge(prev, cur), {});\n\nexport function getAvailableLocales() {\n  const valueFromServer = sbaConfig.uiSettings.availableLanguages;\n\n  const strings = Object.keys(messages);\n  return isEmpty(valueFromServer)\n    ? strings\n    : valueFromServer.filter((language) => strings.includes(language));\n}\n\nlet browserLanguage = navigator.language;\nif (!browserLanguage.includes('zh')) {\n  browserLanguage = browserLanguage.split('-')[0];\n}\n\nconst i18n = createI18n({\n  locale: getAvailableLocales().includes(browserLanguage)\n    ? browserLanguage\n    : 'en',\n  fallbackLocale: 'en',\n  legacy: false,\n  silentFallbackWarn: process.env.NODE_ENV === 'production',\n  silentTranslationWarn: process.env.NODE_ENV === 'production',\n  messages,\n});\n\nexport default i18n;\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/index.css",
    "content": "/*!\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n@import './toast-theme.css';\n\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\nbody {\n  font-family:\n    BlinkMacSystemFont,\n    -apple-system,\n    Segoe UI,\n    Roboto,\n    Oxygen,\n    Ubuntu,\n    Cantarell,\n    Fira Sans,\n    Droid Sans,\n    Helvetica Neue,\n    Helvetica,\n    Arial,\n    sans-serif;\n\n  @apply text-base;\n}\n\n#app {\n  @apply h-full;\n}\n\n.container {\n  @apply px-2;\n}\n\nth {\n  text-align: left;\n}\n\n.table {\n  @apply text-left text-gray-500 dark:text-gray-400 bg-white border-collapse;\n}\n\n.table thead {\n  @apply border-b bg-gray-100 rounded-md;\n}\n\n.table th,\n.table td {\n  @apply py-3 px-3;\n}\n\n.table.table-full {\n  @apply w-full;\n}\n\n.table.table-sm {\n  @apply text-sm;\n}\n\n.table thead {\n  @apply text-gray-700 bg-gray-50 dark:bg-gray-700 dark:text-gray-400;\n}\n\n.table tr:not(:last-of-type) {\n  @apply border-b;\n}\n\n.-rotate-90 {\n  --tw-rotate: -90deg;\n  transform: rotate(var(--tw-rotate));\n}\n\n.rotate-90 {\n  --tw-rotate: 90deg;\n  transform: rotate(var(--tw-rotate));\n}\n\ntable.table-wide td {\n  @apply px-2 py-1.5;\n}\n\ntable.table-striped tr:nth-child(even) {\n  @apply bg-gray-50;\n}\n\ntd.label,\ntd .label {\n  @apply text-sm font-medium text-gray-500;\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/index.html",
    "content": "<!--\n  ~ Copyright 2014-2020 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<!doctype html>\n<html class=\"h-full\">\n  <head>\n    <base href=\"/\" th:href=\"${baseUrl}\" />\n    <meta charset=\"utf-8\" />\n    <title th:text=\"${uiSettings.title}\">Spring Boot Admin</title>\n    <link\n      rel=\"shortcut icon\"\n      th:href=\"${uiSettings.favicon}\"\n      type=\"image/png\"\n    />\n    <meta content=\"width=device-width, initial-scale=1\" name=\"viewport\" />\n    <meta content=\"IE=edge,chrome=1\" http-equiv=\"X-UA-Compatible\" />\n    <meta content=\"telephone=no,email=no\" name=\"format-detection\" />\n    <meta\n      content=\"#42d3a5\"\n      name=\"theme-color\"\n      th:content=\"${uiSettings.theme.color}\"\n    />\n\n    <link href=\"./index.css\" rel=\"stylesheet\" />\n    <link href=\"/variables.css\" rel=\"stylesheet\" />\n\n    <link\n      as=\"style\"\n      rel=\"preload\"\n      th:each=\"cssExtension : ${cssExtensions}\"\n      th:href=\"'extensions/' + ${cssExtension.resourcePath}\"\n    />\n    <script lang=\"javascript\" src=\"sba-settings.js\"></script>\n    <link\n      as=\"script\"\n      rel=\"preload\"\n      th:each=\"jsExtension : ${jsExtensions}\"\n      th:href=\"'extensions/' + ${jsExtension.resourcePath}\"\n    />\n  </head>\n  <body class=\"h-full\">\n    <div id=\"app\"></div>\n\n    <script src=\"./index.ts\" type=\"module\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/index.stories.jsx",
    "content": "import { DocsContainer } from '@storybook/addon-docs/blocks';\n\nconst ExampleContainer = ({ children, ...props }) => {\n  return (\n    <DocsContainer {...props}>\n      <div className=\"prose\">{children}</div>\n    </DocsContainer>\n  );\n};\n\nexport default {\n  parameters: {\n    controls: {\n      matchers: {\n        color: /(background|color)$/i,\n        date: /Date$/,\n      },\n    },\n    docs: {\n      container: ExampleContainer,\n    },\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/index.ts",
    "content": "/*\n * Copyright 2014-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { definePreset } from '@primeuix/themes';\nimport Aura from '@primeuix/themes/aura';\nimport { usePrimeVue } from '@primevue/core';\nimport NotificationcenterPlugin from '@stekoe/vue-toast-notificationcenter';\nimport moment from 'moment';\nimport { Tooltip } from 'primevue';\nimport PrimeVue from 'primevue/config';\nimport * as Vue from 'vue';\nimport {\n  createApp,\n  h,\n  onBeforeMount,\n  onBeforeUnmount,\n  reactive,\n  watch,\n} from 'vue';\nimport { useI18n } from 'vue-i18n';\nimport { useRoute, useRouter } from 'vue-router';\n\nimport components from './components';\nimport {\n  CUSTOM_ROUTES_ADDED_EVENT,\n  createViewRegistry,\n  useViewRegistry,\n} from './composables/ViewRegistry';\nimport {\n  createApplicationStore,\n  useApplicationStore,\n} from './composables/useApplicationStore';\nimport i18n from './i18n';\nimport Notifications from './notifications';\nimport SbaModalPlugin from './plugins/modal';\nimport sbaConfig from './sba-config';\nimport views from './views';\n\nimport { PrimeLocale } from '@/i18n/PrimeLocale';\nimport eventBus from '@/services/bus';\nimport sbaShell from '@/shell';\n\nconst applicationStore = createApplicationStore();\nconst viewRegistry = createViewRegistry();\n\nglobalThis.Vue = Vue;\nglobalThis.SBA.viewRegistry = useViewRegistry();\nglobalThis.SBA.useApplicationStore = useApplicationStore;\nglobalThis.SBA.useI18n = () => i18n.global;\nglobalThis.SBA.use = ({ install }) => {\n  install({\n    viewRegistry: globalThis.SBA.viewRegistry,\n    applicationStore: globalThis.SBA.useApplicationStore,\n    i18n: i18n.global,\n  });\n};\n\nsbaConfig.extensions.js.forEach((extension) => {\n  const script = document.createElement('script');\n  script.src = `extensions/${extension.resourcePath}`;\n  document.head.appendChild(script);\n});\n\nsbaConfig.extensions.css.forEach((extension) => {\n  const link = document.createElement('link');\n  link.rel = 'stylesheet';\n  link.href = `extensions/${extension.resourcePath}`;\n  document.head.appendChild(link);\n});\n\nmoment.locale(navigator.language.split('-')[0]);\n\nconst installables = [Notifications, ...views];\ninstallables.forEach((installable) => {\n  try {\n    installable.install({\n      viewRegistry,\n      applicationStore,\n    });\n  } catch (e) {\n    console.error('Error while installing ', installable, e);\n  }\n});\n\nconst app = createApp({\n  setup() {\n    const router = useRouter();\n    const route = useRoute();\n    const { applications, applicationsInitialized, error } =\n      useApplicationStore();\n    const { t, locale } = useI18n();\n    const primevue = usePrimeVue();\n\n    onBeforeMount(() => {\n      applicationStore.start();\n    });\n\n    onBeforeUnmount(() => {\n      applicationStore.stop();\n    });\n\n    watch(\n      locale,\n      () => {\n        PrimeLocale.setLocale(primevue, locale.value);\n      },\n      { immediate: true },\n    );\n\n    const routesAddedEventHandler = async () => {\n      eventBus.off(CUSTOM_ROUTES_ADDED_EVENT, routesAddedEventHandler);\n      await router.replace(route);\n    };\n    eventBus.on(CUSTOM_ROUTES_ADDED_EVENT, routesAddedEventHandler);\n\n    return () =>\n      h(\n        sbaShell,\n        reactive({\n          applications,\n          applicationsInitialized,\n          error,\n          t,\n        }),\n      );\n  },\n});\n\napp.use(i18n);\napp.use(components);\napp.use(NotificationcenterPlugin, {\n  duration: 10_000,\n});\napp.use(SbaModalPlugin, { i18n });\napp.use(viewRegistry.createRouter());\napp.directive('tooltip', Tooltip);\napp.use(PrimeVue, {\n  theme: {\n    preset: definePreset(Aura, {\n      semantic: {\n        primary: {\n          50: 'rgb(var(--main-50))',\n          100: 'rgb(var(--main-100))',\n          200: 'rgb(var(--main-200))',\n          300: 'rgb(var(--main-300))',\n          400: 'rgb(var(--main-400))',\n          500: 'rgb(var(--main-500))',\n          600: 'rgb(var(--main-600))',\n          700: 'rgb(var(--main-700))',\n          800: 'rgb(var(--main-800))',\n          900: 'rgb(var(--main-900))',\n        },\n      },\n    }),\n    options: {\n      darkModeSelector: false,\n    },\n  },\n});\napp.mount('#app');\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/login/login.i18n.de.json",
    "content": "{\n  \"login\": {\n    \"button_login\": \"Anmelden\",\n    \"logout_successful\": \"Erfolgreich abgemeldet\",\n    \"remember_me\": \"Angemeldet bleiben\",\n    \"error\": {\n      \"invalid_username_or_password\": \"Benutzername oder Passwort falsch\"\n    },\n    \"placeholder\": {\n      \"username\": \"Benutzername\",\n      \"password\": \"Passwort\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/login/login.i18n.en.json",
    "content": "{\n  \"login\": {\n    \"button_login\": \"Login\",\n    \"logout_successful\": \"Logout successful\",\n    \"remember_me\": \"Remember me\",\n    \"error\": {\n      \"login_required\": \"Login required to access the resource <em>(Error: {code})</em>.\",\n      \"invalid_username_or_password\": \"Invalid username or password\"\n    },\n    \"placeholder\": {\n      \"username\": \"Username\",\n      \"password\": \"Password\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/login/login.i18n.es.json",
    "content": "{\n  \"login\": {\n    \"button_login\": \"Ingresar\",\n    \"logout_successful\": \"Logout exitoso\",\n    \"remember_me\": \"Recordarme\",\n    \"error\": {\n      \"invalid_username_or_password\": \"Usuario o contraseña inválida\"\n    },\n    \"placeholder\": {\n      \"username\": \"Usuario\",\n      \"password\": \"Contraseña\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/login/login.i18n.fr.json",
    "content": "{\n  \"login\": {\n    \"button_login\": \"Se Connecter\",\n    \"logout_successful\": \"Déconnexion réussie\",\n    \"remember_me\": \"Se souvenir de moi\",\n    \"error\": {\n      \"invalid_username_or_password\": \"Nom d'utilisateur ou mot de passe incorrect\"\n    },\n    \"placeholder\": {\n      \"username\": \"Nom d'utilisateur\",\n      \"password\": \"Mot de passe\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/login/login.i18n.is.json",
    "content": "{\n  \"login\": {\n    \"button_login\": \"Skrá inn\",\n    \"logout_successful\": \"Skráð út\",\n    \"remember_me\": \"Mundu eftir mér\",\n    \"error\": {\n      \"invalid_username_or_password\": \"Notandanafn eða lykilorð rángt\"\n    },\n    \"placeholder\": {\n      \"username\": \"Notandanafn\",\n      \"password\": \"Lykilorð\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/login/login.i18n.ko.json",
    "content": "{\n  \"login\": {\n    \"button_login\": \"로그인\",\n    \"logout_successful\": \"로그아웃을 성공했습니다.\",\n    \"remember_me\": \"사용자 기억\",\n    \"error\": {\n      \"invalid_username_or_password\": \"잘못된 사용자명 또는 암호 입니다.\"\n    },\n    \"placeholder\": {\n      \"username\": \"사용자명\",\n      \"password\": \"암호\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/login/login.i18n.pt-BR.json",
    "content": "{\n  \"login\": {\n    \"button_login\": \"Entrar\",\n    \"logout_successful\": \"Logout com sucesso\",\n    \"remember_me\": \"Me lembrar\",\n    \"error\": {\n      \"invalid_username_or_password\": \"Usuário ou senha inválidos\"\n    },\n    \"placeholder\": {\n      \"username\": \"Nome do usuário\",\n      \"password\": \"Senha\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/login/login.i18n.ru.json",
    "content": "{\n  \"login\": {\n    \"button_login\": \"Войти\",\n    \"logout_successful\": \"Вы успешно вышли из системы\",\n    \"remember_me\": \"Запомнить меня\",\n    \"error\": {\n      \"invalid_username_or_password\": \"Неверные имя пользователя или пароль\"\n    },\n    \"placeholder\": {\n      \"username\": \"Имя пользователя\",\n      \"password\": \"Пароль\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/login/login.i18n.zh-CN.json",
    "content": "{\n  \"login\": {\n    \"button_login\": \"登录\",\n    \"logout_successful\": \"注销成功\",\n    \"remember_me\": \"记住用户\",\n    \"error\": {\n      \"invalid_username_or_password\": \"无效的用户名或密码\"\n    },\n    \"placeholder\": {\n      \"username\": \"用户名\",\n      \"password\": \"密码\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/login/login.i18n.zh-TW.json",
    "content": "{\n  \"login\": {\n    \"button_login\": \"登入\",\n    \"logout_successful\": \"登出成功\",\n    \"remember_me\": \"記住我\",\n    \"error\": {\n      \"login_required\": \"需要登入才能存取此資源 <em>(錯誤：{code})</em>。\",\n      \"invalid_username_or_password\": \"無效的使用者名稱或密碼\"\n    },\n    \"placeholder\": {\n      \"username\": \"使用者名稱\",\n      \"password\": \"密碼\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/login/login.stories.ts",
    "content": "import Login from './login.vue';\n\nexport default {\n  component: Login,\n  title: 'Components/Form/Login',\n};\n\nconst Template = (args) => ({\n  components: { Login },\n  setup() {\n    return { args };\n  },\n  template: '<Login v-bind=\"args\" />',\n});\n\nexport const LoginForm = {\n  render: Template,\n\n  args: {\n    title: 'Spring Boot Admin',\n    param: {},\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/login/login.vue",
    "content": "<template>\n  <form class=\"w-5/6 md:1/2 max-w-lg\" method=\"post\">\n    <sba-panel>\n      <input\n        v-if=\"csrf\"\n        :name=\"csrf.parameterName\"\n        :value=\"csrf.token\"\n        type=\"hidden\"\n      />\n      <div class=\"flex text-lg pb-3 items-center\">\n        <img v-if=\"icon\" :src=\"icon\" class=\"w-8 h-8 mr-2\" />\n        <h1 class=\"title has-text-primary\" v-text=\"title\" />\n      </div>\n      <div class=\"relative border-t -ml-4 -mr-4 overflow-hidden\">\n        <sba-wave class=\"bg-wave--login\" />\n        <div class=\"ml-4 mr-4 pt-2 z-10 relative\">\n          <sba-alert :error=\"error\" />\n          <sba-alert :error=\"logout\" :severity=\"Severity.INFO\" />\n          <div :class=\"{ 'has-errors': error }\" class=\"pb-4 form-group\">\n            <sba-input\n              :label=\"t('login.placeholder.username')\"\n              autocomplete=\"username\"\n              name=\"username\"\n              type=\"text\"\n              autofocus\n            />\n            <sba-input\n              :label=\"t('login.placeholder.password')\"\n              autocomplete=\"current-password\"\n              name=\"password\"\n              type=\"password\"\n            />\n            <sba-checkbox\n              v-if=\"rememberMeEnabled\"\n              :label=\"t('login.remember_me')\"\n              class=\"justify-end\"\n              name=\"remember-me\"\n            />\n          </div>\n        </div>\n      </div>\n\n      <template #footer>\n        <div class=\"text-right\">\n          <sba-button>\n            {{ t('login.button_login') }}\n          </sba-button>\n        </div>\n      </template>\n    </sba-panel>\n  </form>\n</template>\n\n<script setup>\nimport { computed } from 'vue';\nimport { useI18n } from 'vue-i18n';\n\nimport SbaAlert, { Severity } from '@/components/sba-alert';\nimport SbaButton from '@/components/sba-button';\nimport SbaCheckbox from '@/components/sba-checkbox';\nimport SbaInput from '@/components/sba-input';\nimport SbaPanel from '@/components/sba-panel';\nimport SbaWave from '@/components/sba-wave';\n\nconst i18n = useI18n();\nconst t = i18n.t;\n\nconst props = defineProps({\n  param: {\n    type: Object,\n    default: () => ({}),\n  },\n  icon: {\n    type: String,\n    default: undefined,\n  },\n  title: {\n    type: String,\n    required: true,\n  },\n  csrf: {\n    type: Object,\n    default: undefined,\n  },\n  theme: {\n    type: Object,\n    default: undefined,\n  },\n});\n\nconst { rememberMeEnabled } = window.uiSettings;\n\nconst error = computed(() => {\n  let errors = props.param.error;\n\n  if (Array.isArray(errors)) {\n    if (errors.includes('401')) {\n      return t('login.error.login_required', { code: errors[0] });\n    } else {\n      return t('login.error.invalid_username_or_password');\n    }\n  } else {\n    return undefined;\n  }\n});\n\nconst logout = computed(() => {\n  return props.param.logout !== undefined\n    ? t('login.logout_successful')\n    : undefined;\n});\n</script>\n\n<style scoped>\n.bg-wave--login {\n  @apply z-0 absolute left-0;\n  min-width: 100%;\n  height: 4rem;\n}\n\n.form-group {\n  @apply grid grid-cols-1 gap-2;\n}\n\n.form-group.has-errors label {\n  @apply text-red-500;\n}\n\n.form-group.has-errors input {\n  @apply focus:ring-red-500 focus:border-red-500 border-red-400;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/login.css",
    "content": "/*!\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n:root {\n  --bg-color-start: #71e69c;\n  --bg-color-stop: #09351a;\n}\n\n.bg-color-start {\n  transition: 0.4s ease;\n  stop-color: var(--bg-color-start);\n}\n\n.bg-color-stop {\n  transition: 0.4s ease;\n  stop-color: var(--bg-color-stop);\n}\n\nbody {\n  font-family:\n    BlinkMacSystemFont,\n    -apple-system,\n    Segoe UI,\n    Roboto,\n    Oxygen,\n    Ubuntu,\n    Cantarell,\n    Fira Sans,\n    Droid Sans,\n    Helvetica Neue,\n    Helvetica,\n    Arial,\n    sans-serif;\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/login.html",
    "content": "<!--\n  ~ Copyright 2014-2019 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<!doctype html>\n<html class=\"h-full\">\n  <head>\n    <base href=\"/\" th:href=\"${baseUrl}\" />\n    <meta charset=\"utf-8\" />\n    <meta content=\"width=device-width, initial-scale=1\" name=\"viewport\" />\n    <meta content=\"IE=edge,chrome=1\" http-equiv=\"X-UA-Compatible\" />\n    <meta content=\"telephone=no,email=no\" name=\"format-detection\" />\n    <meta content=\"#42d3a5\" name=\"theme-color\" />\n\n    <link\n      rel=\"shortcut icon\"\n      th:href=\"${uiSettings.favicon}\"\n      type=\"image/png\"\n    />\n    <title th:text=\"${uiSettings.title}\">Spring Boot Admin - Login</title>\n    <script th:inline=\"javascript\">\n      var csrf = /*[[${_csrf}]]*/ {};\n      var uiSettings = /*[[${uiSettings}]]*/ {};\n      var param = /*[[${param}]]*/ {};\n    </script>\n  </head>\n  <body class=\"flex flex-col min-h-full h-full\">\n    <div\n      class=\"absolute w-full h-full t-0 l-0 bg-white bg-opacity-40 backdrop-blur-sm z-0\"\n    ></div>\n    <section\n      id=\"login\"\n      class=\"relative z-10 flex items-center justify-center h-full\"\n    ></section>\n\n    <script lang=\"javascript\" src=\"./login.ts\" type=\"module\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/login.ts",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { createApp } from 'vue';\n\nimport './login.css';\n\nimport i18n from './i18n';\nimport Login from './login/login.vue';\n\nconst app = createApp(Login, {\n  csrf: window.csrf,\n  icon: window.uiSettings.loginIcon,\n  title: window.uiSettings.title,\n  theme: window.uiSettings.theme,\n  param: window.param,\n});\napp.use(i18n);\napp.mount('#login');\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mixins/subscribing.ts",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport default {\n  created() {\n    this.subscribe();\n  },\n  beforeUnmount() {\n    this.unsubscribe();\n  },\n  methods: {\n    async subscribe() {\n      if (!this.subscription) {\n        this.subscription = await this.createSubscription();\n      }\n    },\n    unsubscribe() {\n      if (this.subscription && !this.subscription.closed) {\n        try {\n          this.subscription.unsubscribe();\n        } finally {\n          this.subscription = null;\n        }\n      }\n    },\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/applications/data.ts",
    "content": "/*\n * Copyright 2014-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { HealthStatus } from '../../HealthStatus';\n\nexport const instance = {\n  id: 'bba333956ae6',\n  version: 3,\n  registration: {\n    name: 'spring-boot-admin-sample-servlet',\n    managementUrl: 'http://localhost:8080/actuator',\n    healthUrl: 'http://localhost:8080/actuator/health',\n    serviceUrl: 'http://localhost:8080/',\n    source: 'http-api',\n    metadata: {\n      startup: '2021-10-29T08:50:07.486289+02:00',\n      'tags.environment': 'test',\n    },\n  },\n  registered: true,\n  statusInfo: {\n    status: 'UP',\n    details: {\n      db: {\n        status: 'UP',\n        details: {\n          database: 'HSQL Database Engine',\n          validationQuery: 'isValid()',\n        },\n      },\n      diskSpace: {\n        status: 'UP',\n        details: {\n          total: 499963174912,\n          free: 108980899840,\n          threshold: 10485760,\n          exists: true,\n        },\n      },\n      ping: { status: 'UP' },\n    },\n  },\n  statusTimestamp: '2021-10-29T06:50:09.600276Z',\n  info: {\n    tags: { security: 'insecure' },\n    'scm-url': '@scm.url@',\n    'build-url': 'https://travis-ci.org/codecentric/spring-boot-admin',\n    build: {\n      artifact: 'spring-boot-admin-sample-servlet',\n      name: 'Spring Boot Admin Sample Servlet',\n      time: '2021-09-17T09:53:18.987Z',\n      version: '2.5.2-SNAPSHOT',\n      group: 'de.codecentric',\n    },\n  },\n  endpoints: [\n    { id: 'sessions', url: 'http://localhost:8080/actuator/sessions' },\n    {\n      id: 'httptrace',\n      url: 'http://localhost:8080/actuator/httptrace',\n    },\n    {\n      id: 'httptexchanges',\n      url: 'http://localhost:8080/actuator/httpexchanges',\n    },\n    { id: 'caches', url: 'http://localhost:8080/actuator/caches' },\n    {\n      id: 'loggers',\n      url: 'http://localhost:8080/actuator/loggers',\n    },\n    { id: 'logfile', url: 'http://localhost:8080/actuator/logfile' },\n    {\n      id: 'custom',\n      url: 'http://localhost:8080/actuator/custom',\n    },\n    { id: 'health', url: 'http://localhost:8080/actuator/health' },\n    {\n      id: 'env',\n      url: 'http://localhost:8080/actuator/env',\n    },\n    { id: 'heapdump', url: 'http://localhost:8080/actuator/heapdump' },\n    {\n      id: 'scheduledtasks',\n      url: 'http://localhost:8080/actuator/scheduledtasks',\n    },\n    { id: 'mappings', url: 'http://localhost:8080/actuator/mappings' },\n    {\n      id: 'startup',\n      url: 'http://localhost:8080/actuator/startup',\n    },\n    { id: 'beans', url: 'http://localhost:8080/actuator/beans' },\n    {\n      id: 'configprops',\n      url: 'http://localhost:8080/actuator/configprops',\n    },\n    { id: 'threaddump', url: 'http://localhost:8080/actuator/threaddump' },\n    {\n      id: 'metrics',\n      url: 'http://localhost:8080/actuator/metrics',\n    },\n    { id: 'conditions', url: 'http://localhost:8080/actuator/conditions' },\n    {\n      id: 'auditevents',\n      url: 'http://localhost:8080/actuator/auditevents',\n    },\n    { id: 'info', url: 'http://localhost:8080/actuator/info' },\n    {\n      id: 'shutdown',\n      url: 'http://localhost:8080/actuator/shutdown',\n    },\n    {\n      id: 'restart',\n      url: 'http://localhost:8080/actuator/restart',\n    },\n  ],\n  buildVersion: '2.5.2-SNAPSHOT',\n  tags: { environment: 'test', security: 'insecure' },\n};\n\nexport const applications = Object.entries(HealthStatus).map((e) => {\n  const STATUS = e[0];\n\n  return {\n    name: `application-${STATUS}`,\n    buildVersion: '2.5.2-SNAPSHOT',\n    status: STATUS,\n    statusTimestamp: '2021-10-29T06:50:09.600276Z',\n    instances: [instance],\n  };\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/applications/index.ts",
    "content": "import { http } from 'msw';\n\nimport { applications } from './data';\n\nconst applicationsEndpoint = [\n  http.get('/applications', () => {\n    return res(ctx.status(404), ctx.json(applications));\n  }),\n];\n\nexport default applicationsEndpoint;\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/browser.ts",
    "content": "import { setupWorker } from 'msw/browser';\n\nimport auditEventsEndpoint from './instance/auditevents/index';\nimport flywayEndpoints from './instance/flyway/index';\nimport httpTraceEndpoints from './instance/httptrace/index';\nimport liquibaseEndpoints from './instance/liquibase/index';\nimport mappingsEndpoint from './instance/mappings/index';\nimport metricsEndpoint from './instance/metrics/index';\nimport sessionEndpoints from './instance/sessions/index';\n\nconst handler = [\n  ...mappingsEndpoint,\n  ...liquibaseEndpoints,\n  ...flywayEndpoints,\n  ...auditEventsEndpoint,\n  ...metricsEndpoint,\n  ...httpTraceEndpoints,\n  ...sessionEndpoints,\n];\n\nexport const worker = setupWorker(...handler);\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/fixtures/eventStream/registerWithOneInstance.ts",
    "content": "export const registerWithOneInstance = {\n  name: 'spring-boot-admin-sample-servlet',\n  buildVersion: '3.0.0-SNAPSHOT',\n  status: 'UP',\n  statusTimestamp: '2022-12-19T10:07:20.646905Z',\n  instances: [\n    {\n      id: '25b07dc98984',\n      version: 3,\n      registration: {\n        name: 'spring-boot-admin-sample-servlet',\n        managementUrl:\n          'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator',\n        healthUrl:\n          'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/health',\n        serviceUrl:\n          'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/',\n        source: 'http-api',\n        metadata: {\n          'tags.de-service-test-6': 'A large content',\n          'tags.de-service-test-4': 'A large content',\n          'tags.de-service-test-5': 'A large content',\n          startup: '2022-12-19T11:04:38.171314+01:00',\n          'kubectl.kubernetes.iolast-applied-configuration':\n            '{\"name\":\"jvm.threads.peak\",\"description\":\"The peak live thread count since the Java virtual machine started or peak was reset\",\"baseUnit\":\"threads\",\"measurements\":[{\"statistic\":\"VALUE\",\"value\":64.0}],\"availableTags\":[]}',\n          'user.name': 'user',\n          'tags.de-service-test-2': 'A large content',\n          'user.password': '******',\n          'tags.de-service-test-3': 'A large content',\n          'tags.environment': 'test',\n          'tags.de-service-test-1': 'A large content',\n        },\n      },\n      registered: true,\n      statusInfo: {\n        status: 'UP',\n        details: {\n          reactiveDiscoveryClients: {\n            description: 'Discovery Client not initialized',\n            status: 'UNKNOWN',\n            components: {\n              'Simple Reactive Discovery Client': {\n                description: 'Discovery Client not initialized',\n                status: 'UNKNOWN',\n              },\n            },\n          },\n          clientConfigServer: {\n            status: 'UNKNOWN',\n            details: { error: 'no property sources located' },\n          },\n          diskSpace: {\n            status: 'UP',\n            details: {\n              total: 994662584320,\n              free: 333813846016,\n              threshold: 10485760,\n              path: '/Users/stekoe/workspaces/cc/spring-boot-admin/spring-boot-admin-samples/spring-boot-admin-sample-servlet/.',\n              exists: true,\n            },\n          },\n          ping: { status: 'UP' },\n          discoveryComposite: {\n            description: 'Discovery Client not initialized',\n            status: 'UNKNOWN',\n            components: {\n              discoveryClient: {\n                description: 'Discovery Client not initialized',\n                status: 'UNKNOWN',\n              },\n            },\n          },\n          refreshScope: { status: 'UP' },\n          db: {\n            status: 'UP',\n            details: {\n              database: 'HSQL Database Engine',\n              validationQuery: 'isValid()',\n            },\n          },\n        },\n      },\n      statusTimestamp: '2022-12-19T10:04:39.457363Z',\n      info: {\n        build: {\n          artifact: 'spring-boot-admin-sample-servlet',\n          name: 'Spring Boot Admin Sample Servlet',\n          time: '2022-12-16T07:23:45.732Z',\n          version: '3.0.0-SNAPSHOT',\n          group: 'de.codecentric',\n        },\n      },\n      endpoints: [\n        {\n          id: 'caches',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/caches',\n        },\n        {\n          id: 'loggers',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/loggers',\n        },\n        {\n          id: 'heapdump',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/heapdump',\n        },\n        {\n          id: 'features',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/features',\n        },\n        {\n          id: 'startup',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/startup',\n        },\n        {\n          id: 'beans',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/beans',\n        },\n        {\n          id: 'configprops',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/configprops',\n        },\n        {\n          id: 'threaddump',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/threaddump',\n        },\n        {\n          id: 'auditevents',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/auditevents',\n        },\n        {\n          id: 'info',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/info',\n        },\n        {\n          id: 'resume',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/resume',\n        },\n        {\n          id: 'sessions',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/sessions',\n        },\n        {\n          id: 'restart',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/restart',\n        },\n        {\n          id: 'logfile',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/logfile',\n        },\n        {\n          id: 'custom',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/custom',\n        },\n        {\n          id: 'health',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/health',\n        },\n        {\n          id: 'refresh',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/refresh',\n        },\n        {\n          id: 'env',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/env',\n        },\n        {\n          id: 'pause',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/pause',\n        },\n        {\n          id: 'scheduledtasks',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/scheduledtasks',\n        },\n        {\n          id: 'mappings',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/mappings',\n        },\n        {\n          id: 'metrics',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/metrics',\n        },\n        {\n          id: 'conditions',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/conditions',\n        },\n        {\n          id: 'httpexchanges',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/httpexchanges',\n        },\n        {\n          id: 'shutdown',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/shutdown',\n        },\n      ],\n      buildVersion: '3.0.0-SNAPSHOT',\n      tags: {\n        'de-service-test-6': 'A large content',\n        'de-service-test-4': 'A large content',\n        'de-service-test-5': 'A large content',\n        'de-service-test-2': 'A large content',\n        'de-service-test-3': 'A large content',\n        environment: 'test',\n        'de-service-test-1': 'A large content',\n      },\n    },\n  ],\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/fixtures/eventStream/registerWithTwoInstances.ts",
    "content": "export const registerWithTwoInstances = {\n  name: 'spring-boot-admin-sample-servlet',\n  buildVersion: '3.0.0-SNAPSHOT',\n  status: 'UP',\n  statusTimestamp: '2022-12-19T10:07:20.646905Z',\n  instances: [\n    {\n      id: '25b07dc98984',\n      version: 3,\n      registration: {\n        name: 'spring-boot-admin-sample-servlet',\n        managementUrl:\n          'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator',\n        healthUrl:\n          'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/health',\n        serviceUrl:\n          'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/',\n        source: 'http-api',\n        metadata: {\n          'tags.de-service-test-6': 'A large content',\n          'tags.de-service-test-4': 'A large content',\n          'tags.de-service-test-5': 'A large content',\n          startup: '2022-12-19T11:04:38.171314+01:00',\n          'kubectl.kubernetes.iolast-applied-configuration':\n            '{\"name\":\"jvm.threads.peak\",\"description\":\"The peak live thread count since the Java virtual machine started or peak was reset\",\"baseUnit\":\"threads\",\"measurements\":[{\"statistic\":\"VALUE\",\"value\":64.0}],\"availableTags\":[]}',\n          'user.name': 'user',\n          'tags.de-service-test-2': 'A large content',\n          'user.password': '******',\n          'tags.de-service-test-3': 'A large content',\n          'tags.environment': 'test',\n          'tags.de-service-test-1': 'A large content',\n        },\n      },\n      registered: true,\n      statusInfo: {\n        status: 'UP',\n        details: {\n          reactiveDiscoveryClients: {\n            description: 'Discovery Client not initialized',\n            status: 'UNKNOWN',\n            components: {\n              'Simple Reactive Discovery Client': {\n                description: 'Discovery Client not initialized',\n                status: 'UNKNOWN',\n              },\n            },\n          },\n          clientConfigServer: {\n            status: 'UNKNOWN',\n            details: {\n              error: 'no property sources located',\n            },\n          },\n          diskSpace: {\n            status: 'UP',\n            details: {\n              total: 994662584320,\n              free: 333813846016,\n              threshold: 10485760,\n              path: '/Users/stekoe/workspaces/cc/spring-boot-admin/spring-boot-admin-samples/spring-boot-admin-sample-servlet/.',\n              exists: true,\n            },\n          },\n          ping: {\n            status: 'UP',\n          },\n          discoveryComposite: {\n            description: 'Discovery Client not initialized',\n            status: 'UNKNOWN',\n            components: {\n              discoveryClient: {\n                description: 'Discovery Client not initialized',\n                status: 'UNKNOWN',\n              },\n            },\n          },\n          refreshScope: {\n            status: 'UP',\n          },\n          db: {\n            status: 'UP',\n            details: {\n              database: 'HSQL Database Engine',\n              validationQuery: 'isValid()',\n            },\n          },\n        },\n      },\n      statusTimestamp: '2022-12-19T10:04:39.457363Z',\n      info: {\n        build: {\n          artifact: 'spring-boot-admin-sample-servlet',\n          name: 'Spring Boot Admin Sample Servlet',\n          time: '2022-12-16T07:23:45.732Z',\n          version: '3.0.0-SNAPSHOT',\n          group: 'de.codecentric',\n        },\n      },\n      endpoints: [\n        {\n          id: 'caches',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/caches',\n        },\n        {\n          id: 'loggers',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/loggers',\n        },\n        {\n          id: 'heapdump',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/heapdump',\n        },\n        {\n          id: 'features',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/features',\n        },\n        {\n          id: 'startup',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/startup',\n        },\n        {\n          id: 'beans',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/beans',\n        },\n        {\n          id: 'configprops',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/configprops',\n        },\n        {\n          id: 'threaddump',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/threaddump',\n        },\n        {\n          id: 'auditevents',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/auditevents',\n        },\n        {\n          id: 'info',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/info',\n        },\n        {\n          id: 'resume',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/resume',\n        },\n        {\n          id: 'sessions',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/sessions',\n        },\n        {\n          id: 'restart',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/restart',\n        },\n        {\n          id: 'logfile',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/logfile',\n        },\n        {\n          id: 'custom',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/custom',\n        },\n        {\n          id: 'health',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/health',\n        },\n        {\n          id: 'refresh',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/refresh',\n        },\n        {\n          id: 'env',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/env',\n        },\n        {\n          id: 'pause',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/pause',\n        },\n        {\n          id: 'scheduledtasks',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/scheduledtasks',\n        },\n        {\n          id: 'mappings',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/mappings',\n        },\n        {\n          id: 'metrics',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/metrics',\n        },\n        {\n          id: 'conditions',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/conditions',\n        },\n        {\n          id: 'httpexchanges',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/httpexchanges',\n        },\n        {\n          id: 'shutdown',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/shutdown',\n        },\n      ],\n      buildVersion: '3.0.0-SNAPSHOT',\n      tags: {\n        'de-service-test-6': 'A large content',\n        'de-service-test-4': 'A large content',\n        'de-service-test-5': 'A large content',\n        'de-service-test-2': 'A large content',\n        'de-service-test-3': 'A large content',\n        environment: 'test',\n        'de-service-test-1': 'A large content',\n      },\n    },\n    {\n      id: 'af578c480d41',\n      version: 1,\n      registration: {\n        name: 'spring-boot-admin-sample-servlet',\n        managementUrl:\n          'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator',\n        healthUrl:\n          'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/health',\n        serviceUrl:\n          'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/',\n        source: 'http-api',\n        metadata: {\n          'tags.de-service-test-6': 'A large content',\n          'tags.de-service-test-4': 'A large content',\n          'tags.de-service-test-5': 'A large content',\n          startup: '2022-12-19T11:07:19.690036+01:00',\n          'kubectl.kubernetes.iolast-applied-configuration':\n            '{\"name\":\"jvm.threads.peak\",\"description\":\"The peak live thread count since the Java virtual machine started or peak was reset\",\"baseUnit\":\"threads\",\"measurements\":[{\"statistic\":\"VALUE\",\"value\":64.0}],\"availableTags\":[]}',\n          'user.name': 'user',\n          'tags.de-service-test-2': 'A large content',\n          'user.password': '******',\n          'tags.de-service-test-3': 'A large content',\n          'tags.environment': 'test',\n          'tags.de-service-test-1': 'A large content',\n        },\n      },\n      registered: true,\n      statusInfo: {\n        status: 'UP',\n        details: {\n          reactiveDiscoveryClients: {\n            description: 'Discovery Client not initialized',\n            status: 'UNKNOWN',\n            components: {\n              'Simple Reactive Discovery Client': {\n                description: 'Discovery Client not initialized',\n                status: 'UNKNOWN',\n              },\n            },\n          },\n          clientConfigServer: {\n            status: 'UNKNOWN',\n            details: {\n              error: 'no property sources located',\n            },\n          },\n          diskSpace: {\n            status: 'UP',\n            details: {\n              total: 994662584320,\n              free: 332211322880,\n              threshold: 10485760,\n              path: '/Users/stekoe/workspaces/cc/spring-boot-admin/spring-boot-admin-samples/spring-boot-admin-sample-servlet/.',\n              exists: true,\n            },\n          },\n          ping: {\n            status: 'UP',\n          },\n          discoveryComposite: {\n            description: 'Discovery Client not initialized',\n            status: 'UNKNOWN',\n            components: {\n              discoveryClient: {\n                description: 'Discovery Client not initialized',\n                status: 'UNKNOWN',\n              },\n            },\n          },\n          refreshScope: {\n            status: 'UP',\n          },\n          db: {\n            status: 'UP',\n            details: {\n              database: 'HSQL Database Engine',\n              validationQuery: 'isValid()',\n            },\n          },\n        },\n      },\n      statusTimestamp: '2022-12-19T10:07:20.646905Z',\n      info: {},\n      endpoints: [\n        {\n          id: 'health',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/health',\n        },\n      ],\n      buildVersion: null,\n      tags: {\n        'de-service-test-6': 'A large content',\n        'de-service-test-4': 'A large content',\n        'de-service-test-5': 'A large content',\n        'de-service-test-2': 'A large content',\n        'de-service-test-3': 'A large content',\n        environment: 'test',\n        'de-service-test-1': 'A large content',\n      },\n    },\n  ],\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/fixtures/eventStream/removeInstance.ts",
    "content": "export const removeInstanceEvent = {\n  name: 'spring-boot-admin-sample-servlet',\n  buildVersion: '3.0.0-SNAPSHOT',\n  status: 'RESTRICTED',\n  statusTimestamp: '2022-12-19T10:12:38.430457Z',\n  instances: [\n    {\n      id: '25b07dc98984',\n      version: 3,\n      registration: {\n        name: 'spring-boot-admin-sample-servlet',\n        managementUrl:\n          'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator',\n        healthUrl:\n          'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/health',\n        serviceUrl:\n          'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/',\n        source: 'http-api',\n        metadata: {\n          'tags.de-service-test-6': 'A large content',\n          'tags.de-service-test-4': 'A large content',\n          'tags.de-service-test-5': 'A large content',\n          startup: '2022-12-19T11:04:38.171314+01:00',\n          'kubectl.kubernetes.iolast-applied-configuration':\n            '{\"name\":\"jvm.threads.peak\",\"description\":\"The peak live thread count since the Java virtual machine started or peak was reset\",\"baseUnit\":\"threads\",\"measurements\":[{\"statistic\":\"VALUE\",\"value\":64.0}],\"availableTags\":[]}',\n          'user.name': 'user',\n          'tags.de-service-test-2': 'A large content',\n          'user.password': '******',\n          'tags.de-service-test-3': 'A large content',\n          'tags.environment': 'test',\n          'tags.de-service-test-1': 'A large content',\n        },\n      },\n      registered: true,\n      statusInfo: {\n        status: 'UP',\n        details: {\n          reactiveDiscoveryClients: {\n            description: 'Discovery Client not initialized',\n            status: 'UNKNOWN',\n            components: {\n              'Simple Reactive Discovery Client': {\n                description: 'Discovery Client not initialized',\n                status: 'UNKNOWN',\n              },\n            },\n          },\n          clientConfigServer: {\n            status: 'UNKNOWN',\n            details: {\n              error: 'no property sources located',\n            },\n          },\n          diskSpace: {\n            status: 'UP',\n            details: {\n              total: 994662584320,\n              free: 333813846016,\n              threshold: 10485760,\n              path: '/Users/stekoe/workspaces/cc/spring-boot-admin/spring-boot-admin-samples/spring-boot-admin-sample-servlet/.',\n              exists: true,\n            },\n          },\n          ping: {\n            status: 'UP',\n          },\n          discoveryComposite: {\n            description: 'Discovery Client not initialized',\n            status: 'UNKNOWN',\n            components: {\n              discoveryClient: {\n                description: 'Discovery Client not initialized',\n                status: 'UNKNOWN',\n              },\n            },\n          },\n          refreshScope: {\n            status: 'UP',\n          },\n          db: {\n            status: 'UP',\n            details: {\n              database: 'HSQL Database Engine',\n              validationQuery: 'isValid()',\n            },\n          },\n        },\n      },\n      statusTimestamp: '2022-12-19T10:04:39.457363Z',\n      info: {\n        build: {\n          artifact: 'spring-boot-admin-sample-servlet',\n          name: 'Spring Boot Admin Sample Servlet',\n          time: '2022-12-16T07:23:45.732Z',\n          version: '3.0.0-SNAPSHOT',\n          group: 'de.codecentric',\n        },\n      },\n      endpoints: [\n        {\n          id: 'caches',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/caches',\n        },\n        {\n          id: 'loggers',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/loggers',\n        },\n        {\n          id: 'heapdump',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/heapdump',\n        },\n        {\n          id: 'features',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/features',\n        },\n        {\n          id: 'startup',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/startup',\n        },\n        {\n          id: 'beans',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/beans',\n        },\n        {\n          id: 'configprops',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/configprops',\n        },\n        {\n          id: 'threaddump',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/threaddump',\n        },\n        {\n          id: 'auditevents',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/auditevents',\n        },\n        {\n          id: 'info',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/info',\n        },\n        {\n          id: 'resume',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/resume',\n        },\n        {\n          id: 'sessions',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/sessions',\n        },\n        {\n          id: 'restart',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/restart',\n        },\n        {\n          id: 'logfile',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/logfile',\n        },\n        {\n          id: 'custom',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/custom',\n        },\n        {\n          id: 'health',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/health',\n        },\n        {\n          id: 'refresh',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/refresh',\n        },\n        {\n          id: 'env',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/env',\n        },\n        {\n          id: 'pause',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/pause',\n        },\n        {\n          id: 'scheduledtasks',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/scheduledtasks',\n        },\n        {\n          id: 'mappings',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/mappings',\n        },\n        {\n          id: 'metrics',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/metrics',\n        },\n        {\n          id: 'conditions',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/conditions',\n        },\n        {\n          id: 'httpexchanges',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/httpexchanges',\n        },\n        {\n          id: 'shutdown',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8080/actuator/shutdown',\n        },\n      ],\n      buildVersion: '3.0.0-SNAPSHOT',\n      tags: {\n        'de-service-test-6': 'A large content',\n        'de-service-test-4': 'A large content',\n        'de-service-test-5': 'A large content',\n        'de-service-test-2': 'A large content',\n        'de-service-test-3': 'A large content',\n        environment: 'test',\n        'de-service-test-1': 'A large content',\n      },\n    },\n    {\n      id: 'af578c480d41',\n      version: 4,\n      registration: {\n        name: 'spring-boot-admin-sample-servlet',\n        managementUrl:\n          'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator',\n        healthUrl:\n          'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/health',\n        serviceUrl:\n          'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/',\n        source: 'http-api',\n        metadata: {\n          'tags.de-service-test-6': 'A large content',\n          'tags.de-service-test-4': 'A large content',\n          'tags.de-service-test-5': 'A large content',\n          startup: '2022-12-19T11:07:19.690036+01:00',\n          'kubectl.kubernetes.iolast-applied-configuration':\n            '{\"name\":\"jvm.threads.peak\",\"description\":\"The peak live thread count since the Java virtual machine started or peak was reset\",\"baseUnit\":\"threads\",\"measurements\":[{\"statistic\":\"VALUE\",\"value\":64.0}],\"availableTags\":[]}',\n          'user.name': 'user',\n          'tags.de-service-test-2': 'A large content',\n          'user.password': '******',\n          'tags.de-service-test-3': 'A large content',\n          'tags.environment': 'test',\n          'tags.de-service-test-1': 'A large content',\n        },\n      },\n      registered: true,\n      statusInfo: {\n        status: 'OFFLINE',\n        details: {\n          exception:\n            'org.springframework.web.reactive.function.client.WebClientRequestException',\n          message:\n            'Connection refused: ip-192-168-178-20.eu-central-1.compute.internal/192.168.178.20:8888',\n        },\n      },\n      statusTimestamp: '2022-12-19T10:12:38.430457Z',\n      info: {\n        build: {\n          artifact: 'spring-boot-admin-sample-servlet',\n          name: 'Spring Boot Admin Sample Servlet',\n          time: '2022-12-19T10:04:57.047Z',\n          version: '3.0.0-SNAPSHOT',\n          group: 'de.codecentric',\n        },\n      },\n      endpoints: [\n        {\n          id: 'caches',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/caches',\n        },\n        {\n          id: 'loggers',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/loggers',\n        },\n        {\n          id: 'heapdump',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/heapdump',\n        },\n        {\n          id: 'features',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/features',\n        },\n        {\n          id: 'startup',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/startup',\n        },\n        {\n          id: 'beans',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/beans',\n        },\n        {\n          id: 'configprops',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/configprops',\n        },\n        {\n          id: 'threaddump',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/threaddump',\n        },\n        {\n          id: 'auditevents',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/auditevents',\n        },\n        {\n          id: 'info',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/info',\n        },\n        {\n          id: 'resume',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/resume',\n        },\n        {\n          id: 'sessions',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/sessions',\n        },\n        {\n          id: 'restart',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/restart',\n        },\n        {\n          id: 'logfile',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/logfile',\n        },\n        {\n          id: 'custom',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/custom',\n        },\n        {\n          id: 'health',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/health',\n        },\n        {\n          id: 'refresh',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/refresh',\n        },\n        {\n          id: 'env',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/env',\n        },\n        {\n          id: 'pause',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/pause',\n        },\n        {\n          id: 'scheduledtasks',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/scheduledtasks',\n        },\n        {\n          id: 'mappings',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/mappings',\n        },\n        {\n          id: 'metrics',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/metrics',\n        },\n        {\n          id: 'conditions',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/conditions',\n        },\n        {\n          id: 'httpexchanges',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/httpexchanges',\n        },\n        {\n          id: 'shutdown',\n          url: 'http://ip-192-168-178-20.eu-central-1.compute.internal:8888/actuator/shutdown',\n        },\n      ],\n      buildVersion: '3.0.0-SNAPSHOT',\n      tags: {\n        'de-service-test-6': 'A large content',\n        'de-service-test-4': 'A large content',\n        'de-service-test-5': 'A large content',\n        'de-service-test-2': 'A large content',\n        'de-service-test-3': 'A large content',\n        environment: 'test',\n        'de-service-test-1': 'A large content',\n      },\n    },\n  ],\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/instance/auditevents/data.ts",
    "content": "const now = new Date();\nconst today = [\n  now.getFullYear(),\n  String(now.getMonth() + 1).padStart(2, '0'),\n  String(now.getDate()).padStart(2, '0'),\n].join('-');\n\nexport const auditeventsresponse = {\n  events: [\n    {\n      timestamp: today + 'T05:03:58.546Z',\n      principal: 'anonymous',\n      type: 'CREATE_TEST',\n      data: {\n        json: '\"Test\"',\n        source: 'DUM',\n        sessionId: 'anonymous',\n        objectId: 'Test',\n      },\n    },\n    {\n      timestamp: today + 'T05:03:57.435400Z',\n      principal: 'anonymous',\n      type: 'CREATE_TEST',\n      data: {\n        json: '{\"id\":\"12345\",\"timestamp\":\"2021-10-12T14:48:34.738201+08:00\",\"gender\":\"M\"}',\n        source: 'DUM',\n        sessionId: 'anonymous',\n        objectId: '12345',\n      },\n    },\n    {\n      timestamp: today + 'T05:03:55.729744Z',\n      principal: 'anonymous',\n      type: 'CREATE_TEST',\n      data: {\n        json: '\"Test\"',\n        source: 'DUM',\n        sessionId: 'anonymous',\n        objectId: 'Test',\n      },\n    },\n    {\n      timestamp: today + 'T05:03:55.729498Z',\n      principal: 'anonymous',\n      type: 'CREATE_TEST',\n      data: {\n        json: '{\"id\":\"12345\",\"timestamp\":\"2021-10-12T14:48:34.738201+08:00\",\"gender\":\"M\"}',\n        source: 'DUM',\n        sessionId: 'anonymous',\n        objectId: '12345',\n      },\n    },\n    {\n      timestamp: today + 'T04:42:49.715651Z',\n      principal: 'anonymous',\n      type: 'CREATE_TEST',\n      data: {\n        json: '\"Test\"',\n        source: 'DUM',\n        sessionId: 'anonymous',\n        objectId: 'Test',\n      },\n    },\n    {\n      timestamp: today + 'T04:42:49.715194Z',\n      principal: 'anonymous',\n      type: 'CREATE_TEST',\n      data: {\n        json: '{\"id\":\"12345\",\"timestamp\":\"2021-10-12T14:48:34.738201+08:00\",\"gender\":\"M\"}',\n        source: 'DUM',\n        sessionId: 'anonymous',\n        objectId: '12345',\n      },\n    },\n    {\n      timestamp: today + 'T04:42:49.156299Z',\n      principal: 'anonymous',\n      type: 'CREATE_TEST',\n      data: {\n        json: '\"Test\"',\n        source: 'DUM',\n        sessionId: 'anonymous',\n        objectId: 'Test',\n      },\n    },\n    {\n      timestamp: today + 'T04:42:49.156030Z',\n      principal: 'anonymous',\n      type: 'CREATE_TEST',\n      data: {\n        json: '{\"id\":\"12345\",\"timestamp\":\"2021-10-12T14:48:34.738201+08:00\",\"gender\":\"M\"}',\n        source: 'DUM',\n        sessionId: 'anonymous',\n        objectId: '12345',\n      },\n    },\n    {\n      timestamp: today + 'T04:42:48.773277Z',\n      principal: 'anonymous',\n      type: 'CREATE_TEST',\n      data: {\n        json: '\"Test\"',\n        source: 'DUM',\n        sessionId: 'anonymous',\n        objectId: 'Test',\n      },\n    },\n    {\n      timestamp: today + 'T04:42:48.773099Z',\n      principal: 'anonymous',\n      type: 'CREATE_TEST',\n      data: {\n        json: '{\"id\":\"12345\",\"timestamp\":\"2021-10-12T14:48:34.738201+08:00\",\"gender\":\"M\"}',\n        source: 'DUM',\n        sessionId: 'anonymous',\n        objectId: '12345',\n      },\n    },\n    {\n      timestamp: today + 'T04:42:05.304815Z',\n      principal: 'anonymous',\n      type: 'CREATE_TEST',\n      data: {\n        json: '\"Test\"',\n        source: 'DUM',\n        sessionId: 'anonymous',\n        objectId: 'Test',\n      },\n    },\n    {\n      timestamp: today + 'T04:42:05.297055Z',\n      principal: 'anonymous',\n      type: 'CREATE_TEST',\n      data: {\n        json: '{\"id\":\"12345\",\"timestamp\":\"2021-10-12T14:48:34.738201+08:00\",\"gender\":\"M\"}',\n        source: 'DUM',\n        sessionId: 'anonymous',\n        objectId: '12345',\n      },\n    },\n    {\n      timestamp: today + 'T04:37:54.450217Z',\n      principal: 'anonymous',\n      type: 'CREATE_TEST',\n      data: {\n        json: '\"Test\"',\n        source: 'application',\n        sessionId: 'anonymous',\n        objectId: 'Test',\n      },\n    },\n    {\n      timestamp: today + 'T04:37:54.449951Z',\n      principal: 'anonymous',\n      type: 'CREATE_TEST',\n      data: {\n        json: '{\"id\":\"12345\",\"timestamp\":\"2021-10-12T14:48:34.738201+08:00\",\"gender\":\"M\"}',\n        source: 'application',\n        sessionId: 'anonymous',\n        objectId: '12345',\n      },\n    },\n    {\n      timestamp: today + 'T04:29:40.238505Z',\n      principal: 'anonymous',\n      type: 'CREATE_TEST',\n      data: {\n        json: '\"Test\"',\n        source: 'application',\n        sessionId: 'anonymous',\n        objectId: 'Test',\n      },\n    },\n    {\n      timestamp: today + 'T04:29:40.238317Z',\n      principal: 'anonymous',\n      type: 'CREATE_TEST',\n      data: {\n        json: '{\"id\":\"12345\",\"timestamp\":\"2021-10-12T14:48:34.738201+08:00\",\"gender\":\"M\"}',\n        source: 'application',\n        sessionId: 'anonymous',\n        objectId: '12345',\n      },\n    },\n    {\n      timestamp: today + 'T01:15:34.825846Z',\n      principal: 'anonymous',\n      type: 'CREATE_TEST',\n      data: {\n        json: '\"Test\"',\n        source: 'DUM',\n        sessionId: 'anonymous',\n        objectId: 'Test',\n      },\n    },\n    {\n      timestamp: today + 'T01:15:34.825565Z',\n      principal: 'anonymous',\n      type: 'CREATE_TEST',\n      data: {\n        json: '{\"id\":\"12345\",\"timestamp\":\"2021-10-12T14:48:34.738201+08:00\",\"gender\":\"M\"}',\n        source: 'DUM',\n        sessionId: 'anonymous',\n        objectId: '12345',\n      },\n    },\n    {\n      timestamp: today + 'T01:15:34.001904Z',\n      principal: 'anonymous',\n      type: 'CREATE_TEST',\n      data: {\n        json: '\"Test\"',\n        source: 'DUM',\n        sessionId: 'anonymous',\n        objectId: 'Test',\n      },\n    },\n    {\n      timestamp: today + 'T01:15:34.001034Z',\n      principal: 'anonymous',\n      type: 'CREATE_TEST',\n      data: {\n        json: '{\"id\":\"12345\",\"timestamp\":\"2021-10-12T14:48:34.738201+08:00\",\"gender\":\"M\"}',\n        source: 'DUM',\n        sessionId: 'anonymous',\n        objectId: '12345',\n      },\n    },\n    {\n      timestamp: today + 'T01:15:33.561225Z',\n      principal: 'anonymous',\n      type: 'CREATE_TEST',\n      data: {\n        json: '\"Test\"',\n        source: 'DUM',\n        sessionId: 'anonymous',\n        objectId: 'Test',\n      },\n    },\n    {\n      timestamp: today + 'T01:15:33.560174Z',\n      principal: 'anonymous',\n      type: 'CREATE_TEST',\n      data: {\n        json: '{\"id\":\"12345\",\"timestamp\":\"2021-10-12T14:48:34.738201+08:00\",\"gender\":\"M\"}',\n        source: 'DUM',\n        sessionId: 'anonymous',\n        objectId: '12345',\n      },\n    },\n    {\n      timestamp: today + 'T01:14:41.743788Z',\n      principal: 'anonymous',\n      type: 'CREATE_TEST',\n      data: {\n        json: '\"Test\"',\n        source: 'DUM',\n        sessionId: 'anonymous',\n        objectId: 'Test',\n      },\n    },\n    {\n      timestamp: today + 'T01:14:41.742988Z',\n      principal: 'anonymous',\n      type: 'CREATE_TEST',\n      data: {\n        json: '{\"id\":\"12345\",\"timestamp\":\"2021-10-12T14:48:34.738201+08:00\",\"gender\":\"M\"}',\n        source: 'DUM',\n        sessionId: 'anonymous',\n        objectId: '12345',\n      },\n    },\n    {\n      timestamp: today + 'T01:14:13.969356Z',\n      principal: 'anonymous',\n      type: 'CREATE_TEST',\n      data: {\n        json: '\"Test\"',\n        source: 'DUM',\n        sessionId: 'anonymous',\n        objectId: 'Test',\n      },\n    },\n    {\n      timestamp: today + 'T01:14:13.954901Z',\n      principal: 'anonymous',\n      type: 'CREATE_TEST',\n      data: {\n        json: '{\"id\":\"12345\",\"timestamp\":\"2021-10-12T14:48:34.738201+08:00\",\"gender\":\"M\"}',\n        source: 'DUM',\n        sessionId: 'anonymous',\n        objectId: '12345',\n      },\n    },\n  ],\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/instance/auditevents/index.ts",
    "content": "import { HttpResponse, http } from 'msw';\n\nimport { auditeventsresponse } from '@/mocks/instance/auditevents/data';\n\nconst endpoints = [\n  http.get('/instances/:instanceId/actuator/auditevents', () => {\n    return HttpResponse.json(auditeventsresponse);\n  }),\n];\n\nexport default endpoints;\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/instance/dependencies/data.ts",
    "content": "export const sbomsResponse = {\n  ids: ['application', 'system'],\n};\n\nexport const applicationSbomResponse = {\n  bomFormat: 'CycloneDX',\n  specVersion: '1.5',\n  serialNumber: 'urn:uuid:9f17e432-9d33-3d31-aee7-bbda6211a526',\n  version: 1,\n  metadata: {\n    timestamp: '2024-04-29T14:17:19Z',\n    lifecycles: [\n      {\n        phase: 'build',\n      },\n    ],\n    tools: [\n      {\n        vendor: 'OWASP Foundation',\n        name: 'CycloneDX Maven plugin',\n        version: '2.8.0',\n        hashes: [\n          {\n            alg: 'MD5',\n            content: '76ffec6a7ddd46b2b24517411874eb99',\n          },\n          {\n            alg: 'SHA-1',\n            content: '5b0d5b41975b53be4799b9621b4af0cfc41d44b6',\n          },\n          {\n            alg: 'SHA-256',\n            content:\n              '6852aa0f4e42a2db745bab80e384951a6a65b9215d041081d675780999027e81',\n          },\n          {\n            alg: 'SHA-512',\n            content:\n              '417de20fcdcb11c9713bacbd57290d8e68037fdb4553fd31b8cb08bd760ad52dc65ea88ad4be15844ad3fd5a4d3e440d2f70326f2fe1e63ec78e059c9a883f8d',\n          },\n          {\n            alg: 'SHA-384',\n            content:\n              '5eb755c6492e7a7385fa9a1e1f4517875bcb834b2df437808a37a2d6f5285df428741762305980315a63fcef1406597d',\n          },\n          {\n            alg: 'SHA3-384',\n            content:\n              '0fe16a47cf7aab0b22251dafcc39939b68e8f1778093309d8d2060b51a08df445a8b8ed5a9561669faf2e55f907c76d8',\n          },\n          {\n            alg: 'SHA3-256',\n            content:\n              '3e5a1eb5ab7d0797498862794709ff8eaaa071fe4cc9ec77f52db7e2f97ef487',\n          },\n          {\n            alg: 'SHA3-512',\n            content:\n              '59281a3e29e76270d7f44b40b5b9f05e55f1ae3ec716d80add806f360940809e3813998ac7c5758043b8e248aed73b86e37dc506cdb4cde03c16bb617d8e5a3a',\n          },\n        ],\n      },\n    ],\n    component: {\n      publisher: 'codecentric AG',\n      group: 'de.codecentric',\n      name: 'spring-boot-admin-sample-servlet',\n      version: '3.2.4-SNAPSHOT',\n      description: 'Spring Boot Admin Sample Servlet',\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/de.codecentric/spring-boot-admin-sample-servlet@3.2.4-SNAPSHOT?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://github.com/codecentric/spring-boot-admin/spring-boot-admin-dependencies/spring-boot-admin-build/spring-boot-admin-samples/spring-boot-admin-sample-servlet/',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2/',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/codecentric/spring-boot-admin/spring-boot-admin-dependencies/spring-boot-admin-build/spring-boot-admin-samples/spring-boot-admin-sample-servlet',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/de.codecentric/spring-boot-admin-sample-servlet@3.2.4-SNAPSHOT?type=jar',\n    },\n    properties: [\n      {\n        name: 'maven.goal',\n        value: 'makeBom',\n      },\n      {\n        name: 'maven.scopes',\n        value: 'compile,provided,runtime,system',\n      },\n    ],\n  },\n  components: [\n    {\n      group: 'de.codecentric',\n      name: 'spring-boot-admin-sample-custom-ui',\n      version: '3.2.4-SNAPSHOT',\n      scope: 'required',\n      purl: 'pkg:maven/de.codecentric/spring-boot-admin-sample-custom-ui@3.2.4-SNAPSHOT?type=jar',\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/de.codecentric/spring-boot-admin-sample-custom-ui@3.2.4-SNAPSHOT?type=jar',\n    },\n    {\n      group: 'de.codecentric',\n      name: 'spring-boot-admin-starter-server',\n      version: '3.2.4-SNAPSHOT',\n      scope: 'required',\n      purl: 'pkg:maven/de.codecentric/spring-boot-admin-starter-server@3.2.4-SNAPSHOT?type=jar',\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/de.codecentric/spring-boot-admin-starter-server@3.2.4-SNAPSHOT?type=jar',\n    },\n    {\n      publisher: 'VMware, Inc.',\n      group: 'org.springframework.boot',\n      name: 'spring-boot-starter-security',\n      version: '3.3.0-RC1',\n      description: 'Starter for using Spring Security',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '4dbb08add7a37e1e1f8b9a32cad03125',\n        },\n        {\n          alg: 'SHA-1',\n          content: 'a67dc8d84c8a4562ad257d906a140b35bbe69ea3',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '5142a739b5dcc1fcf0ae83cd395b35375aee5a49a0b431579ed6b8c44690a93b',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            'b3565a006dcb8edd49d77be4cfb65b94c949599e3b4be3eb3291184ac9392ba78c7c9f8fd4316e0d721b92a59d99ac785536ebc8c49419e0029cc9d6f602acdf',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            'bddaf7e20a489e635e28d5511e16bd902ff61eb37f1793bde86f016ec7c5ae18eae07c939af5c7e7edae9bb8fc68911b',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '630b82ecb8da9c548eab50f1e8ec923eeb6d4f3a5543ed1526eab98da7ab8b993df7687fd1d78c7df62a7b5ff8bf0a73',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '37c888764bafa9f554b4030adebc6ce342a36b9132eab3e7ffeceb97e3b04973',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '37660fe7bebb9569d62007fc89292620aeb5b860ce5eed515b5d9e0c7aa386d96b14a258d2d064910910b8c2abcc3ad9a0682d7afca76d3b04e33f53e9840924',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework.boot/spring-boot-starter-security@3.3.0-RC1?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://spring.io/projects/spring-boot',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/spring-projects/spring-boot/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-projects/spring-boot',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.springframework.boot/spring-boot-starter-security@3.3.0-RC1?type=jar',\n    },\n    {\n      publisher: 'VMware, Inc.',\n      group: 'org.springframework.boot',\n      name: 'spring-boot-starter',\n      version: '3.3.0-RC1',\n      description:\n        'Core starter, including auto-configuration support, logging and YAML',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: 'dcf654ba1433f982859b6164500f0cc8',\n        },\n        {\n          alg: 'SHA-1',\n          content: '1fa34d1ded58f3464160bb4796164eb97c793acc',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '161cfb61381a1569f0d0bc6f5bd61c945fc1453ae68690911052315a4aeed8fe',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '8d94109934e6283ce27527c3502c78f31076047f5ac557c029f14bc6db14b0ba995ff6ca7484d4ec70d3d92e01c6ce1d7fafff5ae5b382b54ab18073755cc97d',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '0cf7e45ac5e200e21c2552e8efe15f583b5482fadacf72d1726c2e0bedc57bbe759adb754ead2a6e29a693e69d4d6458',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '6a5de8f018765305adc03a0c659da62e8b24c44e4453848d5cfb0ce1cb7e90bc1924a64aded610815f6d42cd870b6e22',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '5dd5e08afa4f155445dcd7c39f64e53c92aeca801d0a39e0044d19fcfcb83ba7',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '932b5627ad1f29802a1854e78ddb35e8fbe5a41884de4943203203640a7dabdd23ace6670204496152b7c92e45441770dcee9b6be5931f9f5eee5341c9fcc5c8',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework.boot/spring-boot-starter@3.3.0-RC1?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://spring.io/projects/spring-boot',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/spring-projects/spring-boot/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-projects/spring-boot',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.springframework.boot/spring-boot-starter@3.3.0-RC1?type=jar',\n    },\n    {\n      publisher: 'VMware, Inc.',\n      group: 'org.springframework.boot',\n      name: 'spring-boot',\n      version: '3.3.0-RC1',\n      description: 'Spring Boot',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '6670303cfb0ff56e5ac1d42a5dc1a159',\n        },\n        {\n          alg: 'SHA-1',\n          content: 'b861466d5af7da9954661f20f851405aa5f8f5d0',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            'c6197c0ca9cdc8bb1aa47b5194c79d41fb08b4a12db20954faca2b796e395265',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '71932836039ea36ed5c9f00a83b83718083db69806425d49258c3b4c0047d60d7f9d6c2b416cc3fc9f4974496d5bf173fb734f1fc2ca44ba5496ade3edf85a05',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '09b2a5ccea74fea8933f525c919d1294689d1f3df7bc78ef0faff1429cc94edc519b8555acfe03162b677eba17b8cbce',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '86f7da509f3f00ff3eeba112fd2427000499b850c0bcc28bd8496fd49c5bcf7e4c8b6226e1a1e21a79b43207a06a80a8',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '59aae76d129902824c07e70e811087b084f74bbd1b651d0cb5befb8c9ef37d61',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            'dcbf37d6c552fd0d2dae949e603f18181b6c58ebfaf3a51da680e44da77c79d470f237b256fbc53defcc651cd6fc319d42615af6752c735427975b3fa3827de6',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework.boot/spring-boot@3.3.0-RC1?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://spring.io/projects/spring-boot',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/spring-projects/spring-boot/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-projects/spring-boot',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.springframework.boot/spring-boot@3.3.0-RC1?type=jar',\n    },\n    {\n      publisher: 'VMware, Inc.',\n      group: 'org.springframework.boot',\n      name: 'spring-boot-autoconfigure',\n      version: '3.3.0-RC1',\n      description: 'Spring Boot AutoConfigure',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: 'c57c22c83a87833977f3a9d60f538580',\n        },\n        {\n          alg: 'SHA-1',\n          content: '8f2eac2c0527567fbe3149dcda0d86c56fcf658e',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '0c48949b7ee05da897ccb5f06180bf495372163badd4c089fbdf1b3c1a0437bd',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            'ca39d678883d7c78d32eec49df582a2c9e3ce0423e22df33b6a2e19027fd180ed4f3bb3dc814e671c5d149f11fa971e77a064a327c2d4555972c66bc787f415d',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '7394307a140b1344157a016c88d31512ab2794481020b928a62def6a1acc4429028846997d1114182d6381e15ef678d1',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            'be85aa9dd5b4135f5ca46e744cecfd4d7754530dbdabc0baef61fe4dc02fc44eb1f658151fdffe6ba0dcba2b5dbbca3d',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            'b65e1946b8d0d64a949ed174107973227dc00cc14d9b745b09d157472b5ceb02',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '79d79d1026d80dcdc3b967815a39b792e183666e16e55f2a86429b913e276a82c18b4bb2308786a002a68ffb2b89c706b610399d32c5d66750af949be485fad9',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework.boot/spring-boot-autoconfigure@3.3.0-RC1?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://spring.io/projects/spring-boot',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/spring-projects/spring-boot/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-projects/spring-boot',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.springframework.boot/spring-boot-autoconfigure@3.3.0-RC1?type=jar',\n    },\n    {\n      publisher: 'VMware, Inc.',\n      group: 'org.springframework.boot',\n      name: 'spring-boot-starter-logging',\n      version: '3.3.0-RC1',\n      description: 'Starter for logging using Logback. Default logging starter',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: 'e1110a14ce2877746f805cba59913bc5',\n        },\n        {\n          alg: 'SHA-1',\n          content: 'df66b2674eca6a51aadcaf89f8a94f7d60f1e56f',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            'fba9b83103dafe5885fd22244f33a8afbec8fe5f94f9f09c19889aaeb638cd6e',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '8880d8536b0a322373dcc4496c00f44c3257ad5008e8f886f29ee54bcce4380bb9ba43f9a0d7d610ebe4ecd3117b1fd7fca3513895f14bed48ecabd7d7514aa0',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '9429194771ea17146bb151442bf72bc3e238f382c6cb76aadfede5cfd5fbbe9f1616a6cf5676ceecb07e04373cb3f5cb',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '4db4bb42dda7fcdafa74e3543f853b4098efde28ff9d09f706932bb94826dbcf66d2afff86b7d526b8e780a292adb193',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '4ff08b5b2abd9ddf507b5586bda88f7b2bce600143421b7d215f506d85f72345',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '03a275dec748ce967180e40a31e76050ccdf83826335dc1b430b7576ebf86cc2952deff8b46a276edc60d437f373dbbdca6c3789102f7be5c8a19655fd39bf56',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework.boot/spring-boot-starter-logging@3.3.0-RC1?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://spring.io/projects/spring-boot',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/spring-projects/spring-boot/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-projects/spring-boot',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.springframework.boot/spring-boot-starter-logging@3.3.0-RC1?type=jar',\n    },\n    {\n      publisher: 'QOS.ch',\n      group: 'ch.qos.logback',\n      name: 'logback-classic',\n      version: '1.5.6',\n      description: 'logback-classic module',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '83cff9a718cf3449f75d2bda0b9276c6',\n        },\n        {\n          alg: 'SHA-1',\n          content: 'afc75d260d838a3bddfb8f207c2805ed7d1b34f9',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '6115c6cac5ed1d9db810d14f2f7f4dd6a9f21f0acbba8016e4daaca2ba0f5eb8',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '9e3e227c0effccfd4938558f374877cda7c08c3abf3960bfcb6c7eb2bfdbb49d163484f7120c176b1eaef56e83d7e8921d8c19394a91c52d5bdbcbef660d3ec1',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '1c311989271a0dce0bcb38177fac80721100ed472fe4ec9746e56223e44dbfdbfbeb5b9c3e6b4ab635a5ca2635066bac',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '0951ec84907202404fdbfb8c7cf8e4ced6aaf4e6fa8850b75a3692f51403508d2150a2c510744bb007bb31350d9ad2f4',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '9d4d9f5794f39d2e2e83c8f8bae91636bc674d7d355cc450f239dea2905000cd',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            'a3e1e03e9f3def61d619f86ee1126fc6ffa66a9c60624bf120f9beddbb1095195fbc3fddbc488f5393aea19cd5f4bf5dc47939e2daba15b24aacae392969aa70',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'EPL-1.0',\n          },\n        },\n        {\n          license: {\n            name: 'GNU Lesser General Public License',\n            url: 'http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html',\n          },\n        },\n      ],\n      purl: 'pkg:maven/ch.qos.logback/logback-classic@1.5.6?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'http://logback.qos.ch/logback-classic',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2/',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/qos-ch/logback/logback-classic',\n        },\n      ],\n      type: 'library',\n      'bom-ref': 'pkg:maven/ch.qos.logback/logback-classic@1.5.6?type=jar',\n    },\n    {\n      publisher: 'QOS.ch',\n      group: 'ch.qos.logback',\n      name: 'logback-core',\n      version: '1.5.6',\n      description: 'logback-core module',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: 'd0634e717a5e885c6b7eeb1bcfac5b61',\n        },\n        {\n          alg: 'SHA-1',\n          content: '41cbe874701200c5624c19e0ab50d1b88dfcc77d',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '898c7d120199f37e1acc8118d97ab15a4d02b0e72e27ba9f05843cb374e160c6',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '44601eea5e12b2ca4a707cb43a04d863e0c5dedaf690a4d95772de725ea4097dcf4058d6449971253487803fdb6d534f107b2c7f17c7ffca5a4811e9bac71fdf',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            'becf9e457234636944263217e9aaa883eb669f5126e02038afe1e74a8e76dc90edd49b41c5d989b30bfda55f955c42f0',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '8569d0f5bc0bb3ed3b4d4734ae52a88927fa61975c82e2769c18c9a9191659ee91ba095d8fdae7028cfd72455a3d994f',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            'bde00a4a3cc9ab6e6aa4e76c85be1b13cc182a3db0bbc2fb2fe9e6ac2b2af3f3',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            'c092d071084f6b4bab96ba5863fdf1c728b81bb789cc5b5c1ca3f7245303d45f1c59be84a7d06a7590f6362ea5dfe08d52ca108a296e338917282cda63608db1',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'EPL-1.0',\n          },\n        },\n        {\n          license: {\n            name: 'GNU Lesser General Public License',\n            url: 'http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html',\n          },\n        },\n      ],\n      purl: 'pkg:maven/ch.qos.logback/logback-core@1.5.6?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'http://logback.qos.ch/logback-core',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2/',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/qos-ch/logback/logback-core',\n        },\n      ],\n      type: 'library',\n      'bom-ref': 'pkg:maven/ch.qos.logback/logback-core@1.5.6?type=jar',\n    },\n    {\n      publisher: 'The Apache Software Foundation',\n      group: 'org.apache.logging.log4j',\n      name: 'log4j-to-slf4j',\n      version: '2.23.1',\n      description: 'The Apache Log4j binding between Log4j 2 API and SLF4J.',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: 'd60143628bb91f9dfa0148c213388b39',\n        },\n        {\n          alg: 'SHA-1',\n          content: '425ad1eb8a39904d2830e907a324e956fb456520',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '7937a84055156910234e3b42868f55e68ff4b7becbb6ffd10146f72f5bf54dd5',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '86c4dce96d5a929b3adbf2283f7188660831b02f9b04eee55010d132cb50f5677b7bf30c478b432fa2053eb11dbf6744351ce60271bb5e0da3a3f555ed50ad0c',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '3d1423da6781764d19ea13c447da9ec5b9bccec4603dbd710b8e4f26fc53d3051a4d3082973a6b20b5edc024f2d4b4b4',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '9c05c76f928c4ce7b1ced6a8642257a9036c7fa66fa9655964bc7e37d98a2443da550b0b62be7d3caa357ca714b6ad3b',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '71f4969e9e3580f190e3194adc07afec56b676a4de3294600e09570497d8c573',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '483c0ea25d108c651dd80d0b694e13084ea78d64831dbd4435117c2d612f2c25d6fe5ee2e6cd5acafed65aab475890529e3b0201adf9d7e366d4449737dd6d3b',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n            url: 'https://www.apache.org/licenses/LICENSE-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.apache.logging.log4j/log4j-to-slf4j@2.23.1?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://logging.apache.org/log4j/2.x/log4j/log4j-to-slf4j/',\n        },\n        {\n          type: 'build-system',\n          url: 'https://github.com/apache/logging-log4j2/actions',\n        },\n        {\n          type: 'distribution',\n          url: 'https://logging.apache.org/logging-parent/latest/#distribution',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://repository.apache.org/service/local/staging/deploy/maven2',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/apache/logging-log4j2/issues',\n        },\n        {\n          type: 'mailing-list',\n          url: 'https://lists.apache.org/list.html?log4j-user@logging.apache.org',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/apache/logging-log4j2',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.apache.logging.log4j/log4j-to-slf4j@2.23.1?type=jar',\n    },\n    {\n      publisher: 'The Apache Software Foundation',\n      group: 'org.apache.logging.log4j',\n      name: 'log4j-api',\n      version: '2.23.1',\n      description: 'The Apache Log4j API',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: 'bee2e2dcbeeb983bdb6b71c9c3476b6a',\n        },\n        {\n          alg: 'SHA-1',\n          content: '9c15c29c526d9c6783049c0a77722693c66706e1',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '92ec1fd36ab3bc09de6198d2d7c0914685c0f7127ea931acc32fd2ecdd82ea89',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '2a296246b0059ff5fe5c26e2ba3f48aa99e38d7658d613fbd02f32c6d4262f93a67525e6cc4d767fa5c2ab0e39e70bb3c0d3966d38ea4f01608588c084af3162',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '3937cb646009763a94b199a0d6c0065441b9914e2b25e3d58db523874ea760276b445ff300015948d3a813217e0ee404',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '16ea3301ca37fbede2927399209b403066621789c4f1bee531b5153f27b652458900697fb828170d541a5f3b82e77fb7',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '0a3dfffc0f362b0a86ad0cd8b36da313c7500a8bdecb0ad7e628c2637d933548',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '2e230994b8cb7442a2073d60f89c27703ffc78b613dec7891bbfa42e91c95ed684b387ed65df3ee48559ccc06e6877462748f7e2ef985082c8db0741feb576a8',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n            url: 'https://www.apache.org/licenses/LICENSE-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.apache.logging.log4j/log4j-api@2.23.1?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://logging.apache.org/log4j/2.x/log4j/log4j-api/',\n        },\n        {\n          type: 'build-system',\n          url: 'https://github.com/apache/logging-log4j2/actions',\n        },\n        {\n          type: 'distribution',\n          url: 'https://logging.apache.org/logging-parent/latest/#distribution',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://repository.apache.org/service/local/staging/deploy/maven2',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/apache/logging-log4j2/issues',\n        },\n        {\n          type: 'mailing-list',\n          url: 'https://lists.apache.org/list.html?log4j-user@logging.apache.org',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/apache/logging-log4j2',\n        },\n      ],\n      type: 'library',\n      'bom-ref': 'pkg:maven/org.apache.logging.log4j/log4j-api@2.23.1?type=jar',\n    },\n    {\n      publisher: 'QOS.ch',\n      group: 'org.slf4j',\n      name: 'jul-to-slf4j',\n      version: '2.0.13',\n      description: 'JUL to SLF4J bridge',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: 'd44cfe5a86dae2488e228cac617c6f0e',\n        },\n        {\n          alg: 'SHA-1',\n          content: 'a3bcd9d9dd50c71ce69f06b1fd05e40fdeff6ba5',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            'fa5ed8f23df2158d0d4d5c82f85cae289d36cc3cd7b7497deff5a37b0b7d7878',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '0cdd6a11e82b740ac3b720e916f7abd9f081d2b0aec27962f3c2d0e7693640dc4be7cc055a4a0e64c34b5258db4483d79a7595411fe9c748fc914334e47a9b5c',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '4c425ac29e0f96343aa1e388cd96f2dec2ac5ea18979f5b8e744cc444ace195ce3cc43234810cfba535151255488d3e9',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            'fba337469cb78c6764a598e6fed47dcdeeee1a04da2018c28b7108261c7331a3bd7285fafbf658823cec47377438fb24',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '6e9d4f6c4b6c3e9ce8757b32f514759761732da3ebad187abbf5aef0b6c584cf',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '566dff26f42114e7f145d8669b5afe92c8a93f027f8c1b02b5ac03716145e8638efc83a8f1d1739f25c024c365eab34183dda6c80a6f42d882db3c4b5c4e0220',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'MIT',\n            url: 'https://opensource.org/licenses/MIT',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.slf4j/jul-to-slf4j@2.0.13?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'http://www.slf4j.org',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2/',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/qos-ch/slf4j/slf4j-parent/jul-to-slf4j',\n        },\n      ],\n      type: 'library',\n      'bom-ref': 'pkg:maven/org.slf4j/jul-to-slf4j@2.0.13?type=jar',\n    },\n    {\n      publisher: 'Eclipse Foundation',\n      group: 'jakarta.annotation',\n      name: 'jakarta.annotation-api',\n      version: '2.1.1',\n      description: 'Jakarta Annotations API',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '5dac2f68e8288d0add4dc92cb161711d',\n        },\n        {\n          alg: 'SHA-1',\n          content: '48b9bda22b091b1f48b13af03fe36db3be6e1ae3',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '5f65fdaf424eee2b55e1d882ba9bb376be93fb09b37b808be6e22e8851c909fe',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            'eabe8b855b735663684052ec4cc357cc737936fa57cebf144eb09f70b3b6c600db7fa6f1c93a4f36c5994b1b37dad2dfcec87a41448872e69552accfd7f52af6',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '798597a6b80b423844d70609c54b00d725a357031888da7e5c3efd3914d1770be69aa7135de13ddb89a4420a5550e35b',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '9629b8ca82f61674f5573723bbb3c137060e1442062eb52fa9c90fc8f57ea7d836eb2fb765d160ec8bf300bcb6b820be',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            'f71ffc2a2c2bd1a00dfc00c4be67dbe5f374078bd50d5b24c0b29fbcc6634ecb',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            'aa4e29025a55878db6edb0d984bd3a0633f3af03fa69e1d26c97c87c6d29339714003c96e29ff0a977132ce9c2729d0e27e36e9e245a7488266138239bdba15e',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'EPL-2.0',\n          },\n        },\n        {\n          license: {\n            id: 'GPL-2.0-with-classpath-exception',\n          },\n        },\n      ],\n      purl: 'pkg:maven/jakarta.annotation/jakarta.annotation-api@2.1.1?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://projects.eclipse.org/projects/ee4j.ca',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://jakarta.oss.sonatype.org/service/local/staging/deploy/maven2/',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/eclipse-ee4j/common-annotations-api/issues',\n        },\n        {\n          type: 'mailing-list',\n          url: 'https://dev.eclipse.org/mhonarc/lists/ca-dev',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/eclipse-ee4j/common-annotations-api',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/jakarta.annotation/jakarta.annotation-api@2.1.1?type=jar',\n    },\n    {\n      group: 'org.yaml',\n      name: 'snakeyaml',\n      version: '2.2',\n      description: 'YAML 1.1 parser and emitter for Java',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: 'd78aacf5f2de5b52f1a327470efd1ad7',\n        },\n        {\n          alg: 'SHA-1',\n          content: '3af797a25458550a16bf89acc8e4ab2b7f2bfce0',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '1467931448a0817696ae2805b7b8b20bfb082652bf9c4efaed528930dc49389b',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '11547e75cc80bee26f532e2598bc6e4ffa802941496dc0d8ce017f1b15e01ebbb80e91ed17d1047916e32bf2fc58da532bc71a1dfe93afccc277a296d86634ba',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            'dae0cb1a7ab9ccc75413f46f18ae160e12e91dfef0c17a07ea547a365e9fb422c071aa01579f2a320f15ce6ee4c29038',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '654b418f330fa02f1111a20c27395ec5c7f463907ae44f60057c94da04f81e815cf1c3959f005026381ef79030049694',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '2c4deb8d79876b80b210ef72dc5de2b19607e50fbe3abf09a4324576ca0881fc',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '0d9be5610b2bcb6bb7562ee8bcc0d68f81d3771958ce9299c5e57e8ec952c96906d711587b7f72936328c72fb41687b4f908c4de3070b78cc1f3e257cf4b715e',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.yaml/snakeyaml@2.2?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://bitbucket.org/snakeyaml/snakeyaml',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2/',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://bitbucket.org/snakeyaml/snakeyaml/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://bitbucket.org/snakeyaml/snakeyaml/src',\n        },\n      ],\n      type: 'library',\n      'bom-ref': 'pkg:maven/org.yaml/snakeyaml@2.2?type=jar',\n    },\n    {\n      publisher: 'Spring IO',\n      group: 'org.springframework',\n      name: 'spring-aop',\n      version: '6.1.6',\n      description: 'Spring AOP',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '7dd149c85f55789d005cf7ee5e1bc666',\n        },\n        {\n          alg: 'SHA-1',\n          content: '4958f52cb9fcb3adf7e836304550de5431a9347e',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '32ec3db2653d84e5adbb4aa932c8f825d684d6f588b90a4b8674df419e2375c2',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '957dfd69a39d60ca283c2a1f6a08b5ca24d2ad8fb5ef2173b07fbdc6c3b8ee0679aea8f6780e1d426d1d97555f4de27b4c7118183fea0d38b4515207885b0770',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            'e9863cbc573cc4a4f990fe6d7b8d288ac358acda6ff4b33d88e15ec50e7910b64a1a1f7297665666ff2858edef852916',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            'f55459c986cf14feead7c0fa71b78893e9eb810875069d5b60623f3c63f761e344e7068a4b805b52422d8a31e72e73d1',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '922bece712b5b0617966424355a53da7fe1fff5ebd21a512e0b814e210328fd8',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            'c626119f8af6b2cd5c914629d1d29d6d4bb5f14731e2ccb54114a92486ac41d52d8469bb17e457deedebc8ab3928765e10b69e67c42ec2d74b2e5bc4028dc355',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework/spring-aop@6.1.6?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://github.com/spring-projects/spring-framework',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/spring-projects/spring-framework/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-projects/spring-framework',\n        },\n      ],\n      type: 'library',\n      'bom-ref': 'pkg:maven/org.springframework/spring-aop@6.1.6?type=jar',\n    },\n    {\n      publisher: 'Spring IO',\n      group: 'org.springframework',\n      name: 'spring-beans',\n      version: '6.1.6',\n      description: 'Spring Beans',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '70aeef1e6e39b2a0b6c9afa1bf81d4e8',\n        },\n        {\n          alg: 'SHA-1',\n          content: '332d80ff134420db4ebf7614758e6a02a9bd3c41',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            'c3040d1418ef964eb78a39204e04b6685bc840bd010818200f286159be983f30',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            'c1e905c6e5bc9fa0925b14c81dd1c0c8c987b50ab4aa74de392c5fbba8cacb25f8ead28e4c01715c6719c4483f819e9338411173a11f7cad008219c9fa626f94',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            'a6a0a95420b5e3068ad656c6aa14b565b0305244af4f21029590bd6187b68c2962eb33bacbc53358f1bb4df0adee54d0',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            'c85604e1ff89af420e618f8e4710465e82a3604d0fc930a3ce14171ab997f84bd5be4353b96fc4e14d0fb6764325a010',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '0b8c554ab50fa0204f3d7a7b23f717b8384aafa8dfe0a421e95fd1de0080e3a4',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '35648bafc0695efcc0c36ac7189f8b3f907a48fd7a683c9f6f77f6679b083a4531cb8dee501e468841a3cda232e68f0976e2ae9cb65f198ccde5c2028d0e56b8',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework/spring-beans@6.1.6?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://github.com/spring-projects/spring-framework',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/spring-projects/spring-framework/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-projects/spring-framework',\n        },\n      ],\n      type: 'library',\n      'bom-ref': 'pkg:maven/org.springframework/spring-beans@6.1.6?type=jar',\n    },\n    {\n      publisher: 'Pivotal Software, Inc.',\n      group: 'org.springframework.security',\n      name: 'spring-security-config',\n      version: '6.3.0-RC1',\n      description: 'Spring Security',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '6ba304080954b3d8111e68de29ac5051',\n        },\n        {\n          alg: 'SHA-1',\n          content: 'b57a66e8644efa48846b0777038ff5f4b3e311f3',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '1f345acb23cac48452b0803183026581f83828b477600d4f91c7e9422eafef23',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '17d18e20affc4e785fd4a0d126ba331142f68de719fb6290345efe1212e147ccab9ac4e64a0ebf5f3f5c2da128a633a7d5935c70915db5e8d8a1eec74692afb4',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '72f0a72e7a399bd62508ba2ee588028e8c5f2df40294d64f351dcecfc8e2450818b47eeef83b22c5dba64f20b0d0fb32',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '3689d2ecc77e0fa6a4a5d7206fda00b56718db7d78a2f7300845390a3728b887a53eafcb7a10213e1aeec3aa150e3323',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '0c53615a418816c5c44ec36588773ebc302aee9e3fc4ffe909cc654775ea82e7',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '1a9002e646d8e9a0860b1da43eee6fd6f0bc34fa7adc475869009cd22d363e4e29cb215e22d9d7b3da8e611d0ef01ca264f577dbf5dd44df6712b9851c92806c',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework.security/spring-security-config@6.3.0-RC1?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://spring.io/projects/spring-security',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/spring-projects/spring-security/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-projects/spring-security',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.springframework.security/spring-security-config@6.3.0-RC1?type=jar',\n    },\n    {\n      publisher: 'Pivotal Software, Inc.',\n      group: 'org.springframework.security',\n      name: 'spring-security-core',\n      version: '6.3.0-RC1',\n      description: 'Spring Security',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: 'a21081caf802eff1ae6f7df885d2c746',\n        },\n        {\n          alg: 'SHA-1',\n          content: 'a24da460c3ad36fa03c380ba4f5a79e379c4fc88',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '7e9bd0fc450dd1e6f1b02fb7b5cff14204d3171dc86f41301b2a80208c5ac95a',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '71c8cded26e3408fb9e2552c96d9b3ac018169db3fde4d8a65132a36d7e711704cb476e32e70def34f2bd54dbc7d6cff52fb987c37a6e84d6409f512c5763cc5',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '33aab0fd0914443b9fbbcb4ac066007c1f1a6cf2cd0dbb53fc1484c76130d3b014a92726320c7c48149a12481210bcdb',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '3265d45b944a80891195c96c07770b9fa99a4f8dbaab3ab0c39a0f1faff2bd33b2555ecf40283dbf33949e4397c89736',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            'a836031ffb32131bb88b6abc05281525c4a8797b330abb266ed0e2587d713f2b',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            'd05e3fe14397303c85362b16840a0973e9ae91d27fde9ecd0e042eafac104fa4784d3d71c71e403a04f19e98dce4db0d99d950c28ff1330a3a59c6eb76f5cfde',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework.security/spring-security-core@6.3.0-RC1?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://spring.io/projects/spring-security',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/spring-projects/spring-security/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-projects/spring-security',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.springframework.security/spring-security-core@6.3.0-RC1?type=jar',\n    },\n    {\n      publisher: 'Pivotal Software, Inc.',\n      group: 'org.springframework.security',\n      name: 'spring-security-web',\n      version: '6.3.0-RC1',\n      description: 'Spring Security',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: 'e6bafd90bc08d472ccccc904f91f336f',\n        },\n        {\n          alg: 'SHA-1',\n          content: '7ab833ae6faad0a6ae971c8e919ed80a8fb8bf9c',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '0d71b6a6c2fbdfcdab3b1e8d0b7e67d2313bbe807012ba6f88f8f4245d7c2e53',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '65534406625518c123fd61e4e6543aa732010ea552879a6fa39997941a2913cc58a81b5fa6bc0d2eb825198719d890960639c9079586f11e7febaa775a98efcd',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '31dd5f20a0225419cd0378d7e3a0823bdeb6ebe801a186eeb7bea82eb3f0eeb34252ac3b48ba9dafb498499d80b52762',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '6d4398f5cea31a754bf916082df2d946d4b3db1495acbfc64c9f5a53a8653dba8d28bcdc88dbf896bf4efc4b47f3ec02',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '09082e1b2b1238f8a8f4e6a2ce343c1fd5aaeeba3acbb97fcefb3aeb64a0e75f',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '391534ae0377951c76099f264667655748480a9b1afcf5febbea98f786b6dffe4a88753b697bf08d4db4e49b7416278dd4e469313202985e4cf59305bc55f6c8',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework.security/spring-security-web@6.3.0-RC1?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://spring.io/projects/spring-security',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/spring-projects/spring-security/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-projects/spring-security',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.springframework.security/spring-security-web@6.3.0-RC1?type=jar',\n    },\n    {\n      publisher: 'Spring IO',\n      group: 'org.springframework',\n      name: 'spring-expression',\n      version: '6.1.6',\n      description: 'Spring Expression Language (SpEL)',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '80acfe831814a3712ae046de5baa2fe9',\n        },\n        {\n          alg: 'SHA-1',\n          content: '9c3d7f0e17a919a4ea9f087e4e2140ad39776bc8',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '6e53929ab7abda1a43b7d81cefcc441d187eb41aab493d2f61cc6161512c2d97',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '96c4712e5673fb30c55aea7cc7a5d29027780f71b7c0db8d539d95ff7a371cbe16013f59dda4163f5ceda2d09897b5486871cdb3bb22d11fa4858aad0c5aa8b2',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            'da97153d3e4d5665240206d2d93ac2b2edef8be11fe1da0504a41345d58fe3072779922b283c28f62865453269a0b488',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            'ea503cc1a439b0eef0dbf2e131eb3a29ce2f8414afe515211ad08fb233089c051e2a2dd1fd0dfa8ff5d6d5210ee27673',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            'e28bb3971e4e14716bac8c0ff013d2ac3aa66feb357569e0b746d4752d82d175',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '3de81987e28908779b9d7d5714b2c98b905bd045724452c8b84c1814d2d3f0d0ba086cca6564476db41f200182e26332e45a4b278a78fd1b545d1a02a235ca93',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework/spring-expression@6.1.6?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://github.com/spring-projects/spring-framework',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/spring-projects/spring-framework/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-projects/spring-framework',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.springframework/spring-expression@6.1.6?type=jar',\n    },\n    {\n      publisher: 'VMware, Inc.',\n      group: 'org.springframework.boot',\n      name: 'spring-boot-starter-webmvc',\n      version: '3.3.0-RC1',\n      description:\n        'Starter for building web, including RESTful, applications using Spring MVC. Uses Tomcat as the default embedded container',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '45bcad7dddc72b1cb3f89ec71022ed39',\n        },\n        {\n          alg: 'SHA-1',\n          content: '8be23edf4e71e1d631d8c406e08d06ad9744e7fe',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            'e589ae916b15a648f3ad157d669b8d509ad4c128832adf71c31760b46235c3aa',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '846d72082be7e8fd2062769604cae15f92b94a274b9056bd74e6303c20d5531c427148954a68bc0479e4b126e970eb03545c5acbf229bce90fda99a7cfdb9414',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            'd3ade9fdbeb94be500f41ba447f5d5854af1bd0135297fca735b8f085ae2513668da1a366e5c9e1d236bf9b0328e73fe',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '5a03502b89c530e1399618afc2f0b6d619c7cf1a2a59d55d1148bdec518ebb3e85bb92f8bc4552a7db991083be8ac8df',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '20570b3c35f0f5a7126d21fa4a8ddb0ec42f94834f6866ce94925d43931ee85b',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '74ba8db1c56f0f36314ccfb5399395988450151f6b9cb4131506bcbb0038aa05445db441a96afe5c1e1131fde5e1c299d09ca55758f805bc0fa39f438d33713b',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework.boot/spring-boot-starter-webmvc@3.3.0-RC1?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://spring.io/projects/spring-boot',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/spring-projects/spring-boot/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-projects/spring-boot',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.springframework.boot/spring-boot-starter-webmvc@3.3.0-RC1?type=jar',\n    },\n    {\n      publisher: 'VMware, Inc.',\n      group: 'org.springframework.boot',\n      name: 'spring-boot-starter-json',\n      version: '3.3.0-RC1',\n      description: 'Starter for reading and writing json',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '1eda6c496208f03d0cf14805de435376',\n        },\n        {\n          alg: 'SHA-1',\n          content: 'c063d6d99f359b1e16596dee791800d51298b27f',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '1065123cf78e26eb8c94e1cb145f045b9fc35bfd97fc2dac947fb62aecbf15e8',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            'e1019534961d270d89217180e761c86330c3f85412168ae084cdd1ae25139e6a558be89002ffd263a99ffb5ddb3074efd921f1f5755f35d6cb47cdd1ccdfebe9',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '74ceda16372f1eb59c16e3a26d0887d2493f148f0ac9601f6636354fb62def879461a72f17d5af62cc365a7d64201446',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            'e7e34d626727b3d4cef1599d49f71e3930dfba60abb9b8b33caabc38c962f4f9f3694b759e0854769a624ce5c4654db8',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            'ac8220d453e7752437d04bffb7b0750567e99860f1d93f2fbe8a3c50798df24e',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            'dfecf210a3b18c555b3e6e8b2287a91c6c0c7771a430a31a72c798f8a867c5601e0eeeb8d2bec17f71ee59711c5534b41d7c6b03eda280ae311294636da8e26a',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework.boot/spring-boot-starter-json@3.3.0-RC1?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://spring.io/projects/spring-boot',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/spring-projects/spring-boot/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-projects/spring-boot',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.springframework.boot/spring-boot-starter-json@3.3.0-RC1?type=jar',\n    },\n    {\n      publisher: 'FasterXML',\n      group: 'com.fasterxml.jackson.datatype',\n      name: 'jackson-datatype-jdk8',\n      version: '2.17.0',\n      description:\n        'Add-on module for Jackson (https://github.com/FasterXML/jackson) to support JDK 8 data types.',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '65e3b1936136e16cefbe9059c01c4505',\n        },\n        {\n          alg: 'SHA-1',\n          content: '95519a116d909faec29da76cf6b944b4a84c2c26',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            'b090239968a0ae5a172472f4014dbd97133af9426d91bf4805a6ba5fd90d80f1',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '6d7ee0d139fd9f7c24f14cb4bf231c1d9070c785d607b9a7be2f46297985ee8a7f184f9bf0b3a150d6b4a168175352cf8479c0e411342393af6bb259fdf0ec42',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            'd734ba8f8892dd41f63a2faec072cd3b57abf6a8e461c3e04880c285fc13103b50adbade060b387659a49f8d380f4b9c',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            'bb64b906356ef4839cd988be6de66eb7fe1f89e6e6fb1cee3f11097eab26532dbdd791cf99ebf4f9ebc6a3ff975183d7',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '4ae3e4ad6652e7c2c363ca3d9e6c41871d31531aac7f2a4f50b8d62bff4b8b94',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            'b368861aa6108fc6cc6138863d901d9aca49b16baebeb20db8df7e4451f971f1debae8751c4df27c83565d8e6e7dea21a9209b1c9c07a535b888bb965492ac56',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jdk8@2.17.0?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2/',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/FasterXML/jackson-modules-java8/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'http://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jdk8@2.17.0?type=jar',\n    },\n    {\n      publisher: 'FasterXML',\n      group: 'com.fasterxml.jackson.datatype',\n      name: 'jackson-datatype-jsr310',\n      version: '2.17.0',\n      description:\n        'Add-on module to support JSR-310 (Java 8 Date & Time API) data types.',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: 'b60f65312afa00f61e0950c3b5fbff88',\n        },\n        {\n          alg: 'SHA-1',\n          content: '3fab507bba9d477e52ed2302dc3ddbd23cbae339',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '94ea2f224e36632c02db1e668127c3018cacb859afc15ddf6f4c585917a93396',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            'bda1467594001aa22d7d622a5dcbb27a8aea54427d6e77dd7c03fb34ec8f4051b976f92c425d047045a0a1f48e23853b81d01a6a25ab0bf9fd479c05e91b5594',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            'a94642eed5de82b126672562f03c00e8e1668b8a0df388b8f52e19cfa79e5d2665f2160737026acd1c5d1d7fb7bf2423',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            'f6ca2b5923378c65b91e9f6b5a7c8268f1c19413cb3803355272dc63a7092ae5c453a84b7041d83423dae4fec96f11da',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '8703a4132f8ae6f48e37eb55da1bacff6c7e098d5e75c93cf19d72080b5afad7',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '69cc3cfccc7f130cdb67a14e2448c288cf8917e599c046fe9427176135f8e337706032dc313c06ab3ae8548f6a83ab60f6a79d9ec1bc753dc9606302f76e0aef',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jsr310@2.17.0?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2/',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/FasterXML/jackson-modules-java8/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'http://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jsr310@2.17.0?type=jar',\n    },\n    {\n      publisher: 'FasterXML',\n      group: 'com.fasterxml.jackson.module',\n      name: 'jackson-module-parameter-names',\n      version: '2.17.0',\n      description:\n        'Add-on module for Jackson (https://github.com/FasterXML/jackson) to support introspection of method/constructor parameter names, without having to add explicit property name annotation.',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '9f22e80510b61baa23689159ec2293cd',\n        },\n        {\n          alg: 'SHA-1',\n          content: '59340d6d12c15bcc465a91a4b9a2a93a920c4212',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '1fd79421bb95c74cc4c44c1ae4910e9253f255f248d34c3f9e5b2abeb2145b6a',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            'c6d6efabc8e7212ecfe5c3c77b1dd72b8bdae0aa62f08b2a6179aec1bfb56e910240933db0a2bbf62fdc5bfb54ea52b709327e1e1b472c34a1615cdf0c2a350a',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            'bb1466be83197443274697476b338f2878325651e8a6799cfd0f491ca3764713dec2b321a22f61346d256d859498792a',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            'c39bfb70ed88a7a3f45697491fb7e4b3da7bd906008ea00f80b113bc6ebb667fc0982388c6964512bfbf85591354d207',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            'cb3a1f00581b629b760e5af30bb0343c09c80d8eb98e409eaa31555a50782af1',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            'd93aea5c736b4f2062e1175c203aa641443824f77923a8a8a4f65a47cb47cb1aab079c06c2f460ad85d908a51e008d1db45d3947866d401e4f20ef63a4aa1882',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/com.fasterxml.jackson.module/jackson-module-parameter-names@2.17.0?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2/',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/FasterXML/jackson-modules-java8/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'http://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/com.fasterxml.jackson.module/jackson-module-parameter-names@2.17.0?type=jar',\n    },\n    {\n      publisher: 'VMware, Inc.',\n      group: 'org.springframework.boot',\n      name: 'spring-boot-starter-tomcat',\n      version: '3.3.0-RC1',\n      description:\n        'Starter for using Tomcat as the embedded servlet container. Default servlet container starter used by spring-boot-starter-webmvc',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: 'aa98610a3e8b6a74c67467bbbef82875',\n        },\n        {\n          alg: 'SHA-1',\n          content: 'cda596da92cdf96bbb042f6c7c93a6d803c3a6d9',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '7a08fb133c7ce64d845fbe3c602757fcb482bc71f90198990af7ed5f452c9a10',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '37c5ecbb41156a72db314065f3473fbc2c72a87b1d5afe114bfba446c66d64c9b9f9e8013bdc141cb1c0f1bcc5a56908620566cbed6c3650f4c18b775e8f2e5c',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '848b8eed45c458edc89dee5faa8219ecedbe5abf78be3094a0955caa013db5409c33ff14960e044c8fe2bd4d57cf2323',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '2d05d05a755042668cff311be9391c53333695936094cb958cc01be39e767383e7f2402b12a9bc5540b49119e496ab0f',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '2453b3c286f8e1e7a9e72c596fa80884bc5047b0a7a869ed31dab3583da54593',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            'd746199f73100208ebfafb6094ab7a2548d5554f89cd16747b69ec200cfca9582eebfc7d93f74c1582fb8b8cd35c0aa13e2f3cc67da79c79c6e78614eae43939',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework.boot/spring-boot-starter-tomcat@3.3.0-RC1?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://spring.io/projects/spring-boot',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/spring-projects/spring-boot/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-projects/spring-boot',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.springframework.boot/spring-boot-starter-tomcat@3.3.0-RC1?type=jar',\n    },\n    {\n      group: 'org.apache.tomcat.embed',\n      name: 'tomcat-embed-core',\n      version: '10.1.20',\n      description: 'Core Tomcat implementation',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '27154be1577cf6e837e8537d359d5da1',\n        },\n        {\n          alg: 'SHA-1',\n          content: 'ba0dc784e12086f83d8e1d5a10443b166abf5780',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            'd4b04888ede3a3232dc1798f3ae2ed7265f9b0dcf631b4bf16f50b7ed90ba361',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '2b0cf032cbe0195e7621b4e0e97766382cefb5b4185eb89ec1818823c9ced75e94a321e9451f929784734d32b2ddc93b75a9ba635d88d3d3a26a349b2921f098',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            'f64c3a561b496481ba1c26307f4b44f5f0742f954fbda9528bcd0fcb9385b86c6fb0107fceb319218a2cd842e492ffb0',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            'ce4860079353fcbe5ba07028179ee84272118233d94a91f78a8bb5b85bf277788221d52bff0206ba778b60dd7146250e',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '265a4f17f7c3854f907d81905780a5a3e704a96b05e7fec5b9ee4c0a2ccd2f39',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '60e4b7f62dcc53d7fce264e2f56eae21eeb1442076b20da6a2617e84657726625e12f10223c20127509cc8a7ecd95da338989e530cb09e315c8cd7846006a23d',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.apache.tomcat.embed/tomcat-embed-core@10.1.20?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://tomcat.apache.org/',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.apache.tomcat.embed/tomcat-embed-core@10.1.20?type=jar',\n    },\n    {\n      group: 'org.apache.tomcat.embed',\n      name: 'tomcat-embed-el',\n      version: '10.1.20',\n      description: 'Core Tomcat implementation',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '818fdd8c396fedf7117842cd10169d23',\n        },\n        {\n          alg: 'SHA-1',\n          content: 'cc1a42b8228699e92c8eba0187eccf54bf892802',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            'cac3dd6fa016dd85f5751438274c2f35955ce3024fec59d6d2fb0fe7c847c2d2',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            'd17c25d7884a9286843eb809dbff11212dea47946f57d41eae48af2c89f20c21426680b1ba962e2260a0e5fbd5dc6543ff4d90df574a1be12789f5a42eb2ac96',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '20f108b7e2fa1d03df021578cbaefb7f41dadd91cbc2a4c8c0f9ab149fa89e040bc7242c46bf6528d9fe119bce979273',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '8824efde0b5c6f5c619f2e96f1c4b2e37975d05f77bbc9ea51389f5be31869257251d098b60c26ddf5099e5c02762a6a',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '8b9caed961e5b913bad0bb6b73c98bd8b24670eaa1fa9915eb79c5b57686f8a2',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            'e444662720754ad71ac02b03a6e943985fa6ee671c2e9bb5042e465fd03f479860a791994af3d195838f66db9859fae1319910a3e8969d59b862bcd94567bb12',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.apache.tomcat.embed/tomcat-embed-el@10.1.20?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://tomcat.apache.org/',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.apache.tomcat.embed/tomcat-embed-el@10.1.20?type=jar',\n    },\n    {\n      group: 'org.apache.tomcat.embed',\n      name: 'tomcat-embed-websocket',\n      version: '10.1.20',\n      description: 'Core Tomcat implementation',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: 'fca3c1e1675701e2dfb01f4e3b8bd2c3',\n        },\n        {\n          alg: 'SHA-1',\n          content: '21502adffaf9e6e4bc2b63a557e348d7f6c0faf7',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            'e2ec1b5f17c8ae01dcf9b487221f50217c9a03293c0fa98eecdcecd702b4617a',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            'a9bd9b831058a46faa808e6aa5625a4b3a43c287de5d7be96b831f57f2cfa08dbca357b1a3edf54f94555e2d9e526ae69dcb172c1cb5fd713c67f50230ee5f03',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '7eb531e48659fe2c79a27087fb62636946c1b06f2996f146791c2d7160e94735da031112c6b76e536ecf4e47c06a74a2',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '9b62ab366557ac14e52d31dcb81d242ec7c577c9db3ca5203718784d11cea74604be66b8ae88e69ff4927d3504695d82',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '186766a966f4f58833760027095108ba435094664417d3ea93e77e11bbfa753a',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '0cc6608af58570bbbe8f86b9a24a5a2c1eb1f7953a8e96ed0389f24b4c81327e5e3973c39d95032d3b36fc8b10ea9ac032117b7a5a1fabd97dcc9c4a7ab9e784',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.apache.tomcat.embed/tomcat-embed-websocket@10.1.20?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://tomcat.apache.org/',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.apache.tomcat.embed/tomcat-embed-websocket@10.1.20?type=jar',\n    },\n    {\n      publisher: 'Spring IO',\n      group: 'org.springframework',\n      name: 'spring-web',\n      version: '6.1.6',\n      description: 'Spring Web',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: 'bea30302bcb6ef493a8123e4a40ae6a2',\n        },\n        {\n          alg: 'SHA-1',\n          content: '49a32e3497fe39550da3b280bda5d9933ae2d51d',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '0f33f5530ef848063958b4b437e3df3119c04a92aea58f9e37fc46948cbbde8e',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '7dcd359d76d3d924305130f9cb9f0758f9a7b5574fa737cccd44629bb6bb946bebec3a9323d6a84b4ca4bb5083ce1f99bd7ba78682f8751ad4185d32cda604c2',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '549fef67d6afd82c177548429390d959986064c051103edc063d691e10ff663d5743fab4fd1cfdeaf203cb925acf2c3a',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            'aaf3913b5df31048b841c305b29ec6be978c615846f36eaa4f5e8e278c4af6db96c7e441c7eeb9828c3728b40e5fe5e7',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '387f0d730eff538a56c7e0da1c75e512e888f73a1373c91e24ab5019ee969902',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '01856c9595cdc7aa2501f82f7482a710aad3050098114e185c2a05a7fcdc2a06a6ca932540ae0a4ff84c084a621b913b50dd29eb894376707554134be9cacdff',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework/spring-web@6.1.6?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://github.com/spring-projects/spring-framework',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/spring-projects/spring-framework/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-projects/spring-framework',\n        },\n      ],\n      type: 'library',\n      'bom-ref': 'pkg:maven/org.springframework/spring-web@6.1.6?type=jar',\n    },\n    {\n      group: 'io.micrometer',\n      name: 'micrometer-observation',\n      version: '1.13.0-RC1',\n      description: 'Module containing Observation related code',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '465af4c7b39389b4b64ee821c58391c4',\n        },\n        {\n          alg: 'SHA-1',\n          content: 'a9977cbdfad0d4271377c197e4273260e7d05200',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '30cf97a63e36e1016e6e482ecf7569abf0711f34991e30c8a6180b7c27961890',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            'ebbca3cfe52d16124a079d95db429e3a82fe5ae7d0b09380db1d47fdec2207848ac4192afb06852e13d08cf042b10e2ab2da179eb0f3b0e382e32db3b5487bbc',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '1c06ea65a6e4e7fa4226db67817769aa347be244d268941351b1972dd7e92efa3b9d78f357a06e8a7f940fb59cbab34c',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '7aad6d20027ff90e9d02e1840387a72716e7ee1264a08a0cfe95bd6ea4da038d49577821b1823cd2e26145330a37c2df',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            'db7a863b261f3b4eed015bcde2989acd4ea71de29e0ace7049e1de43a0c9ffad',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '49aeec650a29421aeffb7233f1da579b9cb35d362130a52f19366cbdbb790ba8edf9309b4bf508bd96d69caa9ae094a7eb9c5a934607b437b73c7635188b6d81',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/io.micrometer/micrometer-observation@1.13.0-RC1?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://github.com/micrometer-metrics/micrometer',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/io.micrometer/micrometer-observation@1.13.0-RC1?type=jar',\n    },\n    {\n      group: 'io.micrometer',\n      name: 'micrometer-commons',\n      version: '1.13.0-RC1',\n      description: 'Module containing common code',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: 'e4d7efe7ad5ffd085e174a976307c86f',\n        },\n        {\n          alg: 'SHA-1',\n          content: '63f08fc273b44773c417014a8f1897502137bcbe',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '3cfe50d4f7cb3d223def27f83397d879ed66214a40fadb3d1da1970faf083e16',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            'b011d2ed89193195554bf08199c5f06cb74207c1be3916cc2783050ef259710049fc571720f113439fd26f00a648ebf3ce70114c21e6a97422daef0e33981777',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '7987407064defaf0218c13217fd2d4573161e25fc5b2dee1f1d020dd15c34c82eddebb533ca45a91c53cb3fa0c39d9e2',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            'b66eda2bcf16c13c3d7a54975e0dccbb9c27bf891b170db953e1229db145753fc9a63222fce7abdfab3540b684c56198',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            'e5fe43477ea8e5b35feca18e3c52337f6bba9439af00bc055fe75201c4491275',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '881fc90acd90b72db2d8cd6fe13ac0d8018e8450d739178a58c56c923fc74430ff7baad68e3dcea2b72563e00d0f350315f3e52202b876fc9438b3c8b20f648e',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/io.micrometer/micrometer-commons@1.13.0-RC1?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://github.com/micrometer-metrics/micrometer',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/io.micrometer/micrometer-commons@1.13.0-RC1?type=jar',\n    },\n    {\n      publisher: 'Spring IO',\n      group: 'org.springframework',\n      name: 'spring-webmvc',\n      version: '6.1.6',\n      description: 'Spring Web MVC',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '9a174b724dac50617689536e658be1e4',\n        },\n        {\n          alg: 'SHA-1',\n          content: 'ef1f76db6d94bac428839cb91fa59235c8356e56',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '22f21b895c1d85d54d2357498b7aa2ea4e0e05646a360b98a1b67515f17666a9',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            'f5dad19b989c885241365657a20ab68c1ccef9a827d28f0594dc0e7502cb1a8f52673d68c85e9e241ca5d349ca853606033fc6ae68a37a848d53be154c20960f',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '7ab7a42380d3635158a20bea9702f4f6c32259a95611627956638fd220a163cbc8234dd1e7d2a9c6c61cd9897594cd0f',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            'f924555166988fb9c980bfffea0b7b3c1fc55b309aa2de8492302eae281632de76c616e35e5cd6e3b7d029220aa1b896',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '5fd7c65d3a4a7476af53d09e8948d9ffbd64bdc4684ced7b1aba62031caa1a46',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            'dd705a6dfa4ad993fa4734da16522c9e5eb590ec210cdb14df179026193e631afd38ced4b122803441e5bb483fcb238dd316f45d4ac81f20b042ab700003c7da',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework/spring-webmvc@6.1.6?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://github.com/spring-projects/spring-framework',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/spring-projects/spring-framework/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-projects/spring-framework',\n        },\n      ],\n      type: 'library',\n      'bom-ref': 'pkg:maven/org.springframework/spring-webmvc@6.1.6?type=jar',\n    },\n    {\n      publisher: 'Pivotal Software, Inc.',\n      group: 'org.springframework.cloud',\n      name: 'spring-cloud-starter',\n      version: '4.1.2',\n      description: 'Spring Cloud Starter',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '61f6ed7c42e186cd32941cb34e886eac',\n        },\n        {\n          alg: 'SHA-1',\n          content: '757a6f0ecdb191fb04c0aed2055e91f50f89231d',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '4d154b87d2601eceefd7eddfce08092c3e6c7cdf8ecef49809d56f14aaae7686',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '0f618660887c0da46552ff99ff48a80609d6ec2df03c9a9db3c28a85c65eabe020bc810f6753e3f0c036a13975746905e4684250b48dddaa08e3ea039e219e56',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            'a17cb39994d1bee8f0e58feb3f451c71640991f8946decb018b22dd0ba9371e82d4828fc1a0fcd85fc96e6f60b86bf6e',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '6ff22a32d93e94f994f7da8aaa6db6eb06bc5f468ce6b1b8f8783435e34b86d6960e06ea69fb774f9057cf98544be0a1',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '5b01491c4ce024e79378fe7c6f7be0dbb81128a2d2e2083bfedd330a8fab9e6f',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '7e71ec57c00b33e0934c4f40c23434a5a3b0ac4571c796cb441aa04b51c6267cb8a4096d8932466e7b6556c359dd4a5cca3abdaaffe4bbfd7424dd9b2bcc2557',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework.cloud/spring-cloud-starter@4.1.2?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://projects.spring.io/spring-cloud',\n        },\n        {\n          type: 'distribution',\n          url: 'https://github.com/spring-cloud',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-cloud/spring-cloud-commons/spring-cloud-starter',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.springframework.cloud/spring-cloud-starter@4.1.2?type=jar',\n    },\n    {\n      publisher: 'Pivotal Software, Inc.',\n      group: 'org.springframework.cloud',\n      name: 'spring-cloud-context',\n      version: '4.1.2',\n      description: 'Spring Cloud Context',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '791fc874f57106f112882318c640db8c',\n        },\n        {\n          alg: 'SHA-1',\n          content: '069d9edfe8c4b4037653d28b29f3184afc573603',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            'c8594e855931216df95433735750b7a229bc1d52f60299041b38f9998f57b06c',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '5f234a5a762287e64c951269a37aca9c1cb5dea90c5d0cd84b0e49beb5d8ad87fd83480f854371ab136195354f953dc4eb6646da4186f17e54ceec58fbb1e07a',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            'd4b15bf701af1a7614c433aa792f9945541eb6089fef2933b23271867bbaca479396e1643a08ea4b37c332dda98f9354',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '6bcda9d7275df6d7fdbf7c2aada404b13696bfa3430d98e9dc61ae2700691cd89f727a48d5a121fc607819425df86d9b',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '00fcba56a744c1ded7a5e67caae7d8ef02af961fb4da0b8ca1b2f6e91af7a169',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '32fb2b12b806c016e2a52b04ea207fe6310f54be28cfd9e0b1f608cccf78ae0bea2baf1995b7632d322c8e89aa134d9b2d9f99a7729f68c8a755e09f16d3b8a2',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework.cloud/spring-cloud-context@4.1.2?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://projects.spring.io/spring-cloud/spring-cloud-context/',\n        },\n        {\n          type: 'distribution',\n          url: 'https://github.com/spring-cloud',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-cloud/spring-cloud-commons/spring-cloud-context',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.springframework.cloud/spring-cloud-context@4.1.2?type=jar',\n    },\n    {\n      publisher: 'Pivotal Software, Inc.',\n      group: 'org.springframework.security',\n      name: 'spring-security-crypto',\n      version: '6.3.0-RC1',\n      description: 'Spring Security',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '313c83d23c769416f746767f03484b3e',\n        },\n        {\n          alg: 'SHA-1',\n          content: '13b842fc45bf78c54f42151774789f63a4c7395f',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '4961c7f1da2c362444a9a047bd4d26513f5b9d90b623cc9e6d9c5a95052e6ded',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            'ed03b0027d869f4aa0381f01c5826a9385500ee081b91a844b7b72e80e83bf5a4106327a348f6809c005bec578c7dbe1ba22e29c6320dc0896d08547b9c42b3b',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '6008c6d0a0bb6ac403484d01ea7b99c88d437c8f3033d7caedfe9e63eca8ffc204ca0813c9e03e7fa5c1317bc4fed979',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            'c8d8dc5a5097bc9d9a301eeabdbf54c73640e7e4d2c390257c1eeb0aace8793f71ef4c6cc715590f55dc745f322af61d',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '1ba8a3ca028e574f4ae1455c05c5b12401ae129dcce8d2770736c9f0091d08d3',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '019d9a59c2e0bd995367417662210e061c6f5540859ae32fdcd5602ff334b446afb7b71d537302577500182a912130c600657c3bebf1c313ad8b795ef05b3f98',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework.security/spring-security-crypto@6.3.0-RC1?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://spring.io/projects/spring-security',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/spring-projects/spring-security/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-projects/spring-security',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.springframework.security/spring-security-crypto@6.3.0-RC1?type=jar',\n    },\n    {\n      publisher: 'Pivotal Software, Inc.',\n      group: 'org.springframework.cloud',\n      name: 'spring-cloud-commons',\n      version: '4.1.2',\n      description: 'Spring Cloud Commons',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: 'e8b1acf9de6507b9f6900dfe6ee6a137',\n        },\n        {\n          alg: 'SHA-1',\n          content: '84377482af72a3ef008b6c981e77897a04ae20aa',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            'e4e1e4d511422b9d79cb26cb7597fbee0a2d25e28a34527fc7a24f5e7900f084',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            'edf6ee48a22e532ceeff37e9e7536dca0f0fd30dd136c844fa39308929c9cb87a66259c5fe86d89c5e91bbdbcd5c7d7b8d23e0dae12510fa040363d3e1f97810',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '8674cd5c6931066ec77972f0176fb1b16be8a3607092286fd0f1183ea11abdead5d80ef31666a07679e042d2fdf1ba15',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            'f60ce002a0d45e08b87854c95ccd67b1c4e6d7297da5f7f28072b903f73038bb24c0a06c6012002e4a8fc5b81ce54a51',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            'ef84cc527220b23fea91cce6b1c6619a0d8d1d89a65bf5f6fd453cb408e64e39',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            'd1fe7229c4a75eccc49ea12ee61e7f5105949f26aa8604ddfda8549b2ca2f4b0ec034f54fc7051ff705c15f324ca7f10016d4514fd0fac32067f334be472efa7',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework.cloud/spring-cloud-commons@4.1.2?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://projects.spring.io/spring-cloud/spring-cloud-commons/',\n        },\n        {\n          type: 'distribution',\n          url: 'https://github.com/spring-cloud',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-cloud/spring-cloud-commons/spring-cloud-commons',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.springframework.cloud/spring-cloud-commons@4.1.2?type=jar',\n    },\n    {\n      publisher: 'SpringSource',\n      group: 'org.springframework.security',\n      name: 'spring-security-rsa',\n      version: '1.1.2',\n      description:\n        'Spring Security RSA is a small utility library for RSA ciphers. It belongs to the family of Spring Security crypto libraries that handle encoding and decoding text as a general, useful thing to be able to do.',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '44755711c5e1d2bd12ea7bd669aff3a5',\n        },\n        {\n          alg: 'SHA-1',\n          content: 'ca388d615a60199186ec248ac2a9806a76db4014',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '6483d1ece7049e58c85b2904c1030d653840516e7b80bb4d4c00dbbb95a2c564',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            'b204ac9aac553d1243889305d600a6ee79737e482e7c8b51833183a555a03c37c1631635a39b4ab442a74097881b9d7f1618517dce1d9a99318b2699cf16047b',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            'f7be923a5b035df4f15caa1e8a9fa7dee58bbb14a9c93f348349b0c798ea9c0f907bde5654a9de5b84ff6afa9c355d0f',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            'cd9271f4c75cb2c9da22cf4322205fab4dba6f17e114dba7f2b4d7cb53545b5e8ca2f38db8a378d4b0107fc573ebd1d2',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '830f5c790d73c1390b1ab2f64a453e44a5ad20217aae0ed3b2f9c23cc8209463',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '2d1c4292f43052e08a0783dc1f9b723cfbc56662f67da5fba38f9b579aab60ba6ce7a871e19c3aa732cd28135e22be20c90c251aedb2b1cbaca3be2ec47ed684',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework.security/spring-security-rsa@1.1.2?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'http://github.com/spring-projects/spring-security-oauth',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://repo.spring.io/libs-release-local',\n        },\n        {\n          type: 'vcs',\n          url: 'http://github.com/spring-projects/spring-security-rsa',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.springframework.security/spring-security-rsa@1.1.2?type=jar',\n    },\n    {\n      group: 'org.bouncycastle',\n      name: 'bcprov-jdk18on',\n      version: '1.77',\n      description:\n        'The Bouncy Castle Crypto package is a Java implementation of cryptographic algorithms. This jar contains JCE provider and lightweight API for the Bouncy Castle Cryptography APIs for JDK 1.8 and up.',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: 'ca01387064e08db12e1345b474521ff1',\n        },\n        {\n          alg: 'SHA-1',\n          content: '2cc971b6c20949c1ff98d1a4bc741ee848a09523',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            'dabb98c24d72c9b9f585633d1df9c5cd58d9ad373d0cd681367e6a603a495d58',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '56c359f1370131f91eaeae3ec1d44884358f4296defd8d7516c7b81b9b66158454a667eb1c859d8ad919faee074ae3ecb4ceba2a39a3dd799bef9ada2d8c3da3',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '2951d9bb941e960287cc4a8947a0239ccfb9bd5058002bd5b9fe045b0bb22e4b23f31357f65211c191384cedf3ef3555',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            'b02af7de4704cf8f93fcd876055595bd9d117afd5eecf0fa883c43e30a285cbbd71473dd9197d6bb41f2b7702bc2620f',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '6e69119cc95e642da12dcb0043589137bc7b36ba11ff3299598aaa510b8f0c03',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            'da87498233675c659ed554261a641aeb2eecc83df76864f199fee9d5c63564c2fe9465baf86d9ac9e409bba74a4de1e7197eda8736852f4f4a729301ea8c9233',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            name: 'Bouncy Castle Licence',\n            url: 'https://www.bouncycastle.org/licence.html',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.bouncycastle/bcprov-jdk18on@1.77?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://www.bouncycastle.org/java.html',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/bcgit/bc-java/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/bcgit/bc-java',\n        },\n      ],\n      type: 'library',\n      'bom-ref': 'pkg:maven/org.bouncycastle/bcprov-jdk18on@1.77?type=jar',\n    },\n    {\n      publisher: 'VMware, Inc.',\n      group: 'org.springframework.boot',\n      name: 'spring-boot-starter-mail',\n      version: '3.3.0-RC1',\n      description:\n        \"Starter for using Java Mail and Spring Framework's email sending support\",\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '408a1076991aedc862f183cf7cd072ec',\n        },\n        {\n          alg: 'SHA-1',\n          content: 'b93d26b0a94995568fdf7a81616d8cf6238f7a76',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '77698db1dafe474e9fc430f6f8532bf80ae7d74f6b8d3d26a28f112875f3dd88',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '521352975f69713d274e94d8adf7e327a48c48047243839e31b5a5767f9a67cb7f725e00dc4b390732bcf82f8f0470a0051e431563046cc9f861e8ffc94e6808',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '511c84f78c10532dfe1ed1fbfc2ec7fd4e263fcd349e6d4c37d67b16cf811ca25192b30c4cef2bad5f4935cc0f0bff3e',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            'c5e2c1b5a331d1a1032b6674fd76d86e7de43e2611c40ba1bb7c1cf668439725216b553d51d7467c8ac6899bf44af875',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '7ceb912921ce9f446be9eb0ac542a253690570dbce25fecc8ad85afb445faf96',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '8fc7aa1eddeacbc592018db332c3be52ecfc13bb793f930903e009197082ce4dd4ae2546ad9a3597d50552d28aa23b766aa837f684afe9b02208908967ce5f8f',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework.boot/spring-boot-starter-mail@3.3.0-RC1?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://spring.io/projects/spring-boot',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/spring-projects/spring-boot/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-projects/spring-boot',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.springframework.boot/spring-boot-starter-mail@3.3.0-RC1?type=jar',\n    },\n    {\n      publisher: 'Spring IO',\n      group: 'org.springframework',\n      name: 'spring-context-support',\n      version: '6.1.6',\n      description: 'Spring Context Support',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '8d5862cfaec6d2d9b6292992317859cb',\n        },\n        {\n          alg: 'SHA-1',\n          content: '7cc404d7f0c6e1b1ecfa30080fe194b52867d6b2',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '0f62f0fef682b9ca10e83a292d01e934f0cb38207b07521bcb57e71050523fef',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '82f8fe4eb3bd776c0ea866eb5b11a3b5a1fbbc83348abe6d7c22fb5767fcac1ef4955969378d7a63059df4445fac059724e6f175099932cae95c381fa28bc3b0',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '0b7e95883e480c6bac9358517568c0bfc1f1fd1abf329f4b6c870ab0c7a24810d3f6c2beda6a19e8830393cc6dc8ec58',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '79310e3758ea66e467fbb1908c8018bdb4eeec3ec52b3274965808fd252803b466214199e9aa357048aec0b958276853',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '450c4fb5ae0ac74f111274395746f8eaf7ee68a7bdcc6c58582ead738653bda9',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '14ff0935a90a396cf9f8597a7fb60a51441ae8773fc23acff541cf8cd758e1d364880b0cf1b543ce8f67d9c8b309f991484a7fa8c581eb69784ac137e0c1a71e',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework/spring-context-support@6.1.6?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://github.com/spring-projects/spring-framework',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/spring-projects/spring-framework/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-projects/spring-framework',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.springframework/spring-context-support@6.1.6?type=jar',\n    },\n    {\n      publisher: 'Eclipse Foundation',\n      group: 'org.eclipse.angus',\n      name: 'jakarta.mail',\n      version: '2.0.3',\n      description: 'Angus Mail default provider',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '33043478b24ab3845a3bf702c18f9226',\n        },\n        {\n          alg: 'SHA-1',\n          content: '3dea6aeee9603f573687b0d4da5dc1316d921bb7',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            'efb946424933806bc6f8136752d22fdb3ba887ea0527ff849c474e51f7b3715e',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '6e4abfa2efb985e2bb77e248c8788ea8dfd1fa2a3f88337399a0e4d0c748777100fb6b17c4d1f1e25ce2595fbbec78625f527b21a4986d4d16ea2132847fea51',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '9ab6e99b3d90c3ce031923dbed8ba018c6ae951d2b82d83b9918bc97da06b2096a74a47ae72f4be65bc60471745566b6',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            'c408a694e3c7db7e500f361ce0f999c905d27ba98d1fc4a1597904af2e5ae83689d5792b1a21369c440ef81f262a3c63',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '8106e97a462f4fd311b9d5a1a3dbd1fa0e6df2ae3d1944240d8015bec1264c9a',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '03a3e514d879d6533181f6e99cb234bc4c39cc67e19053ad33fd92547c3e41b8a744293b4bd90d6e89aa129c410b5fe819e6c852b360cb8d9f2a1bbe92126916',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'EPL-2.0',\n          },\n        },\n        {\n          license: {\n            id: 'GPL-2.0-with-classpath-exception',\n          },\n        },\n        {\n          license: {\n            id: 'BSD-3-Clause',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.eclipse.angus/jakarta.mail@2.0.3?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'http://eclipse-ee4j.github.io/angus-mail/jakarta.mail',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://jakarta.oss.sonatype.org/service/local/staging/deploy/maven2/',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/eclipse-ee4j/angus-mail/issues',\n        },\n        {\n          type: 'mailing-list',\n          url: 'https://dev.eclipse.org/mhonarc/lists/jakarta.ee-community/',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/eclipse-ee4j/angus-mail/jakarta.mail',\n        },\n      ],\n      type: 'library',\n      'bom-ref': 'pkg:maven/org.eclipse.angus/jakarta.mail@2.0.3?type=jar',\n    },\n    {\n      publisher: 'Eclipse Foundation',\n      group: 'jakarta.activation',\n      name: 'jakarta.activation-api',\n      version: '2.1.3',\n      description: 'Jakarta Activation API 2.1 Specification',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '76e7b680375ea9f40f3ddbd702efcd25',\n        },\n        {\n          alg: 'SHA-1',\n          content: 'fa165bd70cda600368eee31555222776a46b881f',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '01b176d718a169263e78290691fc479977186bcc6b333487325084d6586f4627',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            'aaabd4d6085a07035eaaae7b5a81aef429fea76e7fe1c8d29971e6595f0adad6bcf1088cff8a1c8936d739b0e3fce4b845323032f046b7edab2eaebd0e10a2ad',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '4c4e73f59bf09342ca7691fd4855b41d3466da80618a5b7df059a2d89cf6d9779a4af751a6c4a9c48e5025c3ff75f42e',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '20be816700c87778e9453d41b6d8cb9dc992a092a308a9b7f2dfbf72e2393940a7d666c46625f130a2b57bc414df85ca',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '8a574b0a249842ea1b397d4cdef9b6d00b34ce8a849ea34184cdf45ac5aafe67',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '69cfb7dddda70ac1fca272ace0a3d5551b85dd60a6dbaf987ee777fbf573b420d13f06b8990ae70e8fe063f92b78c8a447cf9309ba516a5e993ba2d49cca8d23',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'BSD-3-Clause',\n          },\n        },\n      ],\n      purl: 'pkg:maven/jakarta.activation/jakarta.activation-api@2.1.3?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://github.com/jakartaee/jaf-api',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://jakarta.oss.sonatype.org/service/local/staging/deploy/maven2/',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/jakartaee/jaf-api/issues/',\n        },\n        {\n          type: 'mailing-list',\n          url: 'https://dev.eclipse.org/mhonarc/lists/jakarta.ee-community/',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/jakartaee/jaf-api',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/jakarta.activation/jakarta.activation-api@2.1.3?type=jar',\n    },\n    {\n      publisher: 'Eclipse Foundation',\n      group: 'org.eclipse.angus',\n      name: 'angus-activation',\n      version: '2.0.2',\n      description: 'Angus Activation Registries Implementation',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '42bba74155dc773eca277ee7a16f74be',\n        },\n        {\n          alg: 'SHA-1',\n          content: '41f1e0ddd157c856926ed149ab837d110955a9fc',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '6dd3bcffc22bce83b07376a0e2e094e4964a3195d4118fb43e380ef35436cc1e',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '1482c759843c23e0343ca554194862d53ac18a04ab4691b3bf05145abb77283617022a895c5ba2e33f62b77c2cfb906b90d0cb690623621b11f35194b54b1180',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '0263b0f42e56f9cbf4a2446c26a29d6397477561c2149f7b7d0e62fb28ab4315d50faf4e96aff088d3ac204b16f90892',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            'e77e5bf8be9f98ed06a652e2317253bb29e8f79b26910075332823987b2e1bd3dfbb2d7aeb5a57a454c8632241abcc0a',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '41d7d300d1399e4706a0ead464e13702d85023598a0a81899e40ee8eed847826',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            'dbdcb824069f0dcf9f9d362b8db7c2efa77f28d77e07c204a28e56b79ebfc478d9c5f9e5f01c7269d3afc0db0e6126d74237cc5a51b5e9ec6b6664580a06de8c',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'BSD-3-Clause',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.eclipse.angus/angus-activation@2.0.2?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://github.com/eclipse-ee4j/angus-activation/angus-activation',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://jakarta.oss.sonatype.org/service/local/staging/deploy/maven2/',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/eclipse-ee4j/angus-activation/issues/',\n        },\n        {\n          type: 'mailing-list',\n          url: 'https://dev.eclipse.org/mhonarc/lists/jakarta.ee-community/',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/eclipse-ee4j/angus-activation/angus-activation',\n        },\n      ],\n      type: 'library',\n      'bom-ref': 'pkg:maven/org.eclipse.angus/angus-activation@2.0.2?type=jar',\n    },\n    {\n      group: 'de.codecentric',\n      name: 'spring-boot-admin-starter-client',\n      version: '3.2.4-SNAPSHOT',\n      scope: 'required',\n      purl: 'pkg:maven/de.codecentric/spring-boot-admin-starter-client@3.2.4-SNAPSHOT?type=jar',\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/de.codecentric/spring-boot-admin-starter-client@3.2.4-SNAPSHOT?type=jar',\n    },\n    {\n      publisher: 'Pivotal Software, Inc.',\n      group: 'org.springframework.session',\n      name: 'spring-session-core',\n      version: '3.3.0-RC1',\n      description: 'Spring Session',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: 'ccb0d954843dc820edeeeaf5152bbd2a',\n        },\n        {\n          alg: 'SHA-1',\n          content: '7a795db51bf3380c327f945a5addaf949e5edfa8',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '4a63d762c211e49ae21a13cc56acd1c25ac10882f481a261323c9f0b7397fc0a',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '809bc5e693b3bf2db798ec040dd0cc5b9c5d33ae43cf536333c2efadd33a4559b7d01aec8d2b939113ce4e18f7e4c548e0ee346236a1999fdcac5d2ae448ca2b',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            'f5ba3c2d09289e1a6dd773eea4aa75ba2cf60209db4304eb3f05508b596e6ca5e18fcab02dbd5621a272fd1781d30f11',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            'a827feca6a8b8bc82f9bed8485c9cf3a1130e3d404bd4ba9798ad6a6ab15409f495b4bdf8cab95274151a6e9d8092f92',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '903ab26a5e3269afbf5a94456db29bc01afda1273b29abcafd8b88f9bff6e17d',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '63efe99ccbb959d5a66a177108949e4c07b329af11fdcfe270f01fe42ef221b33e8d44af12e88c606616bfe7c46d67b04c137a6bcda8ae00c497200d777abe3e',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework.session/spring-session-core@3.3.0-RC1?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://spring.io/projects/spring-session',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/spring-projects/spring-session/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-projects/spring-session',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.springframework.session/spring-session-core@3.3.0-RC1?type=jar',\n    },\n    {\n      publisher: 'Spring IO',\n      group: 'org.springframework',\n      name: 'spring-jcl',\n      version: '6.1.6',\n      description: 'Spring Commons Logging Bridge',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: 'acf8ba19c939bace96969efc2c5a6c2b',\n        },\n        {\n          alg: 'SHA-1',\n          content: '84cb19b30b22feca73c2ac005ca849c5890935a3',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            'bfbd972fbd94dfb40cc2b19de21b769e8157497cf55555523a0e01b468b8e9b8',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            'ae26ff2bdbb1928eb4349a3b0375376c64ba1e528006f826cf37b92af1552532cf5efaa49a115665dd2e426ea3cb2ee12e7120615766c41c2c7aab9f510240dc',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            'db3928758d60ffd564c54a9ca173405910166e30e1dd799499019b85fac437aae515b50da9cd8f6c17eea94078c88c18',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '9423a4259723be7647a237a150d22521dc412b3519f5d607e2a83dfbda4c26ff98e34b386ddbfcdc15aa5dd31375da4d',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '9921f099221d6690b3bab7582b53d254c17560922c591f70e608d144fce564fb',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '8aff52cc2ced97617ae8df8597045eee432a92a07cf34ccf03c7d3b8ecba455c86b16752a38a841a3cdaf050b19c0eebba447bc854caa92824564f67a304397f',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework/spring-jcl@6.1.6?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://github.com/spring-projects/spring-framework',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/spring-projects/spring-framework/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-projects/spring-framework',\n        },\n      ],\n      type: 'library',\n      'bom-ref': 'pkg:maven/org.springframework/spring-jcl@6.1.6?type=jar',\n    },\n    {\n      publisher: 'Pivotal Software, Inc.',\n      group: 'org.springframework.session',\n      name: 'spring-session-jdbc',\n      version: '3.3.0-RC1',\n      description: 'Spring Session and Spring JDBC integration',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: 'd950e3c7212c0994d66c4f6dabe6df06',\n        },\n        {\n          alg: 'SHA-1',\n          content: '1f633f053c2005b540513a1295ea4d38a57fb1cb',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            'd2f3a9caa0f5812d412d20423852d50208292a0fe7e56777f8332f1b8b989201',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '4593eb3c20d6fe41b3c5566bfbb989c0c563dd81b0a796428c7e598da972d7a15aa9e55dd774c5b6e74e004e4c4bac9df03e9306be3dc20894df2eccbf09b600',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '708c0bdc7f191aaa9f005d29dfcbc53635666b7ac670082a270b77bff878ea2c90f874c02af8b0e2f86638df1febf579',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '3e563a19f6bde339bff361595b2d3ac6ed6e627a384068ad5cf3636f2b1b77b8cc8d00453e980f5afc3da31977ad373c',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '1de39e1781ec3f371b49acdee594256ec48e2a05d8fa5d71642a74427a5d23fe',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '74699f35b8d1275420b989b1fa0b191e4013247d6a2e4309e6f1051692ea335643648142f63e390265c6d3807aef0ae0bdccaba97d50d56819423e31b9a6d910',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework.session/spring-session-jdbc@3.3.0-RC1?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://spring.io/projects/spring-session',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/spring-projects/spring-session/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-projects/spring-session',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.springframework.session/spring-session-jdbc@3.3.0-RC1?type=jar',\n    },\n    {\n      publisher: 'Spring IO',\n      group: 'org.springframework',\n      name: 'spring-context',\n      version: '6.1.6',\n      description: 'Spring Context',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: 'bff5b9db23e0dfe995e1f4a4a160c5d0',\n        },\n        {\n          alg: 'SHA-1',\n          content: '2be30298638975efaf7fff22f1570d79b2679814',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '452f82d693ada09ebd54666de9c1ad561cb77a1e9574e2076637c08d0b1393ce',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '70edce09aad5fe76ce0d98a6562a14d77d12c7f7fdedcdf3ca1f6fdc356f05de87bc3310268caca1ff7afef8b759a9a40c1ef78e88aa699b9f1eb76acbc968f5',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            'f4072a357ee4e1c403cb0f6d4638f93ba85b611a0d27eafb481dc21933060116a20723dd9cc584559782c5ac0fc9ff37',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '41424e53a13405ec20d5e21e32c078960bf456030bf268d8484c4a5390784b083b93b4524b86fe497e6af3c9c598d260',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            'b2fcf0269d3a9309ed817775b81ad748338dd071a1b51e77fe095f41806a0aa7',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '563b6d0faff0662d57c1619e83135facc819abfb97d91f2cda542a1b034e6d521950fc09457b3b897ea8b20f96cc83379e0479837553a8434a9d7a3c01ed03ba',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework/spring-context@6.1.6?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://github.com/spring-projects/spring-framework',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/spring-projects/spring-framework/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-projects/spring-framework',\n        },\n      ],\n      type: 'library',\n      'bom-ref': 'pkg:maven/org.springframework/spring-context@6.1.6?type=jar',\n    },\n    {\n      publisher: 'Spring IO',\n      group: 'org.springframework',\n      name: 'spring-jdbc',\n      version: '6.1.6',\n      description: 'Spring JDBC',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: 'c8c19b4161ee251c20a0b6eba94ed825',\n        },\n        {\n          alg: 'SHA-1',\n          content: '3f8a440a49c15264ff438598b715bd00c5a88109',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '4f575ff3515214853590f07ccbdac48947a4bd1246596017fb048ab77d0290ae',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '3c513ec606ab67e69c1bb4b49b07a31c41cda60a064b0b83e30f0b2cb40ee10ade84f8e18f2c37d8c10d45a5fa4c0054d827219ad3aa0efb2f158d32928c3d4b',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            'f414896caf82ba89de3dff44b78c9e43b9de9e625c75e4d696c20a8ddf35941d0bb52dbd268ba0896ca87f8b7007e032',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '13a4c9fc471326b80243ce2d737f9cee633b3798e29888ba24447734f4b5ecbd4a8725e4198956aa7aeae052477ec5ac',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            'fe64c52eed719a2be7a11b7f35c115f68ee6e3d653212c909e1d417fcea16f46',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            'fab938f5c5734258958c30f9a4876d84dc94b1771b5d9ba63c8911176eca110a4b1ffc86ed73f1e5c98671f4fce14798adf387e330b01d3fd8e0c8c8430ad22d',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework/spring-jdbc@6.1.6?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://github.com/spring-projects/spring-framework',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/spring-projects/spring-framework/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-projects/spring-framework',\n        },\n      ],\n      type: 'library',\n      'bom-ref': 'pkg:maven/org.springframework/spring-jdbc@6.1.6?type=jar',\n    },\n    {\n      publisher: 'Spring IO',\n      group: 'org.springframework',\n      name: 'spring-tx',\n      version: '6.1.6',\n      description: 'Spring Transaction',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '290de6a7f55216181b65e1f682fd9bc4',\n        },\n        {\n          alg: 'SHA-1',\n          content: '4e18554fb6669f266108cc838a4619bbc8f7db8d',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            'dc76ab1629b986555fc83a0f83803aa591b91af46e9d187ac9eb999a99898b8f',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '5456bf6a0bada70a25bb477534749f39a27e607d7b55144908c076882da9dc9a8a05e95051675ef635b6a285a91e51860a14d3dba94db110ac3309a040fe5f85',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '72cdfa7450e9706dcfccb735c8dbc7af2588cc566d88e3deb65f3f1c9db889e5d8520cd49a21daee503da3193f058435',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            'cd7807ba85d96ecfb8f019372dd658fc777990578b3e088e059bf37a5518ed126808009ac73d1e41d4030fc3b9524041',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            'd920b7548652e6a43344eb289e5f47b701ce1746f426c09d08d1960b3d6e831c',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '5099e4245f5172cad173f62506596f09b5096f7777715527b07d4e88005ce4f48d16be35c4ebb4f3629f3a3ec3ff406d2a36e39ec9464e7364751b56ebb3b5fe',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework/spring-tx@6.1.6?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://github.com/spring-projects/spring-framework',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/spring-projects/spring-framework/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-projects/spring-framework',\n        },\n      ],\n      type: 'library',\n      'bom-ref': 'pkg:maven/org.springframework/spring-tx@6.1.6?type=jar',\n    },\n    {\n      publisher: 'Pivotal Software, Inc.',\n      group: 'org.springframework.cloud',\n      name: 'spring-cloud-starter-config',\n      version: '4.1.1',\n      description: 'Spring Cloud Starter',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: 'f1e90f5c16f40b9c6aa52273d42a36a5',\n        },\n        {\n          alg: 'SHA-1',\n          content: 'c90d7c91c5b422fc416f20a994b303074aa37388',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            'b9b6d56cac78c59467001cd0fa904bc9e6dcc3e26e02872e0ac3d29b2c8031c2',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '5ce61142994ba355a8ee8cfe37e0b07a0b2b4be5ff3c39ab430d253a10f5f37cc910eb0eb4bc7f982e64f379012fa7d339d400a3c96e969b03ebc65cee2c0279',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            'c3159a313b718fbb558d393768d7aecfd3bd73b89ec246435c6de75b152a27889cd946ef58632155a964732b733f07c4',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '747a95ac4d7a2f237861a2cdb068fc42092e3a2aecd660e33cf075392360b1043c4919de8fb57cee82634178ede0d0c3',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '72bab7c132b389f0ec733a0e29a4a0532b8cbddd49e8a923cf02f762fb6fed24',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            'c177c7c5073b1621b93220e790d1402d44ba9339c7468f9515930e96be26672c3e02703de1a61c636b9edee6207466b5c37250a73890290099126a237345decd',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework.cloud/spring-cloud-starter-config@4.1.1?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://projects.spring.io/spring-cloud',\n        },\n        {\n          type: 'distribution',\n          url: 'https://github.com/spring-cloud',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-cloud/spring-cloud-config/spring-cloud-starter-config',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.springframework.cloud/spring-cloud-starter-config@4.1.1?type=jar',\n    },\n    {\n      publisher: 'Pivotal Software, Inc.',\n      group: 'org.springframework.cloud',\n      name: 'spring-cloud-config-client',\n      version: '4.1.1',\n      description: 'This project is a Spring configuration client.',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '02567f0e59035f8b9cf4916468533939',\n        },\n        {\n          alg: 'SHA-1',\n          content: 'eb8e3991ab2a7c944a22544dc2b87763554b8409',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '556349cf937fbaf14428f4ee69976cf13c70af7701abb97489395572e5221031',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            'f0e9ae214fca3739ec110ad32878de64acf9e45069b58f66a7c31694b932607f5b973f6332e4560a84de5ad8b0d6e1d537662b990aa967ed365f72dab38fb12d',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            'cdbf51bcbdc08d8a9fcca1bdcab3c201be85053613fb12b2a866aefea6cf479d8c16aa7ae1b384c15f70f06ab7ad606b',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '5fd2d1526a1e487bfbaa51143043bfd864854d8317f9d51d861047305c2d4ea9e143cc0d24fad9369ea89a4c2170cade',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            'db1ddb5759a6889da3fc99405529daf4556795b7ea6deac63fca9633f9fe69fc',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '529441dfcf47e687640b432ad2dff0b365b3bfdfcf2090a8e64d650bca9b618b4b7c97c8f45b04087d711bb09d5477141ca4c53803ebb35ab2bb9ac2d6ac82ca',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework.cloud/spring-cloud-config-client@4.1.1?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://spring.io',\n        },\n        {\n          type: 'distribution',\n          url: 'https://github.com/spring-cloud',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-cloud/spring-cloud-config/spring-cloud-config-client',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.springframework.cloud/spring-cloud-config-client@4.1.1?type=jar',\n    },\n    {\n      publisher: 'FasterXML',\n      group: 'com.fasterxml.jackson.core',\n      name: 'jackson-annotations',\n      version: '2.17.0',\n      description:\n        'Core annotations used for value types, used by Jackson data binding package.',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '7529e022796db72bc17288e950c24b3f',\n        },\n        {\n          alg: 'SHA-1',\n          content: '880a742337010da4c851f843d8cac150e22dff9f',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '8562569a001d46e84ea23802257e33c8f68b24eb47c1e0efd133a0372c512959',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '20104840da168653b27ffcbef6600d29d04b7f315934531f6521b30cfc0438893ac5e3b2476ba03a6a47f3aed8882cc7d5a57b66163ad19aac217a258826e51b',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            'c597370368f411e8f63500537a94f503f44f3bbd653c77d39871eb65745ee2a3d8d83bb8c303790c1e26f30e76219000',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '6c446264fac7209fc435be283dbb6d578ed7328e84756d7e987a0871a9119bf9bffbfe40827e84324ea7924f83aad770',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '437fa185a964c155377819fed79558491f31a7ee20a60c4624d252f6c6bf75bc',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            'ad18bc120cfafe0ee6c961c5422242361de7b3154d3252a2ffaecf5d981865c141a25fa8706709eabc351d42f3593dd2832219657671f21d9672c2488e5d1bf4',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/com.fasterxml.jackson.core/jackson-annotations@2.17.0?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://github.com/FasterXML/jackson',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2/',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/FasterXML/jackson-annotations/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/FasterXML/jackson-annotations',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/com.fasterxml.jackson.core/jackson-annotations@2.17.0?type=jar',\n    },\n    {\n      publisher: 'The Apache Software Foundation',\n      group: 'org.apache.httpcomponents.client5',\n      name: 'httpclient5',\n      version: '5.3.1',\n      description: 'Apache HttpComponents Client',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: 'de1810a606b27192cbf5bbad9c25a648',\n        },\n        {\n          alg: 'SHA-1',\n          content: '56b53c8f4bcdaada801d311cf2ff8a24d6d96883',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '08346a757c617f6ecc66af9f099260adde1f3a1351fa81cb22fc17482b31f823',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '4c2d75106af8470789f0e08305e64ad86528f2f737da230e561892d33dbca0b6e2dbced2a075f0744cee7801c06ef174481540661b3c9a1bec6d6f93938b05bc',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '27470f74660b89f8a0af562a4edbd244afff4947b0fa7364c61e53ca49713efbca49e661214590f532c4acf9cfd66eac',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            'd25be0f1c5e0c02de0adf7113e591f10bd7fab20c168a20b7d15c859b252a6dc3ae3a24098e838d95c179ab3107f07b6',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '9e22ce6935e71d12d1be70ef0b7cea9a87191c767de2904cb82fcb6e58d0e9b2',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '9d36e201e469dd357ef715bba7beba62dbea98daefcea3b793fd285c2ffade97d72b35a07f05015fbc2d5b4fa5db58ff5ecf40e1269582a6c3e53ed62cbf97f4',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.apache.httpcomponents.client5/httpclient5@5.3.1?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://hc.apache.org/httpcomponents-client-5.0.x/5.3.1/httpclient5/',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://repository.apache.org/service/local/staging/deploy/maven2',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://issues.apache.org/jira/browse/HTTPCLIENT',\n        },\n        {\n          type: 'mailing-list',\n          url: 'https://lists.apache.org/list.html?httpclient-users@hc.apache.org',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/apache/httpcomponents-client/tree/5.3.1/httpclient5',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.apache.httpcomponents.client5/httpclient5@5.3.1?type=jar',\n    },\n    {\n      publisher: 'The Apache Software Foundation',\n      group: 'org.apache.httpcomponents.core5',\n      name: 'httpcore5',\n      version: '5.2.4',\n      description: 'Apache HttpComponents HTTP/1.1 core components',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '5a3d417ea4e65e0f74194263dc5c6c43',\n        },\n        {\n          alg: 'SHA-1',\n          content: '34d8332b975f9e9a8298efe4c883ec43d45b7059',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            'a7f62496113f66f9e27c26b84c44f5ce4555c6270083cdf2d45f255336cd52af',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '9fb4134d85e665e15410af005b21cd2f9b5e60d75112945d37b879f96f769a70be034557526ea7d05f8b83dda91c56d00f946763c44a183d7aea2857549b4481',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '8ec2da2fd22a23e9f740589947398a907795ab310d4ed166ecc1448ceea7035a50090cf645dad28f3c84c08599f1e57e',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            'e52f4f1c073ec9d893cffa353a27939054a0b537fe49e4aacfdd6c265dfba037309913a001c025d85ccbb06a1e9e72b0',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '23fb185f22dac603ba579c4f707671f43b3b08ab049ae519fb492ea0232c5ba9',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '8371cd7e6f94ccb1590bd90d5868a90a3344123b0e6a4f7113d76b816c16d58efc6f2cab124a45ffd93739990fed05201f294f890aa989345f52e736383b4261',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.apache.httpcomponents.core5/httpcore5@5.2.4?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://hc.apache.org/httpcomponents-core-5.2.x/5.2.4/httpcore5/',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://repository.apache.org/service/local/staging/deploy/maven2',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://issues.apache.org/jira/browse/HTTPCORE',\n        },\n        {\n          type: 'mailing-list',\n          url: 'https://lists.apache.org/list.html?httpclient-users@hc.apache.org',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/apache/httpcomponents-core/tree/5.2.4/httpcore5',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.apache.httpcomponents.core5/httpcore5@5.2.4?type=jar',\n    },\n    {\n      publisher: 'The Apache Software Foundation',\n      group: 'org.apache.httpcomponents.core5',\n      name: 'httpcore5-h2',\n      version: '5.2.4',\n      description: 'Apache HttpComponents HTTP/2 Core Components',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: 'd407b8144029db656ac5ba3d54ef801f',\n        },\n        {\n          alg: 'SHA-1',\n          content: '2872764df7b4857549e2880dd32a6f9009166289',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            'dc1a95e73eb04db93451533d390ce02c53b301a10dc343d08c862f2934b3d30e',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '72fbee55f173c43d9ffc0cc5a83d59e60be1002c06ab81de39ba700cc30b04e84fdfed73d3a8985d561a1aa8ac3ca905f9259d01b431e1ff14da6fae622f787d',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '2f96537af2866fa96aae46138febe3009dca97cc9b4284cf18510c12d159ad3f5d34c3c9bafc8026215da81520331660',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '9900a3aeaf434d7f32a7500e29e16d354857ef34e6af3fb7de9e1ab7683b6a1c4bfa9b9f70bb779a8ec8d8be82b6bca4',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            'da34ed59342e368229b74245d2268a457588adea9e276a1ac2fb57419c605f31',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            'ca5b03cf34c7e344fd0b809c582e60f0eaea796372cf68e2e95087ac5943154e51472595f6554b810a5ac4789ba6f7c06cae46437badecbf31c57907123a49fc',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.apache.httpcomponents.core5/httpcore5-h2@5.2.4?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://hc.apache.org/httpcomponents-core-5.2.x/5.2.4/httpcore5-h2/',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://repository.apache.org/service/local/staging/deploy/maven2',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://issues.apache.org/jira/browse/HTTPCORE',\n        },\n        {\n          type: 'mailing-list',\n          url: 'https://lists.apache.org/list.html?httpclient-users@hc.apache.org',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/apache/httpcomponents-core/tree/5.2.4/httpcore5-h2',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/org.apache.httpcomponents.core5/httpcore5-h2@5.2.4?type=jar',\n    },\n    {\n      publisher: 'FasterXML',\n      group: 'com.fasterxml.jackson.core',\n      name: 'jackson-databind',\n      version: '2.17.0',\n      description:\n        'General data-binding functionality for Jackson: works on core streaming API',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '09dd83868b44c6a3dc48911f4b3bbbc1',\n        },\n        {\n          alg: 'SHA-1',\n          content: '7173e9e1d4bc6d7ca03bc4eeedcd548b8b580b34',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            'd0ed5b54cb1b0bbb0828e24ce752a43a006dc188b34e3a4ae3238acc7b637418',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            'c6b06d4b20941d9e32b462552031e6c98378e5edce57693e55adcc73cf7d5088af5b3a666a59e94a7f0b57066ac694863919f398f28ee0d7ceb362c8c05f7491',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '02875865ef42573114755ab7147d64f8e5a791f2a2b8debe51dda22886370ef34af8c159d8efa8b90735f33f90089187',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            'ee93411dc73337c11d48609fbf79ae606ccd0ab712e3d2c12c91103964182910a810b9fa062a0afc47d19c720c97c5e7',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            'eb5b5dfb8afb2538a2c31caab47d909970bb647763a0eccabaae8e6f0a9ad988',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '09ce7a8d928d42b221e0f6151653248f78eb68b6228cfc36fabd48ae48a3890f235fb33b82cc2faa24069efdf5a14d7433014e3bfb0d5173047d1911e5a55fe4',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.17.0?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://github.com/FasterXML/jackson',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2/',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/FasterXML/jackson-databind/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/FasterXML/jackson-databind',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.17.0?type=jar',\n    },\n    {\n      publisher: 'FasterXML',\n      group: 'com.fasterxml.jackson.core',\n      name: 'jackson-core',\n      version: '2.17.0',\n      description:\n        'Core Jackson processing abstractions (aka Streaming API), implementation for JSON',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '3e4b82b6e29693927dd289a344c35e46',\n        },\n        {\n          alg: 'SHA-1',\n          content: 'a6e5058ef9720623c517252d17162f845306ff3a',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            '55be130f6a68038088a261856c4e383ce79957a0fc1a29ecb213a9efd6ef4389',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '85611fb7687450eb6078855c46d94dabf1cebcf179e23455cd1069aaded3b169112ec5d3d8d8510a7076166dc146e2f684f8527c5ef5b9ed99a7ec91f0825523',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '12bbfe5721ecd374a77ede24cca8a39f1415fd50dd95938c5e3365c703a02b5f6c0e7b10c7b44b7c9c5a874dd0b971c7',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            'd1b1f9c3e53603ccc690d76ac1a90dd1ddf07723f4eb53f58acfa266f17a675b311b0f29b94d9f1bb0ab32ad1a8aea4a',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '62a76265cdc48c8a7f80c9d5566a179bd796c646b25c5cb937fd0a10cfffff1f',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '87c42bea365905e9d877bda162c9d79d962a969a53a46861c350b9a9a87d09f4986c6ff67c4f00eba6df8c86f0621cfb52359a91321ace7eec3bb5d7c82feeb9',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/com.fasterxml.jackson.core/jackson-core@2.17.0?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://github.com/FasterXML/jackson-core',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2/',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/FasterXML/jackson-core/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/FasterXML/jackson-core',\n        },\n      ],\n      type: 'library',\n      'bom-ref':\n        'pkg:maven/com.fasterxml.jackson.core/jackson-core@2.17.0?type=jar',\n    },\n    {\n      group: 'net.bytebuddy',\n      name: 'byte-buddy',\n      version: '1.14.13',\n      description:\n        'Byte Buddy is a Java library for creating Java classes at run time. This artifact is a build of Byte Buddy with all ASM dependencies repackaged into its own name space.',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '7f4df0c9277f4c1c418a742cc3178ac9',\n        },\n        {\n          alg: 'SHA-1',\n          content: '45cf516d9a23485200950549ff72b204c307fc9d',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            'ba8254ff6d612af49acee4cac1108453ce3a417efa548b24f2f4f268cd2b441a',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            'c7f76ce1bf108c98af398c3b2df01d8bd0a81a8eb6efe669fb23ef3cfb33419fe7f975c2523579f4d48567da7786751a31ef79f41babc391be250da485c93b0e',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '31ea6c6cc36495936761edee2ce2a3ba61edf66e5e2de78d7ae243db03e84cf57081e8434b5c4cafbf042a5ab15799ec',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '0528e8facb1eb96e7c2f825bbda814432ca7b269de046c9d49eac047762fc5dc0c3d6b91cca08a9853d8744497e28e62',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            'a1da765e093a8b14bb5c2e3eb7012ced676840f8201a3ffaf47c3da4784018fd',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '05581ee76ccce6fb141e55863db63e03a9885381a185a3413046bf5f40302d8314141ff093f396ef99a3b0948fc58346b0b5c7c139ccae2294cd3040bd0493cd',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/net.bytebuddy/byte-buddy@1.14.13?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://bytebuddy.net/byte-buddy',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/raphw/byte-buddy/issues',\n        },\n      ],\n      type: 'library',\n      'bom-ref': 'pkg:maven/net.bytebuddy/byte-buddy@1.14.13?type=jar',\n    },\n    {\n      publisher: 'The HSQL Development Group',\n      group: 'org.hsqldb',\n      name: 'hsqldb',\n      version: '2.7.2',\n      description: 'HSQLDB - Lightweight 100% Java SQL Database Engine',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: 'dab42304e10a7983af59ce89a8ccee12',\n        },\n        {\n          alg: 'SHA-1',\n          content: 'd92d4d2aa515714da2165c9d640d584c2896c9df',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            'aa455133e664f6a7e6f30cd0cd4f8ad83dfbd94eb717c438548e446784614a92',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '0b997354bae288f84925cfb0aca0525257225a150a84ecbc687b3c7e6ef38cddbcdf2cf24404ac2bf4990a5d8baad2394c38bb5b299a7cfcbd2982741cd35b20',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            'f2d2e3fa488ffc041cef71acb951a9475177974c98a9f6b9f711d0ea67bd6c344dd0601856423fd96e63b080d9a41dfc',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '84b769ff1bb3ace9e00b9e499405b445304bc55892d093e8306a9f94e100c2fb3aefa569290e74f42b207c980f0baaef',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            'b981fa576356765be24044e6bcc3d273891a8e53a2caa5a4541a6039cfc4ae67',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '042bfe7d7d30f16c23bb2305e37c526999553c2ba1df39ab7d1108943a4699b84fc9515e5ab272733e39882aac22d59fb1691c95e0d1d99f8bb012ce8caf8aa3',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            name: 'HSQLDB License, a BSD open source license',\n            url: 'http://hsqldb.org/web/hsqlLicense.html',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.hsqldb/hsqldb@2.7.2?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'http://hsqldb.org',\n        },\n        {\n          type: 'vcs',\n          url: 'http://sourceforge.net/p/hsqldb/svn/HEAD/tree/base/tags/2.7.2',\n        },\n      ],\n      type: 'library',\n      'bom-ref': 'pkg:maven/org.hsqldb/hsqldb@2.7.2?type=jar',\n    },\n    {\n      publisher: 'QOS.ch',\n      group: 'org.slf4j',\n      name: 'slf4j-api',\n      version: '2.0.13',\n      description: 'The slf4j API',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '7f4028aa04f75427327f3f30cd62ba4e',\n        },\n        {\n          alg: 'SHA-1',\n          content: '80229737f704b121a318bba5d5deacbcf395bc77',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            'e7c2a48e8515ba1f49fa637d57b4e2f590b3f5bd97407ac699c3aa5efb1204a9',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            'b4eeb5757118e264ec7f107d879270784357380d6f53471b7874dd7e0166fdf5686a95eb66bab867abbe9536da032ab052e207165211391c293cbf6178431fb6',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            'b67cbb4ef32141423000dd4e067bf32e0c1dd2c4689c611522b9fedfc1744513175a22f4b1276f2cec4721c9467cf882',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            '817fc9641f4fc52bfd76006886c6eba975f6f09b2a7cc59334729a8cc033807c8e89be9ec4309acfc16ed65ff6eee018',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            'f26080cceb5a2e605f3844d6dc8dd3f14c543cb14510765d841d71a64fa454dc',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            '00646c78d65ec854e157638f40735f1888aa585ede59915d58386c599c2fe54ec8c1da73284aeff00ce3142165e33c4c995ad39d08843c31e9e4d7e32c746836',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'MIT',\n            url: 'https://opensource.org/licenses/MIT',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.slf4j/slf4j-api@2.0.13?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'http://www.slf4j.org',\n        },\n        {\n          type: 'distribution-intake',\n          url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2/',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/qos-ch/slf4j/slf4j-parent/slf4j-api',\n        },\n      ],\n      type: 'library',\n      'bom-ref': 'pkg:maven/org.slf4j/slf4j-api@2.0.13?type=jar',\n    },\n    {\n      publisher: 'Spring IO',\n      group: 'org.springframework',\n      name: 'spring-core',\n      version: '6.1.6',\n      description: 'Spring Core',\n      scope: 'required',\n      hashes: [\n        {\n          alg: 'MD5',\n          content: '852be6055a31d2ce17b5d231b17f732e',\n        },\n        {\n          alg: 'SHA-1',\n          content: 'dea4b8e110b7b54a02a4907e32dbb0adee8a7168',\n        },\n        {\n          alg: 'SHA-256',\n          content:\n            'caf51f3d51c5d95e931f411027688f1dde3986d5f2aad67ff1096ddddac36ac5',\n        },\n        {\n          alg: 'SHA-512',\n          content:\n            '893d9c5956c3005717dc7f09b31908dfed3588f9c81fb6180781ca687f305157cf3481f246d1a493fa348991d41a660b54e5db7ff5a4e4676570062b8c22b38b',\n        },\n        {\n          alg: 'SHA-384',\n          content:\n            '984ff65f605a97d0ecea2a30986fb6c443deb83c7f58450bc661d9060563e1f199f9d472794520106ccbd23b29de0531',\n        },\n        {\n          alg: 'SHA3-384',\n          content:\n            'edf8fafbef9d85a15d226dcc85d4fd71c6eaca2cd885867b8a81c37424c9ce024dc16a8f8169a2b2d1214b8b6532d278',\n        },\n        {\n          alg: 'SHA3-256',\n          content:\n            '71f60a76d6b31290bb2024f17e82e5f920d2dc576a2649c054767ea574baf685',\n        },\n        {\n          alg: 'SHA3-512',\n          content:\n            'cf4ab0e13b212c22f9cbea6afdf4aac815c62bd904d2f0d91198e29098988495b4038b6b3c56b6cb48d5b26efc39dec92283e067a969be1d75db817949d73cc4',\n        },\n      ],\n      licenses: [\n        {\n          license: {\n            id: 'Apache-2.0',\n          },\n        },\n      ],\n      purl: 'pkg:maven/org.springframework/spring-core@6.1.6?type=jar',\n      externalReferences: [\n        {\n          type: 'website',\n          url: 'https://github.com/spring-projects/spring-framework',\n        },\n        {\n          type: 'issue-tracker',\n          url: 'https://github.com/spring-projects/spring-framework/issues',\n        },\n        {\n          type: 'vcs',\n          url: 'https://github.com/spring-projects/spring-framework',\n        },\n      ],\n      type: 'library',\n      'bom-ref': 'pkg:maven/org.springframework/spring-core@6.1.6?type=jar',\n    },\n  ],\n  dependencies: [\n    {\n      ref: 'pkg:maven/de.codecentric/spring-boot-admin-sample-servlet@3.2.4-SNAPSHOT?type=jar',\n      dependsOn: [\n        'pkg:maven/de.codecentric/spring-boot-admin-sample-custom-ui@3.2.4-SNAPSHOT?type=jar',\n        'pkg:maven/de.codecentric/spring-boot-admin-starter-server@3.2.4-SNAPSHOT?type=jar',\n        'pkg:maven/org.springframework.boot/spring-boot-starter-security@3.3.0-RC1?type=jar',\n        'pkg:maven/org.springframework.boot/spring-boot-starter-webmvc@3.3.0-RC1?type=jar',\n        'pkg:maven/org.springframework.cloud/spring-cloud-starter@4.1.2?type=jar',\n        'pkg:maven/org.springframework.boot/spring-boot-starter-mail@3.3.0-RC1?type=jar',\n        'pkg:maven/de.codecentric/spring-boot-admin-starter-client@3.2.4-SNAPSHOT?type=jar',\n        'pkg:maven/org.springframework.session/spring-session-core@3.3.0-RC1?type=jar',\n        'pkg:maven/org.springframework.session/spring-session-jdbc@3.3.0-RC1?type=jar',\n        'pkg:maven/org.springframework.cloud/spring-cloud-starter-config@4.1.1?type=jar',\n        'pkg:maven/org.hsqldb/hsqldb@2.7.2?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/de.codecentric/spring-boot-admin-sample-custom-ui@3.2.4-SNAPSHOT?type=jar',\n      dependsOn: [],\n    },\n    {\n      ref: 'pkg:maven/de.codecentric/spring-boot-admin-starter-server@3.2.4-SNAPSHOT?type=jar',\n      dependsOn: [],\n    },\n    {\n      ref: 'pkg:maven/org.springframework.boot/spring-boot-starter-security@3.3.0-RC1?type=jar',\n      dependsOn: [\n        'pkg:maven/org.springframework.boot/spring-boot-starter@3.3.0-RC1?type=jar',\n        'pkg:maven/org.springframework/spring-aop@6.1.6?type=jar',\n        'pkg:maven/org.springframework.security/spring-security-config@6.3.0-RC1?type=jar',\n        'pkg:maven/org.springframework.security/spring-security-web@6.3.0-RC1?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.springframework.boot/spring-boot-starter@3.3.0-RC1?type=jar',\n      dependsOn: [\n        'pkg:maven/org.springframework.boot/spring-boot@3.3.0-RC1?type=jar',\n        'pkg:maven/org.springframework.boot/spring-boot-autoconfigure@3.3.0-RC1?type=jar',\n        'pkg:maven/org.springframework.boot/spring-boot-starter-logging@3.3.0-RC1?type=jar',\n        'pkg:maven/jakarta.annotation/jakarta.annotation-api@2.1.1?type=jar',\n        'pkg:maven/org.springframework/spring-core@6.1.6?type=jar',\n        'pkg:maven/org.yaml/snakeyaml@2.2?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.springframework.boot/spring-boot@3.3.0-RC1?type=jar',\n      dependsOn: [\n        'pkg:maven/org.springframework/spring-core@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-context@6.1.6?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.springframework/spring-core@6.1.6?type=jar',\n      dependsOn: ['pkg:maven/org.springframework/spring-jcl@6.1.6?type=jar'],\n    },\n    {\n      ref: 'pkg:maven/org.springframework/spring-jcl@6.1.6?type=jar',\n      dependsOn: [],\n    },\n    {\n      ref: 'pkg:maven/org.springframework/spring-context@6.1.6?type=jar',\n      dependsOn: [\n        'pkg:maven/org.springframework/spring-aop@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-beans@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-core@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-expression@6.1.6?type=jar',\n        'pkg:maven/io.micrometer/micrometer-observation@1.13.0-RC1?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.springframework/spring-aop@6.1.6?type=jar',\n      dependsOn: [\n        'pkg:maven/org.springframework/spring-beans@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-core@6.1.6?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.springframework/spring-beans@6.1.6?type=jar',\n      dependsOn: ['pkg:maven/org.springframework/spring-core@6.1.6?type=jar'],\n    },\n    {\n      ref: 'pkg:maven/org.springframework/spring-expression@6.1.6?type=jar',\n      dependsOn: ['pkg:maven/org.springframework/spring-core@6.1.6?type=jar'],\n    },\n    {\n      ref: 'pkg:maven/io.micrometer/micrometer-observation@1.13.0-RC1?type=jar',\n      dependsOn: [\n        'pkg:maven/io.micrometer/micrometer-commons@1.13.0-RC1?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/io.micrometer/micrometer-commons@1.13.0-RC1?type=jar',\n      dependsOn: [],\n    },\n    {\n      ref: 'pkg:maven/org.springframework.boot/spring-boot-autoconfigure@3.3.0-RC1?type=jar',\n      dependsOn: [\n        'pkg:maven/org.springframework.boot/spring-boot@3.3.0-RC1?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.springframework.boot/spring-boot-starter-logging@3.3.0-RC1?type=jar',\n      dependsOn: [\n        'pkg:maven/ch.qos.logback/logback-classic@1.5.6?type=jar',\n        'pkg:maven/org.apache.logging.log4j/log4j-to-slf4j@2.23.1?type=jar',\n        'pkg:maven/org.slf4j/jul-to-slf4j@2.0.13?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/ch.qos.logback/logback-classic@1.5.6?type=jar',\n      dependsOn: [\n        'pkg:maven/ch.qos.logback/logback-core@1.5.6?type=jar',\n        'pkg:maven/org.slf4j/slf4j-api@2.0.13?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/ch.qos.logback/logback-core@1.5.6?type=jar',\n      dependsOn: [],\n    },\n    {\n      ref: 'pkg:maven/org.slf4j/slf4j-api@2.0.13?type=jar',\n      dependsOn: [],\n    },\n    {\n      ref: 'pkg:maven/org.apache.logging.log4j/log4j-to-slf4j@2.23.1?type=jar',\n      dependsOn: [\n        'pkg:maven/org.apache.logging.log4j/log4j-api@2.23.1?type=jar',\n        'pkg:maven/org.slf4j/slf4j-api@2.0.13?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.apache.logging.log4j/log4j-api@2.23.1?type=jar',\n      dependsOn: [],\n    },\n    {\n      ref: 'pkg:maven/org.slf4j/jul-to-slf4j@2.0.13?type=jar',\n      dependsOn: ['pkg:maven/org.slf4j/slf4j-api@2.0.13?type=jar'],\n    },\n    {\n      ref: 'pkg:maven/jakarta.annotation/jakarta.annotation-api@2.1.1?type=jar',\n      dependsOn: [],\n    },\n    {\n      ref: 'pkg:maven/org.yaml/snakeyaml@2.2?type=jar',\n      dependsOn: [],\n    },\n    {\n      ref: 'pkg:maven/org.springframework.security/spring-security-config@6.3.0-RC1?type=jar',\n      dependsOn: [\n        'pkg:maven/org.springframework.security/spring-security-core@6.3.0-RC1?type=jar',\n        'pkg:maven/org.springframework/spring-aop@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-beans@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-context@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-core@6.1.6?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.springframework.security/spring-security-core@6.3.0-RC1?type=jar',\n      dependsOn: [\n        'pkg:maven/org.springframework.security/spring-security-crypto@6.3.0-RC1?type=jar',\n        'pkg:maven/org.springframework/spring-aop@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-beans@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-context@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-core@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-expression@6.1.6?type=jar',\n        'pkg:maven/io.micrometer/micrometer-observation@1.13.0-RC1?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.springframework.security/spring-security-crypto@6.3.0-RC1?type=jar',\n      dependsOn: [],\n    },\n    {\n      ref: 'pkg:maven/org.springframework.security/spring-security-web@6.3.0-RC1?type=jar',\n      dependsOn: [\n        'pkg:maven/org.springframework.security/spring-security-core@6.3.0-RC1?type=jar',\n        'pkg:maven/org.springframework/spring-core@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-aop@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-beans@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-context@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-expression@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-web@6.1.6?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.springframework/spring-web@6.1.6?type=jar',\n      dependsOn: [\n        'pkg:maven/org.springframework/spring-beans@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-core@6.1.6?type=jar',\n        'pkg:maven/io.micrometer/micrometer-observation@1.13.0-RC1?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.springframework.boot/spring-boot-starter-webmvc@3.3.0-RC1?type=jar',\n      dependsOn: [\n        'pkg:maven/org.springframework.boot/spring-boot-starter@3.3.0-RC1?type=jar',\n        'pkg:maven/org.springframework.boot/spring-boot-starter-json@3.3.0-RC1?type=jar',\n        'pkg:maven/org.springframework.boot/spring-boot-starter-tomcat@3.3.0-RC1?type=jar',\n        'pkg:maven/org.springframework/spring-web@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-webmvc@6.1.6?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.springframework.boot/spring-boot-starter-json@3.3.0-RC1?type=jar',\n      dependsOn: [\n        'pkg:maven/org.springframework.boot/spring-boot-starter@3.3.0-RC1?type=jar',\n        'pkg:maven/org.springframework/spring-web@6.1.6?type=jar',\n        'pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.17.0?type=jar',\n        'pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jdk8@2.17.0?type=jar',\n        'pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jsr310@2.17.0?type=jar',\n        'pkg:maven/com.fasterxml.jackson.module/jackson-module-parameter-names@2.17.0?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.17.0?type=jar',\n      dependsOn: [\n        'pkg:maven/com.fasterxml.jackson.core/jackson-annotations@2.17.0?type=jar',\n        'pkg:maven/com.fasterxml.jackson.core/jackson-core@2.17.0?type=jar',\n        'pkg:maven/net.bytebuddy/byte-buddy@1.14.13?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/com.fasterxml.jackson.core/jackson-annotations@2.17.0?type=jar',\n      dependsOn: [],\n    },\n    {\n      ref: 'pkg:maven/com.fasterxml.jackson.core/jackson-core@2.17.0?type=jar',\n      dependsOn: [],\n    },\n    {\n      ref: 'pkg:maven/net.bytebuddy/byte-buddy@1.14.13?type=jar',\n      dependsOn: [],\n    },\n    {\n      ref: 'pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jdk8@2.17.0?type=jar',\n      dependsOn: [\n        'pkg:maven/com.fasterxml.jackson.core/jackson-core@2.17.0?type=jar',\n        'pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.17.0?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jsr310@2.17.0?type=jar',\n      dependsOn: [\n        'pkg:maven/com.fasterxml.jackson.core/jackson-annotations@2.17.0?type=jar',\n        'pkg:maven/com.fasterxml.jackson.core/jackson-core@2.17.0?type=jar',\n        'pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.17.0?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/com.fasterxml.jackson.module/jackson-module-parameter-names@2.17.0?type=jar',\n      dependsOn: [\n        'pkg:maven/com.fasterxml.jackson.core/jackson-core@2.17.0?type=jar',\n        'pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.17.0?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.springframework.boot/spring-boot-starter-tomcat@3.3.0-RC1?type=jar',\n      dependsOn: [\n        'pkg:maven/jakarta.annotation/jakarta.annotation-api@2.1.1?type=jar',\n        'pkg:maven/org.apache.tomcat.embed/tomcat-embed-core@10.1.20?type=jar',\n        'pkg:maven/org.apache.tomcat.embed/tomcat-embed-el@10.1.20?type=jar',\n        'pkg:maven/org.apache.tomcat.embed/tomcat-embed-websocket@10.1.20?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.apache.tomcat.embed/tomcat-embed-core@10.1.20?type=jar',\n      dependsOn: [],\n    },\n    {\n      ref: 'pkg:maven/org.apache.tomcat.embed/tomcat-embed-el@10.1.20?type=jar',\n      dependsOn: [],\n    },\n    {\n      ref: 'pkg:maven/org.apache.tomcat.embed/tomcat-embed-websocket@10.1.20?type=jar',\n      dependsOn: [\n        'pkg:maven/org.apache.tomcat.embed/tomcat-embed-core@10.1.20?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.springframework/spring-webmvc@6.1.6?type=jar',\n      dependsOn: [\n        'pkg:maven/org.springframework/spring-aop@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-beans@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-context@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-core@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-expression@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-web@6.1.6?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.springframework.cloud/spring-cloud-starter@4.1.2?type=jar',\n      dependsOn: [\n        'pkg:maven/org.springframework.boot/spring-boot-starter@3.3.0-RC1?type=jar',\n        'pkg:maven/org.springframework.cloud/spring-cloud-context@4.1.2?type=jar',\n        'pkg:maven/org.springframework.cloud/spring-cloud-commons@4.1.2?type=jar',\n        'pkg:maven/org.springframework.security/spring-security-rsa@1.1.2?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.springframework.cloud/spring-cloud-context@4.1.2?type=jar',\n      dependsOn: [\n        'pkg:maven/org.springframework.security/spring-security-crypto@6.3.0-RC1?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.springframework.cloud/spring-cloud-commons@4.1.2?type=jar',\n      dependsOn: [\n        'pkg:maven/org.springframework.security/spring-security-crypto@6.3.0-RC1?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.springframework.security/spring-security-rsa@1.1.2?type=jar',\n      dependsOn: ['pkg:maven/org.bouncycastle/bcprov-jdk18on@1.77?type=jar'],\n    },\n    {\n      ref: 'pkg:maven/org.bouncycastle/bcprov-jdk18on@1.77?type=jar',\n      dependsOn: [],\n    },\n    {\n      ref: 'pkg:maven/org.springframework.boot/spring-boot-starter-mail@3.3.0-RC1?type=jar',\n      dependsOn: [\n        'pkg:maven/org.springframework.boot/spring-boot-starter@3.3.0-RC1?type=jar',\n        'pkg:maven/org.springframework/spring-context-support@6.1.6?type=jar',\n        'pkg:maven/org.eclipse.angus/jakarta.mail@2.0.3?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.springframework/spring-context-support@6.1.6?type=jar',\n      dependsOn: [\n        'pkg:maven/org.springframework/spring-beans@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-context@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-core@6.1.6?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.eclipse.angus/jakarta.mail@2.0.3?type=jar',\n      dependsOn: [\n        'pkg:maven/jakarta.activation/jakarta.activation-api@2.1.3?type=jar',\n        'pkg:maven/org.eclipse.angus/angus-activation@2.0.2?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/jakarta.activation/jakarta.activation-api@2.1.3?type=jar',\n      dependsOn: [],\n    },\n    {\n      ref: 'pkg:maven/org.eclipse.angus/angus-activation@2.0.2?type=jar',\n      dependsOn: [\n        'pkg:maven/jakarta.activation/jakarta.activation-api@2.1.3?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/de.codecentric/spring-boot-admin-starter-client@3.2.4-SNAPSHOT?type=jar',\n      dependsOn: [],\n    },\n    {\n      ref: 'pkg:maven/org.springframework.session/spring-session-core@3.3.0-RC1?type=jar',\n      dependsOn: ['pkg:maven/org.springframework/spring-jcl@6.1.6?type=jar'],\n    },\n    {\n      ref: 'pkg:maven/org.springframework.session/spring-session-jdbc@3.3.0-RC1?type=jar',\n      dependsOn: [\n        'pkg:maven/org.springframework.session/spring-session-core@3.3.0-RC1?type=jar',\n        'pkg:maven/org.springframework/spring-context@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-jdbc@6.1.6?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.springframework/spring-jdbc@6.1.6?type=jar',\n      dependsOn: [\n        'pkg:maven/org.springframework/spring-beans@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-core@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-tx@6.1.6?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.springframework/spring-tx@6.1.6?type=jar',\n      dependsOn: [\n        'pkg:maven/org.springframework/spring-beans@6.1.6?type=jar',\n        'pkg:maven/org.springframework/spring-core@6.1.6?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.springframework.cloud/spring-cloud-starter-config@4.1.1?type=jar',\n      dependsOn: [\n        'pkg:maven/org.springframework.cloud/spring-cloud-starter@4.1.2?type=jar',\n        'pkg:maven/org.springframework.cloud/spring-cloud-config-client@4.1.1?type=jar',\n        'pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.17.0?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.springframework.cloud/spring-cloud-config-client@4.1.1?type=jar',\n      dependsOn: [\n        'pkg:maven/org.springframework.boot/spring-boot-autoconfigure@3.3.0-RC1?type=jar',\n        'pkg:maven/org.springframework.cloud/spring-cloud-commons@4.1.2?type=jar',\n        'pkg:maven/org.springframework.cloud/spring-cloud-context@4.1.2?type=jar',\n        'pkg:maven/org.springframework/spring-web@6.1.6?type=jar',\n        'pkg:maven/com.fasterxml.jackson.core/jackson-annotations@2.17.0?type=jar',\n        'pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.17.0?type=jar',\n        'pkg:maven/org.apache.httpcomponents.client5/httpclient5@5.3.1?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.apache.httpcomponents.client5/httpclient5@5.3.1?type=jar',\n      dependsOn: [\n        'pkg:maven/org.apache.httpcomponents.core5/httpcore5@5.2.4?type=jar',\n        'pkg:maven/org.apache.httpcomponents.core5/httpcore5-h2@5.2.4?type=jar',\n        'pkg:maven/org.slf4j/slf4j-api@2.0.13?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.apache.httpcomponents.core5/httpcore5@5.2.4?type=jar',\n      dependsOn: [],\n    },\n    {\n      ref: 'pkg:maven/org.apache.httpcomponents.core5/httpcore5-h2@5.2.4?type=jar',\n      dependsOn: [\n        'pkg:maven/org.apache.httpcomponents.core5/httpcore5@5.2.4?type=jar',\n      ],\n    },\n    {\n      ref: 'pkg:maven/org.hsqldb/hsqldb@2.7.2?type=jar',\n      dependsOn: [],\n    },\n  ],\n};\n\nexport const systemSbomResponse = applicationSbomResponse;\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/instance/dependencies/index.ts",
    "content": "import { HttpResponse, http } from 'msw';\n\nimport {\n  applicationSbomResponse,\n  sbomsResponse,\n  systemSbomResponse,\n} from '@/mocks/instance/dependencies/data';\n\nconst dependenciesEndpoints = [\n  http.get('/instances/:instanceId/actuator/sbom', () => {\n    return HttpResponse.json(sbomsResponse);\n  }),\n  http.get('/instances/:instanceId/actuator/sbom/application', () => {\n    return HttpResponse.json(applicationSbomResponse);\n  }),\n  http.get('/instances/:instanceId/actuator/sbom/system', () => {\n    return HttpResponse.json(systemSbomResponse);\n  }),\n];\n\nexport default dependenciesEndpoints;\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/instance/flyway/data.ts",
    "content": "export const flyway = {\n  contexts: {\n    application: {\n      flywayBeans: {\n        flyway: {\n          migrations: [\n            {\n              type: 'SQL',\n              checksum: -156244537,\n              version: '1',\n              description: 'init',\n              script: 'V1__init.sql',\n              state: 'SUCCESS',\n              installedBy: 'SA',\n              installedOn: '2022-04-21T08:50:41.580Z',\n              installedRank: 1,\n              executionTime: 3,\n            },\n          ],\n        },\n      },\n    },\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/instance/flyway/index.ts",
    "content": "import { HttpResponse, http } from 'msw';\n\nimport { flyway } from './data';\n\nconst flywayEndpoints = [\n  http.get('/instances/:instanceId/actuator/flyway', () => {\n    return HttpResponse.json(flyway);\n  }),\n];\n\nexport default flywayEndpoints;\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/instance/health/index.ts",
    "content": "import { HttpResponse, http } from 'msw';\n\nconst healthEndpoint = [\n  http.get('/instances/:instanceId/actuator/health', () => {\n    return HttpResponse.json({\n      status: 'UP',\n      details: {\n        clientConfigServer: {\n          status: 'UNKNOWN',\n          details: { error: 'no property sources located' },\n        },\n        db: {\n          status: 'UP',\n          details: {\n            database: 'HSQL Database Engine',\n            validationQuery: 'isValid()',\n          },\n        },\n        discoveryComposite: {\n          description: 'Discovery Client not initialized',\n          status: 'UNKNOWN',\n          details: {\n            discoveryClient: {\n              description: 'Discovery Client not initialized',\n              status: 'UNKNOWN',\n            },\n          },\n        },\n        diskSpace: {\n          status: 'UP',\n          details: {\n            total: 994662584320,\n            free: 300063879168,\n            threshold: 10485760,\n            exists: true,\n          },\n        },\n        ping: { status: 'UP' },\n        reactiveDiscoveryClients: {\n          description: 'Discovery Client not initialized',\n          status: 'UNKNOWN',\n          details: {\n            'Simple Reactive Discovery Client': {\n              description: 'Discovery Client not initialized',\n              status: 'UNKNOWN',\n            },\n          },\n        },\n        refreshScope: { status: 'UP' },\n      },\n    });\n  }),\n];\n\nexport default healthEndpoint;\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/instance/httptrace/data.ts",
    "content": "const now = new Date();\nconst today = [\n  now.getFullYear(),\n  String(now.getMonth() + 1).padStart(2, '0'),\n  String(now.getDate()).padStart(2, '0'),\n].join('-');\n\nexport const httptraceresponse = {\n  traces: [\n    {\n      timestamp: today + 'T09:12:34.567Z',\n      principal: 'admin',\n      session: 'D43F6A32C1E34A7B',\n      request: {\n        method: 'GET',\n        uri: 'http://localhost:8080/api/users',\n        headers: {\n          accept: ['application/json'],\n          'user-agent': [\n            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',\n          ],\n          authorization: ['Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'],\n        },\n        remoteAddress: '192.168.1.105',\n      },\n      response: {\n        status: 200,\n        headers: {\n          'content-type': ['application/json'],\n          'content-length': ['1024'],\n          'cache-control': ['no-cache, no-store, max-age=0, must-revalidate'],\n        },\n        timeTaken: 125,\n      },\n    },\n    {\n      timestamp: today + 'T09:13:45.678Z',\n      principal: 'user123',\n      session: 'E54G7B43D2F45B8C',\n      request: {\n        method: 'POST',\n        uri: 'http://localhost:8080/api/orders',\n        headers: {\n          'content-type': ['application/json'],\n          'user-agent': ['PostmanRuntime/7.29.2'],\n          authorization: ['Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'],\n        },\n        remoteAddress: '192.168.1.110',\n      },\n      response: {\n        status: 201,\n        headers: {\n          'content-type': ['application/json'],\n          location: ['/api/orders/12345'],\n          'content-length': ['256'],\n        },\n        timeTaken: 187,\n      },\n    },\n    {\n      timestamp: today + today + 'T09:14:56.789Z',\n      principal: null,\n      session: null,\n      request: {\n        method: 'GET',\n        uri: 'http://localhost:8080/api/products',\n        headers: {\n          accept: ['application/json'],\n          'user-agent': ['curl/7.68.0'],\n        },\n        remoteAddress: '192.168.1.115',\n      },\n      response: {\n        status: 200,\n        headers: {\n          'content-type': ['application/json'],\n          'content-length': ['2048'],\n          'cache-control': ['max-age=3600'],\n        },\n        timeTaken: 95,\n      },\n    },\n    {\n      timestamp: today + 'T09:15:23.456Z',\n      principal: 'user456',\n      session: 'F65H8C54E3G56D9E',\n      request: {\n        method: 'PUT',\n        uri: 'http://localhost:8080/api/users/profile',\n        headers: {\n          'content-type': ['application/json'],\n          'user-agent': ['Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)'],\n          authorization: ['Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'],\n        },\n        remoteAddress: '192.168.1.120',\n      },\n      response: {\n        status: 200,\n        headers: {\n          'content-type': ['application/json'],\n          'content-length': ['512'],\n        },\n        timeTaken: 143,\n      },\n    },\n    {\n      timestamp: today + 'T09:16:34.567Z',\n      principal: null,\n      session: null,\n      request: {\n        method: 'GET',\n        uri: 'http://localhost:8080/actuator/health',\n        headers: {\n          accept: ['application/json'],\n          'user-agent': ['Prometheus/2.40.0'],\n        },\n        remoteAddress: '192.168.1.200',\n      },\n      response: {\n        status: 200,\n        headers: {\n          'content-type': ['application/json'],\n          'content-length': ['15'],\n        },\n        timeTaken: 12,\n      },\n    },\n    {\n      timestamp: today + 'T09:17:45.678Z',\n      principal: 'admin',\n      session: 'D43F6A32C1E34A7B',\n      request: {\n        method: 'DELETE',\n        uri: 'http://localhost:8080/api/products/54321',\n        headers: {\n          'user-agent': [\n            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',\n          ],\n          authorization: ['Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'],\n        },\n        remoteAddress: '192.168.1.105',\n      },\n      response: {\n        status: 204,\n        headers: {\n          'cache-control': ['no-cache, no-store, max-age=0, must-revalidate'],\n        },\n        timeTaken: 78,\n      },\n    },\n    {\n      timestamp: today + 'T09:18:56.789Z',\n      principal: null,\n      session: null,\n      request: {\n        method: 'OPTIONS',\n        uri: 'http://localhost:8080/api/users',\n        headers: {\n          origin: ['https://example.com'],\n          'access-control-request-method': ['GET'],\n          'user-agent': [\n            'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X)',\n          ],\n        },\n        remoteAddress: '192.168.1.130',\n      },\n      response: {\n        status: 200,\n        headers: {\n          'access-control-allow-origin': ['https://example.com'],\n          'access-control-allow-methods': ['GET, POST, PUT, DELETE'],\n          'access-control-allow-headers': ['Authorization, Content-Type'],\n          'access-control-max-age': ['3600'],\n        },\n        timeTaken: 5,\n      },\n    },\n    {\n      timestamp: today + 'T09:19:23.456Z',\n      principal: 'user789',\n      session: 'G76I9D65F4H67E0F',\n      request: {\n        method: 'GET',\n        uri: 'http://localhost:8080/api/orders/history?page=0&size=10',\n        headers: {\n          accept: ['application/json'],\n          'user-agent': ['Mozilla/5.0 (Android 12; Mobile)'],\n          authorization: ['Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'],\n        },\n        remoteAddress: '192.168.1.140',\n      },\n      response: {\n        status: 200,\n        headers: {\n          'content-type': ['application/json'],\n          'content-length': ['1536'],\n          'x-total-count': ['45'],\n        },\n        timeTaken: 167,\n      },\n    },\n    {\n      timestamp: today + 'T09:20:34.567Z',\n      principal: null,\n      session: null,\n      request: {\n        method: 'GET',\n        uri: 'http://localhost:8080/api/nonexistent',\n        headers: {\n          accept: ['application/json'],\n          'user-agent': ['curl/7.68.0'],\n        },\n        remoteAddress: '192.168.1.150',\n      },\n      response: {\n        status: 404,\n        headers: {\n          'content-type': ['application/json'],\n          'content-length': ['64'],\n        },\n        timeTaken: 23,\n      },\n    },\n    {\n      timestamp: today + 'T09:21:45.678Z',\n      principal: 'user123',\n      session: 'E54G7B43D2F45B8C',\n      request: {\n        method: 'POST',\n        uri: 'http://localhost:8080/api/checkout',\n        headers: {\n          'content-type': ['application/json'],\n          'user-agent': ['PostmanRuntime/7.29.2'],\n          authorization: ['Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'],\n        },\n        remoteAddress: '192.168.1.110',\n      },\n      response: {\n        status: 400,\n        headers: {\n          'content-type': ['application/json'],\n          'content-length': ['128'],\n        },\n        timeTaken: 45,\n      },\n    },\n  ],\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/instance/httptrace/index.ts",
    "content": "import { HttpResponse, http } from 'msw';\n\nimport { httptraceresponse } from '@/mocks/instance/httptrace/data';\n\nconst endpoints = [\n  http.get('/instances/:instanceId/actuator/httptrace', () => {\n    return HttpResponse.json(httptraceresponse);\n  }),\n];\n\nexport default endpoints;\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/instance/info/index.ts",
    "content": "import { HttpResponse, http } from 'msw';\n\nconst infoEndpoint = [\n  http.get('/instances/:instanceId/actuator/info', () => {\n    return HttpResponse.json({});\n  }),\n];\n\nexport default infoEndpoint;\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/instance/jolokia/data.read.ts",
    "content": "export const jolokiaRead = {\n  request: {\n    mbean:\n      'com.codecentric.boot.sample:name=stringMapManagedBean,type=StringMapManagedBean',\n    type: 'read',\n  },\n  value: { Test: 0, Size: 0 },\n  timestamp: 1673727499,\n  status: 200,\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/instance/jolokia/data.ts",
    "content": "export const jolokiaList = {\n  request: { type: 'list' },\n  value: {\n    'jdk.management.jfr': {\n      'type=FlightRecorder': {\n        op: {\n          getRecordingOptions: {\n            args: [{ name: 'p0', type: 'long', desc: 'p0' }],\n            ret: 'javax.management.openmbean.TabularData',\n            desc: 'getRecordingOptions',\n          },\n          takeSnapshot: { args: [], ret: 'long', desc: 'takeSnapshot' },\n          closeRecording: {\n            args: [{ name: 'p0', type: 'long', desc: 'p0' }],\n            ret: 'void',\n            desc: 'closeRecording',\n          },\n          newRecording: { args: [], ret: 'long', desc: 'newRecording' },\n          setRecordingSettings: {\n            args: [\n              { name: 'p0', type: 'long', desc: 'p0' },\n              {\n                name: 'p1',\n                type: 'javax.management.openmbean.TabularData',\n                desc: 'p1',\n              },\n            ],\n            ret: 'void',\n            desc: 'setRecordingSettings',\n          },\n          openStream: {\n            args: [\n              { name: 'p0', type: 'long', desc: 'p0' },\n              {\n                name: 'p1',\n                type: 'javax.management.openmbean.TabularData',\n                desc: 'p1',\n              },\n            ],\n            ret: 'long',\n            desc: 'openStream',\n          },\n          cloneRecording: {\n            args: [\n              { name: 'p0', type: 'long', desc: 'p0' },\n              {\n                name: 'p1',\n                type: 'boolean',\n                desc: 'p1',\n              },\n            ],\n            ret: 'long',\n            desc: 'cloneRecording',\n          },\n          setRecordingOptions: {\n            args: [\n              { name: 'p0', type: 'long', desc: 'p0' },\n              {\n                name: 'p1',\n                type: 'javax.management.openmbean.TabularData',\n                desc: 'p1',\n              },\n            ],\n            ret: 'void',\n            desc: 'setRecordingOptions',\n          },\n          copyTo: {\n            args: [\n              { name: 'p0', type: 'long', desc: 'p0' },\n              {\n                name: 'p1',\n                type: 'java.lang.String',\n                desc: 'p1',\n              },\n            ],\n            ret: 'void',\n            desc: 'copyTo',\n          },\n          startRecording: {\n            args: [{ name: 'p0', type: 'long', desc: 'p0' }],\n            ret: 'void',\n            desc: 'startRecording',\n          },\n          closeStream: {\n            args: [{ name: 'p0', type: 'long', desc: 'p0' }],\n            ret: 'void',\n            desc: 'closeStream',\n          },\n          getRecordingSettings: {\n            args: [{ name: 'p0', type: 'long', desc: 'p0' }],\n            ret: 'javax.management.openmbean.TabularData',\n            desc: 'getRecordingSettings',\n          },\n          setPredefinedConfiguration: {\n            args: [\n              { name: 'p0', type: 'long', desc: 'p0' },\n              {\n                name: 'p1',\n                type: 'java.lang.String',\n                desc: 'p1',\n              },\n            ],\n            ret: 'void',\n            desc: 'setPredefinedConfiguration',\n          },\n          readStream: {\n            args: [{ name: 'p0', type: 'long', desc: 'p0' }],\n            ret: '[B',\n            desc: 'readStream',\n          },\n          setConfiguration: {\n            args: [\n              { name: 'p0', type: 'long', desc: 'p0' },\n              {\n                name: 'p1',\n                type: 'java.lang.String',\n                desc: 'p1',\n              },\n            ],\n            ret: 'void',\n            desc: 'setConfiguration',\n          },\n          stopRecording: {\n            args: [{ name: 'p0', type: 'long', desc: 'p0' }],\n            ret: 'boolean',\n            desc: 'stopRecording',\n          },\n        },\n        attr: {\n          EventTypes: {\n            rw: false,\n            type: '[Ljavax.management.openmbean.CompositeData;',\n            desc: 'EventTypes',\n          },\n          Recordings: {\n            rw: false,\n            type: '[Ljavax.management.openmbean.CompositeData;',\n            desc: 'Recordings',\n          },\n          Configurations: {\n            rw: false,\n            type: '[Ljavax.management.openmbean.CompositeData;',\n            desc: 'Configurations',\n          },\n          ObjectName: {\n            rw: false,\n            type: 'javax.management.ObjectName',\n            desc: 'ObjectName',\n          },\n        },\n        class: 'jdk.management.jfr.FlightRecorderMXBeanImpl',\n        desc: 'Information on the management interface of the MBean',\n      },\n    },\n    'java.util.logging': {\n      'type=Logging': {\n        op: {\n          getLoggerLevel: {\n            args: [\n              {\n                name: 'p0',\n                type: 'java.lang.String',\n                desc: 'p0',\n              },\n            ],\n            ret: 'java.lang.String',\n            desc: 'getLoggerLevel',\n          },\n          getParentLoggerName: {\n            args: [{ name: 'p0', type: 'java.lang.String', desc: 'p0' }],\n            ret: 'java.lang.String',\n            desc: 'getParentLoggerName',\n          },\n          setLoggerLevel: {\n            args: [\n              { name: 'p0', type: 'java.lang.String', desc: 'p0' },\n              {\n                name: 'p1',\n                type: 'java.lang.String',\n                desc: 'p1',\n              },\n            ],\n            ret: 'void',\n            desc: 'setLoggerLevel',\n          },\n        },\n        attr: {\n          LoggerNames: {\n            rw: false,\n            type: '[Ljava.lang.String;',\n            desc: 'LoggerNames',\n          },\n          ObjectName: {\n            rw: false,\n            type: 'javax.management.ObjectName',\n            desc: 'ObjectName',\n          },\n        },\n        class: 'sun.management.ManagementFactoryHelper$PlatformLoggingImpl',\n        desc: 'Information on the management interface of the MBean',\n      },\n    },\n    'java.nio': {\n      'name=direct,type=BufferPool': {\n        attr: {\n          TotalCapacity: {\n            rw: false,\n            type: 'long',\n            desc: 'TotalCapacity',\n          },\n          MemoryUsed: { rw: false, type: 'long', desc: 'MemoryUsed' },\n          Count: { rw: false, type: 'long', desc: 'Count' },\n          Name: { rw: false, type: 'java.lang.String', desc: 'Name' },\n          ObjectName: {\n            rw: false,\n            type: 'javax.management.ObjectName',\n            desc: 'ObjectName',\n          },\n        },\n        class: 'sun.management.ManagementFactoryHelper$1',\n        desc: 'Information on the management interface of the MBean',\n      },\n      'name=mapped,type=BufferPool': {\n        attr: {\n          TotalCapacity: {\n            rw: false,\n            type: 'long',\n            desc: 'TotalCapacity',\n          },\n          MemoryUsed: { rw: false, type: 'long', desc: 'MemoryUsed' },\n          Count: { rw: false, type: 'long', desc: 'Count' },\n          Name: { rw: false, type: 'java.lang.String', desc: 'Name' },\n          ObjectName: {\n            rw: false,\n            type: 'javax.management.ObjectName',\n            desc: 'ObjectName',\n          },\n        },\n        class: 'sun.management.ManagementFactoryHelper$1',\n        desc: 'Information on the management interface of the MBean',\n      },\n      \"name=mapped - 'non-volatile memory',type=BufferPool\": {\n        attr: {\n          TotalCapacity: {\n            rw: false,\n            type: 'long',\n            desc: 'TotalCapacity',\n          },\n          MemoryUsed: { rw: false, type: 'long', desc: 'MemoryUsed' },\n          Count: { rw: false, type: 'long', desc: 'Count' },\n          Name: { rw: false, type: 'java.lang.String', desc: 'Name' },\n          ObjectName: {\n            rw: false,\n            type: 'javax.management.ObjectName',\n            desc: 'ObjectName',\n          },\n        },\n        class: 'sun.management.ManagementFactoryHelper$1',\n        desc: 'Information on the management interface of the MBean',\n      },\n    },\n    DefaultDomain: {\n      'application=': {\n        attr: {\n          SnapshotAsJson: {\n            rw: false,\n            type: 'java.lang.String',\n            desc: 'Attribute exposed for management',\n          },\n        },\n        class: 'org.springframework.context.support.LiveBeansView',\n        desc: 'Information on the management interface of the MBean',\n      },\n    },\n    'org.springframework.boot': {\n      'type=Endpoint,name=Scheduledtasks': {\n        op: {\n          scheduledTasks: {\n            args: [],\n            ret: 'java.util.Map',\n            desc: 'Invoke scheduledTasks for endpoint scheduledtasks',\n          },\n        },\n        class: 'org.springframework.boot.actuate.endpoint.jmx.EndpointMBean',\n        desc: 'MBean operations for endpoint scheduledtasks',\n      },\n      'type=Endpoint,name=Loggers': {\n        op: {\n          configureLogLevel: {\n            args: [\n              {\n                name: 'name',\n                type: 'java.lang.String',\n                desc: null,\n              },\n              { name: 'configuredLevel', type: 'java.lang.String', desc: null },\n            ],\n            ret: 'java.util.Map',\n            desc: 'Invoke configureLogLevel for endpoint loggers',\n          },\n          loggers: {\n            args: [],\n            ret: 'java.util.Map',\n            desc: 'Invoke loggers for endpoint loggers',\n          },\n          loggerLevels: {\n            args: [{ name: 'name', type: 'java.lang.String', desc: null }],\n            ret: 'java.util.Map',\n            desc: 'Invoke loggerLevels for endpoint loggers',\n          },\n        },\n        class: 'org.springframework.boot.actuate.endpoint.jmx.EndpointMBean',\n        desc: 'MBean operations for endpoint loggers',\n      },\n      'type=Endpoint,name=Mappings': {\n        op: {\n          mappings: {\n            args: [],\n            ret: 'java.util.Map',\n            desc: 'Invoke mappings for endpoint mappings',\n          },\n        },\n        class: 'org.springframework.boot.actuate.endpoint.jmx.EndpointMBean',\n        desc: 'MBean operations for endpoint mappings',\n      },\n      'type=Endpoint,name=Features': {\n        op: {\n          features: {\n            args: [],\n            ret: 'java.util.Map',\n            desc: 'Invoke features for endpoint features',\n          },\n        },\n        class: 'org.springframework.boot.actuate.endpoint.jmx.EndpointMBean',\n        desc: 'MBean operations for endpoint features',\n      },\n      'type=Endpoint,name=Info': {\n        op: {\n          info: {\n            args: [],\n            ret: 'java.util.Map',\n            desc: 'Invoke info for endpoint info',\n          },\n        },\n        class: 'org.springframework.boot.actuate.endpoint.jmx.EndpointMBean',\n        desc: 'MBean operations for endpoint info',\n      },\n      'type=Endpoint,name=Env': {\n        op: {\n          environment: {\n            args: [\n              {\n                name: 'pattern',\n                type: 'java.lang.String',\n                desc: null,\n              },\n            ],\n            ret: 'java.util.Map',\n            desc: 'Invoke environment for endpoint env',\n          },\n          environmentEntry: {\n            args: [{ name: 'toMatch', type: 'java.lang.String', desc: null }],\n            ret: 'java.util.Map',\n            desc: 'Invoke environmentEntry for endpoint env',\n          },\n        },\n        class: 'org.springframework.boot.actuate.endpoint.jmx.EndpointMBean',\n        desc: 'MBean operations for endpoint env',\n      },\n      'type=Endpoint,name=Caches': {\n        op: {\n          clearCaches: {\n            args: [],\n            ret: 'java.util.Map',\n            desc: 'Invoke clearCaches for endpoint caches',\n          },\n          cache: {\n            args: [\n              { name: 'cache', type: 'java.lang.String', desc: null },\n              {\n                name: 'cacheManager',\n                type: 'java.lang.String',\n                desc: null,\n              },\n            ],\n            ret: 'java.util.Map',\n            desc: 'Invoke cache for endpoint caches',\n          },\n          caches: {\n            args: [],\n            ret: 'java.util.Map',\n            desc: 'Invoke caches for endpoint caches',\n          },\n          clearCache: {\n            args: [\n              {\n                name: 'cache',\n                type: 'java.lang.String',\n                desc: null,\n              },\n              { name: 'cacheManager', type: 'java.lang.String', desc: null },\n            ],\n            ret: 'java.util.Map',\n            desc: 'Invoke clearCache for endpoint caches',\n          },\n        },\n        class: 'org.springframework.boot.actuate.endpoint.jmx.EndpointMBean',\n        desc: 'MBean operations for endpoint caches',\n      },\n      'type=Endpoint,name=Beans': {\n        op: {\n          beans: {\n            args: [],\n            ret: 'java.util.Map',\n            desc: 'Invoke beans for endpoint beans',\n          },\n        },\n        class: 'org.springframework.boot.actuate.endpoint.jmx.EndpointMBean',\n        desc: 'MBean operations for endpoint beans',\n      },\n      'type=Endpoint,name=Refresh': {\n        op: {\n          refresh: {\n            args: [],\n            ret: 'java.util.List',\n            desc: 'Invoke refresh for endpoint refresh',\n          },\n        },\n        class: 'org.springframework.boot.actuate.endpoint.jmx.EndpointMBean',\n        desc: 'MBean operations for endpoint refresh',\n      },\n      'type=Endpoint,name=Flyway': {\n        op: {\n          flywayBeans: {\n            args: [],\n            ret: 'java.util.Map',\n            desc: 'Invoke flywayBeans for endpoint flyway',\n          },\n        },\n        class: 'org.springframework.boot.actuate.endpoint.jmx.EndpointMBean',\n        desc: 'MBean operations for endpoint flyway',\n      },\n      'type=Endpoint,name=Threaddump': {\n        op: {\n          threadDump: {\n            args: [],\n            ret: 'java.util.Map',\n            desc: 'Invoke threadDump for endpoint threaddump',\n          },\n          textThreadDump: {\n            args: [],\n            ret: 'java.lang.String',\n            desc: 'Invoke textThreadDump for endpoint threaddump',\n          },\n        },\n        class: 'org.springframework.boot.actuate.endpoint.jmx.EndpointMBean',\n        desc: 'MBean operations for endpoint threaddump',\n      },\n      'type=Endpoint,name=Metrics': {\n        op: {\n          metric: {\n            args: [\n              {\n                name: 'requiredMetricName',\n                type: 'java.lang.String',\n                desc: null,\n              },\n              { name: 'tag', type: 'java.util.List', desc: null },\n            ],\n            ret: 'java.util.Map',\n            desc: 'Invoke metric for endpoint metrics',\n          },\n          listNames: {\n            args: [],\n            ret: 'java.util.Map',\n            desc: 'Invoke listNames for endpoint metrics',\n          },\n        },\n        class: 'org.springframework.boot.actuate.endpoint.jmx.EndpointMBean',\n        desc: 'MBean operations for endpoint metrics',\n      },\n      'type=Endpoint,name=Configprops': {\n        op: {\n          configurationPropertiesWithPrefix: {\n            args: [\n              {\n                name: 'prefix',\n                type: 'java.lang.String',\n                desc: null,\n              },\n            ],\n            ret: 'java.util.Map',\n            desc: 'Invoke configurationPropertiesWithPrefix for endpoint configprops',\n          },\n          configurationProperties: {\n            args: [],\n            ret: 'java.util.Map',\n            desc: 'Invoke configurationProperties for endpoint configprops',\n          },\n        },\n        class: 'org.springframework.boot.actuate.endpoint.jmx.EndpointMBean',\n        desc: 'MBean operations for endpoint configprops',\n      },\n      'type=Endpoint,name=Startup': {\n        op: {\n          startup: {\n            args: [],\n            ret: 'java.util.Map',\n            desc: 'Invoke startup for endpoint startup',\n          },\n          startupSnapshot: {\n            args: [],\n            ret: 'java.util.Map',\n            desc: 'Invoke startupSnapshot for endpoint startup',\n          },\n        },\n        class: 'org.springframework.boot.actuate.endpoint.jmx.EndpointMBean',\n        desc: 'MBean operations for endpoint startup',\n      },\n      'type=Admin,name=SpringApplication': {\n        op: {\n          getProperty: {\n            args: [\n              {\n                name: 'p0',\n                type: 'java.lang.String',\n                desc: 'p0',\n              },\n            ],\n            ret: 'java.lang.String',\n            desc: 'getProperty',\n          },\n          shutdown: { args: [], ret: 'void', desc: 'shutdown' },\n        },\n        attr: {\n          Ready: { rw: false, type: 'boolean', desc: 'Ready' },\n          EmbeddedWebApplication: {\n            rw: false,\n            type: 'boolean',\n            desc: 'EmbeddedWebApplication',\n          },\n        },\n        class:\n          'org.springframework.boot.admin.SpringApplicationAdminMXBeanRegistrar$SpringApplicationAdmin',\n        desc: 'Information on the management interface of the MBean',\n      },\n      'type=Endpoint,name=Liquibase': {\n        op: {\n          liquibaseBeans: {\n            args: [],\n            ret: 'java.util.Map',\n            desc: 'Invoke liquibaseBeans for endpoint liquibase',\n          },\n        },\n        class: 'org.springframework.boot.actuate.endpoint.jmx.EndpointMBean',\n        desc: 'MBean operations for endpoint liquibase',\n      },\n      'type=Endpoint,name=Conditions': {\n        op: {\n          applicationConditionEvaluation: {\n            args: [],\n            ret: 'java.util.Map',\n            desc: 'Invoke applicationConditionEvaluation for endpoint conditions',\n          },\n        },\n        class: 'org.springframework.boot.actuate.endpoint.jmx.EndpointMBean',\n        desc: 'MBean operations for endpoint conditions',\n      },\n      'type=Endpoint,name=Health': {\n        op: {\n          health: {\n            args: [],\n            ret: 'java.util.Map',\n            desc: 'Invoke health for endpoint health',\n          },\n          healthForPath: {\n            args: [{ name: 'path', type: 'java.lang.Object', desc: null }],\n            ret: 'java.util.Map',\n            desc: 'Invoke healthForPath for endpoint health',\n          },\n        },\n        class: 'org.springframework.boot.actuate.endpoint.jmx.EndpointMBean',\n        desc: 'MBean operations for endpoint health',\n      },\n    },\n    JMImplementation: {\n      'type=MBeanServerDelegate': {\n        attr: {\n          ImplementationName: {\n            rw: false,\n            type: 'java.lang.String',\n            desc: 'The JMX implementation name (the name of this product)',\n          },\n          MBeanServerId: {\n            rw: false,\n            type: 'java.lang.String',\n            desc: 'The MBean server agent identification',\n          },\n          ImplementationVersion: {\n            rw: false,\n            type: 'java.lang.String',\n            desc: 'The JMX implementation version (the version of this product).',\n          },\n          SpecificationVersion: {\n            rw: false,\n            type: 'java.lang.String',\n            desc: 'The version of the JMX specification implemented by this product.',\n          },\n          SpecificationVendor: {\n            rw: false,\n            type: 'java.lang.String',\n            desc: 'The vendor of the JMX specification implemented by this product.',\n          },\n          SpecificationName: {\n            rw: false,\n            type: 'java.lang.String',\n            desc: 'The full name of the JMX specification implemented by this product.',\n          },\n          ImplementationVendor: {\n            rw: false,\n            type: 'java.lang.String',\n            desc: 'the JMX implementation vendor (the vendor of this product).',\n          },\n        },\n        class: 'javax.management.MBeanServerDelegate',\n        desc: 'Represents  the MBean server from the management point of view.',\n      },\n    },\n    'java.lang': {\n      'name=G1 Survivor Space,type=MemoryPool': {\n        op: {\n          resetPeakUsage: { args: [], ret: 'void', desc: 'resetPeakUsage' },\n        },\n        attr: {\n          Usage: {\n            rw: false,\n            type: 'javax.management.openmbean.CompositeData',\n            desc: 'Usage',\n          },\n          UsageThresholdCount: {\n            rw: false,\n            type: 'long',\n            desc: 'UsageThresholdCount',\n          },\n          MemoryManagerNames: {\n            rw: false,\n            type: '[Ljava.lang.String;',\n            desc: 'MemoryManagerNames',\n          },\n          UsageThresholdSupported: {\n            rw: false,\n            type: 'boolean',\n            desc: 'UsageThresholdSupported',\n          },\n          UsageThreshold: { rw: true, type: 'long', desc: 'UsageThreshold' },\n          CollectionUsageThresholdCount: {\n            rw: false,\n            type: 'long',\n            desc: 'CollectionUsageThresholdCount',\n          },\n          PeakUsage: {\n            rw: false,\n            type: 'javax.management.openmbean.CompositeData',\n            desc: 'PeakUsage',\n          },\n          UsageThresholdExceeded: {\n            rw: false,\n            type: 'boolean',\n            desc: 'UsageThresholdExceeded',\n          },\n          CollectionUsageThreshold: {\n            rw: true,\n            type: 'long',\n            desc: 'CollectionUsageThreshold',\n          },\n          Name: { rw: false, type: 'java.lang.String', desc: 'Name' },\n          ObjectName: {\n            rw: false,\n            type: 'javax.management.ObjectName',\n            desc: 'ObjectName',\n          },\n          Type: { rw: false, type: 'java.lang.String', desc: 'Type' },\n          CollectionUsageThresholdSupported: {\n            rw: false,\n            type: 'boolean',\n            desc: 'CollectionUsageThresholdSupported',\n          },\n          Valid: { rw: false, type: 'boolean', desc: 'Valid' },\n          CollectionUsage: {\n            rw: false,\n            type: 'javax.management.openmbean.CompositeData',\n            desc: 'CollectionUsage',\n          },\n          CollectionUsageThresholdExceeded: {\n            rw: false,\n            type: 'boolean',\n            desc: 'CollectionUsageThresholdExceeded',\n          },\n        },\n        class: 'sun.management.MemoryPoolImpl',\n        desc: 'Information on the management interface of the MBean',\n      },\n      'type=Threading': {\n        op: {\n          getThreadCpuTime: [\n            {\n              args: [{ name: 'p0', type: '[J', desc: 'p0' }],\n              ret: '[J',\n              desc: 'getThreadCpuTime',\n            },\n            {\n              args: [{ name: 'p0', type: 'long', desc: 'p0' }],\n              ret: 'long',\n              desc: 'getThreadCpuTime',\n            },\n          ],\n          getThreadInfo: [\n            {\n              args: [{ name: 'p0', type: 'long', desc: 'p0' }],\n              ret: 'javax.management.openmbean.CompositeData',\n              desc: 'getThreadInfo',\n            },\n            {\n              args: [{ name: 'p0', type: '[J', desc: 'p0' }],\n              ret: '[Ljavax.management.openmbean.CompositeData;',\n              desc: 'getThreadInfo',\n            },\n            {\n              args: [\n                { name: 'p0', type: '[J', desc: 'p0' },\n                {\n                  name: 'p1',\n                  type: 'boolean',\n                  desc: 'p1',\n                },\n                { name: 'p2', type: 'boolean', desc: 'p2' },\n                { name: 'p3', type: 'int', desc: 'p3' },\n              ],\n              ret: '[Ljavax.management.openmbean.CompositeData;',\n              desc: 'getThreadInfo',\n            },\n            {\n              args: [\n                { name: 'p0', type: '[J', desc: 'p0' },\n                {\n                  name: 'p1',\n                  type: 'boolean',\n                  desc: 'p1',\n                },\n                { name: 'p2', type: 'boolean', desc: 'p2' },\n              ],\n              ret: '[Ljavax.management.openmbean.CompositeData;',\n              desc: 'getThreadInfo',\n            },\n            {\n              args: [\n                { name: 'p0', type: '[J', desc: 'p0' },\n                { name: 'p1', type: 'int', desc: 'p1' },\n              ],\n              ret: '[Ljavax.management.openmbean.CompositeData;',\n              desc: 'getThreadInfo',\n            },\n            {\n              args: [\n                { name: 'p0', type: 'long', desc: 'p0' },\n                { name: 'p1', type: 'int', desc: 'p1' },\n              ],\n              ret: 'javax.management.openmbean.CompositeData',\n              desc: 'getThreadInfo',\n            },\n          ],\n          findDeadlockedThreads: {\n            args: [],\n            ret: '[J',\n            desc: 'findDeadlockedThreads',\n          },\n          getThreadAllocatedBytes: [\n            {\n              args: [{ name: 'p0', type: '[J', desc: 'p0' }],\n              ret: '[J',\n              desc: 'getThreadAllocatedBytes',\n            },\n            {\n              args: [{ name: 'p0', type: 'long', desc: 'p0' }],\n              ret: 'long',\n              desc: 'getThreadAllocatedBytes',\n            },\n          ],\n          getThreadUserTime: [\n            {\n              args: [{ name: 'p0', type: '[J', desc: 'p0' }],\n              ret: '[J',\n              desc: 'getThreadUserTime',\n            },\n            {\n              args: [{ name: 'p0', type: 'long', desc: 'p0' }],\n              ret: 'long',\n              desc: 'getThreadUserTime',\n            },\n          ],\n          findMonitorDeadlockedThreads: {\n            args: [],\n            ret: '[J',\n            desc: 'findMonitorDeadlockedThreads',\n          },\n          resetPeakThreadCount: {\n            args: [],\n            ret: 'void',\n            desc: 'resetPeakThreadCount',\n          },\n          dumpAllThreads: [\n            {\n              args: [\n                { name: 'p0', type: 'boolean', desc: 'p0' },\n                {\n                  name: 'p1',\n                  type: 'boolean',\n                  desc: 'p1',\n                },\n              ],\n              ret: '[Ljavax.management.openmbean.CompositeData;',\n              desc: 'dumpAllThreads',\n            },\n            {\n              args: [\n                { name: 'p0', type: 'boolean', desc: 'p0' },\n                {\n                  name: 'p1',\n                  type: 'boolean',\n                  desc: 'p1',\n                },\n                { name: 'p2', type: 'int', desc: 'p2' },\n              ],\n              ret: '[Ljavax.management.openmbean.CompositeData;',\n              desc: 'dumpAllThreads',\n            },\n          ],\n        },\n        attr: {\n          ThreadAllocatedMemorySupported: {\n            rw: false,\n            type: 'boolean',\n            desc: 'ThreadAllocatedMemorySupported',\n          },\n          ThreadContentionMonitoringEnabled: {\n            rw: true,\n            type: 'boolean',\n            desc: 'ThreadContentionMonitoringEnabled',\n          },\n          CurrentThreadAllocatedBytes: {\n            rw: false,\n            type: 'long',\n            desc: 'CurrentThreadAllocatedBytes',\n          },\n          TotalStartedThreadCount: {\n            rw: false,\n            type: 'long',\n            desc: 'TotalStartedThreadCount',\n          },\n          CurrentThreadCpuTimeSupported: {\n            rw: false,\n            type: 'boolean',\n            desc: 'CurrentThreadCpuTimeSupported',\n          },\n          CurrentThreadUserTime: {\n            rw: false,\n            type: 'long',\n            desc: 'CurrentThreadUserTime',\n          },\n          PeakThreadCount: { rw: false, type: 'int', desc: 'PeakThreadCount' },\n          AllThreadIds: { rw: false, type: '[J', desc: 'AllThreadIds' },\n          ThreadAllocatedMemoryEnabled: {\n            rw: true,\n            type: 'boolean',\n            desc: 'ThreadAllocatedMemoryEnabled',\n          },\n          CurrentThreadCpuTime: {\n            rw: false,\n            type: 'long',\n            desc: 'CurrentThreadCpuTime',\n          },\n          ObjectName: {\n            rw: false,\n            type: 'javax.management.ObjectName',\n            desc: 'ObjectName',\n          },\n          ThreadContentionMonitoringSupported: {\n            rw: false,\n            type: 'boolean',\n            desc: 'ThreadContentionMonitoringSupported',\n          },\n          ThreadCpuTimeSupported: {\n            rw: false,\n            type: 'boolean',\n            desc: 'ThreadCpuTimeSupported',\n          },\n          ThreadCount: { rw: false, type: 'int', desc: 'ThreadCount' },\n          ThreadCpuTimeEnabled: {\n            rw: true,\n            type: 'boolean',\n            desc: 'ThreadCpuTimeEnabled',\n          },\n          ObjectMonitorUsageSupported: {\n            rw: false,\n            type: 'boolean',\n            desc: 'ObjectMonitorUsageSupported',\n          },\n          SynchronizerUsageSupported: {\n            rw: false,\n            type: 'boolean',\n            desc: 'SynchronizerUsageSupported',\n          },\n          DaemonThreadCount: {\n            rw: false,\n            type: 'int',\n            desc: 'DaemonThreadCount',\n          },\n        },\n        class: 'com.sun.management.internal.HotSpotThreadImpl',\n        desc: 'Information on the management interface of the MBean',\n      },\n      'name=CodeCache,type=MemoryPool': {\n        op: {\n          resetPeakUsage: { args: [], ret: 'void', desc: 'resetPeakUsage' },\n        },\n        attr: {\n          Usage: {\n            rw: false,\n            type: 'javax.management.openmbean.CompositeData',\n            desc: 'Usage',\n          },\n          UsageThresholdCount: {\n            rw: false,\n            type: 'long',\n            desc: 'UsageThresholdCount',\n          },\n          MemoryManagerNames: {\n            rw: false,\n            type: '[Ljava.lang.String;',\n            desc: 'MemoryManagerNames',\n          },\n          UsageThresholdSupported: {\n            rw: false,\n            type: 'boolean',\n            desc: 'UsageThresholdSupported',\n          },\n          UsageThreshold: { rw: true, type: 'long', desc: 'UsageThreshold' },\n          CollectionUsageThresholdCount: {\n            rw: false,\n            type: 'long',\n            desc: 'CollectionUsageThresholdCount',\n          },\n          PeakUsage: {\n            rw: false,\n            type: 'javax.management.openmbean.CompositeData',\n            desc: 'PeakUsage',\n          },\n          UsageThresholdExceeded: {\n            rw: false,\n            type: 'boolean',\n            desc: 'UsageThresholdExceeded',\n          },\n          CollectionUsageThreshold: {\n            rw: true,\n            type: 'long',\n            desc: 'CollectionUsageThreshold',\n          },\n          Name: { rw: false, type: 'java.lang.String', desc: 'Name' },\n          ObjectName: {\n            rw: false,\n            type: 'javax.management.ObjectName',\n            desc: 'ObjectName',\n          },\n          Type: { rw: false, type: 'java.lang.String', desc: 'Type' },\n          CollectionUsageThresholdSupported: {\n            rw: false,\n            type: 'boolean',\n            desc: 'CollectionUsageThresholdSupported',\n          },\n          Valid: { rw: false, type: 'boolean', desc: 'Valid' },\n          CollectionUsage: {\n            rw: false,\n            type: 'javax.management.openmbean.CompositeData',\n            desc: 'CollectionUsage',\n          },\n          CollectionUsageThresholdExceeded: {\n            rw: false,\n            type: 'boolean',\n            desc: 'CollectionUsageThresholdExceeded',\n          },\n        },\n        class: 'sun.management.MemoryPoolImpl',\n        desc: 'Information on the management interface of the MBean',\n      },\n      'type=Memory': {\n        op: { gc: { args: [], ret: 'void', desc: 'gc' } },\n        attr: {\n          ObjectPendingFinalizationCount: {\n            rw: false,\n            type: 'int',\n            desc: 'ObjectPendingFinalizationCount',\n          },\n          Verbose: { rw: true, type: 'boolean', desc: 'Verbose' },\n          HeapMemoryUsage: {\n            rw: false,\n            type: 'javax.management.openmbean.CompositeData',\n            desc: 'HeapMemoryUsage',\n          },\n          NonHeapMemoryUsage: {\n            rw: false,\n            type: 'javax.management.openmbean.CompositeData',\n            desc: 'NonHeapMemoryUsage',\n          },\n          ObjectName: {\n            rw: false,\n            type: 'javax.management.ObjectName',\n            desc: 'ObjectName',\n          },\n        },\n        class: 'sun.management.MemoryImpl',\n        desc: 'Information on the management interface of the MBean',\n      },\n      'name=Metaspace,type=MemoryPool': {\n        op: {\n          resetPeakUsage: { args: [], ret: 'void', desc: 'resetPeakUsage' },\n        },\n        attr: {\n          Usage: {\n            rw: false,\n            type: 'javax.management.openmbean.CompositeData',\n            desc: 'Usage',\n          },\n          UsageThresholdCount: {\n            rw: false,\n            type: 'long',\n            desc: 'UsageThresholdCount',\n          },\n          MemoryManagerNames: {\n            rw: false,\n            type: '[Ljava.lang.String;',\n            desc: 'MemoryManagerNames',\n          },\n          UsageThresholdSupported: {\n            rw: false,\n            type: 'boolean',\n            desc: 'UsageThresholdSupported',\n          },\n          UsageThreshold: { rw: true, type: 'long', desc: 'UsageThreshold' },\n          CollectionUsageThresholdCount: {\n            rw: false,\n            type: 'long',\n            desc: 'CollectionUsageThresholdCount',\n          },\n          PeakUsage: {\n            rw: false,\n            type: 'javax.management.openmbean.CompositeData',\n            desc: 'PeakUsage',\n          },\n          UsageThresholdExceeded: {\n            rw: false,\n            type: 'boolean',\n            desc: 'UsageThresholdExceeded',\n          },\n          CollectionUsageThreshold: {\n            rw: true,\n            type: 'long',\n            desc: 'CollectionUsageThreshold',\n          },\n          Name: { rw: false, type: 'java.lang.String', desc: 'Name' },\n          ObjectName: {\n            rw: false,\n            type: 'javax.management.ObjectName',\n            desc: 'ObjectName',\n          },\n          Type: { rw: false, type: 'java.lang.String', desc: 'Type' },\n          CollectionUsageThresholdSupported: {\n            rw: false,\n            type: 'boolean',\n            desc: 'CollectionUsageThresholdSupported',\n          },\n          Valid: { rw: false, type: 'boolean', desc: 'Valid' },\n          CollectionUsage: {\n            rw: false,\n            type: 'javax.management.openmbean.CompositeData',\n            desc: 'CollectionUsage',\n          },\n          CollectionUsageThresholdExceeded: {\n            rw: false,\n            type: 'boolean',\n            desc: 'CollectionUsageThresholdExceeded',\n          },\n        },\n        class: 'sun.management.MemoryPoolImpl',\n        desc: 'Information on the management interface of the MBean',\n      },\n      'name=G1 Eden Space,type=MemoryPool': {\n        op: {\n          resetPeakUsage: { args: [], ret: 'void', desc: 'resetPeakUsage' },\n        },\n        attr: {\n          Usage: {\n            rw: false,\n            type: 'javax.management.openmbean.CompositeData',\n            desc: 'Usage',\n          },\n          UsageThresholdCount: {\n            rw: false,\n            type: 'long',\n            desc: 'UsageThresholdCount',\n          },\n          MemoryManagerNames: {\n            rw: false,\n            type: '[Ljava.lang.String;',\n            desc: 'MemoryManagerNames',\n          },\n          UsageThresholdSupported: {\n            rw: false,\n            type: 'boolean',\n            desc: 'UsageThresholdSupported',\n          },\n          UsageThreshold: { rw: true, type: 'long', desc: 'UsageThreshold' },\n          CollectionUsageThresholdCount: {\n            rw: false,\n            type: 'long',\n            desc: 'CollectionUsageThresholdCount',\n          },\n          PeakUsage: {\n            rw: false,\n            type: 'javax.management.openmbean.CompositeData',\n            desc: 'PeakUsage',\n          },\n          UsageThresholdExceeded: {\n            rw: false,\n            type: 'boolean',\n            desc: 'UsageThresholdExceeded',\n          },\n          CollectionUsageThreshold: {\n            rw: true,\n            type: 'long',\n            desc: 'CollectionUsageThreshold',\n          },\n          Name: { rw: false, type: 'java.lang.String', desc: 'Name' },\n          ObjectName: {\n            rw: false,\n            type: 'javax.management.ObjectName',\n            desc: 'ObjectName',\n          },\n          Type: { rw: false, type: 'java.lang.String', desc: 'Type' },\n          CollectionUsageThresholdSupported: {\n            rw: false,\n            type: 'boolean',\n            desc: 'CollectionUsageThresholdSupported',\n          },\n          Valid: { rw: false, type: 'boolean', desc: 'Valid' },\n          CollectionUsage: {\n            rw: false,\n            type: 'javax.management.openmbean.CompositeData',\n            desc: 'CollectionUsage',\n          },\n          CollectionUsageThresholdExceeded: {\n            rw: false,\n            type: 'boolean',\n            desc: 'CollectionUsageThresholdExceeded',\n          },\n        },\n        class: 'sun.management.MemoryPoolImpl',\n        desc: 'Information on the management interface of the MBean',\n      },\n      'type=OperatingSystem': {\n        attr: {\n          OpenFileDescriptorCount: {\n            rw: false,\n            type: 'long',\n            desc: 'OpenFileDescriptorCount',\n          },\n          CommittedVirtualMemorySize: {\n            rw: false,\n            type: 'long',\n            desc: 'CommittedVirtualMemorySize',\n          },\n          FreePhysicalMemorySize: {\n            rw: false,\n            type: 'long',\n            desc: 'FreePhysicalMemorySize',\n          },\n          SystemLoadAverage: {\n            rw: false,\n            type: 'double',\n            desc: 'SystemLoadAverage',\n          },\n          Arch: { rw: false, type: 'java.lang.String', desc: 'Arch' },\n          ProcessCpuLoad: { rw: false, type: 'double', desc: 'ProcessCpuLoad' },\n          FreeSwapSpaceSize: {\n            rw: false,\n            type: 'long',\n            desc: 'FreeSwapSpaceSize',\n          },\n          TotalPhysicalMemorySize: {\n            rw: false,\n            type: 'long',\n            desc: 'TotalPhysicalMemorySize',\n          },\n          Name: { rw: false, type: 'java.lang.String', desc: 'Name' },\n          ObjectName: {\n            rw: false,\n            type: 'javax.management.ObjectName',\n            desc: 'ObjectName',\n          },\n          TotalSwapSpaceSize: {\n            rw: false,\n            type: 'long',\n            desc: 'TotalSwapSpaceSize',\n          },\n          TotalMemorySize: { rw: false, type: 'long', desc: 'TotalMemorySize' },\n          ProcessCpuTime: { rw: false, type: 'long', desc: 'ProcessCpuTime' },\n          MaxFileDescriptorCount: {\n            rw: false,\n            type: 'long',\n            desc: 'MaxFileDescriptorCount',\n          },\n          SystemCpuLoad: { rw: false, type: 'double', desc: 'SystemCpuLoad' },\n          Version: { rw: false, type: 'java.lang.String', desc: 'Version' },\n          AvailableProcessors: {\n            rw: false,\n            type: 'int',\n            desc: 'AvailableProcessors',\n          },\n          CpuLoad: { rw: false, type: 'double', desc: 'CpuLoad' },\n          FreeMemorySize: { rw: false, type: 'long', desc: 'FreeMemorySize' },\n        },\n        class: 'com.sun.management.internal.OperatingSystemImpl',\n        desc: 'Information on the management interface of the MBean',\n      },\n      'name=CodeCacheManager,type=MemoryManager': {\n        attr: {\n          MemoryPoolNames: {\n            rw: false,\n            type: '[Ljava.lang.String;',\n            desc: 'MemoryPoolNames',\n          },\n          Valid: { rw: false, type: 'boolean', desc: 'Valid' },\n          Name: { rw: false, type: 'java.lang.String', desc: 'Name' },\n          ObjectName: {\n            rw: false,\n            type: 'javax.management.ObjectName',\n            desc: 'ObjectName',\n          },\n        },\n        class: 'sun.management.MemoryManagerImpl',\n        desc: 'Information on the management interface of the MBean',\n      },\n      'name=G1 Old Gen,type=MemoryPool': {\n        op: {\n          resetPeakUsage: { args: [], ret: 'void', desc: 'resetPeakUsage' },\n        },\n        attr: {\n          Usage: {\n            rw: false,\n            type: 'javax.management.openmbean.CompositeData',\n            desc: 'Usage',\n          },\n          UsageThresholdCount: {\n            rw: false,\n            type: 'long',\n            desc: 'UsageThresholdCount',\n          },\n          MemoryManagerNames: {\n            rw: false,\n            type: '[Ljava.lang.String;',\n            desc: 'MemoryManagerNames',\n          },\n          UsageThresholdSupported: {\n            rw: false,\n            type: 'boolean',\n            desc: 'UsageThresholdSupported',\n          },\n          UsageThreshold: { rw: true, type: 'long', desc: 'UsageThreshold' },\n          CollectionUsageThresholdCount: {\n            rw: false,\n            type: 'long',\n            desc: 'CollectionUsageThresholdCount',\n          },\n          PeakUsage: {\n            rw: false,\n            type: 'javax.management.openmbean.CompositeData',\n            desc: 'PeakUsage',\n          },\n          UsageThresholdExceeded: {\n            rw: false,\n            type: 'boolean',\n            desc: 'UsageThresholdExceeded',\n          },\n          CollectionUsageThreshold: {\n            rw: true,\n            type: 'long',\n            desc: 'CollectionUsageThreshold',\n          },\n          Name: { rw: false, type: 'java.lang.String', desc: 'Name' },\n          ObjectName: {\n            rw: false,\n            type: 'javax.management.ObjectName',\n            desc: 'ObjectName',\n          },\n          Type: { rw: false, type: 'java.lang.String', desc: 'Type' },\n          CollectionUsageThresholdSupported: {\n            rw: false,\n            type: 'boolean',\n            desc: 'CollectionUsageThresholdSupported',\n          },\n          Valid: { rw: false, type: 'boolean', desc: 'Valid' },\n          CollectionUsage: {\n            rw: false,\n            type: 'javax.management.openmbean.CompositeData',\n            desc: 'CollectionUsage',\n          },\n          CollectionUsageThresholdExceeded: {\n            rw: false,\n            type: 'boolean',\n            desc: 'CollectionUsageThresholdExceeded',\n          },\n        },\n        class: 'sun.management.MemoryPoolImpl',\n        desc: 'Information on the management interface of the MBean',\n      },\n      'name=Compressed Class Space,type=MemoryPool': {\n        op: {\n          resetPeakUsage: { args: [], ret: 'void', desc: 'resetPeakUsage' },\n        },\n        attr: {\n          Usage: {\n            rw: false,\n            type: 'javax.management.openmbean.CompositeData',\n            desc: 'Usage',\n          },\n          UsageThresholdCount: {\n            rw: false,\n            type: 'long',\n            desc: 'UsageThresholdCount',\n          },\n          MemoryManagerNames: {\n            rw: false,\n            type: '[Ljava.lang.String;',\n            desc: 'MemoryManagerNames',\n          },\n          UsageThresholdSupported: {\n            rw: false,\n            type: 'boolean',\n            desc: 'UsageThresholdSupported',\n          },\n          UsageThreshold: { rw: true, type: 'long', desc: 'UsageThreshold' },\n          CollectionUsageThresholdCount: {\n            rw: false,\n            type: 'long',\n            desc: 'CollectionUsageThresholdCount',\n          },\n          PeakUsage: {\n            rw: false,\n            type: 'javax.management.openmbean.CompositeData',\n            desc: 'PeakUsage',\n          },\n          UsageThresholdExceeded: {\n            rw: false,\n            type: 'boolean',\n            desc: 'UsageThresholdExceeded',\n          },\n          CollectionUsageThreshold: {\n            rw: true,\n            type: 'long',\n            desc: 'CollectionUsageThreshold',\n          },\n          Name: { rw: false, type: 'java.lang.String', desc: 'Name' },\n          ObjectName: {\n            rw: false,\n            type: 'javax.management.ObjectName',\n            desc: 'ObjectName',\n          },\n          Type: { rw: false, type: 'java.lang.String', desc: 'Type' },\n          CollectionUsageThresholdSupported: {\n            rw: false,\n            type: 'boolean',\n            desc: 'CollectionUsageThresholdSupported',\n          },\n          Valid: { rw: false, type: 'boolean', desc: 'Valid' },\n          CollectionUsage: {\n            rw: false,\n            type: 'javax.management.openmbean.CompositeData',\n            desc: 'CollectionUsage',\n          },\n          CollectionUsageThresholdExceeded: {\n            rw: false,\n            type: 'boolean',\n            desc: 'CollectionUsageThresholdExceeded',\n          },\n        },\n        class: 'sun.management.MemoryPoolImpl',\n        desc: 'Information on the management interface of the MBean',\n      },\n      'name=G1 Old Generation,type=GarbageCollector': {\n        attr: {\n          MemoryPoolNames: {\n            rw: false,\n            type: '[Ljava.lang.String;',\n            desc: 'MemoryPoolNames',\n          },\n          LastGcInfo: {\n            rw: false,\n            type: 'javax.management.openmbean.CompositeData',\n            desc: 'LastGcInfo',\n          },\n          CollectionTime: { rw: false, type: 'long', desc: 'CollectionTime' },\n          Valid: { rw: false, type: 'boolean', desc: 'Valid' },\n          CollectionCount: { rw: false, type: 'long', desc: 'CollectionCount' },\n          Name: { rw: false, type: 'java.lang.String', desc: 'Name' },\n          ObjectName: {\n            rw: false,\n            type: 'javax.management.ObjectName',\n            desc: 'ObjectName',\n          },\n        },\n        class: 'com.sun.management.internal.GarbageCollectorExtImpl',\n        desc: 'Information on the management interface of the MBean',\n      },\n      'type=ClassLoading': {\n        attr: {\n          UnloadedClassCount: {\n            rw: false,\n            type: 'long',\n            desc: 'UnloadedClassCount',\n          },\n          LoadedClassCount: {\n            rw: false,\n            type: 'int',\n            desc: 'LoadedClassCount',\n          },\n          Verbose: { rw: true, type: 'boolean', desc: 'Verbose' },\n          TotalLoadedClassCount: {\n            rw: false,\n            type: 'long',\n            desc: 'TotalLoadedClassCount',\n          },\n          ObjectName: {\n            rw: false,\n            type: 'javax.management.ObjectName',\n            desc: 'ObjectName',\n          },\n        },\n        class: 'sun.management.ClassLoadingImpl',\n        desc: 'Information on the management interface of the MBean',\n      },\n      'name=G1 Young Generation,type=GarbageCollector': {\n        attr: {\n          MemoryPoolNames: {\n            rw: false,\n            type: '[Ljava.lang.String;',\n            desc: 'MemoryPoolNames',\n          },\n          LastGcInfo: {\n            rw: false,\n            type: 'javax.management.openmbean.CompositeData',\n            desc: 'LastGcInfo',\n          },\n          CollectionTime: { rw: false, type: 'long', desc: 'CollectionTime' },\n          Valid: { rw: false, type: 'boolean', desc: 'Valid' },\n          CollectionCount: { rw: false, type: 'long', desc: 'CollectionCount' },\n          Name: { rw: false, type: 'java.lang.String', desc: 'Name' },\n          ObjectName: {\n            rw: false,\n            type: 'javax.management.ObjectName',\n            desc: 'ObjectName',\n          },\n        },\n        class: 'com.sun.management.internal.GarbageCollectorExtImpl',\n        desc: 'Information on the management interface of the MBean',\n      },\n      'type=Compilation': {\n        attr: {\n          CompilationTimeMonitoringSupported: {\n            rw: false,\n            type: 'boolean',\n            desc: 'CompilationTimeMonitoringSupported',\n          },\n          TotalCompilationTime: {\n            rw: false,\n            type: 'long',\n            desc: 'TotalCompilationTime',\n          },\n          Name: { rw: false, type: 'java.lang.String', desc: 'Name' },\n          ObjectName: {\n            rw: false,\n            type: 'javax.management.ObjectName',\n            desc: 'ObjectName',\n          },\n        },\n        class: 'sun.management.CompilationImpl',\n        desc: 'Information on the management interface of the MBean',\n      },\n      'name=Metaspace Manager,type=MemoryManager': {\n        attr: {\n          MemoryPoolNames: {\n            rw: false,\n            type: '[Ljava.lang.String;',\n            desc: 'MemoryPoolNames',\n          },\n          Valid: { rw: false, type: 'boolean', desc: 'Valid' },\n          Name: { rw: false, type: 'java.lang.String', desc: 'Name' },\n          ObjectName: {\n            rw: false,\n            type: 'javax.management.ObjectName',\n            desc: 'ObjectName',\n          },\n        },\n        class: 'sun.management.MemoryManagerImpl',\n        desc: 'Information on the management interface of the MBean',\n      },\n      'type=Runtime': {\n        attr: {\n          SpecVendor: {\n            rw: false,\n            type: 'java.lang.String',\n            desc: 'SpecVendor',\n          },\n          ClassPath: { rw: false, type: 'java.lang.String', desc: 'ClassPath' },\n          InputArguments: {\n            rw: false,\n            type: '[Ljava.lang.String;',\n            desc: 'InputArguments',\n          },\n          Uptime: { rw: false, type: 'long', desc: 'Uptime' },\n          VmName: { rw: false, type: 'java.lang.String', desc: 'VmName' },\n          StartTime: { rw: false, type: 'long', desc: 'StartTime' },\n          VmVersion: { rw: false, type: 'java.lang.String', desc: 'VmVersion' },\n          SpecName: { rw: false, type: 'java.lang.String', desc: 'SpecName' },\n          Pid: { rw: false, type: 'long', desc: 'Pid' },\n          ManagementSpecVersion: {\n            rw: false,\n            type: 'java.lang.String',\n            desc: 'ManagementSpecVersion',\n          },\n          Name: { rw: false, type: 'java.lang.String', desc: 'Name' },\n          ObjectName: {\n            rw: false,\n            type: 'javax.management.ObjectName',\n            desc: 'ObjectName',\n          },\n          VmVendor: { rw: false, type: 'java.lang.String', desc: 'VmVendor' },\n          LibraryPath: {\n            rw: false,\n            type: 'java.lang.String',\n            desc: 'LibraryPath',\n          },\n          BootClassPath: {\n            rw: false,\n            type: 'java.lang.String',\n            desc: 'BootClassPath',\n          },\n          SpecVersion: {\n            rw: false,\n            type: 'java.lang.String',\n            desc: 'SpecVersion',\n          },\n          SystemProperties: {\n            rw: false,\n            type: 'javax.management.openmbean.TabularData',\n            desc: 'SystemProperties',\n          },\n          BootClassPathSupported: {\n            rw: false,\n            type: 'boolean',\n            desc: 'BootClassPathSupported',\n          },\n        },\n        class: 'sun.management.RuntimeImpl',\n        desc: 'Information on the management interface of the MBean',\n      },\n    },\n    'org.springframework.cloud.context.properties': {\n      'name=configurationPropertiesRebinder,type=ConfigurationPropertiesRebinder':\n        {\n          op: {\n            getNeverRefreshable: {\n              args: [],\n              ret: 'java.util.Set',\n              desc: 'getNeverRefreshable',\n            },\n            getBeanNames: {\n              args: [],\n              ret: 'java.util.Set',\n              desc: 'getBeanNames',\n            },\n            rebind: [\n              {\n                args: [\n                  { name: 'name', type: 'java.lang.String', desc: 'name' },\n                ],\n                ret: 'boolean',\n                desc: 'rebind',\n              },\n              { args: [], ret: 'void', desc: 'rebind' },\n            ],\n          },\n          attr: {\n            NeverRefreshable: {\n              rw: false,\n              type: 'java.util.Set',\n              desc: 'neverRefreshable',\n            },\n            BeanNames: { rw: false, type: 'java.util.Set', desc: 'beanNames' },\n          },\n          class:\n            'org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder',\n          desc: '',\n        },\n    },\n    'org.springframework.cloud.context.scope.refresh': {\n      'name=refreshScope,type=RefreshScope': {\n        op: {\n          refreshAll: {\n            args: [],\n            ret: 'void',\n            desc: 'Dispose of the current instance of all beans in this scope and force a refresh on next method execution.',\n          },\n          refresh: {\n            args: [{ name: 'name', type: 'java.lang.String', desc: 'name' }],\n            ret: 'boolean',\n            desc: 'Dispose of the current instance of bean name provided and force a refresh on next method execution.',\n          },\n        },\n        class: 'org.springframework.cloud.context.scope.refresh.RefreshScope',\n        desc: '',\n      },\n    },\n    'com.sun.management': {\n      'type=HotSpotDiagnostic': {\n        op: {\n          setVMOption: {\n            args: [\n              {\n                name: 'p0',\n                type: 'java.lang.String',\n                desc: 'p0',\n              },\n              { name: 'p1', type: 'java.lang.String', desc: 'p1' },\n            ],\n            ret: 'void',\n            desc: 'setVMOption',\n          },\n          getVMOption: {\n            args: [{ name: 'p0', type: 'java.lang.String', desc: 'p0' }],\n            ret: 'javax.management.openmbean.CompositeData',\n            desc: 'getVMOption',\n          },\n          dumpHeap: {\n            args: [\n              { name: 'p0', type: 'java.lang.String', desc: 'p0' },\n              {\n                name: 'p1',\n                type: 'boolean',\n                desc: 'p1',\n              },\n            ],\n            ret: 'void',\n            desc: 'dumpHeap',\n          },\n        },\n        attr: {\n          DiagnosticOptions: {\n            rw: false,\n            type: '[Ljavax.management.openmbean.CompositeData;',\n            desc: 'DiagnosticOptions',\n          },\n          ObjectName: {\n            rw: false,\n            type: 'javax.management.ObjectName',\n            desc: 'ObjectName',\n          },\n        },\n        class: 'com.sun.management.internal.HotSpotDiagnostic',\n        desc: 'Information on the management interface of the MBean',\n      },\n      'type=DiagnosticCommand': {\n        op: {\n          vmUptime: {\n            args: [\n              {\n                name: 'arguments',\n                type: '[Ljava.lang.String;',\n                desc: 'Array of Diagnostic Commands Arguments and Options',\n              },\n            ],\n            ret: 'java.lang.String',\n            desc: 'Print VM uptime.',\n          },\n          jfrDump: {\n            args: [\n              {\n                name: 'arguments',\n                type: '[Ljava.lang.String;',\n                desc: 'Array of Diagnostic Commands Arguments and Options',\n              },\n            ],\n            ret: 'java.lang.String',\n            desc: 'Copies contents of a JFR recording to file. Either the name or the recording id must be specified.',\n          },\n          jfrStart: {\n            args: [\n              {\n                name: 'arguments',\n                type: '[Ljava.lang.String;',\n                desc: 'Array of Diagnostic Commands Arguments and Options',\n              },\n            ],\n            ret: 'java.lang.String',\n            desc: 'Starts a new JFR recording',\n          },\n          threadPrint: {\n            args: [\n              {\n                name: 'arguments',\n                type: '[Ljava.lang.String;',\n                desc: 'Array of Diagnostic Commands Arguments and Options',\n              },\n            ],\n            ret: 'java.lang.String',\n            desc: 'Print all threads with stacktraces.',\n          },\n          jfrStop: {\n            args: [\n              {\n                name: 'arguments',\n                type: '[Ljava.lang.String;',\n                desc: 'Array of Diagnostic Commands Arguments and Options',\n              },\n            ],\n            ret: 'java.lang.String',\n            desc: 'Stops a JFR recording',\n          },\n          vmCds: {\n            args: [\n              {\n                name: 'arguments',\n                type: '[Ljava.lang.String;',\n                desc: 'Array of Diagnostic Commands Arguments and Options',\n              },\n            ],\n            ret: 'java.lang.String',\n            desc: 'Dump a static or dynamic shared archive including all shareable classes',\n          },\n          compilerCodelist: {\n            args: [],\n            ret: 'java.lang.String',\n            desc: 'Print all compiled methods in code cache that are alive',\n          },\n          vmEvents: {\n            args: [\n              {\n                name: 'arguments',\n                type: '[Ljava.lang.String;',\n                desc: 'Array of Diagnostic Commands Arguments and Options',\n              },\n            ],\n            ret: 'java.lang.String',\n            desc: 'Print VM event logs',\n          },\n          jfrCheck: {\n            args: [\n              {\n                name: 'arguments',\n                type: '[Ljava.lang.String;',\n                desc: 'Array of Diagnostic Commands Arguments and Options',\n              },\n            ],\n            ret: 'java.lang.String',\n            desc: 'Checks running JFR recording(s)',\n          },\n          vmSymboltable: {\n            args: [\n              {\n                name: 'arguments',\n                type: '[Ljava.lang.String;',\n                desc: 'Array of Diagnostic Commands Arguments and Options',\n              },\n            ],\n            ret: 'java.lang.String',\n            desc: 'Dump symbol table.',\n          },\n          gcRun: {\n            args: [],\n            ret: 'java.lang.String',\n            desc: 'Call java.lang.System.gc().',\n          },\n          vmClassloaders: {\n            args: [\n              {\n                name: 'arguments',\n                type: '[Ljava.lang.String;',\n                desc: 'Array of Diagnostic Commands Arguments and Options',\n              },\n            ],\n            ret: 'java.lang.String',\n            desc: 'Prints classloader hierarchy.',\n          },\n          vmMetaspace: {\n            args: [\n              {\n                name: 'arguments',\n                type: '[Ljava.lang.String;',\n                desc: 'Array of Diagnostic Commands Arguments and Options',\n              },\n            ],\n            ret: 'java.lang.String',\n            desc: 'Prints the statistics for the metaspace',\n          },\n          compilerDirectivesPrint: {\n            args: [],\n            ret: 'java.lang.String',\n            desc: 'Print all active compiler directives.',\n          },\n          vmSetFlag: {\n            args: [\n              {\n                name: 'arguments',\n                type: '[Ljava.lang.String;',\n                desc: 'Array of Diagnostic Commands Arguments and Options',\n              },\n            ],\n            ret: 'java.lang.String',\n            desc: 'Sets VM flag option using the provided value.',\n          },\n          compilerDirectivesAdd: {\n            args: [\n              {\n                name: 'arguments',\n                type: '[Ljava.lang.String;',\n                desc: 'Array of Diagnostic Commands Arguments and Options',\n              },\n            ],\n            ret: 'java.lang.String',\n            desc: 'Add compiler directives from file.',\n          },\n          vmDynlibs: {\n            args: [],\n            ret: 'java.lang.String',\n            desc: 'Print loaded dynamic libraries.',\n          },\n          vmPrintTouchedMethods: {\n            args: [],\n            ret: 'java.lang.String',\n            desc: 'Print all methods that have ever been touched during the lifetime of this JVM.',\n          },\n          compilerCodecache: {\n            args: [],\n            ret: 'java.lang.String',\n            desc: 'Print code cache layout and bounds.',\n          },\n          vmNativeMemory: {\n            args: [\n              {\n                name: 'arguments',\n                type: '[Ljava.lang.String;',\n                desc: 'Array of Diagnostic Commands Arguments and Options',\n              },\n            ],\n            ret: 'java.lang.String',\n            desc: 'Print native memory usage',\n          },\n          gcClassHistogram: {\n            args: [\n              {\n                name: 'arguments',\n                type: '[Ljava.lang.String;',\n                desc: 'Array of Diagnostic Commands Arguments and Options',\n              },\n            ],\n            ret: 'java.lang.String',\n            desc: 'Provide statistics about the Java heap usage.',\n          },\n          gcRunFinalization: {\n            args: [],\n            ret: 'java.lang.String',\n            desc: 'Call java.lang.System.runFinalization().',\n          },\n          jvmtiDataDump: {\n            args: [],\n            ret: 'java.lang.String',\n            desc: 'Signal the JVM to do a data-dump request for JVMTI.',\n          },\n          gcFinalizerInfo: {\n            args: [],\n            ret: 'java.lang.String',\n            desc: 'Provide information about Java finalization queue.',\n          },\n          vmStringtable: {\n            args: [\n              {\n                name: 'arguments',\n                type: '[Ljava.lang.String;',\n                desc: 'Array of Diagnostic Commands Arguments and Options',\n              },\n            ],\n            ret: 'java.lang.String',\n            desc: 'Dump string table.',\n          },\n          help: {\n            args: [\n              {\n                name: 'arguments',\n                type: '[Ljava.lang.String;',\n                desc: 'Array of Diagnostic Commands Arguments and Options',\n              },\n            ],\n            ret: 'java.lang.String',\n            desc: \"For more information about a specific command use 'help <command>'. With no argument this will show a list of available commands. 'help all' will show help for all commands.\",\n          },\n          jfrConfigure: {\n            args: [\n              {\n                name: 'arguments',\n                type: '[Ljava.lang.String;',\n                desc: 'Array of Diagnostic Commands Arguments and Options',\n              },\n            ],\n            ret: 'java.lang.String',\n            desc: 'Configure JFR',\n          },\n          vmSystemProperties: {\n            args: [],\n            ret: 'java.lang.String',\n            desc: 'Print system properties.',\n          },\n          compilerDirectivesClear: {\n            args: [],\n            ret: 'java.lang.String',\n            desc: 'Remove all compiler directives.',\n          },\n          vmSystemdictionary: {\n            args: [\n              {\n                name: 'arguments',\n                type: '[Ljava.lang.String;',\n                desc: 'Array of Diagnostic Commands Arguments and Options',\n              },\n            ],\n            ret: 'java.lang.String',\n            desc: 'Prints the statistics for dictionary hashtable sizes and bucket length',\n          },\n          vmClassloaderStats: {\n            args: [],\n            ret: 'java.lang.String',\n            desc: 'Print statistics about all ClassLoaders.',\n          },\n          compilerDirectivesRemove: {\n            args: [],\n            ret: 'java.lang.String',\n            desc: 'Remove latest added compiler directive.',\n          },\n          gcHeapInfo: {\n            args: [],\n            ret: 'java.lang.String',\n            desc: 'Provide generic Java heap information.',\n          },\n          compilerCodeHeapAnalytics: {\n            args: [\n              {\n                name: 'arguments',\n                type: '[Ljava.lang.String;',\n                desc: 'Array of Diagnostic Commands Arguments and Options',\n              },\n            ],\n            ret: 'java.lang.String',\n            desc: 'Print CodeHeap analytics',\n          },\n          vmVersion: {\n            args: [],\n            ret: 'java.lang.String',\n            desc: 'Print JVM version information.',\n          },\n          vmInfo: {\n            args: [],\n            ret: 'java.lang.String',\n            desc: 'Print information about JVM environment and status.',\n          },\n          compilerQueue: {\n            args: [],\n            ret: 'java.lang.String',\n            desc: 'Print methods queued for compilation.',\n          },\n          vmFlags: {\n            args: [\n              {\n                name: 'arguments',\n                type: '[Ljava.lang.String;',\n                desc: 'Array of Diagnostic Commands Arguments and Options',\n              },\n            ],\n            ret: 'java.lang.String',\n            desc: 'Print VM flag options and their current values.',\n          },\n          vmLog: {\n            args: [\n              {\n                name: 'arguments',\n                type: '[Ljava.lang.String;',\n                desc: 'Array of Diagnostic Commands Arguments and Options',\n              },\n            ],\n            ret: 'java.lang.String',\n            desc: 'Lists current log configuration, enables/disables/configures a log output, or rotates all logs.',\n          },\n          jvmtiAgentLoad: {\n            args: [\n              {\n                name: 'arguments',\n                type: '[Ljava.lang.String;',\n                desc: 'Array of Diagnostic Commands Arguments and Options',\n              },\n            ],\n            ret: 'java.lang.String',\n            desc: 'Load JVMTI native agent.',\n          },\n          vmClassHierarchy: {\n            args: [\n              {\n                name: 'arguments',\n                type: '[Ljava.lang.String;',\n                desc: 'Array of Diagnostic Commands Arguments and Options',\n              },\n            ],\n            ret: 'java.lang.String',\n            desc: 'Print a list of all loaded classes, indented to show the class hiearchy. The name of each class is followed by the ClassLoaderData* of its ClassLoader, or \"null\" if loaded by the bootstrap class loader.',\n          },\n          vmCommandLine: {\n            args: [],\n            ret: 'java.lang.String',\n            desc: 'Print the command line used to start this VM instance.',\n          },\n        },\n        class: 'com.sun.management.internal.DiagnosticCommandImpl',\n        desc: 'Diagnostic Commands',\n      },\n    },\n    jmx4perl: {\n      'type=Config': {\n        op: {\n          setHistoryEntriesForOperation: {\n            args: [\n              {\n                name: 'p1',\n                type: 'java.lang.String',\n                desc: '',\n              },\n              { name: 'p2', type: 'java.lang.String', desc: '' },\n              {\n                name: 'p3',\n                type: 'java.lang.String',\n                desc: '',\n              },\n              { name: 'p4', type: 'int', desc: '' },\n            ],\n            ret: 'void',\n            desc: 'Operation exposed for management',\n          },\n          setHistoryLimitForOperation: {\n            args: [\n              {\n                name: 'p1',\n                type: 'java.lang.String',\n                desc: '',\n              },\n              { name: 'p2', type: 'java.lang.String', desc: '' },\n              {\n                name: 'p3',\n                type: 'java.lang.String',\n                desc: '',\n              },\n              { name: 'p4', type: 'int', desc: '' },\n              { name: 'p5', type: 'long', desc: '' },\n            ],\n            ret: 'void',\n            desc: 'Operation exposed for management',\n          },\n          resetDebugInfo: {\n            args: [],\n            ret: 'void',\n            desc: 'Operation exposed for management',\n          },\n          resetHistoryEntries: {\n            args: [],\n            ret: 'void',\n            desc: 'Operation exposed for management',\n          },\n          setHistoryEntriesForAttribute: {\n            args: [\n              {\n                name: 'p1',\n                type: 'java.lang.String',\n                desc: '',\n              },\n              { name: 'p2', type: 'java.lang.String', desc: '' },\n              {\n                name: 'p3',\n                type: 'java.lang.String',\n                desc: '',\n              },\n              { name: 'p4', type: 'java.lang.String', desc: '' },\n              { name: 'p5', type: 'int', desc: '' },\n            ],\n            ret: 'void',\n            desc: 'Operation exposed for management',\n          },\n          setHistoryLimitForAttribute: {\n            args: [\n              {\n                name: 'p1',\n                type: 'java.lang.String',\n                desc: '',\n              },\n              { name: 'p2', type: 'java.lang.String', desc: '' },\n              {\n                name: 'p3',\n                type: 'java.lang.String',\n                desc: '',\n              },\n              { name: 'p4', type: 'java.lang.String', desc: '' },\n              {\n                name: 'p5',\n                type: 'int',\n                desc: '',\n              },\n              { name: 'p6', type: 'long', desc: '' },\n            ],\n            ret: 'void',\n            desc: 'Operation exposed for management',\n          },\n          debugInfo: {\n            args: [],\n            ret: 'java.lang.String',\n            desc: 'Operation exposed for management',\n          },\n        },\n        attr: {\n          HistorySize: {\n            rw: false,\n            type: 'int',\n            desc: 'Attribute exposed for management',\n          },\n          MaxDebugEntries: {\n            rw: true,\n            type: 'int',\n            desc: 'Attribute exposed for management',\n          },\n          HistoryMaxEntries: {\n            rw: true,\n            type: 'int',\n            desc: 'Attribute exposed for management',\n          },\n          Debug: {\n            rw: true,\n            type: 'boolean',\n            desc: 'Attribute exposed for management',\n          },\n        },\n        class: 'org.jolokia.backend.Config',\n        desc: 'Information on the management interface of the MBean',\n      },\n    },\n    'com.zaxxer.hikari': {\n      'name=dataSource,type=HikariDataSource': {\n        attr: {\n          MaxLifetime: {\n            rw: true,\n            type: 'long',\n            desc: 'MaxLifetime',\n          },\n          ConnectionTimeout: {\n            rw: true,\n            type: 'long',\n            desc: 'ConnectionTimeout',\n          },\n          MaximumPoolSize: { rw: true, type: 'int', desc: 'MaximumPoolSize' },\n          PoolName: { rw: false, type: 'java.lang.String', desc: 'PoolName' },\n          Username: { rw: false, type: 'java.lang.String', desc: 'Username' },\n          IdleTimeout: { rw: true, type: 'long', desc: 'IdleTimeout' },\n          LeakDetectionThreshold: {\n            rw: true,\n            type: 'long',\n            desc: 'LeakDetectionThreshold',\n          },\n          ValidationTimeout: {\n            rw: true,\n            type: 'long',\n            desc: 'ValidationTimeout',\n          },\n          Catalog: { rw: true, type: 'java.lang.String', desc: 'Catalog' },\n          Password: { rw: false, type: 'java.lang.String', desc: 'Password' },\n          MinimumIdle: { rw: true, type: 'int', desc: 'MinimumIdle' },\n        },\n        class: 'com.zaxxer.hikari.HikariDataSource',\n        desc: 'Information on the management interface of the MBean',\n      },\n    },\n    'com.codecentric.boot.sample': {\n      'name=stringMapManagedBean.StringSetter,type=StringMapManagedBean.StringSetter':\n        {\n          op: {\n            getConfigKeys: {\n              args: [],\n              ret: 'java.util.List',\n              desc: 'getConfigKeys',\n            },\n            setTest: {\n              args: [{ name: 'test', type: 'java.lang.String', desc: 'test' }],\n              ret: 'void',\n              desc: 'Set the value',\n            },\n            getTest: {\n              args: [],\n              ret: 'java.lang.String',\n              desc: 'Get the value',\n            },\n          },\n          attr: {\n            Test: { rw: true, type: 'java.lang.String', desc: 'Get the value' },\n            ConfigKeys: {\n              rw: false,\n              type: 'java.util.List',\n              desc: 'configKeys',\n            },\n          },\n          class:\n            'com.codecentric.boot.sample.StringMapManagedBean$StringSetter',\n          desc: 'String Setter MBean inside other MBEAN',\n        },\n      'name=stringMapManagedBean,type=StringMapManagedBean': {\n        op: {\n          getSize: {\n            args: [],\n            ret: 'int',\n            desc: 'getSize',\n          },\n          setTest: {\n            args: [{ name: 'test', type: 'int', desc: 'test' }],\n            ret: 'void',\n            desc: 'Set the value of the test instance variable',\n          },\n          get: {\n            args: [{ name: 'key', type: 'java.lang.String', desc: 'key' }],\n            ret: 'java.lang.String',\n            desc: 'get',\n          },\n          getTest: {\n            args: [],\n            ret: 'int',\n            desc: 'Get the value of the test instance variable',\n          },\n          put: {\n            args: [\n              { name: 'key', type: 'java.lang.String', desc: 'key' },\n              {\n                name: 'value',\n                type: 'java.lang.String',\n                desc: 'value',\n              },\n            ],\n            ret: 'java.lang.String',\n            desc: 'PUT DESCRIPTION',\n          },\n        },\n        attr: {\n          Test: {\n            rw: true,\n            type: 'int',\n            desc: 'Get the value of the test instance variable',\n          },\n          Size: { rw: false, type: 'int', desc: 'size' },\n        },\n        class: 'com.codecentric.boot.sample.StringMapManagedBean',\n        desc: '',\n      },\n    },\n    'org.springframework.cloud.context.environment': {\n      'name=environmentManager,type=EnvironmentManager': {\n        op: {\n          getProperty: {\n            args: [\n              {\n                name: 'name',\n                type: 'java.lang.String',\n                desc: 'name',\n              },\n            ],\n            ret: 'java.lang.Object',\n            desc: 'getProperty',\n          },\n          setProperty: {\n            args: [\n              { name: 'name', type: 'java.lang.String', desc: 'name' },\n              {\n                name: 'value',\n                type: 'java.lang.String',\n                desc: 'value',\n              },\n            ],\n            ret: 'void',\n            desc: 'setProperty',\n          },\n          reset: { args: [], ret: 'java.util.Map', desc: 'reset' },\n        },\n        class:\n          'org.springframework.cloud.context.environment.EnvironmentManager',\n        desc: '',\n      },\n    },\n    jolokia: {\n      'type=Discovery': {\n        op: {\n          lookupAgentsWithTimeout: {\n            args: [{ name: 'p1', type: 'int', desc: '' }],\n            ret: 'java.util.List',\n            desc: 'Operation exposed for management',\n          },\n          lookupAgentsWithTimeoutAndMulticastAddress: {\n            args: [\n              {\n                name: 'p1',\n                type: 'int',\n                desc: '',\n              },\n              { name: 'p2', type: 'java.lang.String', desc: '' },\n              { name: 'p3', type: 'int', desc: '' },\n            ],\n            ret: 'java.util.List',\n            desc: 'Operation exposed for management',\n          },\n          lookupAgents: {\n            args: [],\n            ret: 'java.util.List',\n            desc: 'Operation exposed for management',\n          },\n        },\n        class: 'org.jolokia.discovery.JolokiaDiscovery',\n        desc: 'Information on the management interface of the MBean',\n      },\n      'type=ServerHandler': {\n        op: {\n          mBeanServersInfo: {\n            args: [],\n            ret: 'java.lang.String',\n            desc: 'Operation exposed for management',\n          },\n        },\n        class: 'org.jolokia.backend.MBeanServerHandler',\n        desc: 'Information on the management interface of the MBean',\n      },\n      'type=Config': {\n        op: {\n          setHistoryEntriesForOperation: {\n            args: [\n              {\n                name: 'p1',\n                type: 'java.lang.String',\n                desc: '',\n              },\n              { name: 'p2', type: 'java.lang.String', desc: '' },\n              {\n                name: 'p3',\n                type: 'java.lang.String',\n                desc: '',\n              },\n              { name: 'p4', type: 'int', desc: '' },\n            ],\n            ret: 'void',\n            desc: 'Operation exposed for management',\n          },\n          setHistoryLimitForOperation: {\n            args: [\n              {\n                name: 'p1',\n                type: 'java.lang.String',\n                desc: '',\n              },\n              { name: 'p2', type: 'java.lang.String', desc: '' },\n              {\n                name: 'p3',\n                type: 'java.lang.String',\n                desc: '',\n              },\n              { name: 'p4', type: 'int', desc: '' },\n              { name: 'p5', type: 'long', desc: '' },\n            ],\n            ret: 'void',\n            desc: 'Operation exposed for management',\n          },\n          resetDebugInfo: {\n            args: [],\n            ret: 'void',\n            desc: 'Operation exposed for management',\n          },\n          resetHistoryEntries: {\n            args: [],\n            ret: 'void',\n            desc: 'Operation exposed for management',\n          },\n          setHistoryEntriesForAttribute: {\n            args: [\n              {\n                name: 'p1',\n                type: 'java.lang.String',\n                desc: '',\n              },\n              { name: 'p2', type: 'java.lang.String', desc: '' },\n              {\n                name: 'p3',\n                type: 'java.lang.String',\n                desc: '',\n              },\n              { name: 'p4', type: 'java.lang.String', desc: '' },\n              { name: 'p5', type: 'int', desc: '' },\n            ],\n            ret: 'void',\n            desc: 'Operation exposed for management',\n          },\n          setHistoryLimitForAttribute: {\n            args: [\n              {\n                name: 'p1',\n                type: 'java.lang.String',\n                desc: '',\n              },\n              { name: 'p2', type: 'java.lang.String', desc: '' },\n              {\n                name: 'p3',\n                type: 'java.lang.String',\n                desc: '',\n              },\n              { name: 'p4', type: 'java.lang.String', desc: '' },\n              {\n                name: 'p5',\n                type: 'int',\n                desc: '',\n              },\n              { name: 'p6', type: 'long', desc: '' },\n            ],\n            ret: 'void',\n            desc: 'Operation exposed for management',\n          },\n          debugInfo: {\n            args: [],\n            ret: 'java.lang.String',\n            desc: 'Operation exposed for management',\n          },\n        },\n        attr: {\n          HistorySize: {\n            rw: false,\n            type: 'int',\n            desc: 'Attribute exposed for management',\n          },\n          MaxDebugEntries: {\n            rw: true,\n            type: 'int',\n            desc: 'Attribute exposed for management',\n          },\n          HistoryMaxEntries: {\n            rw: true,\n            type: 'int',\n            desc: 'Attribute exposed for management',\n          },\n          Debug: {\n            rw: true,\n            type: 'boolean',\n            desc: 'Attribute exposed for management',\n          },\n        },\n        class: 'org.jolokia.backend.Config',\n        desc: 'Information on the management interface of the MBean',\n      },\n    },\n  },\n  timestamp: 1673711911,\n  status: 200,\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/instance/jolokia/index.ts",
    "content": "import { HttpResponse, http } from 'msw';\n\nimport { jolokiaList } from './data';\n\nimport { jolokiaRead } from '@/mocks/instance/jolokia/data.read';\n\nconst jolokiaEndpoint = [\n  http.get('/instances/:instanceId/actuator/jolokia/list', () => {\n    return HttpResponse.json(jolokiaList);\n  }),\n  http.post('/instances/:instanceId/actuator/jolokia', async () => {\n    try {\n      const body = { type: 'read' };\n      if (body.type === 'read') {\n        return HttpResponse.json(jolokiaRead);\n      }\n    } catch (e) {\n      console.error(e);\n    }\n  }),\n];\n\nexport default jolokiaEndpoint;\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/instance/liquibase/data.ts",
    "content": "export const liquibase = {\n  contexts: {\n    application: {\n      liquibaseBeans: {\n        liquibase: {\n          changeSets: [\n            {\n              author: 'marceloverdijk',\n              changeLog: 'db/changelog/db.changelog-master.yaml',\n              comments: '',\n              contexts: ['context1'],\n              dateExecuted: '2022-04-21T08:50:32.817Z',\n              deploymentId: '0531032536',\n              description: 'createTable tableName=customer',\n              execType: 'EXECUTED',\n              id: '1',\n              labels: ['label1', 'label2'],\n              checksum: '8:46debf252cce6d7b25e28ddeb9fc4bf6',\n              orderExecuted: 1,\n            },\n          ],\n        },\n      },\n    },\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/instance/liquibase/index.ts",
    "content": "import { HttpResponse, http } from 'msw';\n\nimport { liquibase } from './data';\n\nconst liquibaseEndpoints = [\n  http.get('/instances/:instanceId/actuator/liquibase', () => {\n    return HttpResponse.json(liquibase);\n  }),\n];\n\nexport default liquibaseEndpoints;\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/instance/mappings/data.ts",
    "content": "export const mappings = {\n  contexts: {\n    'spring-boot-admin-sample-servlet': {\n      mappings: {\n        dispatcherServlets: {\n          dispatcherServlet: [\n            {\n              handler: 'Actuator root web endpoint',\n              predicate:\n                '{GET [/actuator], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping.WebMvcLinksHandler',\n                  name: 'links',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator'],\n                  produces: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'caches'\",\n              predicate:\n                '{GET [/actuator/caches], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator/caches'],\n                  produces: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'httptrace'\",\n              predicate:\n                '{GET [/actuator/httptrace], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator/httptrace'],\n                  produces: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'metrics'\",\n              predicate:\n                '{GET [/actuator/metrics], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator/metrics'],\n                  produces: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'auditevents'\",\n              predicate:\n                '{GET [/actuator/auditevents], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator/auditevents'],\n                  produces: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'loggers-name'\",\n              predicate:\n                '{POST [/actuator/loggers/{name}], consumes [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                  headers: [],\n                  methods: ['POST'],\n                  params: [],\n                  patterns: ['/actuator/loggers/{name}'],\n                  produces: [],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'custom'\",\n              predicate:\n                '{GET [/actuator/custom], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator/custom'],\n                  produces: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'logfile'\",\n              predicate:\n                '{GET [/actuator/logfile], produces [text/plain;charset=UTF-8]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator/logfile'],\n                  produces: [\n                    { mediaType: 'text/plain;charset=UTF-8', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'metrics-requiredMetricName'\",\n              predicate:\n                '{GET [/actuator/metrics/{requiredMetricName}], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator/metrics/{requiredMetricName}'],\n                  produces: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'loggers'\",\n              predicate:\n                '{GET [/actuator/loggers], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator/loggers'],\n                  produces: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'threaddump'\",\n              predicate:\n                '{GET [/actuator/threaddump], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator/threaddump'],\n                  produces: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'env'\",\n              predicate:\n                '{GET [/actuator/env], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator/env'],\n                  produces: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'startup'\",\n              predicate:\n                '{GET [/actuator/startup], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator/startup'],\n                  produces: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'threaddump'\",\n              predicate:\n                '{GET [/actuator/threaddump], produces [text/plain;charset=UTF-8]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator/threaddump'],\n                  produces: [\n                    { mediaType: 'text/plain;charset=UTF-8', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'info'\",\n              predicate:\n                '{GET [/actuator/info], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator/info'],\n                  produces: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'sessions-sessionId'\",\n              predicate: '{DELETE [/actuator/sessions/{sessionId}]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['DELETE'],\n                  params: [],\n                  patterns: ['/actuator/sessions/{sessionId}'],\n                  produces: [],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'caches-cache'\",\n              predicate:\n                '{DELETE [/actuator/caches/{cache}], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['DELETE'],\n                  params: [],\n                  patterns: ['/actuator/caches/{cache}'],\n                  produces: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'scheduledtasks'\",\n              predicate:\n                '{GET [/actuator/scheduledtasks], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator/scheduledtasks'],\n                  produces: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'configprops'\",\n              predicate:\n                '{GET [/actuator/configprops], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator/configprops'],\n                  produces: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'configprops-prefix'\",\n              predicate:\n                '{GET [/actuator/configprops/{prefix}], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator/configprops/{prefix}'],\n                  produces: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'env-toMatch'\",\n              predicate:\n                '{GET [/actuator/env/{toMatch}], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator/env/{toMatch}'],\n                  produces: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'startup'\",\n              predicate:\n                '{POST [/actuator/startup], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['POST'],\n                  params: [],\n                  patterns: ['/actuator/startup'],\n                  produces: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'conditions'\",\n              predicate:\n                '{GET [/actuator/conditions], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator/conditions'],\n                  produces: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'sessions-sessionId'\",\n              predicate:\n                '{GET [/actuator/sessions/{sessionId}], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator/sessions/{sessionId}'],\n                  produces: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'mappings'\",\n              predicate:\n                '{GET [/actuator/mappings], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator/mappings'],\n                  produces: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'caches-cache'\",\n              predicate:\n                '{GET [/actuator/caches/{cache}], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator/caches/{cache}'],\n                  produces: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'beans'\",\n              predicate:\n                '{GET [/actuator/beans], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator/beans'],\n                  produces: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'health'\",\n              predicate:\n                '{GET [/actuator/health], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator/health'],\n                  produces: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'health-path'\",\n              predicate:\n                '{GET [/actuator/health/**], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator/health/**'],\n                  produces: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'caches'\",\n              predicate: '{DELETE [/actuator/caches]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['DELETE'],\n                  params: [],\n                  patterns: ['/actuator/caches'],\n                  produces: [],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'heapdump'\",\n              predicate:\n                '{GET [/actuator/heapdump], produces [application/octet-stream]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator/heapdump'],\n                  produces: [\n                    { mediaType: 'application/octet-stream', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'loggers-name'\",\n              predicate:\n                '{GET [/actuator/loggers/{name}], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator/loggers/{name}'],\n                  produces: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler: \"Actuator web endpoint 'sessions'\",\n              predicate:\n                '{GET [/actuator/sessions], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler',\n                  name: 'handle',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/actuator/sessions'],\n                  produces: [\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v3+json',\n                      negated: false,\n                    },\n                    {\n                      mediaType: 'application/vnd.spring-boot.actuator.v2+json',\n                      negated: false,\n                    },\n                    { mediaType: 'application/json', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler:\n                'org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)',\n              predicate: '{ [/error]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController',\n                  name: 'error',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;)Lorg/springframework/http/ResponseEntity;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: [],\n                  params: [],\n                  patterns: ['/error'],\n                  produces: [],\n                },\n              },\n            },\n            {\n              handler:\n                'org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#errorHtml(HttpServletRequest, HttpServletResponse)',\n              predicate: '{ [/error], produces [text/html]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController',\n                  name: 'errorHtml',\n                  descriptor:\n                    '(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)Lorg/springframework/web/servlet/ModelAndView;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: [],\n                  params: [],\n                  patterns: ['/error'],\n                  produces: [{ mediaType: 'text/html', negated: false }],\n                },\n              },\n            },\n            {\n              handler:\n                'de.codecentric.boot.admin.server.web.InstancesController#instances()',\n              predicate: '{GET [/instances], produces [application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'de.codecentric.boot.admin.server.web.InstancesController',\n                  name: 'instances',\n                  descriptor: '()Lreactor/core/publisher/Flux;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/instances'],\n                  produces: [{ mediaType: 'application/json', negated: false }],\n                },\n              },\n            },\n            {\n              handler:\n                'de.codecentric.boot.admin.server.web.InstancesController#instance(String)',\n              predicate: '{GET [/instances/{id}], produces [application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'de.codecentric.boot.admin.server.web.InstancesController',\n                  name: 'instance',\n                  descriptor:\n                    '(Ljava/lang/String;)Lreactor/core/publisher/Mono;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/instances/{id}'],\n                  produces: [{ mediaType: 'application/json', negated: false }],\n                },\n              },\n            },\n            {\n              handler:\n                'de.codecentric.boot.admin.server.web.ApplicationsController#unregister(String)',\n              predicate: '{DELETE [/applications/{name}]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'de.codecentric.boot.admin.server.web.ApplicationsController',\n                  name: 'unregister',\n                  descriptor:\n                    '(Ljava/lang/String;)Lreactor/core/publisher/Mono;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['DELETE'],\n                  params: [],\n                  patterns: ['/applications/{name}'],\n                  produces: [],\n                },\n              },\n            },\n            {\n              handler:\n                'de.codecentric.boot.admin.server.notify.filter.web.NotificationFilterController#getFilters()',\n              predicate:\n                '{GET [/notifications/filters], produces [application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'de.codecentric.boot.admin.server.notify.filter.web.NotificationFilterController',\n                  name: 'getFilters',\n                  descriptor: '()Ljava/util/Collection;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/notifications/filters'],\n                  produces: [{ mediaType: 'application/json', negated: false }],\n                },\n              },\n            },\n            {\n              handler:\n                'de.codecentric.boot.admin.server.notify.filter.web.NotificationFilterController#deleteFilter(String)',\n              predicate: '{DELETE [/notifications/filters/{id}]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'de.codecentric.boot.admin.server.notify.filter.web.NotificationFilterController',\n                  name: 'deleteFilter',\n                  descriptor:\n                    '(Ljava/lang/String;)Lorg/springframework/http/ResponseEntity;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['DELETE'],\n                  params: [],\n                  patterns: ['/notifications/filters/{id}'],\n                  produces: [],\n                },\n              },\n            },\n            {\n              handler:\n                'de.codecentric.boot.admin.server.ui.web.UiController#sbaSettings()',\n              predicate:\n                '{GET [/sba-settings.js], produces [application/javascript]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'de.codecentric.boot.admin.server.ui.web.UiController',\n                  name: 'sbaSettings',\n                  descriptor: '()Ljava/lang/String;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/sba-settings'],\n                  produces: [\n                    { mediaType: 'application/javascript', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler:\n                'de.codecentric.boot.admin.server.web.ApplicationsController#applicationsStream()',\n              predicate: '{GET [/applications], produces [text/event-stream]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'de.codecentric.boot.admin.server.web.ApplicationsController',\n                  name: 'applicationsStream',\n                  descriptor: '()Lreactor/core/publisher/Flux;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/applications'],\n                  produces: [\n                    { mediaType: 'text/event-stream', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler:\n                'de.codecentric.boot.admin.server.web.InstancesController#instanceStream(String)',\n              predicate:\n                '{GET [/instances/{id}], produces [text/event-stream]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'de.codecentric.boot.admin.server.web.InstancesController',\n                  name: 'instanceStream',\n                  descriptor:\n                    '(Ljava/lang/String;)Lreactor/core/publisher/Flux;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/instances/{id}'],\n                  produces: [\n                    { mediaType: 'text/event-stream', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler:\n                'de.codecentric.boot.admin.server.web.InstancesController#unregister(String)',\n              predicate: '{DELETE [/instances/{id}]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'de.codecentric.boot.admin.server.web.InstancesController',\n                  name: 'unregister',\n                  descriptor:\n                    '(Ljava/lang/String;)Lreactor/core/publisher/Mono;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['DELETE'],\n                  params: [],\n                  patterns: ['/instances/{id}'],\n                  produces: [],\n                },\n              },\n            },\n            {\n              handler:\n                'de.codecentric.boot.admin.server.web.InstancesController#register(Registration, UriComponentsBuilder)',\n              predicate: '{POST [/instances], consumes [application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'de.codecentric.boot.admin.server.web.InstancesController',\n                  name: 'register',\n                  descriptor:\n                    '(Lde/codecentric/boot/admin/server/domain/values/Registration;Lorg/springframework/web/util/UriComponentsBuilder;)Lreactor/core/publisher/Mono;',\n                },\n                requestMappingConditions: {\n                  consumes: [{ mediaType: 'application/json', negated: false }],\n                  headers: [],\n                  methods: ['POST'],\n                  params: [],\n                  patterns: ['/instances'],\n                  produces: [],\n                },\n              },\n            },\n            {\n              handler:\n                'de.codecentric.boot.admin.server.web.servlet.InstancesProxyController#endpointProxy(String, HttpServletRequest)',\n              predicate:\n                '{[GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS] [/applications/{applicationName}/actuator/**]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'de.codecentric.boot.admin.server.web.servlet.InstancesProxyController',\n                  name: 'endpointProxy',\n                  descriptor:\n                    '(Ljava/lang/String;Ljavax/servlet/http/HttpServletRequest;)Lreactor/core/publisher/Flux;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: [\n                    'GET',\n                    'HEAD',\n                    'POST',\n                    'PUT',\n                    'PATCH',\n                    'DELETE',\n                    'OPTIONS',\n                  ],\n                  params: [],\n                  patterns: ['/applications/{applicationName}/actuator/**'],\n                  produces: [],\n                },\n              },\n            },\n            {\n              handler:\n                'de.codecentric.boot.admin.server.notify.filter.web.NotificationFilterController#addFilter(String, String, Long)',\n              predicate:\n                '{POST [/notifications/filters], produces [application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'de.codecentric.boot.admin.server.notify.filter.web.NotificationFilterController',\n                  name: 'addFilter',\n                  descriptor:\n                    '(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;)Lorg/springframework/http/ResponseEntity;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['POST'],\n                  params: [],\n                  patterns: ['/notifications/filters'],\n                  produces: [{ mediaType: 'application/json', negated: false }],\n                },\n              },\n            },\n            {\n              handler:\n                'de.codecentric.boot.admin.server.ui.web.UiController#index()',\n              predicate: '{GET [/], produces [text/html]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'de.codecentric.boot.admin.server.ui.web.UiController',\n                  name: 'index',\n                  descriptor: '()Ljava/lang/String;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/'],\n                  produces: [{ mediaType: 'text/html', negated: false }],\n                },\n              },\n            },\n            {\n              handler:\n                'de.codecentric.boot.admin.server.web.ApplicationsController#application(String)',\n              predicate:\n                '{GET [/applications/{name}], produces [application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'de.codecentric.boot.admin.server.web.ApplicationsController',\n                  name: 'application',\n                  descriptor:\n                    '(Ljava/lang/String;)Lreactor/core/publisher/Mono;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/applications/{name}'],\n                  produces: [{ mediaType: 'application/json', negated: false }],\n                },\n              },\n            },\n            {\n              handler:\n                'de.codecentric.boot.admin.server.web.servlet.InstancesProxyController#endpointProxy(String, HttpServletRequest, HttpServletResponse)',\n              predicate:\n                '{[GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS] [/instances/{instanceId}/actuator/**]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'de.codecentric.boot.admin.server.web.servlet.InstancesProxyController',\n                  name: 'endpointProxy',\n                  descriptor:\n                    '(Ljava/lang/String;Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: [\n                    'GET',\n                    'HEAD',\n                    'POST',\n                    'PUT',\n                    'PATCH',\n                    'DELETE',\n                    'OPTIONS',\n                  ],\n                  params: [],\n                  patterns: ['/instances/{instanceId}/actuator/**'],\n                  produces: [],\n                },\n              },\n            },\n            {\n              handler:\n                'de.codecentric.boot.admin.server.web.InstancesController#events()',\n              predicate:\n                '{GET [/instances/events], produces [application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'de.codecentric.boot.admin.server.web.InstancesController',\n                  name: 'events',\n                  descriptor: '()Lreactor/core/publisher/Flux;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/instances/events'],\n                  produces: [{ mediaType: 'application/json', negated: false }],\n                },\n              },\n            },\n            {\n              handler:\n                'de.codecentric.boot.admin.server.web.InstancesController#instances(String)',\n              predicate:\n                '{GET [/instances], params [name], produces [application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'de.codecentric.boot.admin.server.web.InstancesController',\n                  name: 'instances',\n                  descriptor:\n                    '(Ljava/lang/String;)Lreactor/core/publisher/Flux;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [{ name: 'name', value: null, negated: false }],\n                  patterns: ['/instances'],\n                  produces: [{ mediaType: 'application/json', negated: false }],\n                },\n              },\n            },\n            {\n              handler:\n                'de.codecentric.boot.admin.server.ui.web.UiController#login()',\n              predicate: '{GET [/login], produces [text/html]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'de.codecentric.boot.admin.server.ui.web.UiController',\n                  name: 'login',\n                  descriptor: '()Ljava/lang/String;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/login'],\n                  produces: [{ mediaType: 'text/html', negated: false }],\n                },\n              },\n            },\n            {\n              handler:\n                'de.codecentric.boot.admin.server.web.InstancesController#eventStream()',\n              predicate:\n                '{GET [/instances/events], produces [text/event-stream]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'de.codecentric.boot.admin.server.web.InstancesController',\n                  name: 'eventStream',\n                  descriptor: '()Lreactor/core/publisher/Flux;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/instances/events'],\n                  produces: [\n                    { mediaType: 'text/event-stream', negated: false },\n                  ],\n                },\n              },\n            },\n            {\n              handler:\n                'de.codecentric.boot.admin.server.web.ApplicationsController#applications()',\n              predicate: '{GET [/applications], produces [application/json]}',\n              details: {\n                handlerMethod: {\n                  className:\n                    'de.codecentric.boot.admin.server.web.ApplicationsController',\n                  name: 'applications',\n                  descriptor: '()Lreactor/core/publisher/Flux;',\n                },\n                requestMappingConditions: {\n                  consumes: [],\n                  headers: [],\n                  methods: ['GET'],\n                  params: [],\n                  patterns: ['/applications'],\n                  produces: [{ mediaType: 'application/json', negated: false }],\n                },\n              },\n            },\n            {\n              handler:\n                'ResourceHttpRequestHandler [Classpath [META-INF/resources/webjars/]]',\n              predicate: '/webjars/**',\n              details: null,\n            },\n            {\n              handler: 'ResourceHttpRequestHandler',\n              predicate: '/**',\n              details: null,\n            },\n            {\n              handler: 'ResourceHttpRequestHandler',\n              predicate: '/extensions/**',\n              details: null,\n            },\n          ],\n        },\n        servletFilters: [\n          {\n            servletNameMappings: [],\n            urlPatternMappings: ['/*'],\n            name: 'webMvcMetricsFilter',\n            className:\n              'org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter',\n          },\n          {\n            servletNameMappings: [],\n            urlPatternMappings: ['/*'],\n            name: 'requestContextFilter',\n            className:\n              'org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter',\n          },\n          {\n            servletNameMappings: [],\n            urlPatternMappings: ['/*'],\n            name: 'homepageForwardFilter',\n            className:\n              'de.codecentric.boot.admin.server.ui.web.servlet.HomepageForwardingFilter',\n          },\n          {\n            servletNameMappings: [],\n            urlPatternMappings: ['/*'],\n            name: 'sessionRepositoryFilter',\n            className:\n              'org.springframework.session.web.http.SessionRepositoryFilter',\n          },\n          {\n            servletNameMappings: [],\n            urlPatternMappings: ['/*'],\n            name: 'Tomcat WebSocket (JSR356) Filter',\n            className: 'org.apache.tomcat.websocket.server.WsFilter',\n          },\n          {\n            servletNameMappings: [],\n            urlPatternMappings: ['/*'],\n            name: 'characterEncodingFilter',\n            className:\n              'org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter',\n          },\n          {\n            servletNameMappings: [],\n            urlPatternMappings: ['/*'],\n            name: 'httpTraceFilter',\n            className:\n              'org.springframework.boot.actuate.web.trace.servlet.HttpTraceFilter',\n          },\n          {\n            servletNameMappings: [],\n            urlPatternMappings: ['/*'],\n            name: 'springSecurityFilterChain',\n            className:\n              'org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean$1',\n          },\n          {\n            servletNameMappings: [],\n            urlPatternMappings: ['/*'],\n            name: 'formContentFilter',\n            className:\n              'org.springframework.boot.web.servlet.filter.OrderedFormContentFilter',\n          },\n        ],\n        servlets: [\n          {\n            mappings: ['/'],\n            name: 'dispatcherServlet',\n            className: 'org.springframework.web.servlet.DispatcherServlet',\n          },\n        ],\n      },\n      parentId: null,\n    },\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/instance/mappings/index.ts",
    "content": "import { HttpResponse, http } from 'msw';\n\nimport { mappings } from './data';\n\nconst mappingsEndpoint = [\n  http.get('/instances/:instanceId/actuator/mappings', () => {\n    return HttpResponse.json(mappings);\n  }),\n];\n\nexport default mappingsEndpoint;\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/instance/metrics/data.ts",
    "content": "export const memoryMaxResponse = {\n  name: 'jvm.memory.max',\n  description:\n    'The maximum amount of memory in bytes that can be used for memory management',\n  baseUnit: 'bytes',\n  measurements: [\n    {\n      statistic: 'VALUE',\n      value: 8589934590,\n    },\n  ],\n  availableTags: [\n    {\n      tag: 'id',\n      values: ['G1 Old Gen', 'G1 Survivor Space', 'G1 Eden Space'],\n    },\n  ],\n};\n\nexport const memoryUsedResponse = {\n  name: 'jvm.memory.used',\n  description: 'The amount of used memory',\n  baseUnit: 'bytes',\n  measurements: [\n    {\n      statistic: 'VALUE',\n      value: 115390832,\n    },\n  ],\n  availableTags: [\n    {\n      tag: 'id',\n      values: ['G1 Survivor Space', 'G1 Old Gen', 'G1 Eden Space', 'Metaspace'],\n    },\n  ],\n};\n\nexport const memoryCommittedResponse = {\n  name: 'jvm.memory.committed',\n  description:\n    'The amount of memory in bytes that is committed for the Java virtual machine to use',\n  baseUnit: 'bytes',\n  measurements: [\n    {\n      statistic: 'VALUE',\n      value: 197132288,\n    },\n  ],\n  availableTags: [\n    {\n      tag: 'id',\n      values: ['G1 Survivor Space', 'G1 Old Gen', 'G1 Eden Space'],\n    },\n  ],\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/instance/metrics/index.ts",
    "content": "import { HttpResponse, http } from 'msw';\n\nimport {\n  memoryCommittedResponse,\n  memoryMaxResponse,\n  memoryUsedResponse,\n} from '@/mocks/instance/metrics/data';\n\nconst metricsEntpoints = [\n  http.get('/instances/:instanceId/actuator/metrics/jvm.memory.max', () => {\n    return HttpResponse.json(memoryMaxResponse);\n  }),\n  http.get('/instances/:instanceId/actuator/metrics/jvm.memory.used', () => {\n    return HttpResponse.json(memoryUsedResponse);\n  }),\n  http.get(\n    '/instances/:instanceId/actuator/metrics/jvm.memory.committed',\n    () => {\n      return HttpResponse.json(memoryCommittedResponse);\n    },\n  ),\n  http.get('/instances/:instanceId/actuator/metrics', () => {\n    return HttpResponse.json({\n      names: ['cache.gets', 'jdbc.connections.active'],\n    });\n  }),\n  http.get('/instances/:instanceId/actuator/metrics/cache.gets', () => {\n    return HttpResponse.json({\n      name: 'cache.gets',\n      description: 'The number of cache gets',\n      baseUnit: 'none',\n      measurements: [\n        {\n          statistic: 'COUNT',\n          value: 150,\n        },\n        {\n          statistic: 'TOTAL_TIME',\n          value: 120.5,\n        },\n        {\n          statistic: 'MAX',\n          value: 5.2,\n        },\n      ],\n      availableTags: [\n        {\n          tag: 'name',\n          values: ['myCache'],\n        },\n        {\n          tag: 'cache',\n          values: ['myCache'],\n        },\n        {\n          tag: 'result',\n          values: ['hit', 'miss'],\n        },\n      ],\n    });\n  }),\n  http.get(\n    '/instances/:instanceId/actuator/metrics/jdbc.connections.active',\n    () => {\n      return HttpResponse.json({\n        name: 'jdbc.connections.active',\n        description: 'The number of active JDBC connections',\n        baseUnit: 'connections',\n        measurements: [\n          {\n            statistic: 'VALUE',\n            value: 5,\n          },\n        ],\n        availableTags: [\n          {\n            tag: 'name',\n            values: ['HikariPool-1'],\n          },\n          {\n            tag: 'pool',\n            values: ['HikariPool-1'],\n          },\n          {\n            tag: 'min',\n            values: 1,\n          },\n        ],\n      });\n    },\n  ),\n  http.get('/instances/:instanceId/actuator/metrics/jdbc.connections.min', () =>\n    HttpResponse.json({\n      name: 'jdbc.connections.min',\n      description: 'The minimum number of idle JDBC connections in the pool',\n      baseUnit: 'connections',\n      measurements: [\n        {\n          statistic: 'VALUE',\n          value: 10,\n        },\n      ],\n      availableTags: [\n        {\n          tag: 'pool',\n          values: ['HikariPool-1'],\n        },\n      ],\n    }),\n  ),\n  http.get('/instances/:instanceId/actuator/metrics/jdbc.connections.max', () =>\n    HttpResponse.json({\n      name: 'jdbc.connections.max',\n      description: 'The minimum number of idle JDBC connections in the pool',\n      baseUnit: 'connections',\n      measurements: [\n        {\n          statistic: 'VALUE',\n          value: 10,\n        },\n      ],\n      availableTags: [\n        {\n          tag: 'pool',\n          values: ['HikariPool-1'],\n        },\n      ],\n    }),\n  ),\n];\n\nexport default metricsEntpoints;\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/instance/scheduledtasks/data.ts",
    "content": "export const scheduledtasksResponse = {\n  cron: [\n    {\n      runnable: {\n        target: 'com.example.Processor.processOrders',\n      },\n      expression: '0 0 0/3 1/1 * ?',\n      nextExecution: {\n        time: '2025-05-22T20:59:59.999087966Z',\n      },\n    },\n  ],\n  fixedDelay: [\n    {\n      runnable: {\n        target: 'com.example.Processor.purge',\n      },\n      initialDelay: 0,\n      interval: 5000,\n      nextExecution: {\n        time: '2025-05-22T20:03:39.767908889Z',\n      },\n      lastExecution: {\n        time: '2025-05-22T20:03:34.761508282Z',\n        status: 'SUCCESS',\n      },\n    },\n  ],\n  fixedRate: [\n    {\n      runnable: {\n        target: 'com.example.Processor.retrieveIssues',\n      },\n      initialDelay: 10000,\n      interval: 3000,\n      nextExecution: {\n        time: '2025-05-22T20:03:44.755151650Z',\n      },\n    },\n  ],\n  custom: [\n    {\n      runnable: {\n        target: 'com.example.Processor$CustomTriggeredRunnable@6e5b7446',\n      },\n      trigger: 'com.example.Processor$CustomTrigger@513ad29e',\n      lastExecution: {\n        exception: {\n          message: 'Failed while running custom task',\n          type: 'java.lang.IllegalStateException',\n        },\n        time: '2025-05-22T20:03:34.807437342Z',\n        status: 'ERROR',\n      },\n    },\n  ],\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/instance/scheduledtasks/index.ts",
    "content": "import { HttpResponse, http } from 'msw';\n\nimport { scheduledtasksResponse } from '@/mocks/instance/sessions/data';\n\nconst endpoints = [\n  http.get('/instances/:instanceId/actuator/sessions', () => {\n    return HttpResponse.json(scheduledtasksResponse);\n  }),\n];\n\nexport default endpoints;\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/instance/sessions/data.ts",
    "content": "const now = new Date();\nconst today = [\n  now.getFullYear(),\n  String(now.getMonth() + 1).padStart(2, '0'),\n  String(now.getDate()).padStart(2, '0'),\n].join('-');\n\nexport const scheduledtasksResponse = {\n  sessions: [\n    {\n      id: 'D43F6A32C1E34A7B',\n      creationTime: today + 'T08:23:45.123Z',\n      lastAccessedTime: today + 'T09:12:34.567Z',\n      maxInactiveInterval: 1800,\n      expired: false,\n      principal: 'admin',\n      attributes: {\n        SPRING_SECURITY_CONTEXT: {\n          authentication: {\n            name: 'admin',\n            authorities: ['ROLE_ADMIN', 'ROLE_USER'],\n            authenticated: true,\n          },\n        },\n        sessionTrackingId: 'web-session-01',\n        userAgent:\n          'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',\n        remoteAddress: '192.168.1.105',\n      },\n    },\n    {\n      id: 'E54G7B43D2F45B8C',\n      creationTime: today + 'T08:26:45.234Z',\n      lastAccessedTime: today + 'T09:21:45.678Z',\n      maxInactiveInterval: 1800,\n      expired: false,\n      principal: 'user123',\n      attributes: {\n        SPRING_SECURITY_CONTEXT: {\n          authentication: {\n            name: 'user123',\n            authorities: ['ROLE_USER'],\n            authenticated: true,\n          },\n        },\n        sessionTrackingId: 'web-session-02',\n        userAgent: 'PostmanRuntime/7.29.2',\n        remoteAddress: '192.168.1.110',\n      },\n    },\n    {\n      id: 'F65H8C54E3G56D9E',\n      creationTime: today + 'T08:32:11.678Z',\n      lastAccessedTime: today + 'T09:15:23.456Z',\n      maxInactiveInterval: 1800,\n      expired: false,\n      principal: 'user456',\n      attributes: {\n        SPRING_SECURITY_CONTEXT: {\n          authentication: {\n            name: 'user456',\n            authorities: ['ROLE_USER'],\n            authenticated: true,\n          },\n        },\n        sessionTrackingId: 'web-session-03',\n        userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)',\n        remoteAddress: '192.168.1.120',\n      },\n    },\n    {\n      id: 'G76I9D65F4H67E0F',\n      creationTime: today + 'T09:05:22.345Z',\n      lastAccessedTime: today + 'T09:19:23.456Z',\n      maxInactiveInterval: 1800,\n      expired: false,\n      principal: 'user789',\n      attributes: {\n        SPRING_SECURITY_CONTEXT: {\n          authentication: {\n            name: 'user789',\n            authorities: ['ROLE_USER'],\n            authenticated: true,\n          },\n        },\n        sessionTrackingId: 'mobile-session-01',\n        userAgent: 'Mozilla/5.0 (Android 12; Mobile)',\n        remoteAddress: '192.168.1.140',\n      },\n    },\n    {\n      id: 'H87J0E76G5I78F1G',\n      creationTime: today + 'T08:45:33.456Z',\n      lastAccessedTime: today + 'T09:10:12.345Z',\n      maxInactiveInterval: 1800,\n      expired: false,\n      principal: 'manager1',\n      attributes: {\n        SPRING_SECURITY_CONTEXT: {\n          authentication: {\n            name: 'manager1',\n            authorities: ['ROLE_MANAGER', 'ROLE_USER'],\n            authenticated: true,\n          },\n        },\n        sessionTrackingId: 'web-session-04',\n        userAgent:\n          'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/96.0.4664.110',\n        remoteAddress: '192.168.1.160',\n      },\n    },\n    {\n      id: 'I98K1F87H6J89G2H',\n      creationTime: today + 'T08:50:44.567Z',\n      lastAccessedTime: today + 'T08:55:22.678Z',\n      maxInactiveInterval: 1800,\n      expired: true,\n      principal: 'user321',\n      attributes: {\n        SPRING_SECURITY_CONTEXT: {\n          authentication: {\n            name: 'user321',\n            authorities: ['ROLE_USER'],\n            authenticated: true,\n          },\n        },\n        sessionTrackingId: 'web-session-05',\n        userAgent: 'Mozilla/5.0 (iPad; CPU OS 15_0 like Mac OS X)',\n        remoteAddress: '192.168.1.170',\n      },\n    },\n    {\n      id: 'J09L2G98I7K90H3I',\n      creationTime: today + 'T09:00:55.678Z',\n      lastAccessedTime: today + 'T09:22:33.789Z',\n      maxInactiveInterval: 1800,\n      expired: false,\n      principal: 'analyst1',\n      attributes: {\n        SPRING_SECURITY_CONTEXT: {\n          authentication: {\n            name: 'analyst1',\n            authorities: ['ROLE_ANALYST', 'ROLE_USER'],\n            authenticated: true,\n          },\n        },\n        sessionTrackingId: 'web-session-06',\n        userAgent: 'Mozilla/5.0 (X11; Linux x86_64) Firefox/95.0',\n        remoteAddress: '192.168.1.180',\n      },\n    },\n    {\n      id: 'K10M3H09J8L01I4J',\n      creationTime: today + 'T09:05:06.789Z',\n      lastAccessedTime: today + 'T09:20:44.890Z',\n      maxInactiveInterval: 1800,\n      expired: false,\n      principal: 'support1',\n      attributes: {\n        SPRING_SECURITY_CONTEXT: {\n          authentication: {\n            name: 'support1',\n            authorities: ['ROLE_SUPPORT', 'ROLE_USER'],\n            authenticated: true,\n          },\n        },\n        sessionTrackingId: 'web-session-07',\n        userAgent:\n          'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Edge/96.0.1054.62',\n        remoteAddress: '192.168.1.190',\n      },\n    },\n    {\n      id: 'L21N4I10K9M12J5K',\n      creationTime: today + 'T09:10:17.890Z',\n      lastAccessedTime: today + 'T09:10:17.890Z',\n      maxInactiveInterval: 1800,\n      expired: false,\n      principal: 'guest',\n      attributes: {\n        SPRING_SECURITY_CONTEXT: {\n          authentication: {\n            name: 'guest',\n            authorities: ['ROLE_GUEST'],\n            authenticated: true,\n          },\n        },\n        sessionTrackingId: 'web-session-08',\n        userAgent:\n          'Mozilla/5.0 (iPhone; CPU iPhone OS 15_1 like Mac OS X) Safari/605.1.15',\n        remoteAddress: '192.168.1.200',\n      },\n    },\n    {\n      id: 'M32O5J21L0N23K6L',\n      creationTime: today + 'T09:15:28.901Z',\n      lastAccessedTime: today + 'T09:18:55.012Z',\n      maxInactiveInterval: 1800,\n      expired: false,\n      principal: 'developer1',\n      attributes: {\n        SPRING_SECURITY_CONTEXT: {\n          authentication: {\n            name: 'developer1',\n            authorities: ['ROLE_DEVELOPER', 'ROLE_USER'],\n            authenticated: true,\n          },\n        },\n        sessionTrackingId: 'web-session-09',\n        userAgent:\n          'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Chrome/96.0.4664.110',\n        remoteAddress: '192.168.1.210',\n      },\n    },\n  ],\n  sessionCount: 10,\n  activeSessionCount: 9,\n  expiredSessionCount: 1,\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/instance/sessions/index.ts",
    "content": "import { HttpResponse, http } from 'msw';\n\nimport { scheduledtasksResponse } from '@/mocks/instance/scheduledtasks/data';\n\nconst endpoints = [\n  http.get('/instances/:instanceId/actuator/scheduledtasks', () => {\n    return HttpResponse.json(scheduledtasksResponse);\n  }),\n];\n\nexport default endpoints;\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/mocks/server.ts",
    "content": "import { setupServer } from 'msw/node';\n\nimport auditEventsEndpoint from '@/mocks/instance/auditevents';\nimport dependenciesEndpoints from '@/mocks/instance/dependencies';\nimport flywayEndpoints from '@/mocks/instance/flyway';\nimport healthEndpoint from '@/mocks/instance/health';\nimport infoEndpoint from '@/mocks/instance/info';\nimport jolokiaEndpoint from '@/mocks/instance/jolokia';\nimport liquibaseEndpoints from '@/mocks/instance/liquibase';\nimport mappingsEndpoint from '@/mocks/instance/mappings';\nimport metricsEntpoints from '@/mocks/instance/metrics';\n\nconst handler = [\n  ...infoEndpoint,\n  ...healthEndpoint,\n  ...mappingsEndpoint,\n  ...metricsEntpoints,\n  ...liquibaseEndpoints,\n  ...flywayEndpoints,\n  ...auditEventsEndpoint,\n  ...jolokiaEndpoint,\n  ...dependenciesEndpoints,\n];\n\nexport const server = setupServer(...handler);\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/notificationcenter.d.ts",
    "content": "declare module '@stekoe/vue-toast-notificationcenter';\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/notifications.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { groupBy, values } from 'lodash-es';\nimport { Subject, bufferTime, filter } from 'rxjs';\n\nimport { HealthStatus } from './HealthStatus';\nimport sbaConfig from './sba-config';\n\nlet granted = false;\n\nconst requestPermissions = async () => {\n  if ('Notification' in window) {\n    granted = window.Notification.permission === 'granted';\n    if (!granted && window.Notification.permission !== 'denied') {\n      const permission = await window.Notification.requestPermission();\n\n      granted = permission === 'granted';\n    }\n  }\n};\n\nconst notifyForSingleChange = (application, oldApplication) => {\n  return createNotification(\n    `${application.name} is now ${application.status}`,\n    {\n      tag: `${application.name}-${application.status}`,\n      lang: 'en',\n      body: `was ${oldApplication.status}.`,\n      icon:\n        application.status === HealthStatus.UP\n          ? sbaConfig.uiSettings.favicon\n          : sbaConfig.uiSettings.faviconDanger,\n      renotify: true,\n      timeout: 5000,\n    },\n  );\n};\n\nconst notifyForBulkChange = ({ count, status, oldStatus }) => {\n  return createNotification(`${count} applications are now ${status}`, {\n    lang: 'en',\n    body: `was ${oldStatus}.`,\n    icon:\n      status === HealthStatus.UP\n        ? sbaConfig.uiSettings.favicon\n        : sbaConfig.uiSettings.faviconDanger,\n    timeout: 5000,\n  });\n};\n\nconst createNotification = (title, options) => {\n  if (granted) {\n    const notification = new window.Notification(title, options);\n    if (options.url !== null) {\n      notification.onclick = () => {\n        window.focus();\n        window.open(options.url, '_self');\n      };\n    }\n    if (options.timeout > 0) {\n      notification.onshow = () =>\n        setTimeout(() => notification.close(), options.timeout);\n    }\n  }\n};\n\nexport default {\n  install: ({ applicationStore }) => {\n    requestPermissions();\n\n    const queue = new Subject();\n    queue\n      .pipe(\n        bufferTime(1000),\n        filter((n) => n.length > 0),\n      )\n      .subscribe({\n        next: (events) => {\n          const groupedByChange = groupBy(\n            events,\n            (event) =>\n              `${event.oldApplication.status}-${event.application.status}`,\n          );\n          for (const eventsPerChange of values(groupedByChange)) {\n            if (eventsPerChange.length <= 5) {\n              eventsPerChange.forEach((event) => {\n                notifyForSingleChange(event.application, event.oldApplication);\n              });\n            } else {\n              notifyForBulkChange({\n                status: eventsPerChange[0].application.status,\n                oldStatus: eventsPerChange[0].oldApplication.status,\n                count: events.length,\n              });\n            }\n          }\n        },\n      });\n\n    applicationStore.addEventListener(\n      'updated',\n      (application, oldApplication) => {\n        if (application.status !== oldApplication.status) {\n          queue.next({ application, oldApplication });\n        }\n      },\n    );\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/plugins/modal/ConfirmButtons.vue",
    "content": "<template>\n  <div class=\"flex gap-1 w-full justify-end\">\n    <sba-button primary @click=\"() => close(true)\" v-text=\"labelOk\" />\n    <sba-button\n      class=\"button\"\n      @click=\"() => close(false)\"\n      v-text=\"labelCancel\"\n    />\n  </div>\n</template>\n\n<script>\nimport SbaButton from '@/components/sba-button';\n\nimport eventBus from '@/services/bus';\n\nexport default {\n  components: { SbaButton },\n  props: {\n    labelCancel: {\n      type: String,\n      required: true,\n    },\n    labelOk: {\n      type: String,\n      required: true,\n    },\n  },\n  methods: {\n    close(result) {\n      eventBus.emit('sba-modal-close', result);\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/plugins/modal/Modal.vue",
    "content": "<template>\n  <TransitionRoot\n    ref=\"root\"\n    :show=\"isOpen\"\n    appear\n    as=\"template\"\n    @close=\"closeModal\"\n  >\n    <Dialog as=\"div\" class=\"relative z-10 modal\">\n      <TransitionChild\n        as=\"template\"\n        enter=\"duration-300 ease-out\"\n        enter-from=\"opacity-0\"\n        enter-to=\"opacity-100\"\n        leave=\"duration-200 ease-in\"\n        leave-from=\"opacity-100\"\n        leave-to=\"opacity-0\"\n      >\n        <div class=\"fixed inset-0 bg-black bg-opacity-25\" />\n      </TransitionChild>\n\n      <div class=\"fixed inset-0 overflow-y-auto\">\n        <div\n          class=\"flex min-h-full items-center justify-center p-4 text-center\"\n        >\n          <TransitionChild\n            as=\"template\"\n            enter=\"duration-300 ease-out\"\n            enter-from=\"opacity-0 scale-95\"\n            enter-to=\"opacity-100 scale-100\"\n            leave=\"duration-200 ease-in\"\n            leave-from=\"opacity-100 scale-100\"\n            leave-to=\"opacity-0 scale-95\"\n          >\n            <DialogPanel\n              class=\"w-full max-w-md transform overflow-hidden rounded bg-white p-6 text-left align-middle shadow-xl transition-all\"\n            >\n              <DialogTitle\n                as=\"h3\"\n                class=\"text-lg font-medium leading-6 text-gray-900 flex justify-between\"\n              >\n                {{ title }}\n              </DialogTitle>\n              <div class=\"mt-2 mb-2\">\n                <slot name=\"body\" />\n              </div>\n              <div class=\"mt-4\">\n                <slot name=\"buttons\" />\n              </div>\n            </DialogPanel>\n          </TransitionChild>\n        </div>\n      </div>\n    </Dialog>\n  </TransitionRoot>\n</template>\n\n<script>\nimport {\n  Dialog,\n  DialogPanel,\n  DialogTitle,\n  TransitionChild,\n  TransitionRoot,\n} from '@headlessui/vue';\nimport { defineComponent, ref } from 'vue';\n\nimport eventBus from '@/services/bus';\n\nexport default defineComponent({\n  components: {\n    Dialog,\n    DialogPanel,\n    DialogTitle,\n    TransitionRoot,\n    TransitionChild,\n  },\n  props: {\n    title: {\n      type: String,\n      required: true,\n    },\n  },\n  emits: ['close'],\n  setup() {\n    return {\n      isOpen: ref(true),\n    };\n  },\n  mounted() {\n    eventBus.on('sba-modal-close', this.closeModal);\n  },\n  beforeUnmount() {\n    eventBus.off('sba-modal-close', this.closeModal);\n  },\n  methods: {\n    closeModal() {\n      this.isOpen = false;\n    },\n  },\n});\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/plugins/modal/api.ts",
    "content": "import { h } from 'vue';\n\nimport ConfirmButtons from './ConfirmButtons';\nimport Modal from './Modal';\nimport { createComponent } from './helpers';\n\nimport eventBus from '@/services/bus';\n\nexport const useModal = (globalProps = {}) => {\n  const t =\n    globalProps.i18n?.global.t ||\n    function (value) {\n      return value;\n    };\n\n  return {\n    open(options, slots = {}) {\n      let title = null;\n      if (typeof options === 'string') title = options;\n\n      const defaultProps = {\n        title,\n      };\n\n      const propsData = Object.assign({}, defaultProps, globalProps, options);\n      return createComponent(Modal, propsData, document.body, slots);\n    },\n    async confirm(title, body) {\n      const bodyFn = () =>\n        h(\n          'span',\n          {\n            innerHTML: body,\n          },\n          [],\n        );\n\n      // eslint-disable-next-line @typescript-eslint/no-unused-vars\n      const { vNode, destroy } = this.open(\n        { title },\n        {\n          buttons: () =>\n            h(ConfirmButtons, {\n              labelOk: t('term.ok'),\n              labelCancel: t('term.cancel'),\n            }),\n          body: bodyFn,\n        },\n      );\n\n      return new Promise((resolve) => {\n        const handler = (result) => {\n          eventBus.off('sba-modal-close', handler);\n          destroy();\n          resolve(result);\n        };\n        eventBus.on('sba-modal-close', handler);\n      });\n    },\n  };\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/plugins/modal/helpers.ts",
    "content": "import { h, render } from 'vue';\n\nexport function removeElement(el) {\n  if (typeof el.remove !== 'undefined') {\n    el.remove();\n  } else {\n    el.parentNode?.removeChild(el);\n  }\n}\n\nexport function createComponent(component, props, parentContainer, slots = {}) {\n  let vNode = h(component, props, slots);\n\n  let container = parentContainer.querySelector('.sba-modal--wrapper');\n  container = container || document.createElement('div');\n  container.classList.add('sba-modal--wrapper');\n  parentContainer.appendChild(container);\n  render(vNode, container);\n\n  const destroy = () => {\n    if (container) render(null, container);\n    container = null;\n    vNode = null;\n  };\n\n  return {\n    vNode,\n    destroy,\n  };\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/plugins/modal/index.ts",
    "content": "import { useModal } from './api';\n\nconst SbaModalPlugin = {\n  install: (app, options = {}) => {\n    const instance = useModal(options);\n    app.config.globalProperties.$sbaModal = instance;\n    app.provide('$sbaModal', instance);\n  },\n};\n\nexport default SbaModalPlugin;\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/public/variables.css",
    "content": ":root {\n  --main-50: /*[(${@cssColorUtils.hexToRgb(uiSettings.theme.palette.getShade50())})]*/ #e8fbef;\n  --main-100: /*[(${@cssColorUtils.hexToRgb(uiSettings.theme.palette.getShade100())})]*/ #d0f7df;\n  --main-200: /*[(${@cssColorUtils.hexToRgb(uiSettings.theme.palette.getShade200())})]*/ #a1efbd;\n  --main-300: /*[(${@cssColorUtils.hexToRgb(uiSettings.theme.palette.getShade300())})]*/ #71e69c;\n  --main-400: /*[(${@cssColorUtils.hexToRgb(uiSettings.theme.palette.getShade400())})]*/ #41de7b;\n  --main-500: /*[(${@cssColorUtils.hexToRgb(uiSettings.theme.palette.getShade500())})]*/ #22c55e;\n  --main-600: /*[(${@cssColorUtils.hexToRgb(uiSettings.theme.palette.getShade600())})]*/ #1a9547;\n  --main-700: /*[(${@cssColorUtils.hexToRgb(uiSettings.theme.palette.getShade700())})]*/ #116530;\n  --main-800: /*[(${@cssColorUtils.hexToRgb(uiSettings.theme.palette.getShade800())})]*/ #09351a;\n  --main-900: /*[(${@cssColorUtils.hexToRgb(uiSettings.theme.palette.getShade900())})]*/ #010603;\n\n  --bg-color-start: /*[(${uiSettings.theme.palette.getShade300()})]*/ #71e69c;\n  --bg-color-stop: /*[(${uiSettings.theme.palette.getShade700()})]*/ #09351a;\n}\n\n.bg-color-start {\n  transition: 0.4s ease;\n  stop-color: var(--bg-color-start);\n}\n\n.bg-color-stop {\n  transition: 0.4s ease;\n  stop-color: var(--bg-color-stop);\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/sba-config.ts",
    "content": "/*\n * Copyright 2014-2024 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { merge } from 'lodash-es';\n\nconst brand =\n  '<img src=\"assets/img/icon-spring-boot-admin.svg\">Spring Boot Admin';\n\nconst DEFAULT_CONFIG: SBASettings = {\n  uiSettings: {\n    title: 'Spring Boot Admin',\n    brand,\n    theme: {\n      backgroundEnabled: true,\n      color: '#42d3a5',\n    },\n    rememberMeEnabled: true,\n    enableToasts: false,\n    externalViews: [] as ExternalView[],\n    favicon: 'assets/img/favicon.png',\n    faviconDanger: 'assets/img/favicon-danger.png',\n    notificationFilterEnabled: false,\n    routes: [],\n    availableLanguages: [],\n    viewSettings: [],\n    pollTimer: {\n      cache: 2500,\n      datasource: 2500,\n      gc: 2500,\n      process: 2500,\n      memory: 2500,\n      threads: 2500,\n      logfile: 1000,\n    },\n    hideInstanceUrl: false,\n  },\n  user: null,\n  extensions: {},\n  csrf: {\n    parameterName: '_csrf',\n    headerName: 'X-XSRF-TOKEN',\n  },\n  use: function (ext) {\n    this.extensions.push(ext);\n  },\n};\n\nconst mergedConfig = merge(DEFAULT_CONFIG, window.SBA) as SBASettings;\n\nexport const getCurrentUser = () => {\n  return mergedConfig.user;\n};\n\nexport default mergedConfig;\n\nexport const useSbaConfig = () => {\n  return mergedConfig;\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/sba-settings.js",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n//This is a Thymleaf template whill will be rendered by the backend\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nvar SBA = {\n  uiSettings: /*[[${uiSettings}]]*/ {},\n  user: /*[[${user}]]*/ null,\n  extensions: {\n    js: /*[[${jsExtensions}]]*/ [],\n    css: /*[[${cssExtensions}]]*/ [],\n  },\n  csrf: {\n    parameterName: /*[[${_csrf} ? ${_csrf.parameterName} : 'null']]*/ null,\n    headerName: /*[[${_csrf} ? ${_csrf.headerName} : 'null']]*/ null,\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/services/application.spec.ts",
    "content": "/*\n * Copyright 2014-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { describe, expect, it } from 'vitest';\n\nimport { convertBody, hasMatchingContentType } from './application';\n\ndescribe('hasMatchingContentType', () => {\n  it('should match content-type', () => {\n    const matches = hasMatchingContentType('application/json;charset=UTF-8', [\n      'application/vnd.spring-boot.actuator.v1+json',\n      'application/json',\n    ]);\n    expect(matches).toBe(true);\n  });\n\n  it('should not match content-types', () => {\n    const matches = hasMatchingContentType('application/html;charset=UTF-8', [\n      'application/vnd.spring-boot.actuator.v1+json',\n      'application/json',\n    ]);\n    expect(matches).toBe(false);\n  });\n\n  it('should not match undefined', () => {\n    const matches = hasMatchingContentType(undefined, [\n      'application/vnd.spring-boot.actuator.v1+json',\n      'application/json',\n    ]);\n    expect(matches).toBe(false);\n  });\n});\n\ndescribe('convertBody', () => {\n  it('should not convert empty responses', () => {\n    expect(convertBody([])).toEqual([]);\n  });\n\n  it('should not convert empty body', () => {\n    expect(convertBody([{}])).toEqual([{}]);\n  });\n\n  it('should not convert non-json body', () => {\n    expect(\n      convertBody([{ body: 'foobar', contentType: 'text/plain' }]),\n    ).toEqual([{ body: 'foobar', contentType: 'text/plain' }]);\n  });\n\n  it('should convert json body', () => {\n    expect(\n      convertBody([\n        { body: '{\"foo\": \"bar\"}', contentType: 'application/json' },\n      ]),\n    ).toEqual([{ body: { foo: 'bar' }, contentType: 'application/json' }]);\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/services/application.ts",
    "content": "/*\n * Copyright 2014-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { AxiosInstance } from 'axios';\nimport { sortBy } from 'lodash-es';\nimport { Observable, concat, from, ignoreElements } from 'rxjs';\n\nimport axios, { redirectOn401 } from '../utils/axios';\nimport waitForPolyfill from '../utils/eventsource-polyfill';\nimport uri from '../utils/uri';\nimport Instance, { DOWN_STATES, UNKNOWN_STATES, UP_STATES } from './instance';\n\nimport { actuatorMimeTypes } from '@/services/spring-mime-types';\n\nexport const hasMatchingContentType = (\n  contentType: string,\n  compatibleContentTypes: Array<string>,\n) =>\n  Boolean(contentType) &&\n  compatibleContentTypes.includes(contentType.replace(/;.*$/, ''));\n\nexport const convertBody = (responses: any[]) =>\n  responses.map((res) => {\n    if (\n      res.body &&\n      hasMatchingContentType(res.contentType, actuatorMimeTypes)\n    ) {\n      return {\n        ...res,\n        body: JSON.parse(res.body),\n      };\n    }\n    return res;\n  });\n\nexport const getStatusInfo = (applications: Application[]) => {\n  const instances = applications.flatMap(\n    (application) => application.instances,\n  );\n\n  const upCount = instances.filter((instance) =>\n    UP_STATES.includes(instance.statusInfo.status),\n  ).length;\n\n  const downCount = instances.filter((instance) =>\n    DOWN_STATES.includes(instance.statusInfo.status),\n  ).length;\n\n  const unknownCount = instances.filter((instance) =>\n    UNKNOWN_STATES.includes(instance.statusInfo.status),\n  ).length;\n\n  return {\n    upCount,\n    downCount,\n    unknownCount,\n    allUp: upCount === instances.length,\n    allDown: downCount === instances.length,\n    allUnknown: unknownCount === instances.length,\n    someUnknown: unknownCount > 0 && unknownCount < instances.length,\n    someDown: downCount > 0 && downCount < instances.length,\n  };\n};\n\nclass Application {\n  public readonly name: string;\n  public readonly instances: Instance[];\n  public readonly buildVersion? = {} as { value: string };\n  public readonly status: string;\n  public readonly statusTimestamp: string;\n\n  private readonly axios: AxiosInstance;\n\n  constructor({\n    name,\n    instances,\n    ...application\n  }: {\n    name: string;\n    instances: any[];\n    [key: string]: any;\n  }) {\n    Object.assign(this, application);\n    this.name = name;\n    this.axios = axios.create({\n      baseURL: uri`applications/${this.name}`,\n      headers: {\n        'X-SBA-REQUEST': true,\n      },\n    });\n    this.axios.interceptors.response.use(\n      (response) => response,\n      redirectOn401(),\n    );\n    this.instances = sortBy(\n      instances.map(\n        (i) => new Instance(i),\n        [(instance) => instance.registration.healthUrl],\n      ),\n    );\n  }\n\n  get isUnregisterable() {\n    return this.instances.some((i) => i.isUnregisterable);\n  }\n\n  get hasShutdownEndpoint() {\n    return this.hasEndpoint('shutdown');\n  }\n\n  get hasRestartEndpoint() {\n    return this.hasEndpoint('restart');\n  }\n\n  static async list() {\n    return axios.get('applications', {\n      headers: { Accept: 'application/json', 'X-SBA-REQUEST': true },\n      transformResponse: Application._transformResponse,\n    });\n  }\n\n  static getStream(): Observable<ApplicationStream | unknown> {\n    return concat(\n      from(waitForPolyfill()).pipe(ignoreElements()),\n      Observable.create((observer) => {\n        const eventSource = new EventSource('applications');\n        eventSource.onmessage = (message) =>\n          observer.next({\n            ...message,\n            data: Application._transformResponse(message.data),\n          } as ApplicationStream);\n\n        eventSource.onerror = (err) => observer.error(err);\n        return () => eventSource.close();\n      }),\n    );\n  }\n\n  static _transformResponse(data: string) {\n    if (!data) {\n      return data;\n    }\n    const json = JSON.parse(data);\n    if (json instanceof Array) {\n      const applications = json.map((j) => new Application(j));\n      return sortBy(applications, [(item) => item.name]);\n    }\n    return new Application(json);\n  }\n\n  filterInstances(predicate: (instance: Instance) => boolean) {\n    return new Application({\n      ...this,\n      instances: this.instances.filter(predicate),\n    });\n  }\n\n  hasEndpoint(endpointId: string): boolean {\n    return this.instances.some((i) => i.hasEndpoint(endpointId));\n  }\n\n  findInstance(instanceId: string): Instance | undefined {\n    return this.instances.find((instance) => instance.id === instanceId);\n  }\n\n  async unregister() {\n    return this.axios.delete('', {\n      headers: { Accept: 'application/json' },\n    });\n  }\n\n  async fetchLoggers() {\n    const responses = convertBody(\n      (\n        await this.axios.get(uri`actuator/loggers`, {\n          headers: { Accept: actuatorMimeTypes.join(',') },\n        })\n      ).data,\n    );\n    return { responses };\n  }\n\n  async configureLogger(name: string, level: string | null) {\n    const responses = (\n      await this.axios.post(\n        uri`actuator/loggers/${name}`,\n        level === null ? {} : { configuredLevel: level },\n        { headers: { 'Content-Type': 'application/json' } },\n      )\n    ).data;\n    return { responses };\n  }\n\n  async setEnv(name: string, value: string) {\n    return this.axios.post(\n      uri`actuator/env`,\n      { name, value },\n      {\n        headers: { 'Content-Type': 'application/json' },\n      },\n    );\n  }\n\n  async resetEnv() {\n    return this.axios.delete(uri`actuator/env`);\n  }\n\n  async refreshContext() {\n    return this.axios.post(uri`actuator/refresh`);\n  }\n\n  async clearCaches() {\n    return this.axios.delete(uri`actuator/caches`);\n  }\n\n  async clearCache(name: string, cacheManager?: string) {\n    return this.axios.delete(uri`actuator/caches/${name}`, {\n      params: { cacheManager: cacheManager },\n    });\n  }\n\n  shutdown() {\n    return this.axios.post(uri`actuator/shutdown`);\n  }\n\n  restart() {\n    return this.axios.post(uri`actuator/restart`);\n  }\n\n  async writeMBeanAttribute(\n    domain: string,\n    mBean: string,\n    attribute: string,\n    value: any,\n  ) {\n    const body = {\n      type: 'write',\n      mbean: `${domain}:${mBean}`,\n      attribute,\n      value,\n    };\n    return this.axios.post(uri`actuator/jolokia`, body, {\n      headers: {\n        Accept: 'application/json',\n        'Content-Type': 'application/json',\n      },\n    });\n  }\n\n  async invokeMBeanOperation(\n    domain: string,\n    mBean: string,\n    operation: string,\n    args: any[],\n  ) {\n    const body = {\n      type: 'exec',\n      mbean: `${domain}:${mBean}`,\n      operation,\n      arguments: args,\n    };\n    return this.axios.post(uri`actuator/jolokia`, body, {\n      headers: {\n        Accept: 'application/json',\n        'Content-Type': 'application/json',\n      },\n    });\n  }\n}\n\nexport default Application;\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/services/bus.ts",
    "content": "import mitt from 'mitt';\n\nconst eventBus = mitt();\nexport default eventBus;\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/services/instance.spec.ts",
    "content": "import { describe, expect, test, vi } from 'vitest';\n\nimport Instance from '@/services/instance';\n\nconst { useSbaConfig } = vi.hoisted(() => ({\n  useSbaConfig: vi.fn().mockReturnValue(true),\n}));\n\nvi.mock('@/sba-config', async (importOriginal) => ({\n  ...(await importOriginal<typeof import('@/sba-config')>()),\n  useSbaConfig,\n}));\n\ndescribe('Instance', () => {\n  test.each`\n    hideInstanceUrl | metadataHideUrl | expectUrlToBeShownOnUI\n    ${false}        | ${'true'}       | ${false}\n    ${false}        | ${'false'}      | ${true}\n    ${false}        | ${undefined}    | ${true}\n    ${true}         | ${'true'}       | ${false}\n    ${true}         | ${'false'}      | ${false}\n  `(\n    'showUrl when hideInstanceUrl=$hideInstanceUrl and metadataHideUrl=$metadataHideUrl should return $expectUrlToBeShownOnUI',\n    ({ hideInstanceUrl, metadataHideUrl, expectUrlToBeShownOnUI }) => {\n      useSbaConfig.mockReturnValue({\n        uiSettings: {\n          hideInstanceUrl,\n        },\n      });\n\n      const instance = new Instance({\n        id: 'id',\n        registration: {\n          metadata: {\n            ['hide-url']: metadataHideUrl,\n          },\n        },\n      });\n\n      expect(instance.showUrl()).toEqual(expectUrlToBeShownOnUI);\n    },\n  );\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/services/instance.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { AxiosInstance } from 'axios';\nimport saveAs from 'file-saver';\nimport { Observable, concat, from, ignoreElements } from 'rxjs';\n\nimport axios, {\n  redirectOn401,\n  registerErrorToastInterceptor,\n} from '../utils/axios';\nimport waitForPolyfill from '../utils/eventsource-polyfill';\nimport logtail from '../utils/logtail';\nimport uri from '../utils/uri';\n\nimport { useSbaConfig } from '@/sba-config';\nimport { actuatorMimeTypes } from '@/services/spring-mime-types';\nimport { transformToJSON } from '@/utils/transformToJSON';\n\nconst isInstanceActuatorRequest = (url: string) =>\n  url.match(/^instances[/][^/]+[/]actuator([/].*)?$/);\n\nclass Instance {\n  public readonly id: string;\n  private readonly axios: AxiosInstance;\n  public registration: Registration;\n  public endpoints: Endpoint[] = [];\n  public availableMetrics: string[] = [];\n  public tags: { [key: string]: string }[];\n  public statusTimestamp: string;\n  public buildVersion: string;\n  public statusInfo: StatusInfo;\n\n  constructor({ id, ...instance }: InstanceData) {\n    Object.assign(this, instance);\n    this.id = id;\n    this.axios = axios.create({\n      withCredentials: true,\n      baseURL: uri`instances/${this.id}`,\n      headers: { Accept: actuatorMimeTypes.join(',') },\n    });\n    this.axios.interceptors.response.use(\n      (response) => response,\n      redirectOn401(\n        (error) =>\n          !isInstanceActuatorRequest(error.config.baseURL + error.config.url),\n      ),\n    );\n    registerErrorToastInterceptor(this.axios);\n  }\n\n  get metadata() {\n    return this.registration.metadata;\n  }\n\n  get metadataParsed() {\n    const metadata = this.registration.metadata || {};\n    return transformToJSON(metadata, 'LAX');\n  }\n\n  get isUnregisterable() {\n    return this.registration.source === 'http-api';\n  }\n\n  static async fetchEvents() {\n    return axios.get(uri`instances/events`, {\n      headers: { Accept: 'application/json' },\n    });\n  }\n\n  static getEventStream() {\n    return concat(\n      from(waitForPolyfill()).pipe(ignoreElements()),\n      Observable.create((observer) => {\n        const eventSource = new EventSource('instances/events');\n        eventSource.onmessage = (message) =>\n          observer.next({\n            ...message,\n            data: JSON.parse(message.data),\n          });\n        eventSource.onerror = (err) => observer.error(err);\n        return () => {\n          eventSource.close();\n        };\n      }),\n    );\n  }\n\n  static async get(id: string) {\n    return axios.get(uri`instances/${id}`, {\n      headers: { Accept: 'application/json' },\n      transformResponse(data: string) {\n        if (!data) {\n          return data;\n        }\n        const instance = JSON.parse(data);\n        return new Instance(instance);\n      },\n    });\n  }\n\n  private static _toMBeans(data: string) {\n    if (!data) {\n      return data;\n    }\n    const raw = JSON.parse(data);\n    return Object.entries(raw.value).map(([domain, mBeans]) => ({\n      domain,\n      mBeans: Object.entries(mBeans as Record<string, any>).map(\n        ([descriptor, mBean]) => ({\n          descriptor: descriptor,\n          ...mBean,\n        }),\n      ),\n    }));\n  }\n\n  showUrl() {\n    const sbaConfig = useSbaConfig();\n    if (sbaConfig.uiSettings.hideInstanceUrl) {\n      return false;\n    }\n\n    const hideUrlMetadata = this.registration.metadata?.['hide-url'];\n    return hideUrlMetadata !== 'true';\n  }\n\n  isUrlDisabled() {\n    const sbaConfig = useSbaConfig();\n    if (sbaConfig.uiSettings.disableInstanceUrl) {\n      return true;\n    }\n\n    const disableUrl = this.registration.metadata?.['disable-url'];\n    return disableUrl === 'true';\n  }\n\n  hasEndpoint(endpointId: string): boolean {\n    return this.endpoints.some((endpoint) => endpoint.id === endpointId);\n  }\n\n  async unregister() {\n    return this.axios.delete('', {\n      headers: { Accept: 'application/json' },\n    });\n  }\n\n  async fetchInfo() {\n    return this.axios.get(uri`actuator/info`);\n  }\n\n  async fetchMetrics() {\n    const response = await this.axios.get(uri`actuator/metrics`);\n    this.availableMetrics = response?.data?.names ?? [];\n    return response;\n  }\n\n  async fetchMetric(metric: string, tags: Record<string, any>) {\n    if (this.availableMetrics.length === 0) {\n      try {\n        await this.fetchMetrics();\n      } catch (e) {\n        console.error('Available metrics could not be determined.', e);\n      }\n    }\n\n    if (!this.availableMetrics.includes(metric)) {\n      console.warn(\n        `Metric '${metric}' seems not to be available on instance '${this.id}'.`,\n      );\n      return;\n    }\n\n    const params = new URLSearchParams();\n    if (tags) {\n      let firstElementDuplicated = false;\n      Object.entries(tags)\n        .filter(([, value]) => typeof value !== 'undefined' && value !== null)\n        .forEach(([name, value]) => {\n          params.append('tag', `${name}:${value}`);\n\n          if (!firstElementDuplicated) {\n            // workaround for tags that contains comma\n            // take a look at https://github.com/spring-projects/spring-framework/issues/23820#issuecomment-543087878\n            // If there is single tag specified and name or value contains comma then it will be incorrectly split into several parts\n            // To bypass it we duplicate first tag.\n            params.append('tag', `${name}:${value}`);\n            firstElementDuplicated = true;\n          }\n        });\n    }\n    return this.axios.get(uri`actuator/metrics/${metric}`, {\n      params,\n    });\n  }\n\n  async fetchHealth() {\n    return await this.axios.get(uri`actuator/health`, {\n      validateStatus: null,\n    });\n  }\n\n  async fetchHealthGroup(groupName: string) {\n    return await this.axios.get(uri`actuator/health/${groupName}`, {\n      validateStatus: null,\n    });\n  }\n\n  async fetchEnv(name?: string) {\n    return this.axios.get(uri`actuator/env/${name || ''}`);\n  }\n\n  async fetchConfigprops() {\n    return this.axios.get(uri`actuator/configprops`);\n  }\n\n  async hasEnvManagerSupport() {\n    const response = await this.axios.options(uri`actuator/env`);\n    return (\n      response.headers['allow'] && response.headers['allow'].includes('POST')\n    );\n  }\n\n  async resetEnv() {\n    return this.axios.delete(uri`actuator/env`);\n  }\n\n  async setEnv(name: string, value: string) {\n    return this.axios.post(\n      uri`actuator/env`,\n      { name, value },\n      {\n        headers: { 'Content-Type': 'application/json' },\n      },\n    );\n  }\n\n  async refreshContext() {\n    return this.axios.post(uri`actuator/refresh`);\n  }\n\n  async busRefreshContext() {\n    return this.axios.post(uri`actuator/busrefresh`);\n  }\n\n  async fetchLiquibase() {\n    return this.axios.get(uri`actuator/liquibase`);\n  }\n\n  async fetchScheduledTasks() {\n    return this.axios.get(uri`actuator/scheduledtasks`);\n  }\n\n  async fetchGatewayGlobalFilters() {\n    return this.axios.get(uri`actuator/gateway/globalfilters`);\n  }\n\n  async addGatewayRoute(route: { id: string; [key: string]: any }) {\n    return this.axios.post(uri`actuator/gateway/routes/${route.id}`, route, {\n      headers: { 'Content-Type': 'application/json' },\n    });\n  }\n\n  async fetchGatewayRoutes() {\n    return this.axios.get(uri`actuator/gateway/routes`);\n  }\n\n  async deleteGatewayRoute(routeId: string) {\n    return this.axios.delete(uri`actuator/gateway/routes/${routeId}`);\n  }\n\n  async refreshGatewayRoutesCache() {\n    return this.axios.post(uri`actuator/gateway/refresh`);\n  }\n\n  async fetchCaches() {\n    return this.axios.get(uri`actuator/caches`);\n  }\n\n  async clearCaches() {\n    return this.axios.delete(uri`actuator/caches`);\n  }\n\n  async clearCache(name: string, cacheManager?: string) {\n    return this.axios.delete(uri`actuator/caches/${name}`, {\n      params: { cacheManager: cacheManager },\n    });\n  }\n\n  async fetchFlyway() {\n    return this.axios.get(uri`actuator/flyway`);\n  }\n\n  async fetchLoggers() {\n    return this.axios.get(uri`actuator/loggers`);\n  }\n\n  async configureLogger(name: string, level: string | null) {\n    await this.axios.post(\n      uri`actuator/loggers/${name}`,\n      level === null ? {} : { configuredLevel: level },\n      {\n        headers: { 'Content-Type': 'application/json' },\n      },\n    );\n  }\n\n  async fetchHttptrace() {\n    return this.axios.get(uri`actuator/httptrace`);\n  }\n\n  async fetchHttpExchanges() {\n    return this.axios.get(uri`actuator/httpexchanges`);\n  }\n\n  async fetchBeans() {\n    return this.axios.get(uri`actuator/beans`);\n  }\n\n  async fetchConditions() {\n    return this.axios.get(uri`actuator/conditions`);\n  }\n\n  async fetchThreaddump() {\n    return this.axios.get(uri`actuator/threaddump`);\n  }\n\n  async downloadThreaddump() {\n    const res = await this.axios.get(uri`actuator/threaddump`, {\n      headers: { Accept: 'text/plain' },\n    });\n    const blob = new Blob([res.data], { type: 'text/plain;charset=utf-8' });\n    saveAs(blob, this.registration.name + '-threaddump.txt');\n  }\n\n  async fetchAuditevents({\n    after,\n    type,\n    principal,\n  }: {\n    after: Date;\n    type?: string;\n    principal?: string;\n  }) {\n    return this.axios.get(uri`actuator/auditevents`, {\n      params: {\n        after: after.toISOString(),\n        type: type,\n        principal: principal,\n      },\n    });\n  }\n\n  async fetchSessionsByUsername(username?: string) {\n    return this.axios.get(uri`actuator/sessions`, {\n      params: {\n        username: username,\n      },\n    });\n  }\n\n  async fetchSession(sessionId: string) {\n    return this.axios.get(uri`actuator/sessions/${sessionId}`);\n  }\n\n  async deleteSession(sessionId: string) {\n    return this.axios.delete(uri`actuator/sessions/${sessionId}`);\n  }\n\n  async fetchStartup() {\n    const optionsResponse = await this.axios.options(uri`actuator/startup`);\n    if (\n      optionsResponse.headers.allow &&\n      optionsResponse.headers.allow.includes('GET')\n    ) {\n      return this.axios.get(uri`actuator/startup`);\n    }\n\n    return this.axios.post(uri`actuator/startup`);\n  }\n\n  streamLogfile(interval: number) {\n    return logtail(\n      (opt) => this.axios.get(uri`actuator/logfile`, opt),\n      interval,\n    );\n  }\n\n  async listMBeans() {\n    return this.axios.get(uri`actuator/jolokia/list`, {\n      headers: { Accept: 'application/json' },\n      params: { canonicalNaming: false },\n      transformResponse: Instance._toMBeans,\n    });\n  }\n\n  async readMBeanAttributes(domain: string, mBean: string) {\n    const body = {\n      type: 'read',\n      mbean: `${domain}:${mBean}`,\n      config: { ignoreErrors: true },\n    };\n    return this.axios.post(uri`actuator/jolokia`, body, {\n      headers: {\n        Accept: 'application/json',\n        'Content-Type': 'application/json',\n      },\n    });\n  }\n\n  async writeMBeanAttribute(\n    domain: string,\n    mBean: string,\n    attribute: string,\n    value: any,\n  ) {\n    const body = {\n      type: 'write',\n      mbean: `${domain}:${mBean}`,\n      attribute,\n      value,\n    };\n    return this.axios.post(uri`actuator/jolokia`, body, {\n      headers: {\n        Accept: 'application/json',\n        'Content-Type': 'application/json',\n      },\n    });\n  }\n\n  async invokeMBeanOperation(\n    domain: string,\n    mBean: string,\n    operation: string,\n    args: any[],\n  ) {\n    const body = {\n      type: 'exec',\n      mbean: `${domain}:${mBean}`,\n      operation,\n      arguments: args,\n    };\n    return this.axios.post(uri`actuator/jolokia`, body, {\n      headers: {\n        Accept: 'application/json',\n        'Content-Type': 'application/json',\n      },\n    });\n  }\n\n  async fetchMappings() {\n    return this.axios.get(uri`actuator/mappings`);\n  }\n\n  async fetchQuartzJobs() {\n    return this.axios.get(uri`actuator/quartz/jobs`, {\n      headers: { Accept: 'application/json' },\n    });\n  }\n\n  async fetchQuartzJob(group, name) {\n    return this.axios.get(uri`actuator/quartz/jobs/${group}/${name}`, {\n      headers: { Accept: 'application/json' },\n    });\n  }\n\n  async fetchQuartzTriggers() {\n    return this.axios.get(uri`actuator/quartz/triggers`, {\n      headers: { Accept: 'application/json' },\n    });\n  }\n\n  async fetchQuartzTrigger(group, name) {\n    return this.axios.get(uri`actuator/quartz/triggers/${group}/${name}`, {\n      headers: { Accept: 'application/json' },\n    });\n  }\n\n  async fetchSbomIds() {\n    return this.axios.get(uri`actuator/sbom`, {\n      headers: { Accept: 'application/json' },\n    });\n  }\n\n  async fetchSbom(id: string) {\n    return this.axios.get(uri`actuator/sbom/${id}`, {\n      headers: { Accept: '*/*' },\n    });\n  }\n\n  shutdown() {\n    return this.axios.post(uri`actuator/shutdown`);\n  }\n\n  restart() {\n    return this.axios.post(uri`actuator/restart`);\n  }\n}\n\nexport default Instance;\n\nexport type Registration = {\n  name: string;\n  managementUrl?: string;\n  healthUrl: string;\n  serviceUrl?: string;\n  source: string;\n  metadata?: { [key: string]: string };\n};\n\ntype StatusInfo = {\n  status:\n    | 'UNKNOWN'\n    | 'OUT_OF_SERVICE'\n    | 'UP'\n    | 'DOWN'\n    | 'OFFLINE'\n    | 'RESTRICTED'\n    | string;\n  details: { [key: string]: string };\n};\n\ntype InstanceData = {\n  id: string;\n  registration: Registration;\n  endpoints?: Endpoint[];\n  availableMetrics?: string[];\n  tags?: { [key: string]: string }[];\n  statusTimestamp?: string;\n  buildVersion?: string;\n  statusInfo?: StatusInfo;\n};\n\ntype Endpoint = {\n  id: string;\n  url: string;\n};\n\nexport const DOWN_STATES = ['OUT_OF_SERVICE', 'DOWN', 'OFFLINE', 'RESTRICTED'];\nexport const UP_STATES = ['UP'];\nexport const UNKNOWN_STATES = ['UNKNOWN'];\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/services/notification-filter.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport moment from 'moment';\n\nimport sbaConfig from '@/sba-config';\nimport axios from '@/utils/axios';\nimport uri from '@/utils/uri';\n\nclass NotificationFilter {\n  private id: string;\n  private applicationName: string;\n  private instanceId: string;\n  private expiry: moment.Moment | null;\n\n  constructor({ expiry, ...filter }) {\n    Object.assign(this, filter);\n    this.expiry = expiry ? moment(expiry) : null;\n  }\n\n  affects(obj) {\n    if (!obj) {\n      return false;\n    }\n\n    if (this.isApplicationFilter) {\n      return this.applicationName === obj.name;\n    }\n\n    if (this.isInstanceFilter) {\n      return this.instanceId === obj.id;\n    }\n\n    return false;\n  }\n\n  get isApplicationFilter() {\n    return this.applicationName != null;\n  }\n\n  get isInstanceFilter() {\n    return this.instanceId != null;\n  }\n\n  async delete() {\n    return axios.delete(uri`notifications/filters/${this.id}`);\n  }\n\n  static isSupported() {\n    return Boolean(sbaConfig.uiSettings.notificationFilterEnabled);\n  }\n\n  static async getFilters() {\n    return axios.get('notifications/filters', {\n      transformResponse: NotificationFilter._transformResponse,\n    });\n  }\n\n  static async addFilter(object, ttl) {\n    const params = { ttl };\n    if ('name' in object) {\n      params.applicationName = object.name;\n    } else if ('id' in object) {\n      params.instanceId = object.id;\n    }\n    return axios.post('notifications/filters', null, {\n      params,\n      transformResponse: NotificationFilter._transformResponse,\n    });\n  }\n\n  static _transformResponse(data) {\n    if (!data) {\n      return data;\n    }\n    const json = JSON.parse(data);\n    if (json instanceof Array) {\n      return json\n        .map(NotificationFilter._toNotificationFilters)\n        .filter((f) => !f.expired);\n    }\n    return NotificationFilter._toNotificationFilters(json);\n  }\n\n  static _toNotificationFilters(notificationFilter) {\n    return new NotificationFilter(notificationFilter);\n  }\n}\n\nexport default NotificationFilter;\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/services/spring-mime-types.ts",
    "content": "export const actuatorMimeTypes = [\n  'application/vnd.spring-boot.actuator.v3+json',\n  'application/vnd.spring-boot.actuator.v2+json',\n  'application/vnd.spring-boot.actuator.v1+json',\n  'application/json',\n];\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/services/startup-activator-tree.ts",
    "content": "/*\n * Copyright 2014-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport class StartupActuatorEventTree {\n  constructor(events) {\n    this.events = events;\n  }\n\n  getEvents() {\n    return this.events || [];\n  }\n\n  getRoots() {\n    return this.getByDepth(0);\n  }\n\n  getByDepth(depth) {\n    return this.getEvents().filter(\n      (event) => event.startupStep.depth === depth,\n    );\n  }\n\n  getById(id) {\n    return this.getEvents().find((event) => event.startupStep.id === id);\n  }\n\n  getByParentId(parentId) {\n    return this.getEvents().filter(\n      (event) => event.startupStep.parentId === parentId,\n    );\n  }\n\n  getStartTime() {\n    return this.getEvents()\n      .map((e) => Date.parse(e.startTime))\n      .reduce((a, b) => Math.min(a, b), Number.MAX_VALUE);\n  }\n\n  getEndTime() {\n    return this.getEvents()\n      .map((e) => Date.parse(e.endTime))\n      .reduce((a, b) => Math.max(a, b), Number.MIN_VALUE);\n  }\n\n  getPath(id) {\n    const event = this.getById(id);\n    if (!event) {\n      return [];\n    }\n\n    const path = [id];\n    let parent = event.startupStep.parent;\n    while (parent !== null && parent !== undefined) {\n      path.push(parent.startupStep.id);\n      parent = parent.startupStep.parent;\n    }\n\n    return path;\n  }\n\n  getPeriod(event) {\n    const eventStartTime = Date.parse(event.startTime);\n    const eventEndTime = Date.parse(event.endTime);\n    const treeStartTime = this.getStartTime();\n    const treeEndTime = this.getEndTime();\n\n    const treeTimeSpan = treeEndTime - treeStartTime;\n    const relativeEventStartTime = eventStartTime - treeStartTime;\n    const relativeEventEndTime = eventEndTime - treeStartTime;\n    const relativeStart =\n      relativeEventStartTime > 0 ? relativeEventStartTime / treeTimeSpan : 0;\n    const relativeEnd =\n      relativeEventEndTime > 0 ? relativeEventEndTime / treeTimeSpan : 0;\n\n    return {\n      start: relativeStart,\n      end: relativeEnd,\n    };\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/services/startup-actuator.fixture.spec.json",
    "content": "{\n  \"springBootVersion\": \"2.4.0\",\n  \"timeline\": {\n    \"startTime\": \"2020-12-10T21:53:41.801353Z\",\n    \"events\": [\n      {\n        \"startupStep\": {\n          \"name\": \"spring.boot.application.starting\",\n          \"id\": 1,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"mainApplicationClass\",\n              \"value\": \"de.codecentric.boot.admin.SpringBootAdminServletApplication\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:41.836728041Z\",\n        \"endTime\": \"2020-12-10T21:53:41.882007902Z\",\n        \"duration\": \"PT0.045279861S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.boot.application.environment-prepared\",\n          \"id\": 2,\n          \"parentId\": 0,\n          \"tags\": []\n        },\n        \"startTime\": \"2020-12-10T21:53:42.077870978Z\",\n        \"endTime\": \"2020-12-10T21:53:42.678761997Z\",\n        \"duration\": 0.600891019\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.boot.application.context-prepared\",\n          \"id\": 3,\n          \"parentId\": 0,\n          \"tags\": []\n        },\n        \"startTime\": \"2020-12-10T21:53:42.770730378Z\",\n        \"endTime\": \"2020-12-10T21:53:42.772108879Z\",\n        \"duration\": \"PT0.001378501S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.boot.application.context-loaded\",\n          \"id\": 4,\n          \"parentId\": 0,\n          \"tags\": []\n        },\n        \"startTime\": \"2020-12-10T21:53:42.899752225Z\",\n        \"endTime\": \"2020-12-10T21:53:42.905630362Z\",\n        \"duration\": \"PT0.005878137S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 8,\n          \"parentId\": 7,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:42.958550077Z\",\n        \"endTime\": \"2020-12-10T21:53:42.960040652Z\",\n        \"duration\": \"PT0.001490575S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 7,\n          \"parentId\": 6,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.context.annotation.internalConfigurationAnnotationProcessor\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:42.938902628Z\",\n        \"endTime\": \"2020-12-10T21:53:42.993399929Z\",\n        \"duration\": \"PT0.054497301S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.context.config-classes.parse\",\n          \"id\": 10,\n          \"parentId\": 9,\n          \"tags\": [\n            {\n              \"key\": \"classCount\",\n              \"value\": \"186\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:43.007880681Z\",\n        \"endTime\": \"2020-12-10T21:53:44.822142600Z\",\n        \"duration\": \"PT1.814261919S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.context.beandef-registry.post-process\",\n          \"id\": 9,\n          \"parentId\": 6,\n          \"tags\": [\n            {\n              \"key\": \"postProcessor\",\n              \"value\": \"org.springframework.context.annotation.ConfigurationClassPostProcessor@1d572e62\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:42.993465304Z\",\n        \"endTime\": \"2020-12-10T21:53:44.836797924Z\",\n        \"duration\": \"PT1.84333262S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.context.bean-factory.post-process\",\n          \"id\": 11,\n          \"parentId\": 6,\n          \"tags\": [\n            {\n              \"key\": \"postProcessor\",\n              \"value\": \"org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer$CachingMetadataReaderFactoryPostProcessor@7a8b9166\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.838366162Z\",\n        \"endTime\": \"2020-12-10T21:53:44.838499998Z\",\n        \"duration\": \"PT0.000133836S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.context.bean-factory.post-process\",\n          \"id\": 12,\n          \"parentId\": 6,\n          \"tags\": [\n            {\n              \"key\": \"postProcessor\",\n              \"value\": \"org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer$ConfigurationWarningsPostProcessor@4acc5dff\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.838507052Z\",\n        \"endTime\": \"2020-12-10T21:53:44.838514129Z\",\n        \"duration\": \"PT0.000007077S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.context.config-classes.enhance\",\n          \"id\": 14,\n          \"parentId\": 13,\n          \"tags\": []\n        },\n        \"startTime\": \"2020-12-10T21:53:44.838529850Z\",\n        \"endTime\": \"2020-12-10T21:53:44.839152967Z\",\n        \"duration\": \"PT0.000623117S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.context.bean-factory.post-process\",\n          \"id\": 13,\n          \"parentId\": 6,\n          \"tags\": [\n            {\n              \"key\": \"postProcessor\",\n              \"value\": \"org.springframework.context.annotation.ConfigurationClassPostProcessor@1d572e62\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.838516975Z\",\n        \"endTime\": \"2020-12-10T21:53:44.839430759Z\",\n        \"duration\": \"PT0.000913784S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.context.bean-factory.post-process\",\n          \"id\": 15,\n          \"parentId\": 6,\n          \"tags\": [\n            {\n              \"key\": \"postProcessor\",\n              \"value\": \"org.springframework.boot.LazyInitializationBeanFactoryPostProcessor@7e446d92\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.839436254Z\",\n        \"endTime\": \"2020-12-10T21:53:44.840566795Z\",\n        \"duration\": \"PT0.001130541S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 16,\n          \"parentId\": 6,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"propertySourcesPlaceholderConfigurer\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.beans.factory.config.BeanFactoryPostProcessor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.840830662Z\",\n        \"endTime\": \"2020-12-10T21:53:44.842673263Z\",\n        \"duration\": \"PT0.001842601S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.context.bean-factory.post-process\",\n          \"id\": 17,\n          \"parentId\": 6,\n          \"tags\": [\n            {\n              \"key\": \"postProcessor\",\n              \"value\": \"org.springframework.context.support.PropertySourcesPlaceholderConfigurer@2100d047\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.842686283Z\",\n        \"endTime\": \"2020-12-10T21:53:44.844940434Z\",\n        \"duration\": \"PT0.002254151S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 18,\n          \"parentId\": 6,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.context.event.internalEventListenerProcessor\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.beans.factory.config.BeanFactoryPostProcessor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.844971919Z\",\n        \"endTime\": \"2020-12-10T21:53:44.845821402Z\",\n        \"duration\": \"PT0.000849483S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 19,\n          \"parentId\": 6,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"preserveErrorControllerTargetClassPostProcessor\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.beans.factory.config.BeanFactoryPostProcessor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.845832086Z\",\n        \"endTime\": \"2020-12-10T21:53:44.845957179Z\",\n        \"duration\": \"PT0.000125093S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 20,\n          \"parentId\": 6,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"conversionServicePostProcessor\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.beans.factory.config.BeanFactoryPostProcessor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.845964548Z\",\n        \"endTime\": \"2020-12-10T21:53:44.846387495Z\",\n        \"duration\": \"PT0.000422947S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 22,\n          \"parentId\": 21,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.context.event.internalEventListenerFactory\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.846856708Z\",\n        \"endTime\": \"2020-12-10T21:53:44.846921706Z\",\n        \"duration\": \"PT0.000064998S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 23,\n          \"parentId\": 21,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.transaction.config.internalTransactionalEventListenerFactory\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.846928712Z\",\n        \"endTime\": \"2020-12-10T21:53:44.847038664Z\",\n        \"duration\": \"PT0.000109952S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.context.bean-factory.post-process\",\n          \"id\": 21,\n          \"parentId\": 6,\n          \"tags\": [\n            {\n              \"key\": \"postProcessor\",\n              \"value\": \"org.springframework.context.event.EventListenerMethodProcessor@1f53481b\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.846393630Z\",\n        \"endTime\": \"2020-12-10T21:53:44.847076446Z\",\n        \"duration\": \"PT0.000682816S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.context.bean-factory.post-process\",\n          \"id\": 24,\n          \"parentId\": 6,\n          \"tags\": [\n            {\n              \"key\": \"postProcessor\",\n              \"value\": \"org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$PreserveErrorControllerTargetClassPostProcessor@27e7c77f\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.847080458Z\",\n        \"endTime\": \"2020-12-10T21:53:44.847649741Z\",\n        \"duration\": \"PT0.000569283S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.context.bean-factory.post-process\",\n          \"id\": 25,\n          \"parentId\": 6,\n          \"tags\": [\n            {\n              \"key\": \"postProcessor\",\n              \"value\": \"org.springframework.security.config.crypto.RsaKeyConversionServicePostProcessor@37c36608\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.847655132Z\",\n        \"endTime\": \"2020-12-10T21:53:44.850160232Z\",\n        \"duration\": \"PT0.0025051S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 26,\n          \"parentId\": 6,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.context.annotation.internalAutowiredAnnotationProcessor\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.beans.factory.config.BeanPostProcessor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.851920181Z\",\n        \"endTime\": \"2020-12-10T21:53:44.852304028Z\",\n        \"duration\": \"PT0.000383847S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 27,\n          \"parentId\": 6,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.context.annotation.internalCommonAnnotationProcessor\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.beans.factory.config.BeanPostProcessor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.852315102Z\",\n        \"endTime\": \"2020-12-10T21:53:44.854369560Z\",\n        \"duration\": \"PT0.002054458S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 30,\n          \"parentId\": 29,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.context.internalConfigurationPropertiesBinderFactory\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"class org.springframework.boot.context.properties.ConfigurationPropertiesBinder$Factory\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.854490772Z\",\n        \"endTime\": \"2020-12-10T21:53:44.854559976Z\",\n        \"duration\": \"PT0.000069204S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 29,\n          \"parentId\": 28,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.context.internalConfigurationPropertiesBinder\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"class org.springframework.boot.context.properties.ConfigurationPropertiesBinder\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.854460593Z\",\n        \"endTime\": \"2020-12-10T21:53:44.855712643Z\",\n        \"duration\": \"PT0.00125205S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 28,\n          \"parentId\": 6,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.beans.factory.config.BeanPostProcessor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.854382719Z\",\n        \"endTime\": \"2020-12-10T21:53:44.855721551Z\",\n        \"duration\": \"PT0.001338832S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 31,\n          \"parentId\": 6,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"dataSourceInitializerPostProcessor\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.beans.factory.config.BeanPostProcessor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.855918113Z\",\n        \"endTime\": \"2020-12-10T21:53:44.857439105Z\",\n        \"duration\": \"PT0.001520992S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 33,\n          \"parentId\": 32,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.scheduling.annotation.SchedulingConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.857489672Z\",\n        \"endTime\": \"2020-12-10T21:53:44.858026207Z\",\n        \"duration\": \"PT0.000536535S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 32,\n          \"parentId\": 6,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.context.annotation.internalScheduledAnnotationProcessor\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.beans.factory.config.BeanPostProcessor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.857446720Z\",\n        \"endTime\": \"2020-12-10T21:53:44.859566422Z\",\n        \"duration\": \"PT0.002119702S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 34,\n          \"parentId\": 6,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"persistenceExceptionTranslationPostProcessor\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.beans.factory.config.BeanPostProcessor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.859575037Z\",\n        \"endTime\": \"2020-12-10T21:53:44.868684966Z\",\n        \"duration\": \"PT0.009109929S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 35,\n          \"parentId\": 6,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.aop.config.internalAutoProxyCreator\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.beans.factory.config.BeanPostProcessor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.868701999Z\",\n        \"endTime\": \"2020-12-10T21:53:44.884744010Z\",\n        \"duration\": \"PT0.016042011S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 36,\n          \"parentId\": 6,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"webServerFactoryCustomizerBeanPostProcessor\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.beans.factory.config.BeanPostProcessor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.884781597Z\",\n        \"endTime\": \"2020-12-10T21:53:44.885065574Z\",\n        \"duration\": \"PT0.000283977S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 37,\n          \"parentId\": 6,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"errorPageRegistrarBeanPostProcessor\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.beans.factory.config.BeanPostProcessor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.885074022Z\",\n        \"endTime\": \"2020-12-10T21:53:44.885241629Z\",\n        \"duration\": \"PT0.000167607S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 40,\n          \"parentId\": 39,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.887124534Z\",\n        \"endTime\": \"2020-12-10T21:53:44.904479489Z\",\n        \"duration\": \"PT0.017354955S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 41,\n          \"parentId\": 39,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"transactionAttributeSource\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.905815649Z\",\n        \"endTime\": \"2020-12-10T21:53:44.914842149Z\",\n        \"duration\": \"PT0.0090265S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 42,\n          \"parentId\": 39,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"transactionInterceptor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.915755794Z\",\n        \"endTime\": \"2020-12-10T21:53:44.958518037Z\",\n        \"duration\": \"PT0.042762243S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 39,\n          \"parentId\": 38,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.transaction.config.internalTransactionAdvisor\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.aop.Advisor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.887064010Z\",\n        \"endTime\": \"2020-12-10T21:53:44.967011446Z\",\n        \"duration\": \"PT0.079947436S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 38,\n          \"parentId\": 6,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"healthEndpointGroupsBeanPostProcessor\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.beans.factory.config.BeanPostProcessor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.885249353Z\",\n        \"endTime\": \"2020-12-10T21:53:44.971580308Z\",\n        \"duration\": \"PT0.086330955S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 43,\n          \"parentId\": 6,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"meterRegistryPostProcessor\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.beans.factory.config.BeanPostProcessor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.971632203Z\",\n        \"endTime\": \"2020-12-10T21:53:44.977955438Z\",\n        \"duration\": \"PT0.006323235S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.context.beans.post-process\",\n          \"id\": 6,\n          \"parentId\": 5,\n          \"tags\": []\n        },\n        \"startTime\": \"2020-12-10T21:53:42.926508051Z\",\n        \"endTime\": \"2020-12-10T21:53:44.978094536Z\",\n        \"duration\": \"PT2.051586485S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 46,\n          \"parentId\": 45,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcat\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.985087873Z\",\n        \"endTime\": \"2020-12-10T21:53:44.987186025Z\",\n        \"duration\": \"PT0.002098152S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 48,\n          \"parentId\": 47,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration$TomcatWebSocketConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.067147987Z\",\n        \"endTime\": \"2020-12-10T21:53:45.067973061Z\",\n        \"duration\": \"PT0.000825074S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 47,\n          \"parentId\": 45,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"websocketServletWebServerCustomizer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.066861556Z\",\n        \"endTime\": \"2020-12-10T21:53:45.069323508Z\",\n        \"duration\": \"PT0.002461952S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 50,\n          \"parentId\": 49,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.069563112Z\",\n        \"endTime\": \"2020-12-10T21:53:45.074283657Z\",\n        \"duration\": \"PT0.004720545S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 52,\n          \"parentId\": 51,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.context.properties.BoundConfigurationProperties\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"class org.springframework.boot.context.properties.BoundConfigurationProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.094576558Z\",\n        \"endTime\": \"2020-12-10T21:53:45.095119136Z\",\n        \"duration\": \"PT0.000542578S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 53,\n          \"parentId\": 51,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"conversionService\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.core.convert.ConversionService\"\n            },\n            {\n              \"key\": \"exception\",\n              \"value\": \"class org.springframework.beans.factory.NoSuchBeanDefinitionException\"\n            },\n            {\n              \"key\": \"message\",\n              \"value\": \"No bean named 'conversionService' available\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.104717348Z\",\n        \"endTime\": \"2020-12-10T21:53:45.107373959Z\",\n        \"duration\": \"PT0.002656611S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 51,\n          \"parentId\": 49,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"server-org.springframework.boot.autoconfigure.web.ServerProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.076947344Z\",\n        \"endTime\": \"2020-12-10T21:53:45.117598728Z\",\n        \"duration\": \"PT0.040651384S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 49,\n          \"parentId\": 45,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"servletWebServerFactoryCustomizer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.069373628Z\",\n        \"endTime\": \"2020-12-10T21:53:45.119065020Z\",\n        \"duration\": \"PT0.049691392S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 54,\n          \"parentId\": 45,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"tomcatServletWebServerFactoryCustomizer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.119082527Z\",\n        \"endTime\": \"2020-12-10T21:53:45.120267864Z\",\n        \"duration\": \"PT0.001185337S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 56,\n          \"parentId\": 55,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration$NettyWebServerFactoryCustomizerConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.120324756Z\",\n        \"endTime\": \"2020-12-10T21:53:45.120684988Z\",\n        \"duration\": \"PT0.000360232S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 55,\n          \"parentId\": 45,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"nettyWebServerFactoryCustomizer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.120279417Z\",\n        \"endTime\": \"2020-12-10T21:53:45.124121682Z\",\n        \"duration\": \"PT0.003842265S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 58,\n          \"parentId\": 57,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration$TomcatWebServerFactoryCustomizerConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.124180858Z\",\n        \"endTime\": \"2020-12-10T21:53:45.124549261Z\",\n        \"duration\": \"PT0.000368403S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 57,\n          \"parentId\": 45,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"tomcatWebServerFactoryCustomizer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.124134393Z\",\n        \"endTime\": \"2020-12-10T21:53:45.126687053Z\",\n        \"duration\": \"PT0.00255266S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 60,\n          \"parentId\": 59,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.126740133Z\",\n        \"endTime\": \"2020-12-10T21:53:45.128824942Z\",\n        \"duration\": \"PT0.002084809S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 59,\n          \"parentId\": 45,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"localeCharsetMappingsCustomizer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.126698425Z\",\n        \"endTime\": \"2020-12-10T21:53:45.129202358Z\",\n        \"duration\": \"PT0.002503933S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 62,\n          \"parentId\": 61,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.157395915Z\",\n        \"endTime\": \"2020-12-10T21:53:45.161004523Z\",\n        \"duration\": \"PT0.003608608S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 64,\n          \"parentId\": 63,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.161752769Z\",\n        \"endTime\": \"2020-12-10T21:53:45.163238078Z\",\n        \"duration\": \"PT0.001485309S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 66,\n          \"parentId\": 65,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.165349747Z\",\n        \"endTime\": \"2020-12-10T21:53:45.166886925Z\",\n        \"duration\": \"PT0.001537178S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 67,\n          \"parentId\": 65,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"spring.mvc-org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.168108671Z\",\n        \"endTime\": \"2020-12-10T21:53:45.173566740Z\",\n        \"duration\": \"PT0.005458069S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 65,\n          \"parentId\": 63,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"dispatcherServlet\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.165083780Z\",\n        \"endTime\": \"2020-12-10T21:53:45.185159391Z\",\n        \"duration\": \"PT0.020075611S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 70,\n          \"parentId\": 69,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"spring.servlet.multipart-org.springframework.boot.autoconfigure.web.servlet.MultipartProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.187937179Z\",\n        \"endTime\": \"2020-12-10T21:53:45.191376574Z\",\n        \"duration\": \"PT0.003439395S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 69,\n          \"parentId\": 68,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.187348147Z\",\n        \"endTime\": \"2020-12-10T21:53:45.192068984Z\",\n        \"duration\": \"PT0.004720837S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 68,\n          \"parentId\": 63,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"multipartConfigElement\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.187286256Z\",\n        \"endTime\": \"2020-12-10T21:53:45.193228092Z\",\n        \"duration\": \"PT0.005941836S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 63,\n          \"parentId\": 61,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"dispatcherServletRegistration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.161697628Z\",\n        \"endTime\": \"2020-12-10T21:53:45.196438877Z\",\n        \"duration\": \"PT0.034741249S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 61,\n          \"parentId\": 45,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"errorPageCustomizer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.157275571Z\",\n        \"endTime\": \"2020-12-10T21:53:45.196828278Z\",\n        \"duration\": \"PT0.039552707S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 45,\n          \"parentId\": 44,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"tomcatServletWebServerFactory\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.boot.web.servlet.server.ServletWebServerFactory\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.984760639Z\",\n        \"endTime\": \"2020-12-10T21:53:45.199868600Z\",\n        \"duration\": \"PT0.215107961S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 72,\n          \"parentId\": 71,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.session.SessionRepositoryFilterConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.513059790Z\",\n        \"endTime\": \"2020-12-10T21:53:45.514515212Z\",\n        \"duration\": \"PT0.001455422S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 73,\n          \"parentId\": 71,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"spring.session-org.springframework.boot.autoconfigure.session.SessionProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.516544507Z\",\n        \"endTime\": \"2020-12-10T21:53:45.520674212Z\",\n        \"duration\": \"PT0.004129705S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 77,\n          \"parentId\": 76,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.session.SessionAutoConfiguration$ServletSessionConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.552983786Z\",\n        \"endTime\": \"2020-12-10T21:53:45.553408362Z\",\n        \"duration\": \"PT0.000424576S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 79,\n          \"parentId\": 78,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.session.SessionAutoConfiguration$ServletSessionConfiguration$RememberMeServicesConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.556685340Z\",\n        \"endTime\": \"2020-12-10T21:53:45.557165607Z\",\n        \"duration\": \"PT0.000480267S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 78,\n          \"parentId\": 76,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"rememberMeServicesCookieSerializerCustomizer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.556619909Z\",\n        \"endTime\": \"2020-12-10T21:53:45.558497017Z\",\n        \"duration\": \"PT0.001877108S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 76,\n          \"parentId\": 75,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"cookieSerializer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.552927565Z\",\n        \"endTime\": \"2020-12-10T21:53:45.561209946Z\",\n        \"duration\": \"PT0.008282381S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 81,\n          \"parentId\": 80,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"springBootAdminServletApplication\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.569859823Z\",\n        \"endTime\": \"2020-12-10T21:53:45.570262474Z\",\n        \"duration\": \"PT0.000402651S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 83,\n          \"parentId\": 82,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"spring.datasource-org.springframework.boot.autoconfigure.jdbc.DataSourceProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.011639089Z\",\n        \"endTime\": \"2020-12-10T21:53:46.016211767Z\",\n        \"duration\": \"PT0.004572678S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 82,\n          \"parentId\": 80,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"class org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.010279072Z\",\n        \"endTime\": \"2020-12-10T21:53:46.019919289Z\",\n        \"duration\": \"PT0.009640217S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 80,\n          \"parentId\": 75,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"dataSource\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.569801791Z\",\n        \"endTime\": \"2020-12-10T21:53:46.020439096Z\",\n        \"duration\": \"PT0.450637305S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 85,\n          \"parentId\": 84,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration$JdbcTransactionManagerConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.021092119Z\",\n        \"endTime\": \"2020-12-10T21:53:46.021459004Z\",\n        \"duration\": \"PT0.000366885S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 87,\n          \"parentId\": 86,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.025611341Z\",\n        \"endTime\": \"2020-12-10T21:53:46.026756109Z\",\n        \"duration\": \"PT0.001144768S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 88,\n          \"parentId\": 86,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"spring.transaction-org.springframework.boot.autoconfigure.transaction.TransactionProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.029897812Z\",\n        \"endTime\": \"2020-12-10T21:53:46.031981328Z\",\n        \"duration\": \"PT0.002083516S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 86,\n          \"parentId\": 84,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"platformTransactionManagerCustomizers\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.025555998Z\",\n        \"endTime\": \"2020-12-10T21:53:46.032309168Z\",\n        \"duration\": \"PT0.00675317S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 84,\n          \"parentId\": 75,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"transactionManager\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.021041292Z\",\n        \"endTime\": \"2020-12-10T21:53:46.035375846Z\",\n        \"duration\": \"PT0.014334554S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 89,\n          \"parentId\": 75,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"spring.session.jdbc-org.springframework.boot.autoconfigure.session.JdbcSessionProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.036412704Z\",\n        \"endTime\": \"2020-12-10T21:53:46.039009584Z\",\n        \"duration\": \"PT0.00259688S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 75,\n          \"parentId\": 74,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.session.JdbcSessionConfiguration$SpringBootJdbcHttpSessionConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.524246315Z\",\n        \"endTime\": \"2020-12-10T21:53:46.041705815Z\",\n        \"duration\": \"PT0.5174595S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 90,\n          \"parentId\": 74,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"sessionRepository\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.043456920Z\",\n        \"endTime\": \"2020-12-10T21:53:46.060635130Z\",\n        \"duration\": \"PT0.01717821S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 74,\n          \"parentId\": 71,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"springSessionRepositoryFilter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.523988365Z\",\n        \"endTime\": \"2020-12-10T21:53:46.061618512Z\",\n        \"duration\": \"PT0.537630147S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 71,\n          \"parentId\": 44,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"sessionRepositoryFilterRegistration\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.boot.web.servlet.ServletContextInitializer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:45.512868483Z\",\n        \"endTime\": \"2020-12-10T21:53:46.063374312Z\",\n        \"duration\": \"PT0.550505829S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 93,\n          \"parentId\": 92,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"management.metrics-org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.064045346Z\",\n        \"endTime\": \"2020-12-10T21:53:46.068510549Z\",\n        \"duration\": \"PT0.004465203S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 92,\n          \"parentId\": 91,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.metrics.web.servlet.WebMvcMetricsAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.063447789Z\",\n        \"endTime\": \"2020-12-10T21:53:46.068927169Z\",\n        \"duration\": \"PT0.00547938S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 95,\n          \"parentId\": 94,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.069363717Z\",\n        \"endTime\": \"2020-12-10T21:53:46.070169951Z\",\n        \"duration\": \"PT0.000806234S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 97,\n          \"parentId\": 96,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"management.metrics.export.simple-org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.070891205Z\",\n        \"endTime\": \"2020-12-10T21:53:46.071758035Z\",\n        \"duration\": \"PT0.00086683S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 96,\n          \"parentId\": 94,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"simpleConfig\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.070526597Z\",\n        \"endTime\": \"2020-12-10T21:53:46.075665626Z\",\n        \"duration\": \"PT0.005139029S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 99,\n          \"parentId\": 98,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.076261330Z\",\n        \"endTime\": \"2020-12-10T21:53:46.076595350Z\",\n        \"duration\": \"PT0.00033402S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 98,\n          \"parentId\": 94,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"micrometerClock\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.076202020Z\",\n        \"endTime\": \"2020-12-10T21:53:46.077088339Z\",\n        \"duration\": \"PT0.000886319S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 100,\n          \"parentId\": 94,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"propertiesMeterFilter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.092269013Z\",\n        \"endTime\": \"2020-12-10T21:53:46.095111682Z\",\n        \"duration\": \"PT0.002842669S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 102,\n          \"parentId\": 101,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.metrics.web.client.HttpClientMetricsAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.095176558Z\",\n        \"endTime\": \"2020-12-10T21:53:46.095535187Z\",\n        \"duration\": \"PT0.000358629S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 101,\n          \"parentId\": 94,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"metricsHttpClientUriTagFilter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.095139681Z\",\n        \"endTime\": \"2020-12-10T21:53:46.097800734Z\",\n        \"duration\": \"PT0.002661053S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 103,\n          \"parentId\": 94,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"metricsHttpServerUriTagFilter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.097824090Z\",\n        \"endTime\": \"2020-12-10T21:53:46.098401874Z\",\n        \"duration\": \"PT0.000577784S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 105,\n          \"parentId\": 104,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.metrics.JvmMetricsAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.099352951Z\",\n        \"endTime\": \"2020-12-10T21:53:46.099670586Z\",\n        \"duration\": \"PT0.000317635S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 104,\n          \"parentId\": 94,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"jvmGcMetrics\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.099319820Z\",\n        \"endTime\": \"2020-12-10T21:53:46.104900455Z\",\n        \"duration\": \"PT0.005580635S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 106,\n          \"parentId\": 94,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"jvmMemoryMetrics\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.104928381Z\",\n        \"endTime\": \"2020-12-10T21:53:46.105238153Z\",\n        \"duration\": \"PT0.000309772S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 107,\n          \"parentId\": 94,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"jvmThreadMetrics\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.105259575Z\",\n        \"endTime\": \"2020-12-10T21:53:46.105589129Z\",\n        \"duration\": \"PT0.000329554S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 108,\n          \"parentId\": 94,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"classLoaderMetrics\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.105608441Z\",\n        \"endTime\": \"2020-12-10T21:53:46.105870038Z\",\n        \"duration\": \"PT0.000261597S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 110,\n          \"parentId\": 109,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.metrics.LogbackMetricsAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.105916860Z\",\n        \"endTime\": \"2020-12-10T21:53:46.106203811Z\",\n        \"duration\": \"PT0.000286951S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 109,\n          \"parentId\": 94,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"logbackMetrics\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.105889073Z\",\n        \"endTime\": \"2020-12-10T21:53:46.106749314Z\",\n        \"duration\": \"PT0.000860241S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 112,\n          \"parentId\": 111,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.metrics.SystemMetricsAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.106798012Z\",\n        \"endTime\": \"2020-12-10T21:53:46.107130832Z\",\n        \"duration\": \"PT0.00033282S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 111,\n          \"parentId\": 94,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"uptimeMetrics\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.106768753Z\",\n        \"endTime\": \"2020-12-10T21:53:46.107408250Z\",\n        \"duration\": \"PT0.000639497S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 113,\n          \"parentId\": 94,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"processorMetrics\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.107428064Z\",\n        \"endTime\": \"2020-12-10T21:53:46.108212222Z\",\n        \"duration\": \"PT0.000784158S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 114,\n          \"parentId\": 94,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"fileDescriptorMetrics\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.108232195Z\",\n        \"endTime\": \"2020-12-10T21:53:46.108681150Z\",\n        \"duration\": \"PT0.000448955S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 94,\n          \"parentId\": 91,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"simpleMeterRegistry\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.069316305Z\",\n        \"endTime\": \"2020-12-10T21:53:46.130165728Z\",\n        \"duration\": \"PT0.060849423S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 115,\n          \"parentId\": 91,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"webMvcTagsProvider\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.130714312Z\",\n        \"endTime\": \"2020-12-10T21:53:46.131749702Z\",\n        \"duration\": \"PT0.00103539S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 91,\n          \"parentId\": 44,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"webMvcMetricsFilter\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.boot.web.servlet.ServletContextInitializer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.063387603Z\",\n        \"endTime\": \"2020-12-10T21:53:46.132148141Z\",\n        \"duration\": \"PT0.068760538S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 117,\n          \"parentId\": 116,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.132196889Z\",\n        \"endTime\": \"2020-12-10T21:53:46.132541784Z\",\n        \"duration\": \"PT0.000344895S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 118,\n          \"parentId\": 116,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"spring.security-org.springframework.boot.autoconfigure.security.SecurityProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.132911980Z\",\n        \"endTime\": \"2020-12-10T21:53:46.134132373Z\",\n        \"duration\": \"PT0.001220393S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 116,\n          \"parentId\": 44,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"securityFilterChainRegistration\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.boot.web.servlet.ServletContextInitializer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.132159123Z\",\n        \"endTime\": \"2020-12-10T21:53:46.136038191Z\",\n        \"duration\": \"PT0.003879068S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 120,\n          \"parentId\": 119,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.endpoint.web.ServletEndpointManagementContextConfiguration$WebMvcServletEndpointManagementContextConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.136089138Z\",\n        \"endTime\": \"2020-12-10T21:53:46.136338626Z\",\n        \"duration\": \"PT0.000249488S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 121,\n          \"parentId\": 119,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"management.endpoints.web-org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.136718295Z\",\n        \"endTime\": \"2020-12-10T21:53:46.139729775Z\",\n        \"duration\": \"PT0.00301148S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 123,\n          \"parentId\": 122,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration$WebEndpointServletConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.140298721Z\",\n        \"endTime\": \"2020-12-10T21:53:46.140612166Z\",\n        \"duration\": \"PT0.000313445S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 125,\n          \"parentId\": 124,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.141857394Z\",\n        \"endTime\": \"2020-12-10T21:53:46.143277160Z\",\n        \"duration\": \"PT0.001419766S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 124,\n          \"parentId\": 122,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"webEndpointPathMapper\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.141802028Z\",\n        \"endTime\": \"2020-12-10T21:53:46.144641842Z\",\n        \"duration\": \"PT0.002839814S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 127,\n          \"parentId\": 126,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.endpoint.web.ServletEndpointManagementContextConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.147071119Z\",\n        \"endTime\": \"2020-12-10T21:53:46.148824685Z\",\n        \"duration\": \"PT0.001753566S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 126,\n          \"parentId\": 122,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"servletExposeExcludePropertyEndpointFilter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.146852081Z\",\n        \"endTime\": \"2020-12-10T21:53:46.151329239Z\",\n        \"duration\": \"PT0.004477158S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 122,\n          \"parentId\": 119,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"servletEndpointDiscoverer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.140240187Z\",\n        \"endTime\": \"2020-12-10T21:53:46.156874672Z\",\n        \"duration\": \"PT0.016634485S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 119,\n          \"parentId\": 44,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"servletEndpointRegistrar\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.boot.web.servlet.ServletContextInitializer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.136049717Z\",\n        \"endTime\": \"2020-12-10T21:53:46.185605082Z\",\n        \"duration\": \"PT0.049555365S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 128,\n          \"parentId\": 44,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"requestContextFilter\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface javax.servlet.Filter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.188260303Z\",\n        \"endTime\": \"2020-12-10T21:53:46.189964152Z\",\n        \"duration\": \"PT0.001703849S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 130,\n          \"parentId\": 129,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.190051041Z\",\n        \"endTime\": \"2020-12-10T21:53:46.190416837Z\",\n        \"duration\": \"PT0.000365796S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 129,\n          \"parentId\": 44,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"formContentFilter\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface javax.servlet.Filter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.189996928Z\",\n        \"endTime\": \"2020-12-10T21:53:46.192485138Z\",\n        \"duration\": \"PT0.00248821S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 133,\n          \"parentId\": 132,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"spring.boot.admin.ui-de.codecentric.boot.admin.server.ui.config.AdminServerUiProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.193178440Z\",\n        \"endTime\": \"2020-12-10T21:53:46.203997200Z\",\n        \"duration\": \"PT0.01081876S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 134,\n          \"parentId\": 132,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"spring.boot.admin-de.codecentric.boot.admin.server.config.AdminServerProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.204517539Z\",\n        \"endTime\": \"2020-12-10T21:53:46.206180972Z\",\n        \"duration\": \"PT0.001663433S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 132,\n          \"parentId\": 131,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"de.codecentric.boot.admin.server.ui.config.AdminServerUiAutoConfiguration$ServletUiConfiguration$AdminUiWebMvcConfig\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.192560455Z\",\n        \"endTime\": \"2020-12-10T21:53:46.206961060Z\",\n        \"duration\": \"PT0.014400605S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 131,\n          \"parentId\": 44,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"homepageForwardFilter\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface javax.servlet.Filter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.192511158Z\",\n        \"endTime\": \"2020-12-10T21:53:46.219367974Z\",\n        \"duration\": \"PT0.026856816S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 136,\n          \"parentId\": 135,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.trace.http.HttpTraceAutoConfiguration$ServletTraceFilterConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.219432681Z\",\n        \"endTime\": \"2020-12-10T21:53:46.219600650Z\",\n        \"duration\": \"PT0.000167969S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 137,\n          \"parentId\": 135,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"httpTraceRepository\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.220188847Z\",\n        \"endTime\": \"2020-12-10T21:53:46.221022140Z\",\n        \"duration\": \"PT0.000833293S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 139,\n          \"parentId\": 138,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.trace.http.HttpTraceAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.221481439Z\",\n        \"endTime\": \"2020-12-10T21:53:46.221691883Z\",\n        \"duration\": \"PT0.000210444S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 140,\n          \"parentId\": 138,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"management.trace.http-org.springframework.boot.actuate.autoconfigure.trace.http.HttpTraceProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.222004290Z\",\n        \"endTime\": \"2020-12-10T21:53:46.223087226Z\",\n        \"duration\": \"PT0.001082936S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 138,\n          \"parentId\": 135,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"httpExchangeTracer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.221448477Z\",\n        \"endTime\": \"2020-12-10T21:53:46.223638116Z\",\n        \"duration\": \"PT0.002189639S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 135,\n          \"parentId\": 44,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"httpTraceFilter\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface javax.servlet.Filter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.219383482Z\",\n        \"endTime\": \"2020-12-10T21:53:46.224356231Z\",\n        \"duration\": \"PT0.004972749S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 141,\n          \"parentId\": 44,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"characterEncodingFilter\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface javax.servlet.Filter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.224365251Z\",\n        \"endTime\": \"2020-12-10T21:53:46.225772575Z\",\n        \"duration\": \"PT0.001407324S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.boot.webserver.create\",\n          \"id\": 44,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"factory\",\n              \"value\": \"class org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:44.983103652Z\",\n        \"endTime\": \"2020-12-10T21:53:46.254260217Z\",\n        \"duration\": \"PT1.271156565S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 142,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"auditLog\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.260184056Z\",\n        \"endTime\": \"2020-12-10T21:53:46.261964834Z\",\n        \"duration\": \"PT0.001780778S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 145,\n          \"parentId\": 144,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"de.codecentric.boot.admin.server.config.AdminServerAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.262695873Z\",\n        \"endTime\": \"2020-12-10T21:53:46.263535789Z\",\n        \"duration\": \"PT0.000839916S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 146,\n          \"parentId\": 144,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"eventStore\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.264942674Z\",\n        \"endTime\": \"2020-12-10T21:53:46.309001968Z\",\n        \"duration\": \"PT0.044059294S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 144,\n          \"parentId\": 143,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"instanceRepository\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.262658948Z\",\n        \"endTime\": \"2020-12-10T21:53:46.341252598Z\",\n        \"duration\": \"PT0.07859365S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 143,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"customNotifier\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.261979794Z\",\n        \"endTime\": \"2020-12-10T21:53:46.342816460Z\",\n        \"duration\": \"PT0.080836666S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 147,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"customEndpoint\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.342831884Z\",\n        \"endTime\": \"2020-12-10T21:53:46.343715756Z\",\n        \"duration\": \"PT0.000883872S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 148,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"customHttpHeadersProvider\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.343726987Z\",\n        \"endTime\": \"2020-12-10T21:53:46.344188281Z\",\n        \"duration\": \"PT0.000461294S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 149,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"auditEventRepository\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.344200362Z\",\n        \"endTime\": \"2020-12-10T21:53:46.345116490Z\",\n        \"duration\": \"PT0.000916128S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 151,\n          \"parentId\": 150,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration$ServletConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.345229616Z\",\n        \"endTime\": \"2020-12-10T21:53:46.345456241Z\",\n        \"duration\": \"PT0.000226625S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 152,\n          \"parentId\": 150,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"spring.boot.admin.client.instance-de.codecentric.boot.admin.client.config.InstanceProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.346054405Z\",\n        \"endTime\": \"2020-12-10T21:53:46.352459822Z\",\n        \"duration\": \"PT0.006405417S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 153,\n          \"parentId\": 150,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"management.server-org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.352971413Z\",\n        \"endTime\": \"2020-12-10T21:53:46.354474518Z\",\n        \"duration\": \"PT0.001503105S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 157,\n          \"parentId\": 156,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.357056748Z\",\n        \"endTime\": \"2020-12-10T21:53:46.357291261Z\",\n        \"duration\": \"PT0.000234513S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 156,\n          \"parentId\": 155,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"endpointOperationParameterMapper\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.357021050Z\",\n        \"endTime\": \"2020-12-10T21:53:46.359841644Z\",\n        \"duration\": \"PT0.002820594S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 158,\n          \"parentId\": 155,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"endpointMediaTypes\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.360206450Z\",\n        \"endTime\": \"2020-12-10T21:53:46.360513913Z\",\n        \"duration\": \"PT0.000307463S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 159,\n          \"parentId\": 155,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"endpointCachingOperationInvokerAdvisor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.361394824Z\",\n        \"endTime\": \"2020-12-10T21:53:46.362544246Z\",\n        \"duration\": \"PT0.001149422S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 160,\n          \"parentId\": 155,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"webExposeExcludePropertyEndpointFilter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.363003247Z\",\n        \"endTime\": \"2020-12-10T21:53:46.363217562Z\",\n        \"duration\": \"PT0.000214315S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 155,\n          \"parentId\": 154,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"webEndpointDiscoverer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.356564762Z\",\n        \"endTime\": \"2020-12-10T21:53:46.364225029Z\",\n        \"duration\": \"PT0.007660267S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 162,\n          \"parentId\": 161,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"controllerExposeExcludePropertyEndpointFilter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.364889160Z\",\n        \"endTime\": \"2020-12-10T21:53:46.365073444Z\",\n        \"duration\": \"PT0.000184284S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 161,\n          \"parentId\": 154,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"controllerEndpointDiscoverer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.364340445Z\",\n        \"endTime\": \"2020-12-10T21:53:46.365662473Z\",\n        \"duration\": \"PT0.001322028S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 165,\n          \"parentId\": 164,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"management.endpoints.jmx-org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.367257491Z\",\n        \"endTime\": \"2020-12-10T21:53:46.368269820Z\",\n        \"duration\": \"PT0.001012329S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 164,\n          \"parentId\": 163,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.366361893Z\",\n        \"endTime\": \"2020-12-10T21:53:46.368498750Z\",\n        \"duration\": \"PT0.002136857S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 166,\n          \"parentId\": 163,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"jmxIncludeExcludePropertyEndpointFilter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.368958597Z\",\n        \"endTime\": \"2020-12-10T21:53:46.369205653Z\",\n        \"duration\": \"PT0.000247056S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 163,\n          \"parentId\": 154,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"jmxAnnotationEndpointDiscoverer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.366303853Z\",\n        \"endTime\": \"2020-12-10T21:53:46.369800475Z\",\n        \"duration\": \"PT0.003496622S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 168,\n          \"parentId\": 167,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.cache.CachesEndpointAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.377300778Z\",\n        \"endTime\": \"2020-12-10T21:53:46.378055785Z\",\n        \"duration\": \"PT0.000755007S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 167,\n          \"parentId\": 154,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"cachesEndpoint\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.377138298Z\",\n        \"endTime\": \"2020-12-10T21:53:46.385562251Z\",\n        \"duration\": \"PT0.008423953S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 170,\n          \"parentId\": 169,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.health.HealthEndpointConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.389424738Z\",\n        \"endTime\": \"2020-12-10T21:53:46.389744658Z\",\n        \"duration\": \"PT0.00031992S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 173,\n          \"parentId\": 172,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"management.endpoint.health-org.springframework.boot.actuate.autoconfigure.health.HealthEndpointProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.391374887Z\",\n        \"endTime\": \"2020-12-10T21:53:46.393759473Z\",\n        \"duration\": \"PT0.002384586S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 174,\n          \"parentId\": 172,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"healthStatusAggregator\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.boot.actuate.health.StatusAggregator\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.396613480Z\",\n        \"endTime\": \"2020-12-10T21:53:46.399665859Z\",\n        \"duration\": \"PT0.003052379S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 175,\n          \"parentId\": 172,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"healthHttpCodeStatusMapper\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.boot.actuate.health.HttpCodeStatusMapper\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.400613706Z\",\n        \"endTime\": \"2020-12-10T21:53:46.401583491Z\",\n        \"duration\": \"PT0.000969785S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 172,\n          \"parentId\": 171,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"healthEndpointGroups\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.390941649Z\",\n        \"endTime\": \"2020-12-10T21:53:46.409498838Z\",\n        \"duration\": \"PT0.018557189S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 177,\n          \"parentId\": 176,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.system.DiskSpaceHealthContributorAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.410208934Z\",\n        \"endTime\": \"2020-12-10T21:53:46.410648960Z\",\n        \"duration\": \"PT0.000440026S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 178,\n          \"parentId\": 176,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"management.health.diskspace-org.springframework.boot.actuate.autoconfigure.system.DiskSpaceHealthIndicatorProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.412372572Z\",\n        \"endTime\": \"2020-12-10T21:53:46.415201955Z\",\n        \"duration\": \"PT0.002829383S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 176,\n          \"parentId\": 171,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"diskSpaceHealthIndicator\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.410142099Z\",\n        \"endTime\": \"2020-12-10T21:53:46.417913383Z\",\n        \"duration\": \"PT0.007771284S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 180,\n          \"parentId\": 179,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.418010774Z\",\n        \"endTime\": \"2020-12-10T21:53:46.418287491Z\",\n        \"duration\": \"PT0.000276717S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 179,\n          \"parentId\": 171,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"pingHealthContributor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.417932644Z\",\n        \"endTime\": \"2020-12-10T21:53:46.418587224Z\",\n        \"duration\": \"PT0.00065458S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 182,\n          \"parentId\": 181,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.jdbc.DataSourceHealthContributorAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.418636720Z\",\n        \"endTime\": \"2020-12-10T21:53:46.420897684Z\",\n        \"duration\": \"PT0.002260964S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 183,\n          \"parentId\": 181,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"management.health.db-org.springframework.boot.actuate.autoconfigure.jdbc.DataSourceHealthIndicatorProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.421432227Z\",\n        \"endTime\": \"2020-12-10T21:53:46.422156535Z\",\n        \"duration\": \"PT0.000724308S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 181,\n          \"parentId\": 171,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"dbHealthContributor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.418597281Z\",\n        \"endTime\": \"2020-12-10T21:53:46.423040858Z\",\n        \"duration\": \"PT0.004443577S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 171,\n          \"parentId\": 169,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"healthContributorRegistry\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.390336822Z\",\n        \"endTime\": \"2020-12-10T21:53:46.426737975Z\",\n        \"duration\": \"PT0.036401153S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 169,\n          \"parentId\": 154,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"healthEndpoint\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.389352629Z\",\n        \"endTime\": \"2020-12-10T21:53:46.428264490Z\",\n        \"duration\": \"PT0.038911861S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 185,\n          \"parentId\": 184,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.env.EnvironmentEndpointAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.428531191Z\",\n        \"endTime\": \"2020-12-10T21:53:46.428735911Z\",\n        \"duration\": \"PT0.00020472S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 186,\n          \"parentId\": 184,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"management.endpoint.env-org.springframework.boot.actuate.autoconfigure.env.EnvironmentEndpointProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.429316237Z\",\n        \"endTime\": \"2020-12-10T21:53:46.429951643Z\",\n        \"duration\": \"PT0.000635406S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 184,\n          \"parentId\": 154,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"environmentEndpoint\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.428471233Z\",\n        \"endTime\": \"2020-12-10T21:53:46.431979243Z\",\n        \"duration\": \"PT0.00350801S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 188,\n          \"parentId\": 187,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.audit.AuditEventsEndpointAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.437549072Z\",\n        \"endTime\": \"2020-12-10T21:53:46.437857408Z\",\n        \"duration\": \"PT0.000308336S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 187,\n          \"parentId\": 154,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"auditEventsEndpoint\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.437486934Z\",\n        \"endTime\": \"2020-12-10T21:53:46.439373375Z\",\n        \"duration\": \"PT0.001886441S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 190,\n          \"parentId\": 189,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.441035252Z\",\n        \"endTime\": \"2020-12-10T21:53:46.441232190Z\",\n        \"duration\": \"PT0.000196938S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 189,\n          \"parentId\": 154,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"beansEndpoint\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.440987269Z\",\n        \"endTime\": \"2020-12-10T21:53:46.442236859Z\",\n        \"duration\": \"PT0.00124959S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 191,\n          \"parentId\": 154,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"cachesEndpointWebExtension\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.444272910Z\",\n        \"endTime\": \"2020-12-10T21:53:46.445189903Z\",\n        \"duration\": \"PT0.000916993S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 193,\n          \"parentId\": 192,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.health.HealthEndpointWebExtensionConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.446589507Z\",\n        \"endTime\": \"2020-12-10T21:53:46.446801342Z\",\n        \"duration\": \"PT0.000211835S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 192,\n          \"parentId\": 154,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"healthEndpointWebExtension\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.446539629Z\",\n        \"endTime\": \"2020-12-10T21:53:46.447490370Z\",\n        \"duration\": \"PT0.000950741S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 195,\n          \"parentId\": 194,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.448539830Z\",\n        \"endTime\": \"2020-12-10T21:53:46.448727149Z\",\n        \"duration\": \"PT0.000187319S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 197,\n          \"parentId\": 196,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.info.InfoContributorAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.449362123Z\",\n        \"endTime\": \"2020-12-10T21:53:46.449719543Z\",\n        \"duration\": \"PT0.00035742S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 196,\n          \"parentId\": 194,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"envInfoContributor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.449327785Z\",\n        \"endTime\": \"2020-12-10T21:53:46.450691781Z\",\n        \"duration\": \"PT0.001363996S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 201,\n          \"parentId\": 200,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"spring.info-org.springframework.boot.autoconfigure.info.ProjectInfoProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.451567286Z\",\n        \"endTime\": \"2020-12-10T21:53:46.452275723Z\",\n        \"duration\": \"PT0.000708437S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 200,\n          \"parentId\": 199,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.451159876Z\",\n        \"endTime\": \"2020-12-10T21:53:46.452485436Z\",\n        \"duration\": \"PT0.00132556S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 199,\n          \"parentId\": 198,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"buildProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.451128572Z\",\n        \"endTime\": \"2020-12-10T21:53:46.455883677Z\",\n        \"duration\": \"PT0.004755105S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 198,\n          \"parentId\": 194,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"buildInfoContributor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.450714236Z\",\n        \"endTime\": \"2020-12-10T21:53:46.456919541Z\",\n        \"duration\": \"PT0.006205305S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 194,\n          \"parentId\": 154,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"infoEndpoint\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.448495438Z\",\n        \"endTime\": \"2020-12-10T21:53:46.457247946Z\",\n        \"duration\": \"PT0.008752508S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 203,\n          \"parentId\": 202,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.condition.ConditionsReportEndpointAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.457760391Z\",\n        \"endTime\": \"2020-12-10T21:53:46.457960170Z\",\n        \"duration\": \"PT0.000199779S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 202,\n          \"parentId\": 154,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"conditionsReportEndpoint\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.457711773Z\",\n        \"endTime\": \"2020-12-10T21:53:46.458484305Z\",\n        \"duration\": \"PT0.000772532S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 205,\n          \"parentId\": 204,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.context.properties.ConfigurationPropertiesReportEndpointAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.458942731Z\",\n        \"endTime\": \"2020-12-10T21:53:46.459117626Z\",\n        \"duration\": \"PT0.000174895S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 206,\n          \"parentId\": 204,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"management.endpoint.configprops-org.springframework.boot.actuate.autoconfigure.context.properties.ConfigurationPropertiesReportEndpointProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.459653063Z\",\n        \"endTime\": \"2020-12-10T21:53:46.460265854Z\",\n        \"duration\": \"PT0.000612791S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 204,\n          \"parentId\": 154,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"configurationPropertiesReportEndpoint\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.458903108Z\",\n        \"endTime\": \"2020-12-10T21:53:46.461382121Z\",\n        \"duration\": \"PT0.002479013S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 207,\n          \"parentId\": 154,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"environmentEndpointWebExtension\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.462685348Z\",\n        \"endTime\": \"2020-12-10T21:53:46.463738284Z\",\n        \"duration\": \"PT0.001052936S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 209,\n          \"parentId\": 208,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.logging.LogFileWebEndpointAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.464289055Z\",\n        \"endTime\": \"2020-12-10T21:53:46.464481835Z\",\n        \"duration\": \"PT0.00019278S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 210,\n          \"parentId\": 208,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"management.endpoint.logfile-org.springframework.boot.actuate.autoconfigure.logging.LogFileWebEndpointProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.465015370Z\",\n        \"endTime\": \"2020-12-10T21:53:46.465669737Z\",\n        \"duration\": \"PT0.000654367S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 208,\n          \"parentId\": 154,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"logFileWebEndpoint\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.464242754Z\",\n        \"endTime\": \"2020-12-10T21:53:46.466738329Z\",\n        \"duration\": \"PT0.002495575S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 212,\n          \"parentId\": 211,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.logging.LoggersEndpointAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.467627551Z\",\n        \"endTime\": \"2020-12-10T21:53:46.468331822Z\",\n        \"duration\": \"PT0.000704271S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 211,\n          \"parentId\": 154,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"loggersEndpoint\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.467498723Z\",\n        \"endTime\": \"2020-12-10T21:53:46.471583394Z\",\n        \"duration\": \"PT0.004084671S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 214,\n          \"parentId\": 213,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.management.HeapDumpWebEndpointAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.473282704Z\",\n        \"endTime\": \"2020-12-10T21:53:46.473515393Z\",\n        \"duration\": \"PT0.000232689S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 213,\n          \"parentId\": 154,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"heapDumpWebEndpoint\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.473223716Z\",\n        \"endTime\": \"2020-12-10T21:53:46.474243487Z\",\n        \"duration\": \"PT0.001019771S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 216,\n          \"parentId\": 215,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.management.ThreadDumpEndpointAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.475345170Z\",\n        \"endTime\": \"2020-12-10T21:53:46.475703859Z\",\n        \"duration\": \"PT0.000358689S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 215,\n          \"parentId\": 154,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"dumpEndpoint\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.475266247Z\",\n        \"endTime\": \"2020-12-10T21:53:46.477842581Z\",\n        \"duration\": \"PT0.002576334S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 218,\n          \"parentId\": 217,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.metrics.MetricsEndpointAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.478705153Z\",\n        \"endTime\": \"2020-12-10T21:53:46.478911502Z\",\n        \"duration\": \"PT0.000206349S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 217,\n          \"parentId\": 154,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"metricsEndpoint\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.478646458Z\",\n        \"endTime\": \"2020-12-10T21:53:46.481161798Z\",\n        \"duration\": \"PT0.00251534S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 220,\n          \"parentId\": 219,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.scheduling.ScheduledTasksEndpointAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.483609914Z\",\n        \"endTime\": \"2020-12-10T21:53:46.483892551Z\",\n        \"duration\": \"PT0.000282637S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 219,\n          \"parentId\": 154,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"scheduledTasksEndpoint\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.483437516Z\",\n        \"endTime\": \"2020-12-10T21:53:46.488259244Z\",\n        \"duration\": \"PT0.004821728S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 222,\n          \"parentId\": 221,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.session.SessionsEndpointAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.488812054Z\",\n        \"endTime\": \"2020-12-10T21:53:46.489040934Z\",\n        \"duration\": \"PT0.00022888S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 221,\n          \"parentId\": 154,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"sessionEndpoint\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.488759626Z\",\n        \"endTime\": \"2020-12-10T21:53:46.490654177Z\",\n        \"duration\": \"PT0.001894551S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 224,\n          \"parentId\": 223,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.startup.StartupEndpointAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.491288436Z\",\n        \"endTime\": \"2020-12-10T21:53:46.491612061Z\",\n        \"duration\": \"PT0.000323625S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 223,\n          \"parentId\": 154,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"startupEndpoint\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.491232751Z\",\n        \"endTime\": \"2020-12-10T21:53:46.494185789Z\",\n        \"duration\": \"PT0.002953038S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 226,\n          \"parentId\": 225,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.trace.http.HttpTraceEndpointAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.494542307Z\",\n        \"endTime\": \"2020-12-10T21:53:46.494782247Z\",\n        \"duration\": \"PT0.00023994S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 225,\n          \"parentId\": 154,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"httpTraceEndpoint\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.494481745Z\",\n        \"endTime\": \"2020-12-10T21:53:46.495995647Z\",\n        \"duration\": \"PT0.001513902S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 228,\n          \"parentId\": 227,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.web.mappings.MappingsEndpointAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.496602334Z\",\n        \"endTime\": \"2020-12-10T21:53:46.496797332Z\",\n        \"duration\": \"PT0.000194998S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 230,\n          \"parentId\": 229,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.web.mappings.MappingsEndpointAutoConfiguration$ServletWebConfiguration$SpringMvcConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.497511342Z\",\n        \"endTime\": \"2020-12-10T21:53:46.497890436Z\",\n        \"duration\": \"PT0.000379094S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 229,\n          \"parentId\": 227,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"dispatcherServletMappingDescriptionProvider\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.497470647Z\",\n        \"endTime\": \"2020-12-10T21:53:46.501371507Z\",\n        \"duration\": \"PT0.00390086S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 232,\n          \"parentId\": 231,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.web.mappings.MappingsEndpointAutoConfiguration$ServletWebConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.501558290Z\",\n        \"endTime\": \"2020-12-10T21:53:46.501825886Z\",\n        \"duration\": \"PT0.000267596S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 231,\n          \"parentId\": 227,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"servletMappingDescriptionProvider\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.501429506Z\",\n        \"endTime\": \"2020-12-10T21:53:46.502095666Z\",\n        \"duration\": \"PT0.00066616S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 233,\n          \"parentId\": 227,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"filterMappingDescriptionProvider\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.502118244Z\",\n        \"endTime\": \"2020-12-10T21:53:46.502439504Z\",\n        \"duration\": \"PT0.00032126S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 227,\n          \"parentId\": 154,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"mappingsEndpoint\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.496553162Z\",\n        \"endTime\": \"2020-12-10T21:53:46.504523568Z\",\n        \"duration\": \"PT0.007970406S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 154,\n          \"parentId\": 150,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"pathMappedEndpoints\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.355544900Z\",\n        \"endTime\": \"2020-12-10T21:53:46.547665949Z\",\n        \"duration\": \"PT0.192121049S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 235,\n          \"parentId\": 234,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.555325889Z\",\n        \"endTime\": \"2020-12-10T21:53:46.555650309Z\",\n        \"duration\": \"PT0.00032442S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 234,\n          \"parentId\": 150,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"startupDateMetadataContributor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.555260946Z\",\n        \"endTime\": \"2020-12-10T21:53:46.562847818Z\",\n        \"duration\": \"PT0.007586872S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 150,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"applicationFactory\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.345191173Z\",\n        \"endTime\": \"2020-12-10T21:53:46.566462630Z\",\n        \"duration\": \"PT0.221271457S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 238,\n          \"parentId\": 237,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.568224888Z\",\n        \"endTime\": \"2020-12-10T21:53:46.569006129Z\",\n        \"duration\": \"PT0.000781241S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 240,\n          \"parentId\": 239,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.572258387Z\",\n        \"endTime\": \"2020-12-10T21:53:46.572523189Z\",\n        \"duration\": \"PT0.000264802S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 242,\n          \"parentId\": 241,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorConfiguration$ReactorNetty\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.573065055Z\",\n        \"endTime\": \"2020-12-10T21:53:46.573272694Z\",\n        \"duration\": \"PT0.000207639S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 243,\n          \"parentId\": 241,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"reactorClientResourceFactory\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.573779319Z\",\n        \"endTime\": \"2020-12-10T21:53:46.602076950Z\",\n        \"duration\": \"PT0.028297631S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 241,\n          \"parentId\": 239,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"reactorClientHttpConnector\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.573035243Z\",\n        \"endTime\": \"2020-12-10T21:53:46.673499906Z\",\n        \"duration\": \"PT0.100464663S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 239,\n          \"parentId\": 237,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"clientConnectorCustomizer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.572206200Z\",\n        \"endTime\": \"2020-12-10T21:53:46.674605747Z\",\n        \"duration\": \"PT0.102399547S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 245,\n          \"parentId\": 244,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration$WebClientCodecsConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.674947156Z\",\n        \"endTime\": \"2020-12-10T21:53:46.675754536Z\",\n        \"duration\": \"PT0.00080738S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 247,\n          \"parentId\": 246,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration$DefaultCodecsConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.676664571Z\",\n        \"endTime\": \"2020-12-10T21:53:46.676913383Z\",\n        \"duration\": \"PT0.000248812S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 248,\n          \"parentId\": 246,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"spring.codec-org.springframework.boot.autoconfigure.codec.CodecProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.677357585Z\",\n        \"endTime\": \"2020-12-10T21:53:46.678283721Z\",\n        \"duration\": \"PT0.000926136S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 246,\n          \"parentId\": 244,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"defaultCodecCustomizer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.676611919Z\",\n        \"endTime\": \"2020-12-10T21:53:46.678868862Z\",\n        \"duration\": \"PT0.002256943S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 250,\n          \"parentId\": 249,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration$JacksonCodecConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.678940289Z\",\n        \"endTime\": \"2020-12-10T21:53:46.679133894Z\",\n        \"duration\": \"PT0.000193605S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 252,\n          \"parentId\": 251,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.679626760Z\",\n        \"endTime\": \"2020-12-10T21:53:46.679821661Z\",\n        \"duration\": \"PT0.000194901S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 254,\n          \"parentId\": 253,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperBuilderConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.680306435Z\",\n        \"endTime\": \"2020-12-10T21:53:46.680514183Z\",\n        \"duration\": \"PT0.000207748S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 256,\n          \"parentId\": 255,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$Jackson2ObjectMapperBuilderCustomizerConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.681133453Z\",\n        \"endTime\": \"2020-12-10T21:53:46.681408811Z\",\n        \"duration\": \"PT0.000275358S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 257,\n          \"parentId\": 255,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"spring.jackson-org.springframework.boot.autoconfigure.jackson.JacksonProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.682039385Z\",\n        \"endTime\": \"2020-12-10T21:53:46.685763710Z\",\n        \"duration\": \"PT0.003724325S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 255,\n          \"parentId\": 253,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"standardJacksonObjectMapperBuilderCustomizer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.681077427Z\",\n        \"endTime\": \"2020-12-10T21:53:46.686259287Z\",\n        \"duration\": \"PT0.00518186S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 259,\n          \"parentId\": 258,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$ParameterNamesModuleConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.687793887Z\",\n        \"endTime\": \"2020-12-10T21:53:46.688123511Z\",\n        \"duration\": \"PT0.000329624S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 258,\n          \"parentId\": 253,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"parameterNamesModule\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.687718592Z\",\n        \"endTime\": \"2020-12-10T21:53:46.693183422Z\",\n        \"duration\": \"PT0.00546483S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 261,\n          \"parentId\": 260,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.693246392Z\",\n        \"endTime\": \"2020-12-10T21:53:46.693494596Z\",\n        \"duration\": \"PT0.000248204S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 260,\n          \"parentId\": 253,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"jsonComponentModule\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.693197129Z\",\n        \"endTime\": \"2020-12-10T21:53:46.700240206Z\",\n        \"duration\": \"PT0.007043077S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 263,\n          \"parentId\": 262,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"de.codecentric.boot.admin.server.config.AdminServerWebConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.700484376Z\",\n        \"endTime\": \"2020-12-10T21:53:46.701829179Z\",\n        \"duration\": \"PT0.001344803S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 262,\n          \"parentId\": 253,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"adminJacksonModule\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.700322196Z\",\n        \"endTime\": \"2020-12-10T21:53:46.723724977Z\",\n        \"duration\": \"PT0.023402781S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 253,\n          \"parentId\": 251,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"jacksonObjectMapperBuilder\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.680242195Z\",\n        \"endTime\": \"2020-12-10T21:53:46.726172576Z\",\n        \"duration\": \"PT0.045930381S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 251,\n          \"parentId\": 249,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"jacksonObjectMapper\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.679594474Z\",\n        \"endTime\": \"2020-12-10T21:53:46.749677053Z\",\n        \"duration\": \"PT0.070082579S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 249,\n          \"parentId\": 244,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"jacksonCodecCustomizer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.678905388Z\",\n        \"endTime\": \"2020-12-10T21:53:46.751160892Z\",\n        \"duration\": \"PT0.072255504S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 244,\n          \"parentId\": 237,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"exchangeStrategiesCustomizer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.674727404Z\",\n        \"endTime\": \"2020-12-10T21:53:46.752108598Z\",\n        \"duration\": \"PT0.077381194S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 265,\n          \"parentId\": 264,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.metrics.web.client.WebClientMetricsConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.752231635Z\",\n        \"endTime\": \"2020-12-10T21:53:46.752605151Z\",\n        \"duration\": \"PT0.000373516S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 266,\n          \"parentId\": 264,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"defaultWebClientExchangeTagsProvider\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.754204774Z\",\n        \"endTime\": \"2020-12-10T21:53:46.755271556Z\",\n        \"duration\": \"PT0.001066782S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 264,\n          \"parentId\": 237,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"metricsWebClientCustomizer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.752159453Z\",\n        \"endTime\": \"2020-12-10T21:53:46.756498060Z\",\n        \"duration\": \"PT0.004338607S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 237,\n          \"parentId\": 236,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"webClientBuilder\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.568034754Z\",\n        \"endTime\": \"2020-12-10T21:53:46.759015262Z\",\n        \"duration\": \"PT0.190980508S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 268,\n          \"parentId\": 267,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"de.codecentric.boot.admin.server.config.AdminServerInstanceWebClientConfiguration$InstanceExchangeFiltersConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.761570412Z\",\n        \"endTime\": \"2020-12-10T21:53:46.762444031Z\",\n        \"duration\": \"PT0.000873619S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 270,\n          \"parentId\": 269,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"de.codecentric.boot.admin.server.config.AdminServerInstanceWebClientConfiguration$InstanceExchangeFiltersConfiguration$DefaultInstanceExchangeFiltersConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.763769912Z\",\n        \"endTime\": \"2020-12-10T21:53:46.764181559Z\",\n        \"duration\": \"PT0.000411647S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 272,\n          \"parentId\": 271,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"de.codecentric.boot.admin.server.config.AdminServerInstanceWebClientConfiguration$HttpHeadersProviderConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.765261186Z\",\n        \"endTime\": \"2020-12-10T21:53:46.765905305Z\",\n        \"duration\": \"PT0.000644119S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 271,\n          \"parentId\": 269,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"basicAuthHttpHeadersProvider\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.765190212Z\",\n        \"endTime\": \"2020-12-10T21:53:46.768165295Z\",\n        \"duration\": \"PT0.002975083S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 269,\n          \"parentId\": 267,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"addHeadersInstanceExchangeFilter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.763687545Z\",\n        \"endTime\": \"2020-12-10T21:53:46.775160129Z\",\n        \"duration\": \"PT0.011472584S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 273,\n          \"parentId\": 267,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"rewriteEndpointUrlInstanceExchangeFilter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.775213619Z\",\n        \"endTime\": \"2020-12-10T21:53:46.775633409Z\",\n        \"duration\": \"PT0.00041979S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 274,\n          \"parentId\": 267,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"setDefaultAcceptHeaderInstanceExchangeFilter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.775682571Z\",\n        \"endTime\": \"2020-12-10T21:53:46.776193154Z\",\n        \"duration\": \"PT0.000510583S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 277,\n          \"parentId\": 276,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"de.codecentric.boot.admin.server.config.AdminServerInstanceWebClientConfiguration$LegacyEndpointConvertersConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.778305167Z\",\n        \"endTime\": \"2020-12-10T21:53:46.778863670Z\",\n        \"duration\": \"PT0.000558503S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 276,\n          \"parentId\": 275,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"healthLegacyEndpointConverter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.778137862Z\",\n        \"endTime\": \"2020-12-10T21:53:46.791459792Z\",\n        \"duration\": \"PT0.01332193S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 278,\n          \"parentId\": 275,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"infoLegacyEndpointConverter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.791486149Z\",\n        \"endTime\": \"2020-12-10T21:53:46.791981952Z\",\n        \"duration\": \"PT0.000495803S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 279,\n          \"parentId\": 275,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"envLegacyEndpointConverter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.791998948Z\",\n        \"endTime\": \"2020-12-10T21:53:46.792310010Z\",\n        \"duration\": \"PT0.000311062S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 280,\n          \"parentId\": 275,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"httptraceLegacyEndpointConverter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.792326105Z\",\n        \"endTime\": \"2020-12-10T21:53:46.792639117Z\",\n        \"duration\": \"PT0.000313012S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 281,\n          \"parentId\": 275,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"threaddumpLegacyEndpointConverter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.792654346Z\",\n        \"endTime\": \"2020-12-10T21:53:46.792922212Z\",\n        \"duration\": \"PT0.000267866S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 282,\n          \"parentId\": 275,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"liquibaseLegacyEndpointConverter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.792937267Z\",\n        \"endTime\": \"2020-12-10T21:53:46.793232144Z\",\n        \"duration\": \"PT0.000294877S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 283,\n          \"parentId\": 275,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"flywayLegacyEndpointConverter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.793246558Z\",\n        \"endTime\": \"2020-12-10T21:53:46.793484084Z\",\n        \"duration\": \"PT0.000237526S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 284,\n          \"parentId\": 275,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"beansLegacyEndpointConverter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.793498194Z\",\n        \"endTime\": \"2020-12-10T21:53:46.793741443Z\",\n        \"duration\": \"PT0.000243249S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 285,\n          \"parentId\": 275,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"configpropsLegacyEndpointConverter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.793755993Z\",\n        \"endTime\": \"2020-12-10T21:53:46.794276221Z\",\n        \"duration\": \"PT0.000520228S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 286,\n          \"parentId\": 275,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"mappingsLegacyEndpointConverter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.794297469Z\",\n        \"endTime\": \"2020-12-10T21:53:46.794643430Z\",\n        \"duration\": \"PT0.000345961S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 287,\n          \"parentId\": 275,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"startupLegacyEndpointConverter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.794660743Z\",\n        \"endTime\": \"2020-12-10T21:53:46.794943571Z\",\n        \"duration\": \"PT0.000282828S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 275,\n          \"parentId\": 267,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"legacyEndpointConverterInstanceExchangeFilter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.776253845Z\",\n        \"endTime\": \"2020-12-10T21:53:46.795639034Z\",\n        \"duration\": \"PT0.019385189S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 288,\n          \"parentId\": 267,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"logfileAcceptWorkaround\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.795657382Z\",\n        \"endTime\": \"2020-12-10T21:53:46.796069149Z\",\n        \"duration\": \"PT0.000411767S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 289,\n          \"parentId\": 267,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"retryInstanceExchangeFilter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.796087721Z\",\n        \"endTime\": \"2020-12-10T21:53:46.797023531Z\",\n        \"duration\": \"PT0.00093581S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 290,\n          \"parentId\": 267,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"timeoutInstanceExchangeFilter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.797051338Z\",\n        \"endTime\": \"2020-12-10T21:53:46.797825583Z\",\n        \"duration\": \"PT0.000774245S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 267,\n          \"parentId\": 236,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"filterInstanceWebClientCustomizer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.761417471Z\",\n        \"endTime\": \"2020-12-10T21:53:46.800454081Z\",\n        \"duration\": \"PT0.03903661S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 236,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"de.codecentric.boot.admin.server.config.AdminServerInstanceWebClientConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.566537007Z\",\n        \"endTime\": \"2020-12-10T21:53:46.801318253Z\",\n        \"duration\": \"PT0.234781246S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 292,\n          \"parentId\": 291,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"instanceIdGenerator\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.802802701Z\",\n        \"endTime\": \"2020-12-10T21:53:46.804184331Z\",\n        \"duration\": \"PT0.00138163S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 291,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"instanceRegistry\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.801332149Z\",\n        \"endTime\": \"2020-12-10T21:53:46.804443121Z\",\n        \"duration\": \"PT0.003110972S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 293,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"applicationRegistry\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.804447981Z\",\n        \"endTime\": \"2020-12-10T21:53:46.806633054Z\",\n        \"duration\": \"PT0.002185073S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 295,\n          \"parentId\": 294,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"instanceWebClientBuilder\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.807094641Z\",\n        \"endTime\": \"2020-12-10T21:53:46.807382671Z\",\n        \"duration\": \"PT0.00028803S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 294,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"statusUpdater\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.806638708Z\",\n        \"endTime\": \"2020-12-10T21:53:46.842959075Z\",\n        \"duration\": \"PT0.036320367S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 296,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"statusUpdateTrigger\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.842965895Z\",\n        \"endTime\": \"2020-12-10T21:53:46.873200243Z\",\n        \"duration\": \"PT0.030234348S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 298,\n          \"parentId\": 297,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"instanceWebClientBuilder\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.873384297Z\",\n        \"endTime\": \"2020-12-10T21:53:46.873448450Z\",\n        \"duration\": \"PT0.000064153S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 297,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"endpointDetector\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.873206258Z\",\n        \"endTime\": \"2020-12-10T21:53:46.878419068Z\",\n        \"duration\": \"PT0.00521281S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 299,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"endpointDetectionTrigger\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.878426754Z\",\n        \"endTime\": \"2020-12-10T21:53:46.880483220Z\",\n        \"duration\": \"PT0.002056466S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 301,\n          \"parentId\": 300,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"instanceWebClientBuilder\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.880626520Z\",\n        \"endTime\": \"2020-12-10T21:53:46.880672359Z\",\n        \"duration\": \"PT0.000045839S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 300,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"infoUpdater\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.880488665Z\",\n        \"endTime\": \"2020-12-10T21:53:46.881923220Z\",\n        \"duration\": \"PT0.001434555S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 302,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"infoUpdateTrigger\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.881928216Z\",\n        \"endTime\": \"2020-12-10T21:53:46.883933358Z\",\n        \"duration\": \"PT0.002005142S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 305,\n          \"parentId\": 304,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"de.codecentric.boot.admin.NotifierConfig\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.886166982Z\",\n        \"endTime\": \"2020-12-10T21:53:46.886525386Z\",\n        \"duration\": \"PT0.000358404S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 304,\n          \"parentId\": 303,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"filteringNotifier\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.886151301Z\",\n        \"endTime\": \"2020-12-10T21:53:46.888548365Z\",\n        \"duration\": \"PT0.002397064S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 303,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"de.codecentric.boot.admin.server.config.AdminServerNotifierAutoConfiguration$FilteringNotifierWebConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.883940213Z\",\n        \"endTime\": \"2020-12-10T21:53:46.888676795Z\",\n        \"duration\": \"PT0.004736582S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 306,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"notificationFilterController\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.888680437Z\",\n        \"endTime\": \"2020-12-10T21:53:46.893255831Z\",\n        \"duration\": \"PT0.004575394S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 307,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"de.codecentric.boot.admin.server.config.AdminServerNotifierAutoConfiguration$CompositeNotifierConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.893260064Z\",\n        \"endTime\": \"2020-12-10T21:53:46.893482101Z\",\n        \"duration\": \"PT0.000222037S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 308,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"de.codecentric.boot.admin.server.config.AdminServerNotifierAutoConfiguration$NotifierTriggerConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.893485250Z\",\n        \"endTime\": \"2020-12-10T21:53:46.893614758Z\",\n        \"duration\": \"PT0.000129508S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 310,\n          \"parentId\": 309,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"remindingNotifier\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.893802680Z\",\n        \"endTime\": \"2020-12-10T21:53:46.896112013Z\",\n        \"duration\": \"PT0.002309333S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 309,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"notificationTrigger\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.893617561Z\",\n        \"endTime\": \"2020-12-10T21:53:46.897285691Z\",\n        \"duration\": \"PT0.00366813S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.smart-initialize\",\n          \"id\": 311,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.context.event.internalEventListenerProcessor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.897497373Z\",\n        \"endTime\": \"2020-12-10T21:53:46.930290439Z\",\n        \"duration\": \"PT0.032793066S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.smart-initialize\",\n          \"id\": 312,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.context.annotation.internalScheduledAnnotationProcessor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.930329936Z\",\n        \"endTime\": \"2020-12-10T21:53:46.930447526Z\",\n        \"duration\": \"PT0.00011759S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 314,\n          \"parentId\": 313,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.930666618Z\",\n        \"endTime\": \"2020-12-10T21:53:46.930859878Z\",\n        \"duration\": \"PT0.00019326S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 315,\n          \"parentId\": 313,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"spring.lifecycle-org.springframework.boot.autoconfigure.context.LifecycleProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.931378343Z\",\n        \"endTime\": \"2020-12-10T21:53:46.932055916Z\",\n        \"duration\": \"PT0.000677573S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 313,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"lifecycleProcessor\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.context.LifecycleProcessor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:46.930618623Z\",\n        \"endTime\": \"2020-12-10T21:53:46.933079457Z\",\n        \"duration\": \"PT0.002460834S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 317,\n          \"parentId\": 316,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.015401422Z\",\n        \"endTime\": \"2020-12-10T21:53:47.015681787Z\",\n        \"duration\": \"PT0.000280365S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 319,\n          \"parentId\": 318,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.018289498Z\",\n        \"endTime\": \"2020-12-10T21:53:47.019078107Z\",\n        \"duration\": \"PT0.000788609S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 320,\n          \"parentId\": 318,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"objectNamingStrategy\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.019791002Z\",\n        \"endTime\": \"2020-12-10T21:53:47.021169421Z\",\n        \"duration\": \"PT0.001378419S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 321,\n          \"parentId\": 318,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"mbeanServer\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface javax.management.MBeanServer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.030156313Z\",\n        \"endTime\": \"2020-12-10T21:53:47.041177381Z\",\n        \"duration\": \"PT0.011021068S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 318,\n          \"parentId\": 316,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"mbeanExporter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.018242119Z\",\n        \"endTime\": \"2020-12-10T21:53:47.045939548Z\",\n        \"duration\": \"PT0.027697429S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 316,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"springApplicationAdminRegistrar\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.context.ApplicationListener\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.015349395Z\",\n        \"endTime\": \"2020-12-10T21:53:47.048358116Z\",\n        \"duration\": \"PT0.033008721S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 322,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"delegatingApplicationListener\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.context.ApplicationListener\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.048482567Z\",\n        \"endTime\": \"2020-12-10T21:53:47.048993047Z\",\n        \"duration\": \"PT0.00051048S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 323,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent[source=org.springframework.boot.web.embedded.tomcat.TomcatWebServer@1a4083f6]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.admin.SpringApplicationAdminMXBeanRegistrar@77e9dca8\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.049108484Z\",\n        \"endTime\": \"2020-12-10T21:53:47.050019757Z\",\n        \"duration\": \"PT0.000911273S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 324,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent[source=org.springframework.boot.web.embedded.tomcat.TomcatWebServer@1a4083f6]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.050029878Z\",\n        \"endTime\": \"2020-12-10T21:53:47.050045152Z\",\n        \"duration\": \"PT0.000015274S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 325,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent[source=org.springframework.boot.web.embedded.tomcat.TomcatWebServer@1a4083f6]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer@7c22d4f\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.050047329Z\",\n        \"endTime\": \"2020-12-10T21:53:47.050489300Z\",\n        \"duration\": \"PT0.000441971S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 326,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent[source=org.springframework.boot.web.embedded.tomcat.TomcatWebServer@1a4083f6]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"public void de.codecentric.boot.admin.client.registration.DefaultApplicationFactory.onWebServerInitialized(org.springframework.boot.web.context.WebServerInitializedEvent)\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.050495848Z\",\n        \"endTime\": \"2020-12-10T21:53:47.051239107Z\",\n        \"duration\": \"PT0.000743259S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 327,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent[source=org.springframework.boot.web.embedded.tomcat.TomcatWebServer@1a4083f6]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.051262258Z\",\n        \"endTime\": \"2020-12-10T21:53:47.051370260Z\",\n        \"duration\": \"PT0.000108002S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 330,\n          \"parentId\": 329,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"spring.resources-org.springframework.boot.autoconfigure.web.ResourceProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.059735036Z\",\n        \"endTime\": \"2020-12-10T21:53:47.069104748Z\",\n        \"duration\": \"PT0.009369712S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 331,\n          \"parentId\": 329,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"spring.web-org.springframework.boot.autoconfigure.web.WebProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.070665520Z\",\n        \"endTime\": \"2020-12-10T21:53:47.072631542Z\",\n        \"duration\": \"PT0.001966022S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 332,\n          \"parentId\": 329,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.088060026Z\",\n        \"endTime\": \"2020-12-10T21:53:47.089895037Z\",\n        \"duration\": \"PT0.001835011S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 333,\n          \"parentId\": 329,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"metricsWebMvcConfigurer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.090040829Z\",\n        \"endTime\": \"2020-12-10T21:53:47.091215546Z\",\n        \"duration\": \"PT0.001174717S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 334,\n          \"parentId\": 329,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.091262332Z\",\n        \"endTime\": \"2020-12-10T21:53:47.091952263Z\",\n        \"duration\": \"PT0.000689931S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 329,\n          \"parentId\": 328,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.052636222Z\",\n        \"endTime\": \"2020-12-10T21:53:47.093363172Z\",\n        \"duration\": \"PT0.04072695S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 328,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"mvcResourceUrlProvider\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.context.ApplicationListener\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.052471743Z\",\n        \"endTime\": \"2020-12-10T21:53:47.097683297Z\",\n        \"duration\": \"PT0.045211554S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 335,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@18518ccf, started on Thu Dec 10 22:53:42 CET 2020]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.097927129Z\",\n        \"endTime\": \"2020-12-10T21:53:47.098164107Z\",\n        \"duration\": \"PT0.000236978S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 336,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@18518ccf, started on Thu Dec 10 22:53:42 CET 2020]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener$ConditionEvaluationReportListener@10c8f62\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.098176437Z\",\n        \"endTime\": \"2020-12-10T21:53:47.113772099Z\",\n        \"duration\": \"PT0.015595662S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 337,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@18518ccf, started on Thu Dec 10 22:53:42 CET 2020]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.ClearCachesApplicationListener@957e06\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.113793948Z\",\n        \"endTime\": \"2020-12-10T21:53:47.114213999Z\",\n        \"duration\": \"PT0.000420051S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 338,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@18518ccf, started on Thu Dec 10 22:53:42 CET 2020]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer$SharedMetadataReaderFactoryBean@344344fa\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.114218184Z\",\n        \"endTime\": \"2020-12-10T21:53:47.114278004Z\",\n        \"duration\": \"PT0.00005982S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 340,\n          \"parentId\": 339,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessionConfiguration$SessionCleanupConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.114765617Z\",\n        \"endTime\": \"2020-12-10T21:53:47.116860109Z\",\n        \"duration\": \"PT0.002094492S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 339,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@18518ccf, started on Thu Dec 10 22:53:42 CET 2020]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor@4b039c6d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.114280422Z\",\n        \"endTime\": \"2020-12-10T21:53:47.134151741Z\",\n        \"duration\": \"PT0.019871319S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 341,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@18518ccf, started on Thu Dec 10 22:53:42 CET 2020]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.134157071Z\",\n        \"endTime\": \"2020-12-10T21:53:47.134186008Z\",\n        \"duration\": \"PT0.000028937S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 342,\n          \"parentId\": 5,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@18518ccf, started on Thu Dec 10 22:53:42 CET 2020]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.web.servlet.resource.ResourceUrlProvider@697a92af\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.134187947Z\",\n        \"endTime\": \"2020-12-10T21:53:47.135285408Z\",\n        \"duration\": \"PT0.001097461S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.context.refresh\",\n          \"id\": 5,\n          \"parentId\": 0,\n          \"tags\": []\n        },\n        \"startTime\": \"2020-12-10T21:53:42.906468743Z\",\n        \"endTime\": \"2020-12-10T21:53:47.142210094Z\",\n        \"duration\": \"PT4.235741351S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 345,\n          \"parentId\": 344,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.metrics.web.tomcat.TomcatMetricsAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.144623798Z\",\n        \"endTime\": \"2020-12-10T21:53:47.145388101Z\",\n        \"duration\": \"PT0.000764303S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 344,\n          \"parentId\": 343,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"tomcatMetricsBinder\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.context.ApplicationListener\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.144577414Z\",\n        \"endTime\": \"2020-12-10T21:53:47.145972989Z\",\n        \"duration\": \"PT0.001395575S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 346,\n          \"parentId\": 343,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"org.springframework.boot.context.event.ApplicationStartedEvent[source=org.springframework.boot.SpringApplication@563172d3]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.autoconfigure.BackgroundPreinitializer@1de5f259\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.146057094Z\",\n        \"endTime\": \"2020-12-10T21:53:47.146071184Z\",\n        \"duration\": \"PT0.00001409S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 347,\n          \"parentId\": 343,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"org.springframework.boot.context.event.ApplicationStartedEvent[source=org.springframework.boot.SpringApplication@563172d3]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.146073246Z\",\n        \"endTime\": \"2020-12-10T21:53:47.146081790Z\",\n        \"duration\": \"PT0.000008544S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 348,\n          \"parentId\": 343,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"org.springframework.boot.context.event.ApplicationStartedEvent[source=org.springframework.boot.SpringApplication@563172d3]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.146082729Z\",\n        \"endTime\": \"2020-12-10T21:53:47.146087692Z\",\n        \"duration\": \"PT0.000004963S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 349,\n          \"parentId\": 343,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"org.springframework.boot.context.event.ApplicationStartedEvent[source=org.springframework.boot.SpringApplication@563172d3]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.actuate.metrics.web.tomcat.TomcatMetricsBinder@42cc420b\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.146088274Z\",\n        \"endTime\": \"2020-12-10T21:53:47.150557442Z\",\n        \"duration\": \"PT0.004469168S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 351,\n          \"parentId\": 350,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.152995878Z\",\n        \"endTime\": \"2020-12-10T21:53:47.153287652Z\",\n        \"duration\": \"PT0.000291774S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 350,\n          \"parentId\": 343,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"applicationAvailability\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.context.ApplicationListener\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.152950127Z\",\n        \"endTime\": \"2020-12-10T21:53:47.154290092Z\",\n        \"duration\": \"PT0.001339965S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 352,\n          \"parentId\": 343,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"org.springframework.boot.availability.AvailabilityChangeEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@18518ccf, started on Thu Dec 10 22:53:42 CET 2020]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.154371988Z\",\n        \"endTime\": \"2020-12-10T21:53:47.154435726Z\",\n        \"duration\": \"PT0.000063738S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 353,\n          \"parentId\": 343,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"org.springframework.boot.availability.AvailabilityChangeEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@18518ccf, started on Thu Dec 10 22:53:42 CET 2020]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.154437982Z\",\n        \"endTime\": \"2020-12-10T21:53:47.154463508Z\",\n        \"duration\": \"PT0.000025526S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 354,\n          \"parentId\": 343,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"org.springframework.boot.availability.AvailabilityChangeEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@18518ccf, started on Thu Dec 10 22:53:42 CET 2020]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.availability.ApplicationAvailabilityBean@642c72cf\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.154465373Z\",\n        \"endTime\": \"2020-12-10T21:53:47.154513987Z\",\n        \"duration\": \"PT0.000048614S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.boot.application.started\",\n          \"id\": 343,\n          \"parentId\": 0,\n          \"tags\": []\n        },\n        \"startTime\": \"2020-12-10T21:53:47.144342226Z\",\n        \"endTime\": \"2020-12-10T21:53:47.154517114Z\",\n        \"duration\": \"PT0.010174888S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 356,\n          \"parentId\": 355,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"org.springframework.boot.context.event.ApplicationReadyEvent[source=org.springframework.boot.SpringApplication@563172d3]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.admin.SpringApplicationAdminMXBeanRegistrar@77e9dca8\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.157352462Z\",\n        \"endTime\": \"2020-12-10T21:53:47.157376821Z\",\n        \"duration\": \"PT0.000024359S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 357,\n          \"parentId\": 355,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"org.springframework.boot.context.event.ApplicationReadyEvent[source=org.springframework.boot.SpringApplication@563172d3]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.autoconfigure.BackgroundPreinitializer@1de5f259\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.157382605Z\",\n        \"endTime\": \"2020-12-10T21:53:47.157404236Z\",\n        \"duration\": \"PT0.000021631S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 358,\n          \"parentId\": 355,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"org.springframework.boot.context.event.ApplicationReadyEvent[source=org.springframework.boot.SpringApplication@563172d3]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.157405958Z\",\n        \"endTime\": \"2020-12-10T21:53:47.157410838Z\",\n        \"duration\": \"PT0.00000488S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 361,\n          \"parentId\": 360,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"spring.boot.admin.client-de.codecentric.boot.admin.client.config.ClientProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.158042048Z\",\n        \"endTime\": \"2020-12-10T21:53:47.162614214Z\",\n        \"duration\": \"PT0.004572166S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 364,\n          \"parentId\": 363,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration$BlockingRegistrationClientConfig\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.163609397Z\",\n        \"endTime\": \"2020-12-10T21:53:47.163984182Z\",\n        \"duration\": \"PT0.000374785S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 363,\n          \"parentId\": 362,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"registrationClient\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.163593435Z\",\n        \"endTime\": \"2020-12-10T21:53:47.479438884Z\",\n        \"duration\": \"PT0.315845449S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 362,\n          \"parentId\": 360,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"registrator\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.163195345Z\",\n        \"endTime\": \"2020-12-10T21:53:47.481017056Z\",\n        \"duration\": \"PT0.317821711S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 360,\n          \"parentId\": 359,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"registrationListener\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.157422252Z\",\n        \"endTime\": \"2020-12-10T21:53:47.482542175Z\",\n        \"duration\": \"PT0.325119923S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 359,\n          \"parentId\": 355,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"org.springframework.boot.context.event.ApplicationReadyEvent[source=org.springframework.boot.SpringApplication@563172d3]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"public void de.codecentric.boot.admin.client.registration.RegistrationApplicationListener.onApplicationReady(org.springframework.boot.context.event.ApplicationReadyEvent)\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.157411655Z\",\n        \"endTime\": \"2020-12-10T21:53:47.482965434Z\",\n        \"duration\": \"PT0.325553779S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 365,\n          \"parentId\": 355,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"org.springframework.boot.context.event.ApplicationReadyEvent[source=org.springframework.boot.SpringApplication@563172d3]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.482969219Z\",\n        \"endTime\": \"2020-12-10T21:53:47.482974850Z\",\n        \"duration\": \"PT0.000005631S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 366,\n          \"parentId\": 355,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"org.springframework.boot.availability.AvailabilityChangeEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@18518ccf, started on Thu Dec 10 22:53:42 CET 2020]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.483280744Z\",\n        \"endTime\": \"2020-12-10T21:53:47.483331904Z\",\n        \"duration\": \"PT0.00005116S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 367,\n          \"parentId\": 355,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"org.springframework.boot.availability.AvailabilityChangeEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@18518ccf, started on Thu Dec 10 22:53:42 CET 2020]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.483334154Z\",\n        \"endTime\": \"2020-12-10T21:53:47.483350211Z\",\n        \"duration\": \"PT0.000016057S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 368,\n          \"parentId\": 355,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"org.springframework.boot.availability.AvailabilityChangeEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@18518ccf, started on Thu Dec 10 22:53:42 CET 2020]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.availability.ApplicationAvailabilityBean@642c72cf\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.483351506Z\",\n        \"endTime\": \"2020-12-10T21:53:47.483369802Z\",\n        \"duration\": \"PT0.000018296S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.boot.application.running\",\n          \"id\": 355,\n          \"parentId\": 0,\n          \"tags\": []\n        },\n        \"startTime\": \"2020-12-10T21:53:47.156946692Z\",\n        \"endTime\": \"2020-12-10T21:53:47.483372127Z\",\n        \"duration\": \"PT0.326425435S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 369,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"multipartResolver\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.web.multipart.MultipartResolver\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.658887403Z\",\n        \"endTime\": \"2020-12-10T21:53:47.660893951Z\",\n        \"duration\": \"PT0.002006548S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 370,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"localeResolver\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.web.servlet.LocaleResolver\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.660956866Z\",\n        \"endTime\": \"2020-12-10T21:53:47.662749901Z\",\n        \"duration\": \"PT0.001793035S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 371,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"themeResolver\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.web.servlet.ThemeResolver\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.662770772Z\",\n        \"endTime\": \"2020-12-10T21:53:47.663610323Z\",\n        \"duration\": \"PT0.000839551S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 373,\n          \"parentId\": 372,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"mvcContentNegotiationManager\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.664992763Z\",\n        \"endTime\": \"2020-12-10T21:53:47.669374716Z\",\n        \"duration\": \"PT0.004381953S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 374,\n          \"parentId\": 372,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"mvcConversionService\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.670379598Z\",\n        \"endTime\": \"2020-12-10T21:53:47.674221814Z\",\n        \"duration\": \"PT0.003842216S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 372,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"requestMappingHandlerMapping\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.664252029Z\",\n        \"endTime\": \"2020-12-10T21:53:47.721223497Z\",\n        \"duration\": \"PT0.056971468S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 375,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"welcomePageHandlerMapping\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.721236688Z\",\n        \"endTime\": \"2020-12-10T21:53:47.739118198Z\",\n        \"duration\": \"PT0.01788151S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 376,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"viewControllerHandlerMapping\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.739143272Z\",\n        \"endTime\": \"2020-12-10T21:53:47.741002110Z\",\n        \"duration\": \"PT0.001858838S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 377,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"beanNameHandlerMapping\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.741008644Z\",\n        \"endTime\": \"2020-12-10T21:53:47.744985260Z\",\n        \"duration\": \"PT0.003976616S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 380,\n          \"parentId\": 379,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.747164800Z\",\n        \"endTime\": \"2020-12-10T21:53:47.747780705Z\",\n        \"duration\": \"PT0.000615905S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 382,\n          \"parentId\": 381,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration$StringHttpMessageConverterConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.749025990Z\",\n        \"endTime\": \"2020-12-10T21:53:47.749232417Z\",\n        \"duration\": \"PT0.000206427S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 381,\n          \"parentId\": 379,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"stringHttpMessageConverter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.748999349Z\",\n        \"endTime\": \"2020-12-10T21:53:47.754251557Z\",\n        \"duration\": \"PT0.005252208S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 384,\n          \"parentId\": 383,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration$MappingJackson2HttpMessageConverterConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.754449369Z\",\n        \"endTime\": \"2020-12-10T21:53:47.754632440Z\",\n        \"duration\": \"PT0.000183071S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 383,\n          \"parentId\": 379,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"mappingJackson2HttpMessageConverter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.754413341Z\",\n        \"endTime\": \"2020-12-10T21:53:47.756710408Z\",\n        \"duration\": \"PT0.002297067S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 379,\n          \"parentId\": 378,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"messageConverters\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.747119100Z\",\n        \"endTime\": \"2020-12-10T21:53:47.761438170Z\",\n        \"duration\": \"PT0.01431907S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 378,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"routerFunctionMapping\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.744997982Z\",\n        \"endTime\": \"2020-12-10T21:53:47.765919788Z\",\n        \"duration\": \"PT0.020921806S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 385,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"resourceHandlerMapping\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.765944701Z\",\n        \"endTime\": \"2020-12-10T21:53:47.781276035Z\",\n        \"duration\": \"PT0.015331334S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 386,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"defaultServletHandlerMapping\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.781285944Z\",\n        \"endTime\": \"2020-12-10T21:53:47.781489015Z\",\n        \"duration\": \"PT0.000203071S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 388,\n          \"parentId\": 387,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"de.codecentric.boot.admin.server.config.AdminServerWebConfiguration$ServletRestApiConfirguation\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.781506240Z\",\n        \"endTime\": \"2020-12-10T21:53:47.781889735Z\",\n        \"duration\": \"PT0.000383495S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 387,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"adminHandlerMapping\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.781492647Z\",\n        \"endTime\": \"2020-12-10T21:53:47.806886505Z\",\n        \"duration\": \"PT0.025393858S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 390,\n          \"parentId\": 389,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.actuate.autoconfigure.endpoint.web.servlet.WebMvcEndpointManagementContextConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.807001481Z\",\n        \"endTime\": \"2020-12-10T21:53:47.808386394Z\",\n        \"duration\": \"PT0.001384913S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 391,\n          \"parentId\": 389,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"management.endpoints.web.cors-org.springframework.boot.actuate.autoconfigure.endpoint.web.CorsEndpointProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.814121585Z\",\n        \"endTime\": \"2020-12-10T21:53:47.818240787Z\",\n        \"duration\": \"PT0.004119202S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 389,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"webEndpointServletHandlerMapping\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.806921532Z\",\n        \"endTime\": \"2020-12-10T21:53:47.856959203Z\",\n        \"duration\": \"PT0.050037671S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 392,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"controllerEndpointHandlerMapping\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.856978133Z\",\n        \"endTime\": \"2020-12-10T21:53:47.872082260Z\",\n        \"duration\": \"PT0.015104127S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 394,\n          \"parentId\": 393,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"mvcValidator\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.875064486Z\",\n        \"endTime\": \"2020-12-10T21:53:47.881711050Z\",\n        \"duration\": \"PT0.006646564S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 396,\n          \"parentId\": 395,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.902143183Z\",\n        \"endTime\": \"2020-12-10T21:53:47.902581088Z\",\n        \"duration\": \"PT0.000437905S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 398,\n          \"parentId\": 397,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"spring.task.execution-org.springframework.boot.autoconfigure.task.TaskExecutionProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.904533612Z\",\n        \"endTime\": \"2020-12-10T21:53:47.906055846Z\",\n        \"duration\": \"PT0.001522234S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 397,\n          \"parentId\": 395,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"taskExecutorBuilder\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.903178276Z\",\n        \"endTime\": \"2020-12-10T21:53:47.908637176Z\",\n        \"duration\": \"PT0.0054589S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 395,\n          \"parentId\": 393,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"applicationTaskExecutor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.902092953Z\",\n        \"endTime\": \"2020-12-10T21:53:47.914377651Z\",\n        \"duration\": \"PT0.012284698S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 393,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"requestMappingHandlerAdapter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.872968090Z\",\n        \"endTime\": \"2020-12-10T21:53:47.945916101Z\",\n        \"duration\": \"PT0.072948011S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 399,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"handlerFunctionAdapter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.945924911Z\",\n        \"endTime\": \"2020-12-10T21:53:47.947072810Z\",\n        \"duration\": \"PT0.001147899S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 400,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"httpRequestHandlerAdapter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.947078047Z\",\n        \"endTime\": \"2020-12-10T21:53:47.947311974Z\",\n        \"duration\": \"PT0.000233927S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 401,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"simpleControllerHandlerAdapter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.947316613Z\",\n        \"endTime\": \"2020-12-10T21:53:47.947539290Z\",\n        \"duration\": \"PT0.000222677S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 402,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"errorAttributes\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.948040067Z\",\n        \"endTime\": \"2020-12-10T21:53:47.948995002Z\",\n        \"duration\": \"PT0.000954935S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 403,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"handlerExceptionResolver\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.948999819Z\",\n        \"endTime\": \"2020-12-10T21:53:47.955223255Z\",\n        \"duration\": \"PT0.006223436S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 404,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"viewNameTranslator\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.web.servlet.RequestToViewNameTranslator\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.955256193Z\",\n        \"endTime\": \"2020-12-10T21:53:47.956040772Z\",\n        \"duration\": \"PT0.000784579S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 406,\n          \"parentId\": 405,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.956514208Z\",\n        \"endTime\": \"2020-12-10T21:53:47.957276417Z\",\n        \"duration\": \"PT0.000762209S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 405,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"beanNameViewResolver\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.956497944Z\",\n        \"endTime\": \"2020-12-10T21:53:47.957710083Z\",\n        \"duration\": \"PT0.001212139S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 407,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"mvcViewResolver\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.957714977Z\",\n        \"endTime\": \"2020-12-10T21:53:47.959451143Z\",\n        \"duration\": \"PT0.001736166S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 408,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"defaultViewResolver\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.959458276Z\",\n        \"endTime\": \"2020-12-10T21:53:47.963628481Z\",\n        \"duration\": \"PT0.004170205S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 411,\n          \"parentId\": 410,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration$ThymeleafWebMvcConfiguration$ThymeleafViewResolverConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.965207147Z\",\n        \"endTime\": \"2020-12-10T21:53:47.965359752Z\",\n        \"duration\": \"PT0.000152605S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 412,\n          \"parentId\": 410,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"spring.thymeleaf-org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.965758917Z\",\n        \"endTime\": \"2020-12-10T21:53:47.968126268Z\",\n        \"duration\": \"PT0.002367351S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 414,\n          \"parentId\": 413,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration$ThymeleafDefaultConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.968615493Z\",\n        \"endTime\": \"2020-12-10T21:53:47.968771868Z\",\n        \"duration\": \"PT0.000156375S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 416,\n          \"parentId\": 415,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"de.codecentric.boot.admin.server.ui.config.AdminServerUiAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.975524593Z\",\n        \"endTime\": \"2020-12-10T21:53:47.976524566Z\",\n        \"duration\": \"PT0.000999973S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 415,\n          \"parentId\": 413,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"adminTemplateResolver\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.975494301Z\",\n        \"endTime\": \"2020-12-10T21:53:47.978498606Z\",\n        \"duration\": \"PT0.003004305S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 418,\n          \"parentId\": 417,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration$DefaultTemplateResolverConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.978627292Z\",\n        \"endTime\": \"2020-12-10T21:53:47.981867614Z\",\n        \"duration\": \"PT0.003240322S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 417,\n          \"parentId\": 413,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"defaultTemplateResolver\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.978551973Z\",\n        \"endTime\": \"2020-12-10T21:53:47.982242871Z\",\n        \"duration\": \"PT0.003690898S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 420,\n          \"parentId\": 419,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration$ThymeleafJava8TimeDialect\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.983080846Z\",\n        \"endTime\": \"2020-12-10T21:53:47.983215817Z\",\n        \"duration\": \"PT0.000134971S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 419,\n          \"parentId\": 413,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"java8TimeDialect\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.983056380Z\",\n        \"endTime\": \"2020-12-10T21:53:47.984112153Z\",\n        \"duration\": \"PT0.001055773S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 413,\n          \"parentId\": 410,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"templateEngine\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.968594071Z\",\n        \"endTime\": \"2020-12-10T21:53:47.985490760Z\",\n        \"duration\": \"PT0.016896689S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 410,\n          \"parentId\": 409,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"thymeleafViewResolver\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.965188095Z\",\n        \"endTime\": \"2020-12-10T21:53:47.987396255Z\",\n        \"duration\": \"PT0.02220816S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 409,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"viewResolver\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.963636460Z\",\n        \"endTime\": \"2020-12-10T21:53:47.987696295Z\",\n        \"duration\": \"PT0.024059835S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 421,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"flashMapManager\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.web.servlet.FlashMapManager\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:47.987729234Z\",\n        \"endTime\": \"2020-12-10T21:53:47.989492211Z\",\n        \"duration\": \"PT0.001762977S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 425,\n          \"parentId\": 424,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:48.006455232Z\",\n        \"endTime\": \"2020-12-10T21:53:48.006647218Z\",\n        \"duration\": \"PT0.000191986S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 424,\n          \"parentId\": 423,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"objectPostProcessor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:48.006431903Z\",\n        \"endTime\": \"2020-12-10T21:53:48.008281180Z\",\n        \"duration\": \"PT0.001849277S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 426,\n          \"parentId\": 423,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"autowiredWebSecurityConfigurersIgnoreParents\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:48.010906681Z\",\n        \"endTime\": \"2020-12-10T21:53:48.012279189Z\",\n        \"duration\": \"PT0.001372508S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 429,\n          \"parentId\": 428,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"enableGlobalAuthenticationAutowiredConfigurer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:48.027518433Z\",\n        \"endTime\": \"2020-12-10T21:53:48.029254618Z\",\n        \"duration\": \"PT0.001736185S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 430,\n          \"parentId\": 428,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"initializeUserDetailsBeanManagerConfigurer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:48.029322891Z\",\n        \"endTime\": \"2020-12-10T21:53:48.029959899Z\",\n        \"duration\": \"PT0.000637008S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 431,\n          \"parentId\": 428,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"initializeAuthenticationProviderBeanManagerConfigurer\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:48.029982709Z\",\n        \"endTime\": \"2020-12-10T21:53:48.030595239Z\",\n        \"duration\": \"PT0.00061253S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 428,\n          \"parentId\": 427,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:48.024557848Z\",\n        \"endTime\": \"2020-12-10T21:53:48.030999670Z\",\n        \"duration\": \"PT0.006441822S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 427,\n          \"parentId\": 423,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"de.codecentric.boot.admin.SecurityPermitAllConfig\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:48.015987116Z\",\n        \"endTime\": \"2020-12-10T21:53:48.032529714Z\",\n        \"duration\": \"PT0.016542598S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 423,\n          \"parentId\": 422,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:48.001287385Z\",\n        \"endTime\": \"2020-12-10T21:53:48.045923531Z\",\n        \"duration\": \"PT0.044636146S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 433,\n          \"parentId\": 432,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:48.046295568Z\",\n        \"endTime\": \"2020-12-10T21:53:48.046457882Z\",\n        \"duration\": \"PT0.000162314S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 432,\n          \"parentId\": 422,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"authenticationEventPublisher\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.security.authentication.AuthenticationEventPublisher\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:48.046262755Z\",\n        \"endTime\": \"2020-12-10T21:53:48.052581707Z\",\n        \"duration\": \"PT0.006318952S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 434,\n          \"parentId\": 422,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"authenticationManagerBuilder\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"class org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:48.053048132Z\",\n        \"endTime\": \"2020-12-10T21:53:48.055707789Z\",\n        \"duration\": \"PT0.002659657S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 435,\n          \"parentId\": 422,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.security.servlet.WebSecurityEnablerConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:48.058808080Z\",\n        \"endTime\": \"2020-12-10T21:53:48.058945859Z\",\n        \"duration\": \"PT0.000137779S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 437,\n          \"parentId\": 436,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:48.060237337Z\",\n        \"endTime\": \"2020-12-10T21:53:48.060473508Z\",\n        \"duration\": \"PT0.000236171S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 436,\n          \"parentId\": 422,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"inMemoryUserDetailsManager\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.security.core.userdetails.UserDetailsService\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:48.060216664Z\",\n        \"endTime\": \"2020-12-10T21:53:48.064268462Z\",\n        \"duration\": \"PT0.004051798S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 422,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"springSecurityFilterChain\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface javax.servlet.Filter\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:48.001246217Z\",\n        \"endTime\": \"2020-12-10T21:53:48.154521573Z\",\n        \"duration\": \"PT0.153275356S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 438,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"instancesController\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:48.186163045Z\",\n        \"endTime\": \"2020-12-10T21:53:48.188637342Z\",\n        \"duration\": \"PT0.002474297S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 439,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[274ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:48.453132955Z\",\n        \"endTime\": \"2020-12-10T21:53:48.453194743Z\",\n        \"duration\": \"PT0.000061788S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 440,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[274ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:48.453199721Z\",\n        \"endTime\": \"2020-12-10T21:53:48.453207311Z\",\n        \"duration\": \"PT0.00000759S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 441,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[17ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:48.474352219Z\",\n        \"endTime\": \"2020-12-10T21:53:48.474370051Z\",\n        \"duration\": \"PT0.000017832S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 442,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[17ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:48.474383004Z\",\n        \"endTime\": \"2020-12-10T21:53:48.474391168Z\",\n        \"duration\": \"PT0.000008164S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 443,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"applicationsController\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:50.416268478Z\",\n        \"endTime\": \"2020-12-10T21:53:50.421868315Z\",\n        \"duration\": \"PT0.005599837S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 444,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/applications]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[34ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:50.447323048Z\",\n        \"endTime\": \"2020-12-10T21:53:50.447375543Z\",\n        \"duration\": \"PT0.000052495S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 445,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/applications]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[34ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:50.447405530Z\",\n        \"endTime\": \"2020-12-10T21:53:50.447426836Z\",\n        \"duration\": \"PT0.000021306S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 446,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/applications]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[93ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:50.541949463Z\",\n        \"endTime\": \"2020-12-10T21:53:50.543540835Z\",\n        \"duration\": \"PT0.001591372S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 447,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/applications]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[93ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:50.543547002Z\",\n        \"endTime\": \"2020-12-10T21:53:50.543569297Z\",\n        \"duration\": \"PT0.000022295S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 448,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/applications]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[13ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:50.569306193Z\",\n        \"endTime\": \"2020-12-10T21:53:50.569334750Z\",\n        \"duration\": \"PT0.000028557S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 449,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/applications]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[13ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:50.569337837Z\",\n        \"endTime\": \"2020-12-10T21:53:50.569349819Z\",\n        \"duration\": \"PT0.000011982S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 450,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/assets/img/favicon-danger.png]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[32ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:51.465488296Z\",\n        \"endTime\": \"2020-12-10T21:53:51.465574009Z\",\n        \"duration\": \"PT0.000085713S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 451,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/assets/img/favicon-danger.png]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[32ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:51.465580246Z\",\n        \"endTime\": \"2020-12-10T21:53:51.465606057Z\",\n        \"duration\": \"PT0.000025811S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 452,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:57.498555303Z\",\n        \"endTime\": \"2020-12-10T21:53:57.498571809Z\",\n        \"duration\": \"PT0.000016506S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 453,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:57.498574334Z\",\n        \"endTime\": \"2020-12-10T21:53:57.498586333Z\",\n        \"duration\": \"PT0.000011999S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 454,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:57.501397327Z\",\n        \"endTime\": \"2020-12-10T21:53:57.501419490Z\",\n        \"duration\": \"PT0.000022163S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 455,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:53:57.501422870Z\",\n        \"endTime\": \"2020-12-10T21:53:57.501436886Z\",\n        \"duration\": \"PT0.000014016S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 456,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[37ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:06.986813891Z\",\n        \"endTime\": \"2020-12-10T21:54:06.986833361Z\",\n        \"duration\": \"PT0.00001947S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 457,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[37ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:06.986835773Z\",\n        \"endTime\": \"2020-12-10T21:54:06.986844292Z\",\n        \"duration\": \"PT0.000008519S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 458,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[5ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:07.040020441Z\",\n        \"endTime\": \"2020-12-10T21:54:07.040043664Z\",\n        \"duration\": \"PT0.000023223S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 459,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[5ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:07.040045859Z\",\n        \"endTime\": \"2020-12-10T21:54:07.040063414Z\",\n        \"duration\": \"PT0.000017555S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 460,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:07.076530187Z\",\n        \"endTime\": \"2020-12-10T21:54:07.076550347Z\",\n        \"duration\": \"PT0.00002016S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 461,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:07.076551947Z\",\n        \"endTime\": \"2020-12-10T21:54:07.076559476Z\",\n        \"duration\": \"PT0.000007529S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 463,\n          \"parentId\": 462,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"instanceWebClientBuilder\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:07.402049443Z\",\n        \"endTime\": \"2020-12-10T21:54:07.402167585Z\",\n        \"duration\": \"PT0.000118142S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 462,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"instancesProxyController\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:07.401856549Z\",\n        \"endTime\": \"2020-12-10T21:54:07.405663107Z\",\n        \"duration\": \"PT0.003806558S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 464,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:07.422719678Z\",\n        \"endTime\": \"2020-12-10T21:54:07.422737171Z\",\n        \"duration\": \"PT0.000017493S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 465,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:07.422738916Z\",\n        \"endTime\": \"2020-12-10T21:54:07.422785974Z\",\n        \"duration\": \"PT0.000047058S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 466,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/info]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[35ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:07.435889726Z\",\n        \"endTime\": \"2020-12-10T21:54:07.435911853Z\",\n        \"duration\": \"PT0.000022127S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 467,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/info]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[35ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:07.435914052Z\",\n        \"endTime\": \"2020-12-10T21:54:07.435922084Z\",\n        \"duration\": \"PT0.000008032S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 468,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:07.498004482Z\",\n        \"endTime\": \"2020-12-10T21:54:07.498019447Z\",\n        \"duration\": \"PT0.000014965S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 469,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:07.498021410Z\",\n        \"endTime\": \"2020-12-10T21:54:07.498028110Z\",\n        \"duration\": \"PT0.0000067S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 470,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:07.499642620Z\",\n        \"endTime\": \"2020-12-10T21:54:07.499658918Z\",\n        \"duration\": \"PT0.000016298S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 471,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:07.499660455Z\",\n        \"endTime\": \"2020-12-10T21:54:07.499667079Z\",\n        \"duration\": \"PT0.000006624S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 472,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:17.494677411Z\",\n        \"endTime\": \"2020-12-10T21:54:17.494696886Z\",\n        \"duration\": \"PT0.000019475S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 473,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:17.494700122Z\",\n        \"endTime\": \"2020-12-10T21:54:17.494710443Z\",\n        \"duration\": \"PT0.000010321S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 474,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:17.496566928Z\",\n        \"endTime\": \"2020-12-10T21:54:17.496583273Z\",\n        \"duration\": \"PT0.000016345S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 475,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:17.496585021Z\",\n        \"endTime\": \"2020-12-10T21:54:17.496592609Z\",\n        \"duration\": \"PT0.000007588S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 477,\n          \"parentId\": 476,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"uiExtensions\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:18.155574704Z\",\n        \"endTime\": \"2020-12-10T21:54:18.159466245Z\",\n        \"duration\": \"PT0.003891541S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 476,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"homeUiController\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:18.154960929Z\",\n        \"endTime\": \"2020-12-10T21:54:18.161661162Z\",\n        \"duration\": \"PT0.006700233S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 478,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"requestDataValueProcessor\"\n            },\n            {\n              \"key\": \"beanType\",\n              \"value\": \"interface org.springframework.web.servlet.support.RequestDataValueProcessor\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:18.169379874Z\",\n        \"endTime\": \"2020-12-10T21:54:18.170248855Z\",\n        \"duration\": \"PT0.000868981S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 479,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[260ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:18.413851668Z\",\n        \"endTime\": \"2020-12-10T21:54:18.413869277Z\",\n        \"duration\": \"PT0.000017609S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 480,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[260ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:18.413871196Z\",\n        \"endTime\": \"2020-12-10T21:54:18.413878554Z\",\n        \"duration\": \"PT0.000007358S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 481,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/extensions/css/custom.41bd3967.css]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:18.456829655Z\",\n        \"endTime\": \"2020-12-10T21:54:18.456863425Z\",\n        \"duration\": \"PT0.00003377S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 482,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/extensions/css/custom.41bd3967.css]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:18.456867336Z\",\n        \"endTime\": \"2020-12-10T21:54:18.456879525Z\",\n        \"duration\": \"PT0.000012189S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 483,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/extensions/js/custom.7501e234.js]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:18.497947108Z\",\n        \"endTime\": \"2020-12-10T21:54:18.497968757Z\",\n        \"duration\": \"PT0.000021649S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 484,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/extensions/js/custom.7501e234.js]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:18.497971090Z\",\n        \"endTime\": \"2020-12-10T21:54:18.497982319Z\",\n        \"duration\": \"PT0.000011229S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 485,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/assets/js/chunk-common.js]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[9ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:18.503778634Z\",\n        \"endTime\": \"2020-12-10T21:54:18.503804770Z\",\n        \"duration\": \"PT0.000026136S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 486,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/assets/js/chunk-common.js]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[9ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:18.503807302Z\",\n        \"endTime\": \"2020-12-10T21:54:18.503818013Z\",\n        \"duration\": \"PT0.000010711S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 487,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/assets/js/sba-core.js]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[31ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:18.526077876Z\",\n        \"endTime\": \"2020-12-10T21:54:18.526105515Z\",\n        \"duration\": \"PT0.000027639S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 488,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/assets/js/sba-core.js]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[31ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:18.526108239Z\",\n        \"endTime\": \"2020-12-10T21:54:18.526121023Z\",\n        \"duration\": \"PT0.000012784S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 489,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/assets/js/chunk-vendors.js]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[33ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:18.527764939Z\",\n        \"endTime\": \"2020-12-10T21:54:18.527808087Z\",\n        \"duration\": \"PT0.000043148S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 490,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/assets/js/chunk-vendors.js]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[33ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:18.527811239Z\",\n        \"endTime\": \"2020-12-10T21:54:18.527824443Z\",\n        \"duration\": \"PT0.000013204S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 491,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/sba-settings.js]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[39ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:18.534409722Z\",\n        \"endTime\": \"2020-12-10T21:54:18.534466761Z\",\n        \"duration\": \"PT0.000057039S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 492,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/sba-settings.js]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[39ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:18.534471490Z\",\n        \"endTime\": \"2020-12-10T21:54:18.534486303Z\",\n        \"duration\": \"PT0.000014813S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 493,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/assets/img/icon-spring-boot-admin.svg]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:18.800797682Z\",\n        \"endTime\": \"2020-12-10T21:54:18.800814955Z\",\n        \"duration\": \"PT0.000017273S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 494,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/assets/img/icon-spring-boot-admin.svg]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:18.800816627Z\",\n        \"endTime\": \"2020-12-10T21:54:18.800823289Z\",\n        \"duration\": \"PT0.000006662S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 495,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/applications]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:18.810323191Z\",\n        \"endTime\": \"2020-12-10T21:54:18.810340211Z\",\n        \"duration\": \"PT0.00001702S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 496,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/applications]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:18.810342664Z\",\n        \"endTime\": \"2020-12-10T21:54:18.810350370Z\",\n        \"duration\": \"PT0.000007706S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 497,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/applications]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:18.812196965Z\",\n        \"endTime\": \"2020-12-10T21:54:18.812215071Z\",\n        \"duration\": \"PT0.000018106S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 498,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/applications]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:18.812217019Z\",\n        \"endTime\": \"2020-12-10T21:54:18.812224820Z\",\n        \"duration\": \"PT0.000007801S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 499,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/applications]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:18.832227092Z\",\n        \"endTime\": \"2020-12-10T21:54:18.832242936Z\",\n        \"duration\": \"PT0.000015844S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 500,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/applications]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:18.832244821Z\",\n        \"endTime\": \"2020-12-10T21:54:18.832252464Z\",\n        \"duration\": \"PT0.000007643S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 501,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/assets/img/favicon.png]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:18.870069292Z\",\n        \"endTime\": \"2020-12-10T21:54:18.870088943Z\",\n        \"duration\": \"PT0.000019651S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 502,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/assets/img/favicon.png]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:18.870090533Z\",\n        \"endTime\": \"2020-12-10T21:54:18.870098593Z\",\n        \"duration\": \"PT0.00000806S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 503,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.125905755Z\",\n        \"endTime\": \"2020-12-10T21:54:19.125932982Z\",\n        \"duration\": \"PT0.000027227S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 505,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.125935188Z\",\n        \"endTime\": \"2020-12-10T21:54:19.125949323Z\",\n        \"duration\": \"PT0.000014135S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 504,\n          \"parentId\": 0,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.125905958Z\",\n        \"endTime\": \"2020-12-10T21:54:19.125932982Z\",\n        \"duration\": \"PT0.000027024S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 506,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.125970786Z\",\n        \"endTime\": \"2020-12-10T21:54:19.125984117Z\",\n        \"duration\": \"PT0.000013331S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 507,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[18ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.130579688Z\",\n        \"endTime\": \"2020-12-10T21:54:19.130612381Z\",\n        \"duration\": \"PT0.000032693S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 508,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[18ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.130614904Z\",\n        \"endTime\": \"2020-12-10T21:54:19.130627460Z\",\n        \"duration\": \"PT0.000012556S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 509,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/health]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[19ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.131013245Z\",\n        \"endTime\": \"2020-12-10T21:54:19.131047093Z\",\n        \"duration\": \"PT0.000033848S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 510,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/health]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[19ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.131049177Z\",\n        \"endTime\": \"2020-12-10T21:54:19.131059889Z\",\n        \"duration\": \"PT0.000010712S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 511,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.141141538Z\",\n        \"endTime\": \"2020-12-10T21:54:19.141158410Z\",\n        \"duration\": \"PT0.000016872S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 512,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.141160739Z\",\n        \"endTime\": \"2020-12-10T21:54:19.141168161Z\",\n        \"duration\": \"PT0.000007422S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 513,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/info]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[31ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.143324231Z\",\n        \"endTime\": \"2020-12-10T21:54:19.143347214Z\",\n        \"duration\": \"PT0.000022983S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 514,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/info]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[31ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.143349830Z\",\n        \"endTime\": \"2020-12-10T21:54:19.143359049Z\",\n        \"duration\": \"PT0.000009219S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 515,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/env/PID]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[18ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.207231479Z\",\n        \"endTime\": \"2020-12-10T21:54:19.207250641Z\",\n        \"duration\": \"PT0.000019162S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 516,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/env/PID]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[18ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.207252969Z\",\n        \"endTime\": \"2020-12-10T21:54:19.207261123Z\",\n        \"duration\": \"PT0.000008154S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 517,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/env/PID]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[31ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.209634707Z\",\n        \"endTime\": \"2020-12-10T21:54:19.209656977Z\",\n        \"duration\": \"PT0.00002227S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 518,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/env/PID]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[31ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.209659425Z\",\n        \"endTime\": \"2020-12-10T21:54:19.209670086Z\",\n        \"duration\": \"PT0.000010661S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 520,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/process.uptime]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[36ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.221861229Z\",\n        \"endTime\": \"2020-12-10T21:54:19.221882545Z\",\n        \"duration\": \"PT0.000021316S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 519,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/system.cpu.count]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[36ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.221860903Z\",\n        \"endTime\": \"2020-12-10T21:54:19.221882560Z\",\n        \"duration\": \"PT0.000021657S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 521,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/process.uptime]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[36ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.221884590Z\",\n        \"endTime\": \"2020-12-10T21:54:19.221899332Z\",\n        \"duration\": \"PT0.000014742S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 522,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/system.cpu.count]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[36ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.221891114Z\",\n        \"endTime\": \"2020-12-10T21:54:19.221901886Z\",\n        \"duration\": \"PT0.000010772S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 523,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.gc.pause]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[5ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.223330282Z\",\n        \"endTime\": \"2020-12-10T21:54:19.223399992Z\",\n        \"duration\": \"PT0.00006971S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 524,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.gc.pause]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[5ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.223403126Z\",\n        \"endTime\": \"2020-12-10T21:54:19.223415777Z\",\n        \"duration\": \"PT0.000012651S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 525,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/process.uptime]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[47ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.225524305Z\",\n        \"endTime\": \"2020-12-10T21:54:19.225547339Z\",\n        \"duration\": \"PT0.000023034S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 526,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/process.uptime]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[47ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.225550164Z\",\n        \"endTime\": \"2020-12-10T21:54:19.225564532Z\",\n        \"duration\": \"PT0.000014368S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 527,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/system.cpu.usage]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.225869099Z\",\n        \"endTime\": \"2020-12-10T21:54:19.225901765Z\",\n        \"duration\": \"PT0.000032666S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 528,\n          \"parentId\": 527,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/process.cpu.usage]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.225893168Z\",\n        \"endTime\": \"2020-12-10T21:54:19.225909499Z\",\n        \"duration\": \"PT0.000016331S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 529,\n          \"parentId\": 527,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/system.cpu.usage]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.225904775Z\",\n        \"endTime\": \"2020-12-10T21:54:19.225921885Z\",\n        \"duration\": \"PT0.00001711S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 530,\n          \"parentId\": 527,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/process.cpu.usage]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.225912274Z\",\n        \"endTime\": \"2020-12-10T21:54:19.225925395Z\",\n        \"duration\": \"PT0.000013121S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 531,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.gc.pause]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[17ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.229399336Z\",\n        \"endTime\": \"2020-12-10T21:54:19.229430448Z\",\n        \"duration\": \"PT0.000031112S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 532,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.gc.pause]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[17ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.229435293Z\",\n        \"endTime\": \"2020-12-10T21:54:19.229447508Z\",\n        \"duration\": \"PT0.000012215S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 533,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/system.cpu.usage]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[41ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.229711319Z\",\n        \"endTime\": \"2020-12-10T21:54:19.229729298Z\",\n        \"duration\": \"PT0.000017979S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 534,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/system.cpu.usage]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[41ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.229731390Z\",\n        \"endTime\": \"2020-12-10T21:54:19.229741687Z\",\n        \"duration\": \"PT0.000010297S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 535,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/process.cpu.usage]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[44ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.229899897Z\",\n        \"endTime\": \"2020-12-10T21:54:19.229916070Z\",\n        \"duration\": \"PT0.000016173S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 536,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/process.cpu.usage]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[44ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.229918005Z\",\n        \"endTime\": \"2020-12-10T21:54:19.229928089Z\",\n        \"duration\": \"PT0.000010084S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 539,\n          \"parentId\": 538,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$DefaultErrorViewResolverConfiguration\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.236955869Z\",\n        \"endTime\": \"2020-12-10T21:54:19.237570187Z\",\n        \"duration\": \"PT0.000614318S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 538,\n          \"parentId\": 537,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"conventionErrorViewResolver\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.236897620Z\",\n        \"endTime\": \"2020-12-10T21:54:19.238488559Z\",\n        \"duration\": \"PT0.001590939S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.beans.instantiate\",\n          \"id\": 537,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"beanName\",\n              \"value\": \"basicErrorController\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.235126639Z\",\n        \"endTime\": \"2020-12-10T21:54:19.239225322Z\",\n        \"duration\": \"PT0.004098683S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 540,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.threads.live]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.242069138Z\",\n        \"endTime\": \"2020-12-10T21:54:19.242120903Z\",\n        \"duration\": \"PT0.000051765S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 541,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.threads.live]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.242123760Z\",\n        \"endTime\": \"2020-12-10T21:54:19.242137931Z\",\n        \"duration\": \"PT0.000014171S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 542,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.threads.peak]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.244065024Z\",\n        \"endTime\": \"2020-12-10T21:54:19.244089368Z\",\n        \"duration\": \"PT0.000024344S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 543,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.threads.peak]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.244091Z\",\n        \"endTime\": \"2020-12-10T21:54:19.244098496Z\",\n        \"duration\": \"PT0.000007496S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 544,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.threads.live]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[15ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.245325498Z\",\n        \"endTime\": \"2020-12-10T21:54:19.245386481Z\",\n        \"duration\": \"PT0.000060983S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 545,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.threads.live]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[15ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.245389921Z\",\n        \"endTime\": \"2020-12-10T21:54:19.245403964Z\",\n        \"duration\": \"PT0.000014043S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 546,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.threads.peak]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[14ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.246978855Z\",\n        \"endTime\": \"2020-12-10T21:54:19.246999227Z\",\n        \"duration\": \"PT0.000020372S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 547,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.threads.peak]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[14ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.247001488Z\",\n        \"endTime\": \"2020-12-10T21:54:19.247011184Z\",\n        \"duration\": \"PT0.000009696S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 548,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/system.cpu.count]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[15ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.247215396Z\",\n        \"endTime\": \"2020-12-10T21:54:19.247237309Z\",\n        \"duration\": \"PT0.000021913S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 549,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/system.cpu.count]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[15ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.247239051Z\",\n        \"endTime\": \"2020-12-10T21:54:19.247251917Z\",\n        \"duration\": \"PT0.000012866S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 550,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.threads.daemon]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.247283177Z\",\n        \"endTime\": \"2020-12-10T21:54:19.247297912Z\",\n        \"duration\": \"PT0.000014735S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 551,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.threads.daemon]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.247299458Z\",\n        \"endTime\": \"2020-12-10T21:54:19.247308915Z\",\n        \"duration\": \"PT0.000009457S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 552,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.memory.max]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.247840093Z\",\n        \"endTime\": \"2020-12-10T21:54:19.247859248Z\",\n        \"duration\": \"PT0.000019155S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 553,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.memory.max]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.247860929Z\",\n        \"endTime\": \"2020-12-10T21:54:19.247870101Z\",\n        \"duration\": \"PT0.000009172S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 554,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/system.cpu.count]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[73ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.251377578Z\",\n        \"endTime\": \"2020-12-10T21:54:19.251400914Z\",\n        \"duration\": \"PT0.000023336S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 555,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/system.cpu.count]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[73ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.251403639Z\",\n        \"endTime\": \"2020-12-10T21:54:19.251415056Z\",\n        \"duration\": \"PT0.000011417S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 556,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.memory.max]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[16ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.252149230Z\",\n        \"endTime\": \"2020-12-10T21:54:19.252171499Z\",\n        \"duration\": \"PT0.000022269S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 557,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.memory.max]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[16ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.252173859Z\",\n        \"endTime\": \"2020-12-10T21:54:19.252183896Z\",\n        \"duration\": \"PT0.000010037S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 558,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.threads.daemon]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[15ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.252486658Z\",\n        \"endTime\": \"2020-12-10T21:54:19.252505073Z\",\n        \"duration\": \"PT0.000018415S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 559,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.threads.daemon]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[15ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.252506943Z\",\n        \"endTime\": \"2020-12-10T21:54:19.252516385Z\",\n        \"duration\": \"PT0.000009442S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 560,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.memory.max]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.260627370Z\",\n        \"endTime\": \"2020-12-10T21:54:19.260643336Z\",\n        \"duration\": \"PT0.000015966S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 560,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.memory.used]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.260627329Z\",\n        \"endTime\": \"2020-12-10T21:54:19.260645089Z\",\n        \"duration\": \"PT0.00001776S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 561,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.memory.max]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.260644927Z\",\n        \"endTime\": \"2020-12-10T21:54:19.260652980Z\",\n        \"duration\": \"PT0.000008053S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 562,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.memory.used]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.260646284Z\",\n        \"endTime\": \"2020-12-10T21:54:19.260653335Z\",\n        \"duration\": \"PT0.000007051S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 563,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.memory.max]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[12ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.262775351Z\",\n        \"endTime\": \"2020-12-10T21:54:19.262797934Z\",\n        \"duration\": \"PT0.000022583S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 564,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.memory.max]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[12ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.262800562Z\",\n        \"endTime\": \"2020-12-10T21:54:19.262810709Z\",\n        \"duration\": \"PT0.000010147S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 565,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.memory.used]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.264309235Z\",\n        \"endTime\": \"2020-12-10T21:54:19.264332278Z\",\n        \"duration\": \"PT0.000023043S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 566,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.memory.used]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.264334654Z\",\n        \"endTime\": \"2020-12-10T21:54:19.264345667Z\",\n        \"duration\": \"PT0.000011013S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 567,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.memory.used]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.264622571Z\",\n        \"endTime\": \"2020-12-10T21:54:19.264635127Z\",\n        \"duration\": \"PT0.000012556S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 568,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.memory.used]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.264636398Z\",\n        \"endTime\": \"2020-12-10T21:54:19.264642897Z\",\n        \"duration\": \"PT0.000006499S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 569,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.memory.used]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[10ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.266097373Z\",\n        \"endTime\": \"2020-12-10T21:54:19.266119621Z\",\n        \"duration\": \"PT0.000022248S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 570,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.memory.used]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[10ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.266122285Z\",\n        \"endTime\": \"2020-12-10T21:54:19.266134298Z\",\n        \"duration\": \"PT0.000012013S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 571,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.memory.used]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[16ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.266884376Z\",\n        \"endTime\": \"2020-12-10T21:54:19.266901044Z\",\n        \"duration\": \"PT0.000016668S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 572,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.memory.used]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[16ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.266902478Z\",\n        \"endTime\": \"2020-12-10T21:54:19.266909210Z\",\n        \"duration\": \"PT0.000006732S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 573,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.memory.used]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.285454500Z\",\n        \"endTime\": \"2020-12-10T21:54:19.285470352Z\",\n        \"duration\": \"PT0.000015852S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 574,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.memory.used]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.285471726Z\",\n        \"endTime\": \"2020-12-10T21:54:19.285490812Z\",\n        \"duration\": \"PT0.000019086S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 575,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.memory.committed]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.285855578Z\",\n        \"endTime\": \"2020-12-10T21:54:19.285872917Z\",\n        \"duration\": \"PT0.000017339S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 576,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.memory.committed]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.285874034Z\",\n        \"endTime\": \"2020-12-10T21:54:19.285880930Z\",\n        \"duration\": \"PT0.000006896S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 577,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.memory.used]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[7ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.286968545Z\",\n        \"endTime\": \"2020-12-10T21:54:19.287008461Z\",\n        \"duration\": \"PT0.000039916S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 578,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.memory.used]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[7ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.287011399Z\",\n        \"endTime\": \"2020-12-10T21:54:19.287027874Z\",\n        \"duration\": \"PT0.000016475S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 579,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.memory.committed]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[7ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.287256432Z\",\n        \"endTime\": \"2020-12-10T21:54:19.287272330Z\",\n        \"duration\": \"PT0.000015898S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 580,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.memory.committed]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[7ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:19.287274240Z\",\n        \"endTime\": \"2020-12-10T21:54:19.287284245Z\",\n        \"duration\": \"PT0.000010005S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 581,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/process.cpu.usage]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:21.693293342Z\",\n        \"endTime\": \"2020-12-10T21:54:21.693311813Z\",\n        \"duration\": \"PT0.000018471S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 582,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/process.cpu.usage]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:21.693313679Z\",\n        \"endTime\": \"2020-12-10T21:54:21.693320605Z\",\n        \"duration\": \"PT0.000006926S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 583,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/system.cpu.usage]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:21.694195866Z\",\n        \"endTime\": \"2020-12-10T21:54:21.694218879Z\",\n        \"duration\": \"PT0.000023013S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 584,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/system.cpu.usage]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:21.694221030Z\",\n        \"endTime\": \"2020-12-10T21:54:21.694232159Z\",\n        \"duration\": \"PT0.000011129S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 585,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/process.cpu.usage]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[10ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:21.695890629Z\",\n        \"endTime\": \"2020-12-10T21:54:21.695915345Z\",\n        \"duration\": \"PT0.000024716S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 586,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/process.cpu.usage]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[10ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:21.695917819Z\",\n        \"endTime\": \"2020-12-10T21:54:21.695929077Z\",\n        \"duration\": \"PT0.000011258S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 587,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/system.cpu.usage]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[9ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:21.695938986Z\",\n        \"endTime\": \"2020-12-10T21:54:21.695954904Z\",\n        \"duration\": \"PT0.000015918S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 588,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/system.cpu.usage]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[9ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:21.695956742Z\",\n        \"endTime\": \"2020-12-10T21:54:21.695965677Z\",\n        \"duration\": \"PT0.000008935S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 589,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.gc.pause]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:21.698153674Z\",\n        \"endTime\": \"2020-12-10T21:54:21.698176483Z\",\n        \"duration\": \"PT0.000022809S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 590,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.gc.pause]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:21.698178592Z\",\n        \"endTime\": \"2020-12-10T21:54:21.698189848Z\",\n        \"duration\": \"PT0.000011256S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 591,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.gc.pause]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[11ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:21.699950728Z\",\n        \"endTime\": \"2020-12-10T21:54:21.699968308Z\",\n        \"duration\": \"PT0.00001758S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 592,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.gc.pause]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[11ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:21.699970132Z\",\n        \"endTime\": \"2020-12-10T21:54:21.699977825Z\",\n        \"duration\": \"PT0.000007693S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 593,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.threads.live]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:21.719161716Z\",\n        \"endTime\": \"2020-12-10T21:54:21.719186889Z\",\n        \"duration\": \"PT0.000025173S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 594,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.threads.live]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:21.719189156Z\",\n        \"endTime\": \"2020-12-10T21:54:21.719200966Z\",\n        \"duration\": \"PT0.00001181S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 595,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.threads.live]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[11ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:21.721742308Z\",\n        \"endTime\": \"2020-12-10T21:54:21.721777501Z\",\n        \"duration\": \"PT0.000035193S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 596,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.threads.live]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[11ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:21.721780663Z\",\n        \"endTime\": \"2020-12-10T21:54:21.721789948Z\",\n        \"duration\": \"PT0.000009285S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 597,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.threads.peak]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:21.722211897Z\",\n        \"endTime\": \"2020-12-10T21:54:21.722226712Z\",\n        \"duration\": \"PT0.000014815S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 598,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.threads.peak]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:21.722227886Z\",\n        \"endTime\": \"2020-12-10T21:54:21.722240059Z\",\n        \"duration\": \"PT0.000012173S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 599,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.threads.peak]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[9ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:21.724249922Z\",\n        \"endTime\": \"2020-12-10T21:54:21.724277024Z\",\n        \"duration\": \"PT0.000027102S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 600,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.threads.peak]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[9ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:21.724280079Z\",\n        \"endTime\": \"2020-12-10T21:54:21.724289791Z\",\n        \"duration\": \"PT0.000009712S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 601,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.threads.daemon]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:21.754347930Z\",\n        \"endTime\": \"2020-12-10T21:54:21.754364144Z\",\n        \"duration\": \"PT0.000016214S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 602,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/metrics/jvm.threads.daemon]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:21.754365816Z\",\n        \"endTime\": \"2020-12-10T21:54:21.754372925Z\",\n        \"duration\": \"PT0.000007109S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 603,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.threads.daemon]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[6ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:21.755681594Z\",\n        \"endTime\": \"2020-12-10T21:54:21.755699298Z\",\n        \"duration\": \"PT0.000017704S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 604,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances/572038febb74/actuator/metrics/jvm.threads.daemon]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[6ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:21.755701423Z\",\n        \"endTime\": \"2020-12-10T21:54:21.755708716Z\",\n        \"duration\": \"PT0.000007293S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 605,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:26.882438923Z\",\n        \"endTime\": \"2020-12-10T21:54:26.882456648Z\",\n        \"duration\": \"PT0.000017725S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 606,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:26.882458504Z\",\n        \"endTime\": \"2020-12-10T21:54:26.882473042Z\",\n        \"duration\": \"PT0.000014538S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 607,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:27.500808160Z\",\n        \"endTime\": \"2020-12-10T21:54:27.500921637Z\",\n        \"duration\": \"PT0.000113477S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 608,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:27.500925612Z\",\n        \"endTime\": \"2020-12-10T21:54:27.500937736Z\",\n        \"duration\": \"PT0.000012124S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 609,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:27.503817514Z\",\n        \"endTime\": \"2020-12-10T21:54:27.503871671Z\",\n        \"duration\": \"PT0.000054157S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 610,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:27.503873667Z\",\n        \"endTime\": \"2020-12-10T21:54:27.503882525Z\",\n        \"duration\": \"PT0.000008858S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 611,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:37.494905321Z\",\n        \"endTime\": \"2020-12-10T21:54:37.494921464Z\",\n        \"duration\": \"PT0.000016143S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 612,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:37.494923836Z\",\n        \"endTime\": \"2020-12-10T21:54:37.494932114Z\",\n        \"duration\": \"PT0.000008278S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 613,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:37.496266916Z\",\n        \"endTime\": \"2020-12-10T21:54:37.496279430Z\",\n        \"duration\": \"PT0.000012514S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 614,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:37.496280683Z\",\n        \"endTime\": \"2020-12-10T21:54:37.496287425Z\",\n        \"duration\": \"PT0.000006742S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 615,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[9ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:46.896425787Z\",\n        \"endTime\": \"2020-12-10T21:54:46.896470793Z\",\n        \"duration\": \"PT0.000045006S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 616,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[9ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:46.896473972Z\",\n        \"endTime\": \"2020-12-10T21:54:46.896491747Z\",\n        \"duration\": \"PT0.000017775S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 617,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:47.500784501Z\",\n        \"endTime\": \"2020-12-10T21:54:47.500799266Z\",\n        \"duration\": \"PT0.000014765S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 618,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:47.500801679Z\",\n        \"endTime\": \"2020-12-10T21:54:47.500808304Z\",\n        \"duration\": \"PT0.000006625S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 619,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:47.502129894Z\",\n        \"endTime\": \"2020-12-10T21:54:47.502146408Z\",\n        \"duration\": \"PT0.000016514S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 620,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:47.502148192Z\",\n        \"endTime\": \"2020-12-10T21:54:47.502154661Z\",\n        \"duration\": \"PT0.000006469S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 621,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:47.668386658Z\",\n        \"endTime\": \"2020-12-10T21:54:47.668400781Z\",\n        \"duration\": \"PT0.000014123S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 622,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:47.668402068Z\",\n        \"endTime\": \"2020-12-10T21:54:47.668407996Z\",\n        \"duration\": \"PT0.000005928S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 623,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/extensions/css/custom.41bd3967.css]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:47.715156853Z\",\n        \"endTime\": \"2020-12-10T21:54:47.715192332Z\",\n        \"duration\": \"PT0.000035479S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 624,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/extensions/css/custom.41bd3967.css]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:47.715195067Z\",\n        \"endTime\": \"2020-12-10T21:54:47.715204900Z\",\n        \"duration\": \"PT0.000009833S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 625,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/extensions/js/custom.7501e234.js]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:47.722258991Z\",\n        \"endTime\": \"2020-12-10T21:54:47.722316768Z\",\n        \"duration\": \"PT0.000057777S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 626,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/extensions/js/custom.7501e234.js]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:47.722319432Z\",\n        \"endTime\": \"2020-12-10T21:54:47.722337654Z\",\n        \"duration\": \"PT0.000018222S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 627,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/assets/js/chunk-common.js]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:47.728469276Z\",\n        \"endTime\": \"2020-12-10T21:54:47.728490359Z\",\n        \"duration\": \"PT0.000021083S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 628,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/assets/js/chunk-common.js]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:47.728492442Z\",\n        \"endTime\": \"2020-12-10T21:54:47.728501355Z\",\n        \"duration\": \"PT0.000008913S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 629,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/sba-settings.js]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[5ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:47.729628538Z\",\n        \"endTime\": \"2020-12-10T21:54:47.729648756Z\",\n        \"duration\": \"PT0.000020218S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 630,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/sba-settings.js]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[5ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:47.729650744Z\",\n        \"endTime\": \"2020-12-10T21:54:47.729659814Z\",\n        \"duration\": \"PT0.00000907S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 631,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/assets/js/sba-core.js]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[34ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:47.759525248Z\",\n        \"endTime\": \"2020-12-10T21:54:47.759551200Z\",\n        \"duration\": \"PT0.000025952S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 632,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/assets/js/sba-core.js]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[34ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:47.759554281Z\",\n        \"endTime\": \"2020-12-10T21:54:47.759564496Z\",\n        \"duration\": \"PT0.000010215S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 633,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/assets/js/chunk-vendors.js]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[39ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:47.765466831Z\",\n        \"endTime\": \"2020-12-10T21:54:47.765493361Z\",\n        \"duration\": \"PT0.00002653S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 634,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/assets/js/chunk-vendors.js]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[39ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:47.765495850Z\",\n        \"endTime\": \"2020-12-10T21:54:47.765504614Z\",\n        \"duration\": \"PT0.000008764S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 635,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/assets/img/icon-spring-boot-admin.svg]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:48.036915621Z\",\n        \"endTime\": \"2020-12-10T21:54:48.036934388Z\",\n        \"duration\": \"PT0.000018767S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 636,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/assets/img/icon-spring-boot-admin.svg]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:48.036936160Z\",\n        \"endTime\": \"2020-12-10T21:54:48.036943714Z\",\n        \"duration\": \"PT0.000007554S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 637,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/applications]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:48.045918303Z\",\n        \"endTime\": \"2020-12-10T21:54:48.045938175Z\",\n        \"duration\": \"PT0.000019872S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 638,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/applications]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:48.045941350Z\",\n        \"endTime\": \"2020-12-10T21:54:48.045949658Z\",\n        \"duration\": \"PT0.000008308S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 639,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/applications]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:48.047550018Z\",\n        \"endTime\": \"2020-12-10T21:54:48.047564162Z\",\n        \"duration\": \"PT0.000014144S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 640,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/applications]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:48.047565468Z\",\n        \"endTime\": \"2020-12-10T21:54:48.047571193Z\",\n        \"duration\": \"PT0.000005725S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 641,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/applications]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:48.088792448Z\",\n        \"endTime\": \"2020-12-10T21:54:48.088808160Z\",\n        \"duration\": \"PT0.000015712S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 642,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/applications]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:48.088809667Z\",\n        \"endTime\": \"2020-12-10T21:54:48.088815843Z\",\n        \"duration\": \"PT0.000006176S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 643,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/assets/img/favicon.png]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:48.098445887Z\",\n        \"endTime\": \"2020-12-10T21:54:48.098461403Z\",\n        \"duration\": \"PT0.000015516S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 644,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/assets/img/favicon.png]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:48.098463485Z\",\n        \"endTime\": \"2020-12-10T21:54:48.098470898Z\",\n        \"duration\": \"PT0.000007413S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 645,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:57.493675604Z\",\n        \"endTime\": \"2020-12-10T21:54:57.493690649Z\",\n        \"duration\": \"PT0.000015045S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 646,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:57.493693483Z\",\n        \"endTime\": \"2020-12-10T21:54:57.493700344Z\",\n        \"duration\": \"PT0.000006861S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 647,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:57.494942514Z\",\n        \"endTime\": \"2020-12-10T21:54:57.494955728Z\",\n        \"duration\": \"PT0.000013214S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 648,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:54:57.494956905Z\",\n        \"endTime\": \"2020-12-10T21:54:57.494963035Z\",\n        \"duration\": \"PT0.00000613S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 649,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:06.882038848Z\",\n        \"endTime\": \"2020-12-10T21:55:06.882057112Z\",\n        \"duration\": \"PT0.000018264S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 650,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:06.882058999Z\",\n        \"endTime\": \"2020-12-10T21:55:06.882066324Z\",\n        \"duration\": \"PT0.000007325S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 651,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:07.497825107Z\",\n        \"endTime\": \"2020-12-10T21:55:07.497849338Z\",\n        \"duration\": \"PT0.000024231S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 652,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:07.497852066Z\",\n        \"endTime\": \"2020-12-10T21:55:07.497860478Z\",\n        \"duration\": \"PT0.000008412S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 653,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:07.499318393Z\",\n        \"endTime\": \"2020-12-10T21:55:07.499344739Z\",\n        \"duration\": \"PT0.000026346S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 654,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:07.499346518Z\",\n        \"endTime\": \"2020-12-10T21:55:07.499353923Z\",\n        \"duration\": \"PT0.000007405S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 655,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:17.494212274Z\",\n        \"endTime\": \"2020-12-10T21:55:17.494229920Z\",\n        \"duration\": \"PT0.000017646S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 656,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:17.494232458Z\",\n        \"endTime\": \"2020-12-10T21:55:17.494240053Z\",\n        \"duration\": \"PT0.000007595S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 657,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:17.495745322Z\",\n        \"endTime\": \"2020-12-10T21:55:17.495762407Z\",\n        \"duration\": \"PT0.000017085S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 658,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:17.495764078Z\",\n        \"endTime\": \"2020-12-10T21:55:17.495771824Z\",\n        \"duration\": \"PT0.000007746S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 659,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:26.886506503Z\",\n        \"endTime\": \"2020-12-10T21:55:26.886528483Z\",\n        \"duration\": \"PT0.00002198S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 660,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:26.886530026Z\",\n        \"endTime\": \"2020-12-10T21:55:26.886537945Z\",\n        \"duration\": \"PT0.000007919S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 661,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:27.504243835Z\",\n        \"endTime\": \"2020-12-10T21:55:27.504283989Z\",\n        \"duration\": \"PT0.000040154S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 662,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:27.504286674Z\",\n        \"endTime\": \"2020-12-10T21:55:27.504303239Z\",\n        \"duration\": \"PT0.000016565S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 663,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:27.505467549Z\",\n        \"endTime\": \"2020-12-10T21:55:27.505478383Z\",\n        \"duration\": \"PT0.000010834S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 664,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:27.505479613Z\",\n        \"endTime\": \"2020-12-10T21:55:27.505485657Z\",\n        \"duration\": \"PT0.000006044S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 665,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:37.492753704Z\",\n        \"endTime\": \"2020-12-10T21:55:37.492767580Z\",\n        \"duration\": \"PT0.000013876S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 666,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:37.492769626Z\",\n        \"endTime\": \"2020-12-10T21:55:37.492777015Z\",\n        \"duration\": \"PT0.000007389S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 667,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:37.493969883Z\",\n        \"endTime\": \"2020-12-10T21:55:37.493980563Z\",\n        \"duration\": \"PT0.00001068S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 668,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:37.493981680Z\",\n        \"endTime\": \"2020-12-10T21:55:37.493987638Z\",\n        \"duration\": \"PT0.000005958S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 669,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:46.885118614Z\",\n        \"endTime\": \"2020-12-10T21:55:46.885135608Z\",\n        \"duration\": \"PT0.000016994S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 670,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:46.885137637Z\",\n        \"endTime\": \"2020-12-10T21:55:46.885145604Z\",\n        \"duration\": \"PT0.000007967S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 671,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:46.890841879Z\",\n        \"endTime\": \"2020-12-10T21:55:46.890855185Z\",\n        \"duration\": \"PT0.000013306S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 672,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:46.890856546Z\",\n        \"endTime\": \"2020-12-10T21:55:46.890862374Z\",\n        \"duration\": \"PT0.000005828S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 673,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:47.502003260Z\",\n        \"endTime\": \"2020-12-10T21:55:47.502018117Z\",\n        \"duration\": \"PT0.000014857S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 674,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:47.502020823Z\",\n        \"endTime\": \"2020-12-10T21:55:47.502028223Z\",\n        \"duration\": \"PT0.0000074S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 675,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:47.503439671Z\",\n        \"endTime\": \"2020-12-10T21:55:47.503457818Z\",\n        \"duration\": \"PT0.000018147S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 676,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:47.503459105Z\",\n        \"endTime\": \"2020-12-10T21:55:47.503466031Z\",\n        \"duration\": \"PT0.000006926S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 677,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:57.494691467Z\",\n        \"endTime\": \"2020-12-10T21:55:57.494730539Z\",\n        \"duration\": \"PT0.000039072S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 678,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:57.494733062Z\",\n        \"endTime\": \"2020-12-10T21:55:57.494747026Z\",\n        \"duration\": \"PT0.000013964S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 679,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:57.495895450Z\",\n        \"endTime\": \"2020-12-10T21:55:57.495904568Z\",\n        \"duration\": \"PT0.000009118S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 680,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:55:57.495905516Z\",\n        \"endTime\": \"2020-12-10T21:55:57.495910171Z\",\n        \"duration\": \"PT0.000004655S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 681,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:06.885993268Z\",\n        \"endTime\": \"2020-12-10T21:56:06.886039858Z\",\n        \"duration\": \"PT0.00004659S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 682,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:06.886043395Z\",\n        \"endTime\": \"2020-12-10T21:56:06.886054065Z\",\n        \"duration\": \"PT0.00001067S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 683,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:07.496408552Z\",\n        \"endTime\": \"2020-12-10T21:56:07.496423081Z\",\n        \"duration\": \"PT0.000014529S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 684,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:07.496425061Z\",\n        \"endTime\": \"2020-12-10T21:56:07.496431848Z\",\n        \"duration\": \"PT0.000006787S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 685,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:07.497537167Z\",\n        \"endTime\": \"2020-12-10T21:56:07.497548443Z\",\n        \"duration\": \"PT0.000011276S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 686,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:07.497549626Z\",\n        \"endTime\": \"2020-12-10T21:56:07.497556704Z\",\n        \"duration\": \"PT0.000007078S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 687,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:17.497794404Z\",\n        \"endTime\": \"2020-12-10T21:56:17.497809433Z\",\n        \"duration\": \"PT0.000015029S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 688,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:17.497812115Z\",\n        \"endTime\": \"2020-12-10T21:56:17.497819066Z\",\n        \"duration\": \"PT0.000006951S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 689,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:17.499395505Z\",\n        \"endTime\": \"2020-12-10T21:56:17.499413826Z\",\n        \"duration\": \"PT0.000018321S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 690,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:17.499415936Z\",\n        \"endTime\": \"2020-12-10T21:56:17.499428159Z\",\n        \"duration\": \"PT0.000012223S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 691,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/applications]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:21.705334741Z\",\n        \"endTime\": \"2020-12-10T21:56:21.705347641Z\",\n        \"duration\": \"PT0.0000129S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 692,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/applications]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:21.705349658Z\",\n        \"endTime\": \"2020-12-10T21:56:21.705356097Z\",\n        \"duration\": \"PT0.000006439S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 693,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/applications]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:21.706739782Z\",\n        \"endTime\": \"2020-12-10T21:56:21.706750176Z\",\n        \"duration\": \"PT0.000010394S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 694,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/applications]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:21.706751185Z\",\n        \"endTime\": \"2020-12-10T21:56:21.706756199Z\",\n        \"duration\": \"PT0.000005014S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 695,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/applications]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:21.722153492Z\",\n        \"endTime\": \"2020-12-10T21:56:21.722165923Z\",\n        \"duration\": \"PT0.000012431S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 696,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/applications]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:21.722167668Z\",\n        \"endTime\": \"2020-12-10T21:56:21.722173165Z\",\n        \"duration\": \"PT0.000005497S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 697,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:26.886451644Z\",\n        \"endTime\": \"2020-12-10T21:56:26.886466218Z\",\n        \"duration\": \"PT0.000014574S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 698,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:26.886467803Z\",\n        \"endTime\": \"2020-12-10T21:56:26.886474908Z\",\n        \"duration\": \"PT0.000007105S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 699,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:27.516255502Z\",\n        \"endTime\": \"2020-12-10T21:56:27.516270074Z\",\n        \"duration\": \"PT0.000014572S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 700,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:27.516272124Z\",\n        \"endTime\": \"2020-12-10T21:56:27.516279103Z\",\n        \"duration\": \"PT0.000006979S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 701,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:27.517514973Z\",\n        \"endTime\": \"2020-12-10T21:56:27.517527429Z\",\n        \"duration\": \"PT0.000012456S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 702,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:27.517528960Z\",\n        \"endTime\": \"2020-12-10T21:56:27.517540886Z\",\n        \"duration\": \"PT0.000011926S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 703,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:37.496673688Z\",\n        \"endTime\": \"2020-12-10T21:56:37.496785126Z\",\n        \"duration\": \"PT0.000111438S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 704,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:37.496787615Z\",\n        \"endTime\": \"2020-12-10T21:56:37.496794945Z\",\n        \"duration\": \"PT0.00000733S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 705,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:37.497992249Z\",\n        \"endTime\": \"2020-12-10T21:56:37.498004431Z\",\n        \"duration\": \"PT0.000012182S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 706,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:37.498005671Z\",\n        \"endTime\": \"2020-12-10T21:56:37.498065395Z\",\n        \"duration\": \"PT0.000059724S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 707,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:46.881176730Z\",\n        \"endTime\": \"2020-12-10T21:56:46.881188986Z\",\n        \"duration\": \"PT0.000012256S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 708,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:46.881190699Z\",\n        \"endTime\": \"2020-12-10T21:56:46.881196186Z\",\n        \"duration\": \"PT0.000005487S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 709,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:46.888446097Z\",\n        \"endTime\": \"2020-12-10T21:56:46.888454219Z\",\n        \"duration\": \"PT0.000008122S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 710,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:46.888455576Z\",\n        \"endTime\": \"2020-12-10T21:56:46.888459039Z\",\n        \"duration\": \"PT0.000003463S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 711,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:47.502777686Z\",\n        \"endTime\": \"2020-12-10T21:56:47.502788814Z\",\n        \"duration\": \"PT0.000011128S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 712,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:47.502790856Z\",\n        \"endTime\": \"2020-12-10T21:56:47.502795839Z\",\n        \"duration\": \"PT0.000004983S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 713,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:47.503897855Z\",\n        \"endTime\": \"2020-12-10T21:56:47.503906553Z\",\n        \"duration\": \"PT0.000008698S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 714,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:47.503907964Z\",\n        \"endTime\": \"2020-12-10T21:56:47.503912756Z\",\n        \"duration\": \"PT0.000004792S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 715,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:56.887939332Z\",\n        \"endTime\": \"2020-12-10T21:56:56.887950171Z\",\n        \"duration\": \"PT0.000010839S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 716,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:56.887951880Z\",\n        \"endTime\": \"2020-12-10T21:56:56.887957569Z\",\n        \"duration\": \"PT0.000005689S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 717,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:57.502631004Z\",\n        \"endTime\": \"2020-12-10T21:56:57.502641139Z\",\n        \"duration\": \"PT0.000010135S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 718,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:57.502643011Z\",\n        \"endTime\": \"2020-12-10T21:56:57.502648474Z\",\n        \"duration\": \"PT0.000005463S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 719,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:57.503721912Z\",\n        \"endTime\": \"2020-12-10T21:56:57.503730504Z\",\n        \"duration\": \"PT0.000008592S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 720,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:56:57.503731487Z\",\n        \"endTime\": \"2020-12-10T21:56:57.503735526Z\",\n        \"duration\": \"PT0.000004039S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 721,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:07.504540072Z\",\n        \"endTime\": \"2020-12-10T21:57:07.504566898Z\",\n        \"duration\": \"PT0.000026826S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 722,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:07.504571620Z\",\n        \"endTime\": \"2020-12-10T21:57:07.504583832Z\",\n        \"duration\": \"PT0.000012212S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 723,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:07.507594802Z\",\n        \"endTime\": \"2020-12-10T21:57:07.507616947Z\",\n        \"duration\": \"PT0.000022145S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 724,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:07.507620415Z\",\n        \"endTime\": \"2020-12-10T21:57:07.507632745Z\",\n        \"duration\": \"PT0.00001233S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 725,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:16.890175069Z\",\n        \"endTime\": \"2020-12-10T21:57:16.890201976Z\",\n        \"duration\": \"PT0.000026907S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 726,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:16.890206377Z\",\n        \"endTime\": \"2020-12-10T21:57:16.890219502Z\",\n        \"duration\": \"PT0.000013125S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 727,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:17.507762695Z\",\n        \"endTime\": \"2020-12-10T21:57:17.507788456Z\",\n        \"duration\": \"PT0.000025761S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 728,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:17.507794485Z\",\n        \"endTime\": \"2020-12-10T21:57:17.507808007Z\",\n        \"duration\": \"PT0.000013522S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 729,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[55ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:17.563527525Z\",\n        \"endTime\": \"2020-12-10T21:57:17.563556463Z\",\n        \"duration\": \"PT0.000028938S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 730,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[55ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:17.563561110Z\",\n        \"endTime\": \"2020-12-10T21:57:17.563575524Z\",\n        \"duration\": \"PT0.000014414S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 731,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:27.500863976Z\",\n        \"endTime\": \"2020-12-10T21:57:27.500888929Z\",\n        \"duration\": \"PT0.000024953S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 732,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:27.500893893Z\",\n        \"endTime\": \"2020-12-10T21:57:27.500906594Z\",\n        \"duration\": \"PT0.000012701S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 733,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:27.503886966Z\",\n        \"endTime\": \"2020-12-10T21:57:27.503909848Z\",\n        \"duration\": \"PT0.000022882S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 734,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:27.503912918Z\",\n        \"endTime\": \"2020-12-10T21:57:27.503924936Z\",\n        \"duration\": \"PT0.000012018S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 735,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:36.890368232Z\",\n        \"endTime\": \"2020-12-10T21:57:36.890394663Z\",\n        \"duration\": \"PT0.000026431S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 736,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:36.890398300Z\",\n        \"endTime\": \"2020-12-10T21:57:36.890425011Z\",\n        \"duration\": \"PT0.000026711S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 737,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:37.514939560Z\",\n        \"endTime\": \"2020-12-10T21:57:37.514976131Z\",\n        \"duration\": \"PT0.000036571S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 738,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:37.514982449Z\",\n        \"endTime\": \"2020-12-10T21:57:37.514995769Z\",\n        \"duration\": \"PT0.00001332S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 739,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:37.518099610Z\",\n        \"endTime\": \"2020-12-10T21:57:37.518124128Z\",\n        \"duration\": \"PT0.000024518S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 740,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:37.518128087Z\",\n        \"endTime\": \"2020-12-10T21:57:37.518149202Z\",\n        \"duration\": \"PT0.000021115S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 741,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:46.905986584Z\",\n        \"endTime\": \"2020-12-10T21:57:46.905996199Z\",\n        \"duration\": \"PT0.000009615S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 742,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:46.905997696Z\",\n        \"endTime\": \"2020-12-10T21:57:46.906001796Z\",\n        \"duration\": \"PT0.0000041S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 743,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:47.052268487Z\",\n        \"endTime\": \"2020-12-10T21:57:47.052278064Z\",\n        \"duration\": \"PT0.000009577S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 744,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:47.052279259Z\",\n        \"endTime\": \"2020-12-10T21:57:47.052283439Z\",\n        \"duration\": \"PT0.00000418S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 745,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:47.557167021Z\",\n        \"endTime\": \"2020-12-10T21:57:47.557177993Z\",\n        \"duration\": \"PT0.000010972S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 746,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:47.557179784Z\",\n        \"endTime\": \"2020-12-10T21:57:47.557185010Z\",\n        \"duration\": \"PT0.000005226S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 747,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:47.558115415Z\",\n        \"endTime\": \"2020-12-10T21:57:47.558121542Z\",\n        \"duration\": \"PT0.000006127S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 748,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:47.558122435Z\",\n        \"endTime\": \"2020-12-10T21:57:47.558125973Z\",\n        \"duration\": \"PT0.000003538S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 749,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:57.505192840Z\",\n        \"endTime\": \"2020-12-10T21:57:57.505218326Z\",\n        \"duration\": \"PT0.000025486S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 750,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:57.505223353Z\",\n        \"endTime\": \"2020-12-10T21:57:57.505236182Z\",\n        \"duration\": \"PT0.000012829S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 751,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:57.508076110Z\",\n        \"endTime\": \"2020-12-10T21:57:57.508097432Z\",\n        \"duration\": \"PT0.000021322S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 752,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:57:57.508102211Z\",\n        \"endTime\": \"2020-12-10T21:57:57.508114148Z\",\n        \"duration\": \"PT0.000011937S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 753,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:06.896607428Z\",\n        \"endTime\": \"2020-12-10T21:58:06.896634345Z\",\n        \"duration\": \"PT0.000026917S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 754,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:06.896638297Z\",\n        \"endTime\": \"2020-12-10T21:58:06.896665401Z\",\n        \"duration\": \"PT0.000027104S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 755,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:07.560510387Z\",\n        \"endTime\": \"2020-12-10T21:58:07.560537118Z\",\n        \"duration\": \"PT0.000026731S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 756,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:07.560542019Z\",\n        \"endTime\": \"2020-12-10T21:58:07.560565195Z\",\n        \"duration\": \"PT0.000023176S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 757,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:07.563334873Z\",\n        \"endTime\": \"2020-12-10T21:58:07.563357769Z\",\n        \"duration\": \"PT0.000022896S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 758,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:07.563361110Z\",\n        \"endTime\": \"2020-12-10T21:58:07.563373266Z\",\n        \"duration\": \"PT0.000012156S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 759,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:17.504008917Z\",\n        \"endTime\": \"2020-12-10T21:58:17.504044594Z\",\n        \"duration\": \"PT0.000035677S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 760,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:17.504049483Z\",\n        \"endTime\": \"2020-12-10T21:58:17.504062637Z\",\n        \"duration\": \"PT0.000013154S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 761,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:17.506968910Z\",\n        \"endTime\": \"2020-12-10T21:58:17.506990091Z\",\n        \"duration\": \"PT0.000021181S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 762,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:17.506993193Z\",\n        \"endTime\": \"2020-12-10T21:58:17.507017688Z\",\n        \"duration\": \"PT0.000024495S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 763,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[7ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:26.898961704Z\",\n        \"endTime\": \"2020-12-10T21:58:26.898990313Z\",\n        \"duration\": \"PT0.000028609S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 764,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[7ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:26.898995473Z\",\n        \"endTime\": \"2020-12-10T21:58:26.899009465Z\",\n        \"duration\": \"PT0.000013992S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 765,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:27.513525879Z\",\n        \"endTime\": \"2020-12-10T21:58:27.513550783Z\",\n        \"duration\": \"PT0.000024904S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 766,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:27.513555619Z\",\n        \"endTime\": \"2020-12-10T21:58:27.513569170Z\",\n        \"duration\": \"PT0.000013551S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 767,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:27.516300102Z\",\n        \"endTime\": \"2020-12-10T21:58:27.516323187Z\",\n        \"duration\": \"PT0.000023085S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 768,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:27.516326595Z\",\n        \"endTime\": \"2020-12-10T21:58:27.516338090Z\",\n        \"duration\": \"PT0.000011495S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 769,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:36.971143763Z\",\n        \"endTime\": \"2020-12-10T21:58:36.971153763Z\",\n        \"duration\": \"PT0.00001S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 770,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:36.971155135Z\",\n        \"endTime\": \"2020-12-10T21:58:36.971160143Z\",\n        \"duration\": \"PT0.000005008S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 771,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:37.818808480Z\",\n        \"endTime\": \"2020-12-10T21:58:37.818816284Z\",\n        \"duration\": \"PT0.000007804S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 772,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:37.818818068Z\",\n        \"endTime\": \"2020-12-10T21:58:37.818821330Z\",\n        \"duration\": \"PT0.000003262S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 773,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:37.819589951Z\",\n        \"endTime\": \"2020-12-10T21:58:37.819595949Z\",\n        \"duration\": \"PT0.000005998S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 774,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:37.819596711Z\",\n        \"endTime\": \"2020-12-10T21:58:37.819599741Z\",\n        \"duration\": \"PT0.00000303S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 775,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[5ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:46.897675942Z\",\n        \"endTime\": \"2020-12-10T21:58:46.897704176Z\",\n        \"duration\": \"PT0.000028234S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 776,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[5ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:46.897707900Z\",\n        \"endTime\": \"2020-12-10T21:58:46.897721141Z\",\n        \"duration\": \"PT0.000013241S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 777,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:47.507999968Z\",\n        \"endTime\": \"2020-12-10T21:58:47.508026711Z\",\n        \"duration\": \"PT0.000026743S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 778,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:47.508031727Z\",\n        \"endTime\": \"2020-12-10T21:58:47.508045332Z\",\n        \"duration\": \"PT0.000013605S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 779,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:47.510862094Z\",\n        \"endTime\": \"2020-12-10T21:58:47.510883528Z\",\n        \"duration\": \"PT0.000021434S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 780,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:47.510886673Z\",\n        \"endTime\": \"2020-12-10T21:58:47.510898952Z\",\n        \"duration\": \"PT0.000012279S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 781,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:56.891416873Z\",\n        \"endTime\": \"2020-12-10T21:58:56.891457729Z\",\n        \"duration\": \"PT0.000040856S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 782,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:56.891462419Z\",\n        \"endTime\": \"2020-12-10T21:58:56.891476532Z\",\n        \"duration\": \"PT0.000014113S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 783,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:57.511872021Z\",\n        \"endTime\": \"2020-12-10T21:58:57.511896467Z\",\n        \"duration\": \"PT0.000024446S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 784,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:57.511901136Z\",\n        \"endTime\": \"2020-12-10T21:58:57.511913808Z\",\n        \"duration\": \"PT0.000012672S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 785,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:57.514517067Z\",\n        \"endTime\": \"2020-12-10T21:58:57.514538436Z\",\n        \"duration\": \"PT0.000021369S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 786,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:58:57.514541305Z\",\n        \"endTime\": \"2020-12-10T21:58:57.514552700Z\",\n        \"duration\": \"PT0.000011395S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 787,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:07.507658188Z\",\n        \"endTime\": \"2020-12-10T21:59:07.507683933Z\",\n        \"duration\": \"PT0.000025745S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 788,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:07.507688724Z\",\n        \"endTime\": \"2020-12-10T21:59:07.507701142Z\",\n        \"duration\": \"PT0.000012418S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 789,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:07.510407254Z\",\n        \"endTime\": \"2020-12-10T21:59:07.510428335Z\",\n        \"duration\": \"PT0.000021081S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 790,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:07.510431433Z\",\n        \"endTime\": \"2020-12-10T21:59:07.510444085Z\",\n        \"duration\": \"PT0.000012652S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 791,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:16.890473383Z\",\n        \"endTime\": \"2020-12-10T21:59:16.890500339Z\",\n        \"duration\": \"PT0.000026956S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 792,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:16.890503643Z\",\n        \"endTime\": \"2020-12-10T21:59:16.890516467Z\",\n        \"duration\": \"PT0.000012824S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 793,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:17.514437479Z\",\n        \"endTime\": \"2020-12-10T21:59:17.514462629Z\",\n        \"duration\": \"PT0.00002515S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 794,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:17.514467308Z\",\n        \"endTime\": \"2020-12-10T21:59:17.514479258Z\",\n        \"duration\": \"PT0.00001195S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 795,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:17.517176555Z\",\n        \"endTime\": \"2020-12-10T21:59:17.517197686Z\",\n        \"duration\": \"PT0.000021131S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 796,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:17.517200738Z\",\n        \"endTime\": \"2020-12-10T21:59:17.517213558Z\",\n        \"duration\": \"PT0.00001282S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 797,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:27.506403526Z\",\n        \"endTime\": \"2020-12-10T21:59:27.506429625Z\",\n        \"duration\": \"PT0.000026099S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 798,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:27.506434262Z\",\n        \"endTime\": \"2020-12-10T21:59:27.506446972Z\",\n        \"duration\": \"PT0.00001271S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 799,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:27.509354595Z\",\n        \"endTime\": \"2020-12-10T21:59:27.509375892Z\",\n        \"duration\": \"PT0.000021297S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 800,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:27.509379262Z\",\n        \"endTime\": \"2020-12-10T21:59:27.509400105Z\",\n        \"duration\": \"PT0.000020843S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 801,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:36.890912964Z\",\n        \"endTime\": \"2020-12-10T21:59:36.890940218Z\",\n        \"duration\": \"PT0.000027254S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 802,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:36.890943922Z\",\n        \"endTime\": \"2020-12-10T21:59:36.890957533Z\",\n        \"duration\": \"PT0.000013611S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 803,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:37.502097787Z\",\n        \"endTime\": \"2020-12-10T21:59:37.502123054Z\",\n        \"duration\": \"PT0.000025267S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 804,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:37.502127568Z\",\n        \"endTime\": \"2020-12-10T21:59:37.502140707Z\",\n        \"duration\": \"PT0.000013139S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 805,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:37.504773003Z\",\n        \"endTime\": \"2020-12-10T21:59:37.504795803Z\",\n        \"duration\": \"PT0.0000228S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 806,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:37.504799232Z\",\n        \"endTime\": \"2020-12-10T21:59:37.504810468Z\",\n        \"duration\": \"PT0.000011236S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 807,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:47.500575851Z\",\n        \"endTime\": \"2020-12-10T21:59:47.500610046Z\",\n        \"duration\": \"PT0.000034195S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 808,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:47.500615060Z\",\n        \"endTime\": \"2020-12-10T21:59:47.500628709Z\",\n        \"duration\": \"PT0.000013649S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 809,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:47.503595166Z\",\n        \"endTime\": \"2020-12-10T21:59:47.503617033Z\",\n        \"duration\": \"PT0.000021867S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 810,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:47.503620388Z\",\n        \"endTime\": \"2020-12-10T21:59:47.503644543Z\",\n        \"duration\": \"PT0.000024155S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 811,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:56.889416987Z\",\n        \"endTime\": \"2020-12-10T21:59:56.889441145Z\",\n        \"duration\": \"PT0.000024158S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 812,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:56.889444514Z\",\n        \"endTime\": \"2020-12-10T21:59:56.889456144Z\",\n        \"duration\": \"PT0.00001163S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 813,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:57.518395627Z\",\n        \"endTime\": \"2020-12-10T21:59:57.518419750Z\",\n        \"duration\": \"PT0.000024123S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 814,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:57.518423852Z\",\n        \"endTime\": \"2020-12-10T21:59:57.518434889Z\",\n        \"duration\": \"PT0.000011037S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 815,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:57.520697857Z\",\n        \"endTime\": \"2020-12-10T21:59:57.520718365Z\",\n        \"duration\": \"PT0.000020508S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 816,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T21:59:57.520721379Z\",\n        \"endTime\": \"2020-12-10T21:59:57.520731226Z\",\n        \"duration\": \"PT0.000009847S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 817,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:07.504465789Z\",\n        \"endTime\": \"2020-12-10T22:00:07.504487886Z\",\n        \"duration\": \"PT0.000022097S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 818,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:07.504501455Z\",\n        \"endTime\": \"2020-12-10T22:00:07.504513330Z\",\n        \"duration\": \"PT0.000011875S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 819,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:07.507206626Z\",\n        \"endTime\": \"2020-12-10T22:00:07.507226499Z\",\n        \"duration\": \"PT0.000019873S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 820,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:07.507229699Z\",\n        \"endTime\": \"2020-12-10T22:00:07.507245865Z\",\n        \"duration\": \"PT0.000016166S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 821,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:16.891389224Z\",\n        \"endTime\": \"2020-12-10T22:00:16.891410836Z\",\n        \"duration\": \"PT0.000021612S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 822,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:16.891413626Z\",\n        \"endTime\": \"2020-12-10T22:00:16.891423656Z\",\n        \"duration\": \"PT0.00001003S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 823,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:17.509618729Z\",\n        \"endTime\": \"2020-12-10T22:00:17.509638781Z\",\n        \"duration\": \"PT0.000020052S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 824,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:17.509642282Z\",\n        \"endTime\": \"2020-12-10T22:00:17.509652096Z\",\n        \"duration\": \"PT0.000009814S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 825,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:17.511544709Z\",\n        \"endTime\": \"2020-12-10T22:00:17.511560109Z\",\n        \"duration\": \"PT0.0000154S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 826,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:17.511562366Z\",\n        \"endTime\": \"2020-12-10T22:00:17.511572034Z\",\n        \"duration\": \"PT0.000009668S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 827,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:27.493875347Z\",\n        \"endTime\": \"2020-12-10T22:00:27.493891753Z\",\n        \"duration\": \"PT0.000016406S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 828,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:27.493894714Z\",\n        \"endTime\": \"2020-12-10T22:00:27.493902958Z\",\n        \"duration\": \"PT0.000008244S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 829,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:27.495583489Z\",\n        \"endTime\": \"2020-12-10T22:00:27.495597233Z\",\n        \"duration\": \"PT0.000013744S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 830,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:27.495598876Z\",\n        \"endTime\": \"2020-12-10T22:00:27.495606271Z\",\n        \"duration\": \"PT0.000007395S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 831,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:36.881747377Z\",\n        \"endTime\": \"2020-12-10T22:00:36.881767735Z\",\n        \"duration\": \"PT0.000020358S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 832,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:36.881769895Z\",\n        \"endTime\": \"2020-12-10T22:00:36.881776711Z\",\n        \"duration\": \"PT0.000006816S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 833,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:37.514853556Z\",\n        \"endTime\": \"2020-12-10T22:00:37.514867959Z\",\n        \"duration\": \"PT0.000014403S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 834,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:37.514871498Z\",\n        \"endTime\": \"2020-12-10T22:00:37.514878528Z\",\n        \"duration\": \"PT0.00000703S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 835,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:37.516244113Z\",\n        \"endTime\": \"2020-12-10T22:00:37.516256601Z\",\n        \"duration\": \"PT0.000012488S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 836,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:37.516258215Z\",\n        \"endTime\": \"2020-12-10T22:00:37.516264690Z\",\n        \"duration\": \"PT0.000006475S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 837,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:46.891713555Z\",\n        \"endTime\": \"2020-12-10T22:00:46.891726985Z\",\n        \"duration\": \"PT0.00001343S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 838,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:46.891729394Z\",\n        \"endTime\": \"2020-12-10T22:00:46.891735311Z\",\n        \"duration\": \"PT0.000005917S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 839,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:47.490518894Z\",\n        \"endTime\": \"2020-12-10T22:00:47.490539548Z\",\n        \"duration\": \"PT0.000020654S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 840,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:47.490541990Z\",\n        \"endTime\": \"2020-12-10T22:00:47.490548284Z\",\n        \"duration\": \"PT0.000006294S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 841,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:47.491790824Z\",\n        \"endTime\": \"2020-12-10T22:00:47.491812236Z\",\n        \"duration\": \"PT0.000021412S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 842,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:47.491813647Z\",\n        \"endTime\": \"2020-12-10T22:00:47.491818325Z\",\n        \"duration\": \"PT0.000004678S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 843,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:56.880110424Z\",\n        \"endTime\": \"2020-12-10T22:00:56.880123248Z\",\n        \"duration\": \"PT0.000012824S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 844,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:56.880125241Z\",\n        \"endTime\": \"2020-12-10T22:00:56.880130776Z\",\n        \"duration\": \"PT0.000005535S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 845,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:57.498720590Z\",\n        \"endTime\": \"2020-12-10T22:00:57.498731598Z\",\n        \"duration\": \"PT0.000011008S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 846,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:57.498733703Z\",\n        \"endTime\": \"2020-12-10T22:00:57.498739100Z\",\n        \"duration\": \"PT0.000005397S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 847,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:57.499795159Z\",\n        \"endTime\": \"2020-12-10T22:00:57.499804395Z\",\n        \"duration\": \"PT0.000009236S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 848,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:00:57.499805641Z\",\n        \"endTime\": \"2020-12-10T22:00:57.499810502Z\",\n        \"duration\": \"PT0.000004861S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 849,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:07.489270395Z\",\n        \"endTime\": \"2020-12-10T22:01:07.489281033Z\",\n        \"duration\": \"PT0.000010638S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 850,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:07.489282776Z\",\n        \"endTime\": \"2020-12-10T22:01:07.489291243Z\",\n        \"duration\": \"PT0.000008467S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 851,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:07.490339322Z\",\n        \"endTime\": \"2020-12-10T22:01:07.490351568Z\",\n        \"duration\": \"PT0.000012246S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 852,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:07.490352700Z\",\n        \"endTime\": \"2020-12-10T22:01:07.490360399Z\",\n        \"duration\": \"PT0.000007699S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 853,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:16.879338957Z\",\n        \"endTime\": \"2020-12-10T22:01:16.879349894Z\",\n        \"duration\": \"PT0.000010937S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 854,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:16.879351769Z\",\n        \"endTime\": \"2020-12-10T22:01:16.879357055Z\",\n        \"duration\": \"PT0.000005286S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 855,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:17.503617711Z\",\n        \"endTime\": \"2020-12-10T22:01:17.503628442Z\",\n        \"duration\": \"PT0.000010731S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 856,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:17.503630347Z\",\n        \"endTime\": \"2020-12-10T22:01:17.503634773Z\",\n        \"duration\": \"PT0.000004426S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 857,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:17.504625211Z\",\n        \"endTime\": \"2020-12-10T22:01:17.504634644Z\",\n        \"duration\": \"PT0.000009433S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 858,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:17.504636047Z\",\n        \"endTime\": \"2020-12-10T22:01:17.504640600Z\",\n        \"duration\": \"PT0.000004553S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 859,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:26.885014257Z\",\n        \"endTime\": \"2020-12-10T22:01:26.885025189Z\",\n        \"duration\": \"PT0.000010932S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 860,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:26.885026800Z\",\n        \"endTime\": \"2020-12-10T22:01:26.885031191Z\",\n        \"duration\": \"PT0.000004391S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 861,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:27.489335647Z\",\n        \"endTime\": \"2020-12-10T22:01:27.489345181Z\",\n        \"duration\": \"PT0.000009534S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 862,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:27.489347088Z\",\n        \"endTime\": \"2020-12-10T22:01:27.489353651Z\",\n        \"duration\": \"PT0.000006563S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 863,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:27.490233916Z\",\n        \"endTime\": \"2020-12-10T22:01:27.490243881Z\",\n        \"duration\": \"PT0.000009965S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 864,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:27.490245308Z\",\n        \"endTime\": \"2020-12-10T22:01:27.490255404Z\",\n        \"duration\": \"PT0.000010096S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 865,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:37.499626225Z\",\n        \"endTime\": \"2020-12-10T22:01:37.499635493Z\",\n        \"duration\": \"PT0.000009268S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 866,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:37.499637043Z\",\n        \"endTime\": \"2020-12-10T22:01:37.499644805Z\",\n        \"duration\": \"PT0.000007762S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 867,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:37.500511059Z\",\n        \"endTime\": \"2020-12-10T22:01:37.500521677Z\",\n        \"duration\": \"PT0.000010618S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 868,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:37.500522728Z\",\n        \"endTime\": \"2020-12-10T22:01:37.500525732Z\",\n        \"duration\": \"PT0.000003004S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 869,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:46.887654454Z\",\n        \"endTime\": \"2020-12-10T22:01:46.887665945Z\",\n        \"duration\": \"PT0.000011491S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 870,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:46.887667837Z\",\n        \"endTime\": \"2020-12-10T22:01:46.887673607Z\",\n        \"duration\": \"PT0.00000577S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 871,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:47.500122366Z\",\n        \"endTime\": \"2020-12-10T22:01:47.500132389Z\",\n        \"duration\": \"PT0.000010023S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 872,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:47.500133941Z\",\n        \"endTime\": \"2020-12-10T22:01:47.500138466Z\",\n        \"duration\": \"PT0.000004525S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 873,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:47.500974015Z\",\n        \"endTime\": \"2020-12-10T22:01:47.500980702Z\",\n        \"duration\": \"PT0.000006687S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 874,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:47.500981674Z\",\n        \"endTime\": \"2020-12-10T22:01:47.500985250Z\",\n        \"duration\": \"PT0.000003576S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 875,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:57.502297589Z\",\n        \"endTime\": \"2020-12-10T22:01:57.502308200Z\",\n        \"duration\": \"PT0.000010611S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 876,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:57.502310187Z\",\n        \"endTime\": \"2020-12-10T22:01:57.502315016Z\",\n        \"duration\": \"PT0.000004829S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 877,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:57.503322406Z\",\n        \"endTime\": \"2020-12-10T22:01:57.503330258Z\",\n        \"duration\": \"PT0.000007852S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 878,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:01:57.503331284Z\",\n        \"endTime\": \"2020-12-10T22:01:57.503335343Z\",\n        \"duration\": \"PT0.000004059S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 879,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:06.883235351Z\",\n        \"endTime\": \"2020-12-10T22:02:06.883247521Z\",\n        \"duration\": \"PT0.00001217S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 880,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:06.883249050Z\",\n        \"endTime\": \"2020-12-10T22:02:06.883254422Z\",\n        \"duration\": \"PT0.000005372S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 881,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:07.496545129Z\",\n        \"endTime\": \"2020-12-10T22:02:07.496559085Z\",\n        \"duration\": \"PT0.000013956S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 882,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:07.496560778Z\",\n        \"endTime\": \"2020-12-10T22:02:07.496564427Z\",\n        \"duration\": \"PT0.000003649S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 883,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:07.497326525Z\",\n        \"endTime\": \"2020-12-10T22:02:07.497332970Z\",\n        \"duration\": \"PT0.000006445S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 884,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:07.497333676Z\",\n        \"endTime\": \"2020-12-10T22:02:07.497337164Z\",\n        \"duration\": \"PT0.000003488S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 885,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:17.507322166Z\",\n        \"endTime\": \"2020-12-10T22:02:17.507330775Z\",\n        \"duration\": \"PT0.000008609S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 886,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:17.507332254Z\",\n        \"endTime\": \"2020-12-10T22:02:17.507335889Z\",\n        \"duration\": \"PT0.000003635S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 887,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:17.508012342Z\",\n        \"endTime\": \"2020-12-10T22:02:17.508017709Z\",\n        \"duration\": \"PT0.000005367S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 888,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:17.508018351Z\",\n        \"endTime\": \"2020-12-10T22:02:17.508020747Z\",\n        \"duration\": \"PT0.000002396S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 889,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:26.877475667Z\",\n        \"endTime\": \"2020-12-10T22:02:26.877484092Z\",\n        \"duration\": \"PT0.000008425S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 890,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:26.877485134Z\",\n        \"endTime\": \"2020-12-10T22:02:26.877488653Z\",\n        \"duration\": \"PT0.000003519S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 891,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:27.487834230Z\",\n        \"endTime\": \"2020-12-10T22:02:27.487841949Z\",\n        \"duration\": \"PT0.000007719S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 892,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:27.487843176Z\",\n        \"endTime\": \"2020-12-10T22:02:27.487846492Z\",\n        \"duration\": \"PT0.000003316S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 893,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:27.488490824Z\",\n        \"endTime\": \"2020-12-10T22:02:27.488495391Z\",\n        \"duration\": \"PT0.000004567S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 894,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:27.488495996Z\",\n        \"endTime\": \"2020-12-10T22:02:27.488498563Z\",\n        \"duration\": \"PT0.000002567S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 895,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:37.493861847Z\",\n        \"endTime\": \"2020-12-10T22:02:37.493870223Z\",\n        \"duration\": \"PT0.000008376S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 896,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:37.493871455Z\",\n        \"endTime\": \"2020-12-10T22:02:37.493878287Z\",\n        \"duration\": \"PT0.000006832S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 897,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:37.494659599Z\",\n        \"endTime\": \"2020-12-10T22:02:37.494669833Z\",\n        \"duration\": \"PT0.000010234S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 898,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:37.494670791Z\",\n        \"endTime\": \"2020-12-10T22:02:37.494674168Z\",\n        \"duration\": \"PT0.000003377S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 899,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:46.879764764Z\",\n        \"endTime\": \"2020-12-10T22:02:46.879776372Z\",\n        \"duration\": \"PT0.000011608S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 900,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:46.879778364Z\",\n        \"endTime\": \"2020-12-10T22:02:46.879788439Z\",\n        \"duration\": \"PT0.000010075S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 901,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:46.899432706Z\",\n        \"endTime\": \"2020-12-10T22:02:46.899443552Z\",\n        \"duration\": \"PT0.000010846S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 902,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:46.899445087Z\",\n        \"endTime\": \"2020-12-10T22:02:46.899449950Z\",\n        \"duration\": \"PT0.000004863S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 903,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:47.491355917Z\",\n        \"endTime\": \"2020-12-10T22:02:47.491366885Z\",\n        \"duration\": \"PT0.000010968S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 904,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:47.491368636Z\",\n        \"endTime\": \"2020-12-10T22:02:47.491374112Z\",\n        \"duration\": \"PT0.000005476S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 905,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:47.492345976Z\",\n        \"endTime\": \"2020-12-10T22:02:47.492353306Z\",\n        \"duration\": \"PT0.00000733S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 906,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:47.492354466Z\",\n        \"endTime\": \"2020-12-10T22:02:47.492358979Z\",\n        \"duration\": \"PT0.000004513S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 907,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:57.503356895Z\",\n        \"endTime\": \"2020-12-10T22:02:57.503368789Z\",\n        \"duration\": \"PT0.000011894S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 908,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:57.503370821Z\",\n        \"endTime\": \"2020-12-10T22:02:57.503376026Z\",\n        \"duration\": \"PT0.000005205S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 909,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:57.504413367Z\",\n        \"endTime\": \"2020-12-10T22:02:57.504422665Z\",\n        \"duration\": \"PT0.000009298S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 910,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:02:57.504423692Z\",\n        \"endTime\": \"2020-12-10T22:02:57.504428678Z\",\n        \"duration\": \"PT0.000004986S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 911,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:06.885039019Z\",\n        \"endTime\": \"2020-12-10T22:03:06.885050927Z\",\n        \"duration\": \"PT0.000011908S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 912,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:06.885052822Z\",\n        \"endTime\": \"2020-12-10T22:03:06.885058034Z\",\n        \"duration\": \"PT0.000005212S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 913,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:07.498578715Z\",\n        \"endTime\": \"2020-12-10T22:03:07.498590360Z\",\n        \"duration\": \"PT0.000011645S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 914,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:07.498591969Z\",\n        \"endTime\": \"2020-12-10T22:03:07.498597262Z\",\n        \"duration\": \"PT0.000005293S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 915,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:07.499519844Z\",\n        \"endTime\": \"2020-12-10T22:03:07.499527498Z\",\n        \"duration\": \"PT0.000007654S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 916,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:07.499528594Z\",\n        \"endTime\": \"2020-12-10T22:03:07.499532579Z\",\n        \"duration\": \"PT0.000003985S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 917,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:17.500385073Z\",\n        \"endTime\": \"2020-12-10T22:03:17.500396900Z\",\n        \"duration\": \"PT0.000011827S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 918,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:17.500398763Z\",\n        \"endTime\": \"2020-12-10T22:03:17.500403615Z\",\n        \"duration\": \"PT0.000004852S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 919,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:17.501477528Z\",\n        \"endTime\": \"2020-12-10T22:03:17.501485643Z\",\n        \"duration\": \"PT0.000008115S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 920,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:17.501486802Z\",\n        \"endTime\": \"2020-12-10T22:03:17.501490849Z\",\n        \"duration\": \"PT0.000004047S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 921,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:26.884474639Z\",\n        \"endTime\": \"2020-12-10T22:03:26.884484810Z\",\n        \"duration\": \"PT0.000010171S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 922,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:26.884486085Z\",\n        \"endTime\": \"2020-12-10T22:03:26.884490834Z\",\n        \"duration\": \"PT0.000004749S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 923,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:27.489805605Z\",\n        \"endTime\": \"2020-12-10T22:03:27.489816862Z\",\n        \"duration\": \"PT0.000011257S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 924,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:27.489818720Z\",\n        \"endTime\": \"2020-12-10T22:03:27.489824037Z\",\n        \"duration\": \"PT0.000005317S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 925,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:27.490766124Z\",\n        \"endTime\": \"2020-12-10T22:03:27.490774689Z\",\n        \"duration\": \"PT0.000008565S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 926,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:27.490775914Z\",\n        \"endTime\": \"2020-12-10T22:03:27.490780836Z\",\n        \"duration\": \"PT0.000004922S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 927,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:37.504175143Z\",\n        \"endTime\": \"2020-12-10T22:03:37.504186443Z\",\n        \"duration\": \"PT0.0000113S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 928,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:37.504188238Z\",\n        \"endTime\": \"2020-12-10T22:03:37.504196429Z\",\n        \"duration\": \"PT0.000008191S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 929,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:37.505284115Z\",\n        \"endTime\": \"2020-12-10T22:03:37.505292982Z\",\n        \"duration\": \"PT0.000008867S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 930,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:37.505294237Z\",\n        \"endTime\": \"2020-12-10T22:03:37.505307251Z\",\n        \"duration\": \"PT0.000013014S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 931,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:46.887539845Z\",\n        \"endTime\": \"2020-12-10T22:03:46.887550987Z\",\n        \"duration\": \"PT0.000011142S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 932,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:46.887552471Z\",\n        \"endTime\": \"2020-12-10T22:03:46.887561346Z\",\n        \"duration\": \"PT0.000008875S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 933,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:47.491081975Z\",\n        \"endTime\": \"2020-12-10T22:03:47.491092803Z\",\n        \"duration\": \"PT0.000010828S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 934,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:47.491094387Z\",\n        \"endTime\": \"2020-12-10T22:03:47.491099304Z\",\n        \"duration\": \"PT0.000004917S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 935,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:47.492011791Z\",\n        \"endTime\": \"2020-12-10T22:03:47.492019922Z\",\n        \"duration\": \"PT0.000008131S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 936,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:47.492020993Z\",\n        \"endTime\": \"2020-12-10T22:03:47.492025260Z\",\n        \"duration\": \"PT0.000004267S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 937,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:57.499838040Z\",\n        \"endTime\": \"2020-12-10T22:03:57.499897042Z\",\n        \"duration\": \"PT0.000059002S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 938,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:57.499898879Z\",\n        \"endTime\": \"2020-12-10T22:03:57.499912828Z\",\n        \"duration\": \"PT0.000013949S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 939,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:57.500910995Z\",\n        \"endTime\": \"2020-12-10T22:03:57.500917027Z\",\n        \"duration\": \"PT0.000006032S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 940,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:03:57.500918068Z\",\n        \"endTime\": \"2020-12-10T22:03:57.500921533Z\",\n        \"duration\": \"PT0.000003465S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 941,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:06.881506419Z\",\n        \"endTime\": \"2020-12-10T22:04:06.881514325Z\",\n        \"duration\": \"PT0.000007906S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 942,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:06.881515721Z\",\n        \"endTime\": \"2020-12-10T22:04:06.881519831Z\",\n        \"duration\": \"PT0.00000411S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 943,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:07.491740249Z\",\n        \"endTime\": \"2020-12-10T22:04:07.491746022Z\",\n        \"duration\": \"PT0.000005773S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 944,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:07.491747607Z\",\n        \"endTime\": \"2020-12-10T22:04:07.491750939Z\",\n        \"duration\": \"PT0.000003332S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 945,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:07.492911927Z\",\n        \"endTime\": \"2020-12-10T22:04:07.492915751Z\",\n        \"duration\": \"PT0.000003824S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 946,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:07.492916338Z\",\n        \"endTime\": \"2020-12-10T22:04:07.492919763Z\",\n        \"duration\": \"PT0.000003425S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 947,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:17.506355039Z\",\n        \"endTime\": \"2020-12-10T22:04:17.506362405Z\",\n        \"duration\": \"PT0.000007366S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 948,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:17.506363870Z\",\n        \"endTime\": \"2020-12-10T22:04:17.506386171Z\",\n        \"duration\": \"PT0.000022301S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 949,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:17.507364510Z\",\n        \"endTime\": \"2020-12-10T22:04:17.507370367Z\",\n        \"duration\": \"PT0.000005857S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 950,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:17.507371114Z\",\n        \"endTime\": \"2020-12-10T22:04:17.507373722Z\",\n        \"duration\": \"PT0.000002608S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 951,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:26.879014570Z\",\n        \"endTime\": \"2020-12-10T22:04:26.879019724Z\",\n        \"duration\": \"PT0.000005154S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 952,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:26.879020541Z\",\n        \"endTime\": \"2020-12-10T22:04:26.879027172Z\",\n        \"duration\": \"PT0.000006631S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 953,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:27.498072940Z\",\n        \"endTime\": \"2020-12-10T22:04:27.498080721Z\",\n        \"duration\": \"PT0.000007781S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 954,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:27.498082405Z\",\n        \"endTime\": \"2020-12-10T22:04:27.498086015Z\",\n        \"duration\": \"PT0.00000361S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 955,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:27.499000204Z\",\n        \"endTime\": \"2020-12-10T22:04:27.499004798Z\",\n        \"duration\": \"PT0.000004594S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 956,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:27.499005500Z\",\n        \"endTime\": \"2020-12-10T22:04:27.499007538Z\",\n        \"duration\": \"PT0.000002038S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 957,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:37.498615183Z\",\n        \"endTime\": \"2020-12-10T22:04:37.498620519Z\",\n        \"duration\": \"PT0.000005336S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 958,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:37.498621836Z\",\n        \"endTime\": \"2020-12-10T22:04:37.498624346Z\",\n        \"duration\": \"PT0.00000251S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 959,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:37.499512389Z\",\n        \"endTime\": \"2020-12-10T22:04:37.499533974Z\",\n        \"duration\": \"PT0.000021585S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 960,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:37.499534616Z\",\n        \"endTime\": \"2020-12-10T22:04:37.499536687Z\",\n        \"duration\": \"PT0.000002071S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 961,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:46.880364523Z\",\n        \"endTime\": \"2020-12-10T22:04:46.880376010Z\",\n        \"duration\": \"PT0.000011487S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 962,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:46.880376893Z\",\n        \"endTime\": \"2020-12-10T22:04:46.880379318Z\",\n        \"duration\": \"PT0.000002425S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 963,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:46.891748133Z\",\n        \"endTime\": \"2020-12-10T22:04:46.891753572Z\",\n        \"duration\": \"PT0.000005439S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 964,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:46.891754393Z\",\n        \"endTime\": \"2020-12-10T22:04:46.891756629Z\",\n        \"duration\": \"PT0.000002236S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 965,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:47.488019169Z\",\n        \"endTime\": \"2020-12-10T22:04:47.488024542Z\",\n        \"duration\": \"PT0.000005373S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 966,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:47.488025557Z\",\n        \"endTime\": \"2020-12-10T22:04:47.488027660Z\",\n        \"duration\": \"PT0.000002103S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 967,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:47.488920687Z\",\n        \"endTime\": \"2020-12-10T22:04:47.488941315Z\",\n        \"duration\": \"PT0.000020628S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 968,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:47.488942089Z\",\n        \"endTime\": \"2020-12-10T22:04:47.488944004Z\",\n        \"duration\": \"PT0.000001915S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 969,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:57.502386096Z\",\n        \"endTime\": \"2020-12-10T22:04:57.502392634Z\",\n        \"duration\": \"PT0.000006538S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 970,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:57.502394195Z\",\n        \"endTime\": \"2020-12-10T22:04:57.502396708Z\",\n        \"duration\": \"PT0.000002513S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 971,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:57.503164457Z\",\n        \"endTime\": \"2020-12-10T22:04:57.503168718Z\",\n        \"duration\": \"PT0.000004261S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 972,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:04:57.503169340Z\",\n        \"endTime\": \"2020-12-10T22:04:57.503171491Z\",\n        \"duration\": \"PT0.000002151S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 973,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:06.877654085Z\",\n        \"endTime\": \"2020-12-10T22:05:06.877659530Z\",\n        \"duration\": \"PT0.000005445S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 974,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:06.877660353Z\",\n        \"endTime\": \"2020-12-10T22:05:06.877663202Z\",\n        \"duration\": \"PT0.000002849S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 975,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:07.491654258Z\",\n        \"endTime\": \"2020-12-10T22:05:07.491659629Z\",\n        \"duration\": \"PT0.000005371S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 976,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:07.491660839Z\",\n        \"endTime\": \"2020-12-10T22:05:07.491663601Z\",\n        \"duration\": \"PT0.000002762S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 977,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:07.492240271Z\",\n        \"endTime\": \"2020-12-10T22:05:07.492244189Z\",\n        \"duration\": \"PT0.000003918S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 978,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:07.492244666Z\",\n        \"endTime\": \"2020-12-10T22:05:07.492246275Z\",\n        \"duration\": \"PT0.000001609S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 979,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:17.497418517Z\",\n        \"endTime\": \"2020-12-10T22:05:17.497424486Z\",\n        \"duration\": \"PT0.000005969S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 980,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:17.497425556Z\",\n        \"endTime\": \"2020-12-10T22:05:17.497427971Z\",\n        \"duration\": \"PT0.000002415S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 981,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:17.498159601Z\",\n        \"endTime\": \"2020-12-10T22:05:17.498170047Z\",\n        \"duration\": \"PT0.000010446S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 982,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:17.498170609Z\",\n        \"endTime\": \"2020-12-10T22:05:17.498172449Z\",\n        \"duration\": \"PT0.00000184S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 983,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:26.877290680Z\",\n        \"endTime\": \"2020-12-10T22:05:26.877314186Z\",\n        \"duration\": \"PT0.000023506S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 984,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:26.877315441Z\",\n        \"endTime\": \"2020-12-10T22:05:26.877317835Z\",\n        \"duration\": \"PT0.000002394S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 985,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:27.489784064Z\",\n        \"endTime\": \"2020-12-10T22:05:27.489789129Z\",\n        \"duration\": \"PT0.000005065S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 986,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:27.489790006Z\",\n        \"endTime\": \"2020-12-10T22:05:27.489795689Z\",\n        \"duration\": \"PT0.000005683S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 987,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:27.490408782Z\",\n        \"endTime\": \"2020-12-10T22:05:27.490411891Z\",\n        \"duration\": \"PT0.000003109S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 988,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:27.490412383Z\",\n        \"endTime\": \"2020-12-10T22:05:27.490414168Z\",\n        \"duration\": \"PT0.000001785S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 989,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:37.507817401Z\",\n        \"endTime\": \"2020-12-10T22:05:37.507825330Z\",\n        \"duration\": \"PT0.000007929S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 990,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:37.507827323Z\",\n        \"endTime\": \"2020-12-10T22:05:37.507830928Z\",\n        \"duration\": \"PT0.000003605S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 991,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:37.508885758Z\",\n        \"endTime\": \"2020-12-10T22:05:37.508893042Z\",\n        \"duration\": \"PT0.000007284S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 992,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:37.508894415Z\",\n        \"endTime\": \"2020-12-10T22:05:37.508897530Z\",\n        \"duration\": \"PT0.000003115S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 993,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:46.877488658Z\",\n        \"endTime\": \"2020-12-10T22:05:46.877511044Z\",\n        \"duration\": \"PT0.000022386S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 994,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:46.877511886Z\",\n        \"endTime\": \"2020-12-10T22:05:46.877514484Z\",\n        \"duration\": \"PT0.000002598S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 995,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:47.494622373Z\",\n        \"endTime\": \"2020-12-10T22:05:47.494627998Z\",\n        \"duration\": \"PT0.000005625S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 996,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:47.494629228Z\",\n        \"endTime\": \"2020-12-10T22:05:47.494637117Z\",\n        \"duration\": \"PT0.000007889S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 997,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:47.495334956Z\",\n        \"endTime\": \"2020-12-10T22:05:47.495338448Z\",\n        \"duration\": \"PT0.000003492S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 998,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:47.495339012Z\",\n        \"endTime\": \"2020-12-10T22:05:47.495378140Z\",\n        \"duration\": \"PT0.000039128S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 999,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:57.507058863Z\",\n        \"endTime\": \"2020-12-10T22:05:57.507073424Z\",\n        \"duration\": \"PT0.000014561S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1000,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:57.507074809Z\",\n        \"endTime\": \"2020-12-10T22:05:57.507077998Z\",\n        \"duration\": \"PT0.000003189S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1001,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:57.508070800Z\",\n        \"endTime\": \"2020-12-10T22:05:57.508075867Z\",\n        \"duration\": \"PT0.000005067S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1002,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:05:57.508076552Z\",\n        \"endTime\": \"2020-12-10T22:05:57.508079120Z\",\n        \"duration\": \"PT0.000002568S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1003,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:06.883636946Z\",\n        \"endTime\": \"2020-12-10T22:06:06.883655632Z\",\n        \"duration\": \"PT0.000018686S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1004,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:06.883656830Z\",\n        \"endTime\": \"2020-12-10T22:06:06.883660346Z\",\n        \"duration\": \"PT0.000003516S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1005,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:07.494998389Z\",\n        \"endTime\": \"2020-12-10T22:06:07.495006322Z\",\n        \"duration\": \"PT0.000007933S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1006,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:07.495007700Z\",\n        \"endTime\": \"2020-12-10T22:06:07.495010957Z\",\n        \"duration\": \"PT0.000003257S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1007,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:07.495817924Z\",\n        \"endTime\": \"2020-12-10T22:06:07.495823045Z\",\n        \"duration\": \"PT0.000005121S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1008,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:07.495823786Z\",\n        \"endTime\": \"2020-12-10T22:06:07.495826440Z\",\n        \"duration\": \"PT0.000002654S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1009,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:17.502741320Z\",\n        \"endTime\": \"2020-12-10T22:06:17.502748979Z\",\n        \"duration\": \"PT0.000007659S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1010,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:17.502750364Z\",\n        \"endTime\": \"2020-12-10T22:06:17.502753504Z\",\n        \"duration\": \"PT0.00000314S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1011,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:17.503642678Z\",\n        \"endTime\": \"2020-12-10T22:06:17.503647699Z\",\n        \"duration\": \"PT0.000005021S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1012,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:17.503648368Z\",\n        \"endTime\": \"2020-12-10T22:06:17.503651188Z\",\n        \"duration\": \"PT0.00000282S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1013,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:26.877630800Z\",\n        \"endTime\": \"2020-12-10T22:06:26.877636451Z\",\n        \"duration\": \"PT0.000005651S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1014,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:26.877637157Z\",\n        \"endTime\": \"2020-12-10T22:06:26.877639338Z\",\n        \"duration\": \"PT0.000002181S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1015,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:27.494317188Z\",\n        \"endTime\": \"2020-12-10T22:06:27.494324417Z\",\n        \"duration\": \"PT0.000007229S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1016,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:27.494325733Z\",\n        \"endTime\": \"2020-12-10T22:06:27.494335105Z\",\n        \"duration\": \"PT0.000009372S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1017,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:27.495312186Z\",\n        \"endTime\": \"2020-12-10T22:06:27.495317792Z\",\n        \"duration\": \"PT0.000005606S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1018,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:27.495318458Z\",\n        \"endTime\": \"2020-12-10T22:06:27.495331803Z\",\n        \"duration\": \"PT0.000013345S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1019,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:37.500572018Z\",\n        \"endTime\": \"2020-12-10T22:06:37.500591568Z\",\n        \"duration\": \"PT0.00001955S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1020,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:37.500593569Z\",\n        \"endTime\": \"2020-12-10T22:06:37.500598089Z\",\n        \"duration\": \"PT0.00000452S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1021,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:37.501609531Z\",\n        \"endTime\": \"2020-12-10T22:06:37.501614550Z\",\n        \"duration\": \"PT0.000005019S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1022,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:37.501615318Z\",\n        \"endTime\": \"2020-12-10T22:06:37.501624749Z\",\n        \"duration\": \"PT0.000009431S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1023,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:46.880776301Z\",\n        \"endTime\": \"2020-12-10T22:06:46.880783080Z\",\n        \"duration\": \"PT0.000006779S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1024,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:46.880783946Z\",\n        \"endTime\": \"2020-12-10T22:06:46.880786406Z\",\n        \"duration\": \"PT0.00000246S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1025,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:46.897676657Z\",\n        \"endTime\": \"2020-12-10T22:06:46.897682762Z\",\n        \"duration\": \"PT0.000006105S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1026,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:46.897684082Z\",\n        \"endTime\": \"2020-12-10T22:06:46.897687137Z\",\n        \"duration\": \"PT0.000003055S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1027,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:47.491464696Z\",\n        \"endTime\": \"2020-12-10T22:06:47.491482689Z\",\n        \"duration\": \"PT0.000017993S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1028,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:47.491484506Z\",\n        \"endTime\": \"2020-12-10T22:06:47.491487842Z\",\n        \"duration\": \"PT0.000003336S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1029,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:47.492505587Z\",\n        \"endTime\": \"2020-12-10T22:06:47.492519746Z\",\n        \"duration\": \"PT0.000014159S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1030,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:47.492520612Z\",\n        \"endTime\": \"2020-12-10T22:06:47.492523230Z\",\n        \"duration\": \"PT0.000002618S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1031,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:57.504802479Z\",\n        \"endTime\": \"2020-12-10T22:06:57.504809544Z\",\n        \"duration\": \"PT0.000007065S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1032,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:57.504810936Z\",\n        \"endTime\": \"2020-12-10T22:06:57.504822327Z\",\n        \"duration\": \"PT0.000011391S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1033,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:57.505888896Z\",\n        \"endTime\": \"2020-12-10T22:06:57.505902914Z\",\n        \"duration\": \"PT0.000014018S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1034,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:06:57.505903682Z\",\n        \"endTime\": \"2020-12-10T22:06:57.505906646Z\",\n        \"duration\": \"PT0.000002964S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1035,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:06.877625401Z\",\n        \"endTime\": \"2020-12-10T22:07:06.877630882Z\",\n        \"duration\": \"PT0.000005481S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1036,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:06.877631796Z\",\n        \"endTime\": \"2020-12-10T22:07:06.877639466Z\",\n        \"duration\": \"PT0.00000767S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1037,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:07.490104249Z\",\n        \"endTime\": \"2020-12-10T22:07:07.490110371Z\",\n        \"duration\": \"PT0.000006122S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1038,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:07.490111653Z\",\n        \"endTime\": \"2020-12-10T22:07:07.490114506Z\",\n        \"duration\": \"PT0.000002853S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1039,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:07.491074421Z\",\n        \"endTime\": \"2020-12-10T22:07:07.491079766Z\",\n        \"duration\": \"PT0.000005345S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1040,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:07.491080377Z\",\n        \"endTime\": \"2020-12-10T22:07:07.491083370Z\",\n        \"duration\": \"PT0.000002993S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1041,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:16.883216847Z\",\n        \"endTime\": \"2020-12-10T22:07:16.883223426Z\",\n        \"duration\": \"PT0.000006579S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1042,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:16.883224381Z\",\n        \"endTime\": \"2020-12-10T22:07:16.883227026Z\",\n        \"duration\": \"PT0.000002645S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1043,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:17.495058618Z\",\n        \"endTime\": \"2020-12-10T22:07:17.495065897Z\",\n        \"duration\": \"PT0.000007279S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1044,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:17.495067282Z\",\n        \"endTime\": \"2020-12-10T22:07:17.495070800Z\",\n        \"duration\": \"PT0.000003518S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1045,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:17.495981227Z\",\n        \"endTime\": \"2020-12-10T22:07:17.495986745Z\",\n        \"duration\": \"PT0.000005518S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1046,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:17.495987924Z\",\n        \"endTime\": \"2020-12-10T22:07:17.495990627Z\",\n        \"duration\": \"PT0.000002703S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1047,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:27.488106927Z\",\n        \"endTime\": \"2020-12-10T22:07:27.488117500Z\",\n        \"duration\": \"PT0.000010573S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1048,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:27.488118643Z\",\n        \"endTime\": \"2020-12-10T22:07:27.488121238Z\",\n        \"duration\": \"PT0.000002595S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1049,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:27.488855637Z\",\n        \"endTime\": \"2020-12-10T22:07:27.488859168Z\",\n        \"duration\": \"PT0.000003531S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1050,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:27.488859741Z\",\n        \"endTime\": \"2020-12-10T22:07:27.488868797Z\",\n        \"duration\": \"PT0.000009056S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1051,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:36.879699707Z\",\n        \"endTime\": \"2020-12-10T22:07:36.879708068Z\",\n        \"duration\": \"PT0.000008361S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1052,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:36.879709207Z\",\n        \"endTime\": \"2020-12-10T22:07:36.879713126Z\",\n        \"duration\": \"PT0.000003919S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1053,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:37.507528855Z\",\n        \"endTime\": \"2020-12-10T22:07:37.507535359Z\",\n        \"duration\": \"PT0.000006504S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1054,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:37.507536522Z\",\n        \"endTime\": \"2020-12-10T22:07:37.507539538Z\",\n        \"duration\": \"PT0.000003016S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1055,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:37.508259078Z\",\n        \"endTime\": \"2020-12-10T22:07:37.508263947Z\",\n        \"duration\": \"PT0.000004869S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1056,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:37.508264599Z\",\n        \"endTime\": \"2020-12-10T22:07:37.508266874Z\",\n        \"duration\": \"PT0.000002275S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1057,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:47.489352906Z\",\n        \"endTime\": \"2020-12-10T22:07:47.489358872Z\",\n        \"duration\": \"PT0.000005966S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1058,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:47.489359850Z\",\n        \"endTime\": \"2020-12-10T22:07:47.489362077Z\",\n        \"duration\": \"PT0.000002227S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1059,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:47.490075163Z\",\n        \"endTime\": \"2020-12-10T22:07:47.490079166Z\",\n        \"duration\": \"PT0.000004003S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1060,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:47.490079651Z\",\n        \"endTime\": \"2020-12-10T22:07:47.490081986Z\",\n        \"duration\": \"PT0.000002335S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1061,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:56.887522092Z\",\n        \"endTime\": \"2020-12-10T22:07:56.887530368Z\",\n        \"duration\": \"PT0.000008276S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1062,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:56.887531893Z\",\n        \"endTime\": \"2020-12-10T22:07:56.887535743Z\",\n        \"duration\": \"PT0.00000385S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1063,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:57.505754829Z\",\n        \"endTime\": \"2020-12-10T22:07:57.505762155Z\",\n        \"duration\": \"PT0.000007326S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1064,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:57.505763588Z\",\n        \"endTime\": \"2020-12-10T22:07:57.505774871Z\",\n        \"duration\": \"PT0.000011283S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1065,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:57.506782405Z\",\n        \"endTime\": \"2020-12-10T22:07:57.506787376Z\",\n        \"duration\": \"PT0.000004971S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1066,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:07:57.506787931Z\",\n        \"endTime\": \"2020-12-10T22:07:57.506790490Z\",\n        \"duration\": \"PT0.000002559S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1067,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:07.490828528Z\",\n        \"endTime\": \"2020-12-10T22:08:07.490835591Z\",\n        \"duration\": \"PT0.000007063S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1068,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:07.490837347Z\",\n        \"endTime\": \"2020-12-10T22:08:07.490840385Z\",\n        \"duration\": \"PT0.000003038S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1069,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:07.491615088Z\",\n        \"endTime\": \"2020-12-10T22:08:07.491619413Z\",\n        \"duration\": \"PT0.000004325S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1070,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:07.491619991Z\",\n        \"endTime\": \"2020-12-10T22:08:07.491622229Z\",\n        \"duration\": \"PT0.000002238S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1071,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:16.878198088Z\",\n        \"endTime\": \"2020-12-10T22:08:16.878204994Z\",\n        \"duration\": \"PT0.000006906S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1072,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:16.878205879Z\",\n        \"endTime\": \"2020-12-10T22:08:16.878208829Z\",\n        \"duration\": \"PT0.00000295S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1073,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:17.504795236Z\",\n        \"endTime\": \"2020-12-10T22:08:17.504802062Z\",\n        \"duration\": \"PT0.000006826S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1074,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:17.504803408Z\",\n        \"endTime\": \"2020-12-10T22:08:17.504806246Z\",\n        \"duration\": \"PT0.000002838S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1075,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:17.505638516Z\",\n        \"endTime\": \"2020-12-10T22:08:17.505642852Z\",\n        \"duration\": \"PT0.000004336S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1076,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:17.505643392Z\",\n        \"endTime\": \"2020-12-10T22:08:17.505645638Z\",\n        \"duration\": \"PT0.000002246S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1077,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:26.916236190Z\",\n        \"endTime\": \"2020-12-10T22:08:26.916249718Z\",\n        \"duration\": \"PT0.000013528S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1078,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:26.916250606Z\",\n        \"endTime\": \"2020-12-10T22:08:26.916252987Z\",\n        \"duration\": \"PT0.000002381S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1079,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:27.487949428Z\",\n        \"endTime\": \"2020-12-10T22:08:27.487954729Z\",\n        \"duration\": \"PT0.000005301S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1080,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:27.487955747Z\",\n        \"endTime\": \"2020-12-10T22:08:27.487962942Z\",\n        \"duration\": \"PT0.000007195S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1081,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:27.489135248Z\",\n        \"endTime\": \"2020-12-10T22:08:27.489139595Z\",\n        \"duration\": \"PT0.000004347S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1082,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:27.489140241Z\",\n        \"endTime\": \"2020-12-10T22:08:27.489164621Z\",\n        \"duration\": \"PT0.00002438S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1083,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[5ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:37.509795708Z\",\n        \"endTime\": \"2020-12-10T22:08:37.509812645Z\",\n        \"duration\": \"PT0.000016937S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1084,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[5ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:37.509816824Z\",\n        \"endTime\": \"2020-12-10T22:08:37.509845202Z\",\n        \"duration\": \"PT0.000028378S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1085,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:37.512671648Z\",\n        \"endTime\": \"2020-12-10T22:08:37.512688222Z\",\n        \"duration\": \"PT0.000016574S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1086,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:37.512690731Z\",\n        \"endTime\": \"2020-12-10T22:08:37.512698598Z\",\n        \"duration\": \"PT0.000007867S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1087,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[9ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:46.900565840Z\",\n        \"endTime\": \"2020-12-10T22:08:46.900583721Z\",\n        \"duration\": \"PT0.000017881S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1088,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[9ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:46.900586621Z\",\n        \"endTime\": \"2020-12-10T22:08:46.900596479Z\",\n        \"duration\": \"PT0.000009858S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1089,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:46.946426169Z\",\n        \"endTime\": \"2020-12-10T22:08:46.946444746Z\",\n        \"duration\": \"PT0.000018577S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1090,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:46.946447439Z\",\n        \"endTime\": \"2020-12-10T22:08:46.946457863Z\",\n        \"duration\": \"PT0.000010424S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1091,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:47.517006698Z\",\n        \"endTime\": \"2020-12-10T22:08:47.517024621Z\",\n        \"duration\": \"PT0.000017923S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1092,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:47.517028153Z\",\n        \"endTime\": \"2020-12-10T22:08:47.517055256Z\",\n        \"duration\": \"PT0.000027103S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1093,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:47.519304849Z\",\n        \"endTime\": \"2020-12-10T22:08:47.519319096Z\",\n        \"duration\": \"PT0.000014247S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1094,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:47.519321097Z\",\n        \"endTime\": \"2020-12-10T22:08:47.519354724Z\",\n        \"duration\": \"PT0.000033627S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1095,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:57.498338235Z\",\n        \"endTime\": \"2020-12-10T22:08:57.498355181Z\",\n        \"duration\": \"PT0.000016946S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1096,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:57.498359354Z\",\n        \"endTime\": \"2020-12-10T22:08:57.498376644Z\",\n        \"duration\": \"PT0.00001729S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1097,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:57.500725563Z\",\n        \"endTime\": \"2020-12-10T22:08:57.500748950Z\",\n        \"duration\": \"PT0.000023387S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1098,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:08:57.500751536Z\",\n        \"endTime\": \"2020-12-10T22:08:57.500759558Z\",\n        \"duration\": \"PT0.000008022S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1099,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:06.890378996Z\",\n        \"endTime\": \"2020-12-10T22:09:06.890399834Z\",\n        \"duration\": \"PT0.000020838S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1100,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:06.890402672Z\",\n        \"endTime\": \"2020-12-10T22:09:06.890411212Z\",\n        \"duration\": \"PT0.00000854S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1101,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:07.511491420Z\",\n        \"endTime\": \"2020-12-10T22:09:07.511554921Z\",\n        \"duration\": \"PT0.000063501S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1102,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:07.511558648Z\",\n        \"endTime\": \"2020-12-10T22:09:07.511568273Z\",\n        \"duration\": \"PT0.000009625S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1103,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:07.514026107Z\",\n        \"endTime\": \"2020-12-10T22:09:07.514064432Z\",\n        \"duration\": \"PT0.000038325S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1104,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:07.514066821Z\",\n        \"endTime\": \"2020-12-10T22:09:07.514074597Z\",\n        \"duration\": \"PT0.000007776S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1105,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:17.498341609Z\",\n        \"endTime\": \"2020-12-10T22:09:17.498359609Z\",\n        \"duration\": \"PT0.000018S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1106,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:17.498363300Z\",\n        \"endTime\": \"2020-12-10T22:09:17.498372616Z\",\n        \"duration\": \"PT0.000009316S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1107,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:17.500650941Z\",\n        \"endTime\": \"2020-12-10T22:09:17.500666717Z\",\n        \"duration\": \"PT0.000015776S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1108,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:17.500668933Z\",\n        \"endTime\": \"2020-12-10T22:09:17.500676539Z\",\n        \"duration\": \"PT0.000007606S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1109,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:26.906479151Z\",\n        \"endTime\": \"2020-12-10T22:09:26.906486093Z\",\n        \"duration\": \"PT0.000006942S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1110,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:26.906487350Z\",\n        \"endTime\": \"2020-12-10T22:09:26.906490676Z\",\n        \"duration\": \"PT0.000003326S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1111,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:27.498489563Z\",\n        \"endTime\": \"2020-12-10T22:09:27.498496491Z\",\n        \"duration\": \"PT0.000006928S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1112,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:27.498497787Z\",\n        \"endTime\": \"2020-12-10T22:09:27.498500762Z\",\n        \"duration\": \"PT0.000002975S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1113,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:27.499353245Z\",\n        \"endTime\": \"2020-12-10T22:09:27.499358599Z\",\n        \"duration\": \"PT0.000005354S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1114,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:27.499359370Z\",\n        \"endTime\": \"2020-12-10T22:09:27.499369458Z\",\n        \"duration\": \"PT0.000010088S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1115,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:37.508636877Z\",\n        \"endTime\": \"2020-12-10T22:09:37.508681680Z\",\n        \"duration\": \"PT0.000044803S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1116,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:37.508685531Z\",\n        \"endTime\": \"2020-12-10T22:09:37.508695007Z\",\n        \"duration\": \"PT0.000009476S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1117,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:37.511172766Z\",\n        \"endTime\": \"2020-12-10T22:09:37.511187144Z\",\n        \"duration\": \"PT0.000014378S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1118,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:37.511189093Z\",\n        \"endTime\": \"2020-12-10T22:09:37.511196269Z\",\n        \"duration\": \"PT0.000007176S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1119,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[6ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:46.899800461Z\",\n        \"endTime\": \"2020-12-10T22:09:46.899845846Z\",\n        \"duration\": \"PT0.000045385S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1120,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[6ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:46.899848957Z\",\n        \"endTime\": \"2020-12-10T22:09:46.899858714Z\",\n        \"duration\": \"PT0.000009757S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1121,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:46.909673114Z\",\n        \"endTime\": \"2020-12-10T22:09:46.909692398Z\",\n        \"duration\": \"PT0.000019284S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1122,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:46.909695714Z\",\n        \"endTime\": \"2020-12-10T22:09:46.909736202Z\",\n        \"duration\": \"PT0.000040488S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1123,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:47.499994793Z\",\n        \"endTime\": \"2020-12-10T22:09:47.500012457Z\",\n        \"duration\": \"PT0.000017664S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1124,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:47.500017115Z\",\n        \"endTime\": \"2020-12-10T22:09:47.500044001Z\",\n        \"duration\": \"PT0.000026886S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1125,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:47.502668352Z\",\n        \"endTime\": \"2020-12-10T22:09:47.502684126Z\",\n        \"duration\": \"PT0.000015774S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1126,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:47.502686356Z\",\n        \"endTime\": \"2020-12-10T22:09:47.502727844Z\",\n        \"duration\": \"PT0.000041488S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1127,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:57.515643784Z\",\n        \"endTime\": \"2020-12-10T22:09:57.515660977Z\",\n        \"duration\": \"PT0.000017193S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1128,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:57.515664005Z\",\n        \"endTime\": \"2020-12-10T22:09:57.515671803Z\",\n        \"duration\": \"PT0.000007798S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1129,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:57.517849800Z\",\n        \"endTime\": \"2020-12-10T22:09:57.517864002Z\",\n        \"duration\": \"PT0.000014202S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1130,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:09:57.517866002Z\",\n        \"endTime\": \"2020-12-10T22:09:57.517872710Z\",\n        \"duration\": \"PT0.000006708S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1131,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:06.891155205Z\",\n        \"endTime\": \"2020-12-10T22:10:06.891173489Z\",\n        \"duration\": \"PT0.000018284S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1132,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:06.891176187Z\",\n        \"endTime\": \"2020-12-10T22:10:06.891186157Z\",\n        \"duration\": \"PT0.00000997S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1133,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:07.498275970Z\",\n        \"endTime\": \"2020-12-10T22:10:07.498314047Z\",\n        \"duration\": \"PT0.000038077S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1134,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:07.498317280Z\",\n        \"endTime\": \"2020-12-10T22:10:07.498326307Z\",\n        \"duration\": \"PT0.000009027S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1135,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:07.500776981Z\",\n        \"endTime\": \"2020-12-10T22:10:07.500813790Z\",\n        \"duration\": \"PT0.000036809S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1136,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:07.500815990Z\",\n        \"endTime\": \"2020-12-10T22:10:07.500823353Z\",\n        \"duration\": \"PT0.000007363S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1137,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:16.895527975Z\",\n        \"endTime\": \"2020-12-10T22:10:16.895534720Z\",\n        \"duration\": \"PT0.000006745S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1138,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:16.895535717Z\",\n        \"endTime\": \"2020-12-10T22:10:16.895545716Z\",\n        \"duration\": \"PT0.000009999S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1139,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:17.489418657Z\",\n        \"endTime\": \"2020-12-10T22:10:17.489424979Z\",\n        \"duration\": \"PT0.000006322S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1140,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:17.489426096Z\",\n        \"endTime\": \"2020-12-10T22:10:17.489428927Z\",\n        \"duration\": \"PT0.000002831S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1141,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:17.490261043Z\",\n        \"endTime\": \"2020-12-10T22:10:17.490265395Z\",\n        \"duration\": \"PT0.000004352S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1142,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:17.490265954Z\",\n        \"endTime\": \"2020-12-10T22:10:17.490268057Z\",\n        \"duration\": \"PT0.000002103S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1143,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:27.498968058Z\",\n        \"endTime\": \"2020-12-10T22:10:27.498985563Z\",\n        \"duration\": \"PT0.000017505S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1144,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:27.498989080Z\",\n        \"endTime\": \"2020-12-10T22:10:27.498997041Z\",\n        \"duration\": \"PT0.000007961S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1145,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:27.501631450Z\",\n        \"endTime\": \"2020-12-10T22:10:27.501645612Z\",\n        \"duration\": \"PT0.000014162S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1146,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:27.501647791Z\",\n        \"endTime\": \"2020-12-10T22:10:27.501655118Z\",\n        \"duration\": \"PT0.000007327S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1147,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:36.891515760Z\",\n        \"endTime\": \"2020-12-10T22:10:36.891553849Z\",\n        \"duration\": \"PT0.000038089S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1148,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:36.891557023Z\",\n        \"endTime\": \"2020-12-10T22:10:36.891565794Z\",\n        \"duration\": \"PT0.000008771S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1149,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:37.536341150Z\",\n        \"endTime\": \"2020-12-10T22:10:37.536358259Z\",\n        \"duration\": \"PT0.000017109S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1150,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:37.536362460Z\",\n        \"endTime\": \"2020-12-10T22:10:37.536381881Z\",\n        \"duration\": \"PT0.000019421S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1151,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:37.538807852Z\",\n        \"endTime\": \"2020-12-10T22:10:37.538827614Z\",\n        \"duration\": \"PT0.000019762S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1152,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:37.538829918Z\",\n        \"endTime\": \"2020-12-10T22:10:37.538837597Z\",\n        \"duration\": \"PT0.000007679S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1153,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[5ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:46.903149771Z\",\n        \"endTime\": \"2020-12-10T22:10:46.903168364Z\",\n        \"duration\": \"PT0.000018593S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1154,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[5ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:46.903205378Z\",\n        \"endTime\": \"2020-12-10T22:10:46.903215506Z\",\n        \"duration\": \"PT0.000010128S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1155,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:47.502454258Z\",\n        \"endTime\": \"2020-12-10T22:10:47.502473305Z\",\n        \"duration\": \"PT0.000019047S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1156,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:47.502476939Z\",\n        \"endTime\": \"2020-12-10T22:10:47.502503252Z\",\n        \"duration\": \"PT0.000026313S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1157,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:47.505013336Z\",\n        \"endTime\": \"2020-12-10T22:10:47.505028033Z\",\n        \"duration\": \"PT0.000014697S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1158,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:47.505030194Z\",\n        \"endTime\": \"2020-12-10T22:10:47.505063914Z\",\n        \"duration\": \"PT0.00003372S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1159,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:56.890274421Z\",\n        \"endTime\": \"2020-12-10T22:10:56.890295026Z\",\n        \"duration\": \"PT0.000020605S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1160,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:56.890298060Z\",\n        \"endTime\": \"2020-12-10T22:10:56.890327090Z\",\n        \"duration\": \"PT0.00002903S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1161,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:57.517731844Z\",\n        \"endTime\": \"2020-12-10T22:10:57.517766738Z\",\n        \"duration\": \"PT0.000034894S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1162,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:57.517770577Z\",\n        \"endTime\": \"2020-12-10T22:10:57.517778727Z\",\n        \"duration\": \"PT0.00000815S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1163,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:57.520413126Z\",\n        \"endTime\": \"2020-12-10T22:10:57.520431020Z\",\n        \"duration\": \"PT0.000017894S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1164,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:10:57.520434322Z\",\n        \"endTime\": \"2020-12-10T22:10:57.520442872Z\",\n        \"duration\": \"PT0.00000855S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1165,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:06.879634827Z\",\n        \"endTime\": \"2020-12-10T22:11:06.879651006Z\",\n        \"duration\": \"PT0.000016179S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1166,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:06.879652325Z\",\n        \"endTime\": \"2020-12-10T22:11:06.879655282Z\",\n        \"duration\": \"PT0.000002957S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1167,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:07.489006098Z\",\n        \"endTime\": \"2020-12-10T22:11:07.489011219Z\",\n        \"duration\": \"PT0.000005121S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1168,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:07.489012678Z\",\n        \"endTime\": \"2020-12-10T22:11:07.489014860Z\",\n        \"duration\": \"PT0.000002182S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1169,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:07.489640693Z\",\n        \"endTime\": \"2020-12-10T22:11:07.489649014Z\",\n        \"duration\": \"PT0.000008321S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1170,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:07.489649708Z\",\n        \"endTime\": \"2020-12-10T22:11:07.489651852Z\",\n        \"duration\": \"PT0.000002144S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1171,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:17.506262926Z\",\n        \"endTime\": \"2020-12-10T22:11:17.506279898Z\",\n        \"duration\": \"PT0.000016972S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1172,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:17.506283844Z\",\n        \"endTime\": \"2020-12-10T22:11:17.506292195Z\",\n        \"duration\": \"PT0.000008351S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1173,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:17.511309753Z\",\n        \"endTime\": \"2020-12-10T22:11:17.511368753Z\",\n        \"duration\": \"PT0.000059S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1174,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:17.511372844Z\",\n        \"endTime\": \"2020-12-10T22:11:17.511385690Z\",\n        \"duration\": \"PT0.000012846S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1175,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:26.895205137Z\",\n        \"endTime\": \"2020-12-10T22:11:26.895229338Z\",\n        \"duration\": \"PT0.000024201S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1176,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[4ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:26.895233969Z\",\n        \"endTime\": \"2020-12-10T22:11:26.895245765Z\",\n        \"duration\": \"PT0.000011796S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1177,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[5ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:27.633616668Z\",\n        \"endTime\": \"2020-12-10T22:11:27.633633482Z\",\n        \"duration\": \"PT0.000016814S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1178,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[5ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:27.633637612Z\",\n        \"endTime\": \"2020-12-10T22:11:27.633668129Z\",\n        \"duration\": \"PT0.000030517S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1179,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:27.636240547Z\",\n        \"endTime\": \"2020-12-10T22:11:27.636283384Z\",\n        \"duration\": \"PT0.000042837S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1180,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:27.636285958Z\",\n        \"endTime\": \"2020-12-10T22:11:27.636293935Z\",\n        \"duration\": \"PT0.000007977S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1181,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:37.501252799Z\",\n        \"endTime\": \"2020-12-10T22:11:37.501275079Z\",\n        \"duration\": \"PT0.00002228S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1182,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:37.501279674Z\",\n        \"endTime\": \"2020-12-10T22:11:37.501291307Z\",\n        \"duration\": \"PT0.000011633S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1183,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:37.504279340Z\",\n        \"endTime\": \"2020-12-10T22:11:37.504320445Z\",\n        \"duration\": \"PT0.000041105S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1184,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:37.504323079Z\",\n        \"endTime\": \"2020-12-10T22:11:37.504331242Z\",\n        \"duration\": \"PT0.000008163S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1185,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[5ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:46.896205473Z\",\n        \"endTime\": \"2020-12-10T22:11:46.896229600Z\",\n        \"duration\": \"PT0.000024127S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1186,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[5ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:46.896233878Z\",\n        \"endTime\": \"2020-12-10T22:11:46.896275677Z\",\n        \"duration\": \"PT0.000041799S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1187,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[7ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:46.913410683Z\",\n        \"endTime\": \"2020-12-10T22:11:46.913434941Z\",\n        \"duration\": \"PT0.000024258S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1188,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[7ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:46.913438303Z\",\n        \"endTime\": \"2020-12-10T22:11:46.913450928Z\",\n        \"duration\": \"PT0.000012625S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1189,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:47.515135421Z\",\n        \"endTime\": \"2020-12-10T22:11:47.515153112Z\",\n        \"duration\": \"PT0.000017691S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1190,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[3ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:47.515157502Z\",\n        \"endTime\": \"2020-12-10T22:11:47.515165911Z\",\n        \"duration\": \"PT0.000008409S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1191,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:47.518409979Z\",\n        \"endTime\": \"2020-12-10T22:11:47.518428488Z\",\n        \"duration\": \"PT0.000018509S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1192,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:47.518431512Z\",\n        \"endTime\": \"2020-12-10T22:11:47.518440079Z\",\n        \"duration\": \"PT0.000008567S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1193,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:59.760222952Z\",\n        \"endTime\": \"2020-12-10T22:11:59.760228197Z\",\n        \"duration\": \"PT0.000005245S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1194,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:59.760229671Z\",\n        \"endTime\": \"2020-12-10T22:11:59.760231820Z\",\n        \"duration\": \"PT0.000002149S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1195,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:59.761415021Z\",\n        \"endTime\": \"2020-12-10T22:11:59.761419447Z\",\n        \"duration\": \"PT0.000004426S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1196,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:11:59.761424097Z\",\n        \"endTime\": \"2020-12-10T22:11:59.761426142Z\",\n        \"duration\": \"PT0.000002045S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1197,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:06.880494527Z\",\n        \"endTime\": \"2020-12-10T22:12:06.880507005Z\",\n        \"duration\": \"PT0.000012478S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1198,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:06.880508169Z\",\n        \"endTime\": \"2020-12-10T22:12:06.880510682Z\",\n        \"duration\": \"PT0.000002513S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1199,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:07.491074789Z\",\n        \"endTime\": \"2020-12-10T22:12:07.491080293Z\",\n        \"duration\": \"PT0.000005504S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1200,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:07.491081171Z\",\n        \"endTime\": \"2020-12-10T22:12:07.491086523Z\",\n        \"duration\": \"PT0.000005352S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1201,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:07.491682127Z\",\n        \"endTime\": \"2020-12-10T22:12:07.491690077Z\",\n        \"duration\": \"PT0.00000795S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1202,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:07.491690590Z\",\n        \"endTime\": \"2020-12-10T22:12:07.491692112Z\",\n        \"duration\": \"PT0.000001522S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1203,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:16.889218378Z\",\n        \"endTime\": \"2020-12-10T22:12:16.889230464Z\",\n        \"duration\": \"PT0.000012086S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1204,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:16.889231499Z\",\n        \"endTime\": \"2020-12-10T22:12:16.889234139Z\",\n        \"duration\": \"PT0.00000264S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1205,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:17.504897943Z\",\n        \"endTime\": \"2020-12-10T22:12:17.504904039Z\",\n        \"duration\": \"PT0.000006096S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1206,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:17.504905924Z\",\n        \"endTime\": \"2020-12-10T22:12:17.504908415Z\",\n        \"duration\": \"PT0.000002491S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1207,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:17.506217772Z\",\n        \"endTime\": \"2020-12-10T22:12:17.506224163Z\",\n        \"duration\": \"PT0.000006391S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1208,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:17.506225197Z\",\n        \"endTime\": \"2020-12-10T22:12:17.506227562Z\",\n        \"duration\": \"PT0.000002365S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1209,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:27.496070692Z\",\n        \"endTime\": \"2020-12-10T22:12:27.496076481Z\",\n        \"duration\": \"PT0.000005789S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1210,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:27.496077964Z\",\n        \"endTime\": \"2020-12-10T22:12:27.496086518Z\",\n        \"duration\": \"PT0.000008554S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1211,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:27.496815490Z\",\n        \"endTime\": \"2020-12-10T22:12:27.496827189Z\",\n        \"duration\": \"PT0.000011699S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1212,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:27.496827813Z\",\n        \"endTime\": \"2020-12-10T22:12:27.496829920Z\",\n        \"duration\": \"PT0.000002107S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1213,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:36.879147913Z\",\n        \"endTime\": \"2020-12-10T22:12:36.879154290Z\",\n        \"duration\": \"PT0.000006377S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1214,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:36.879155293Z\",\n        \"endTime\": \"2020-12-10T22:12:36.879157601Z\",\n        \"duration\": \"PT0.000002308S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1215,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:37.528395926Z\",\n        \"endTime\": \"2020-12-10T22:12:37.528401371Z\",\n        \"duration\": \"PT0.000005445S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1216,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:37.528403065Z\",\n        \"endTime\": \"2020-12-10T22:12:37.528410441Z\",\n        \"duration\": \"PT0.000007376S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1217,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:37.529276434Z\",\n        \"endTime\": \"2020-12-10T22:12:37.529287126Z\",\n        \"duration\": \"PT0.000010692S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1218,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:37.529287891Z\",\n        \"endTime\": \"2020-12-10T22:12:37.529289944Z\",\n        \"duration\": \"PT0.000002053S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1219,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:46.895450451Z\",\n        \"endTime\": \"2020-12-10T22:12:46.895457343Z\",\n        \"duration\": \"PT0.000006892S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1220,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/info]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:46.895458397Z\",\n        \"endTime\": \"2020-12-10T22:12:46.895460758Z\",\n        \"duration\": \"PT0.000002361S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1221,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:47.490070038Z\",\n        \"endTime\": \"2020-12-10T22:12:47.490084660Z\",\n        \"duration\": \"PT0.000014622S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1222,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:47.490086428Z\",\n        \"endTime\": \"2020-12-10T22:12:47.490089095Z\",\n        \"duration\": \"PT0.000002667S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1223,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:47.491477441Z\",\n        \"endTime\": \"2020-12-10T22:12:47.491485581Z\",\n        \"duration\": \"PT0.00000814S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1224,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:47.491486983Z\",\n        \"endTime\": \"2020-12-10T22:12:47.491506076Z\",\n        \"duration\": \"PT0.000019093S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1225,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:56.885596621Z\",\n        \"endTime\": \"2020-12-10T22:12:56.885617071Z\",\n        \"duration\": \"PT0.00002045S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1226,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:56.885618407Z\",\n        \"endTime\": \"2020-12-10T22:12:56.885622070Z\",\n        \"duration\": \"PT0.000003663S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1227,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:57.498961595Z\",\n        \"endTime\": \"2020-12-10T22:12:57.498967653Z\",\n        \"duration\": \"PT0.000006058S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1228,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:57.498968701Z\",\n        \"endTime\": \"2020-12-10T22:12:57.498971233Z\",\n        \"duration\": \"PT0.000002532S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1229,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:57.499701683Z\",\n        \"endTime\": \"2020-12-10T22:12:57.499705744Z\",\n        \"duration\": \"PT0.000004061S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1230,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[0ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:12:57.499706330Z\",\n        \"endTime\": \"2020-12-10T22:12:57.499708387Z\",\n        \"duration\": \"PT0.000002057S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1231,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:13:07.494174797Z\",\n        \"endTime\": \"2020-12-10T22:13:07.494188700Z\",\n        \"duration\": \"PT0.000013903S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1232,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:13:07.494190182Z\",\n        \"endTime\": \"2020-12-10T22:13:07.494193562Z\",\n        \"duration\": \"PT0.00000338S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1233,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:13:07.495326852Z\",\n        \"endTime\": \"2020-12-10T22:13:07.495332003Z\",\n        \"duration\": \"PT0.000005151S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1234,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:13:07.495332755Z\",\n        \"endTime\": \"2020-12-10T22:13:07.495343481Z\",\n        \"duration\": \"PT0.000010726S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1235,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:13:16.884228319Z\",\n        \"endTime\": \"2020-12-10T22:13:16.884236016Z\",\n        \"duration\": \"PT0.000007697S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1236,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:13:16.884237184Z\",\n        \"endTime\": \"2020-12-10T22:13:16.884245920Z\",\n        \"duration\": \"PT0.000008736S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1237,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:13:17.496485905Z\",\n        \"endTime\": \"2020-12-10T22:13:17.496493377Z\",\n        \"duration\": \"PT0.000007472S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1238,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:13:17.496494875Z\",\n        \"endTime\": \"2020-12-10T22:13:17.496497484Z\",\n        \"duration\": \"PT0.000002609S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1239,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:13:17.497265619Z\",\n        \"endTime\": \"2020-12-10T22:13:17.497277307Z\",\n        \"duration\": \"PT0.000011688S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1240,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:13:17.497277940Z\",\n        \"endTime\": \"2020-12-10T22:13:17.497279915Z\",\n        \"duration\": \"PT0.000001975S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1241,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:13:27.495137644Z\",\n        \"endTime\": \"2020-12-10T22:13:27.495146320Z\",\n        \"duration\": \"PT0.000008676S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1242,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:13:27.495149347Z\",\n        \"endTime\": \"2020-12-10T22:13:27.495153948Z\",\n        \"duration\": \"PT0.000004601S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1243,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:13:27.496374280Z\",\n        \"endTime\": \"2020-12-10T22:13:27.496380402Z\",\n        \"duration\": \"PT0.000006122S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1244,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:13:27.496381162Z\",\n        \"endTime\": \"2020-12-10T22:13:27.496393874Z\",\n        \"duration\": \"PT0.000012712S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1245,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:13:36.885142231Z\",\n        \"endTime\": \"2020-12-10T22:13:36.885148233Z\",\n        \"duration\": \"PT0.000006002S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1246,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/actuator/health]; client=[192.168.1.12]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:13:36.885149073Z\",\n        \"endTime\": \"2020-12-10T22:13:36.885166134Z\",\n        \"duration\": \"PT0.000017061S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1247,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:13:37.500247814Z\",\n        \"endTime\": \"2020-12-10T22:13:37.500260424Z\",\n        \"duration\": \"PT0.00001261S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1248,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:13:37.500262332Z\",\n        \"endTime\": \"2020-12-10T22:13:37.500264893Z\",\n        \"duration\": \"PT0.000002561S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1249,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:13:37.501035303Z\",\n        \"endTime\": \"2020-12-10T22:13:37.501040330Z\",\n        \"duration\": \"PT0.000005027S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1250,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:13:37.501040987Z\",\n        \"endTime\": \"2020-12-10T22:13:37.501043150Z\",\n        \"duration\": \"PT0.000002163S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1251,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:13:47.494395844Z\",\n        \"endTime\": \"2020-12-10T22:13:47.494401427Z\",\n        \"duration\": \"PT0.000005583S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1252,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[1ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:13:47.494402538Z\",\n        \"endTime\": \"2020-12-10T22:13:47.494409784Z\",\n        \"duration\": \"PT0.000007246S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1253,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.boot.context.config.DelegatingApplicationListener@729d991e\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:13:47.495762691Z\",\n        \"endTime\": \"2020-12-10T22:13:47.495766898Z\",\n        \"duration\": \"PT0.000004207S\"\n      },\n      {\n        \"startupStep\": {\n          \"name\": \"spring.event.invoke-listener\",\n          \"id\": 1254,\n          \"parentId\": null,\n          \"tags\": [\n            {\n              \"key\": \"event\",\n              \"value\": \"ServletRequestHandledEvent: url=[/instances]; client=[127.0.0.1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]\"\n            },\n            {\n              \"key\": \"listener\",\n              \"value\": \"org.springframework.security.context.DelegatingApplicationListener@fcd0e8d\"\n            }\n          ]\n        },\n        \"startTime\": \"2020-12-10T22:13:47.495767434Z\",\n        \"endTime\": \"2020-12-10T22:13:47.495769441Z\",\n        \"duration\": \"PT0.000002007S\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/services/startup-actuator.spec.ts",
    "content": "/*\n * Copyright 2014-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { cloneDeep } from 'lodash-es';\nimport { beforeEach, describe, expect, it } from 'vitest';\n\nimport { StartupActuatorService } from './startup-actuator';\nimport fixture from './startup-actuator.fixture.spec.json';\n\ndescribe('StartupActuatorService', () => {\n  let data: any = {};\n  let events: any = {};\n\n  beforeEach(() => {\n    data = cloneDeep(fixture);\n    events = data.timeline.events;\n  });\n\n  it('should find element by id', () => {\n    const item8 = StartupActuatorService.getById(events, 8);\n\n    expect.assertions(1);\n    expect(item8).toEqual({\n      startupStep: {\n        name: 'spring.beans.instantiate',\n        id: 8,\n        parentId: 7,\n        tags: [\n          {\n            key: 'beanName',\n            value:\n              'org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory',\n          },\n        ],\n      },\n      startTime: '2020-12-10T21:53:42.958550077Z',\n      endTime: '2020-12-10T21:53:42.960040652Z',\n      duration: 'PT0.001490575S',\n    });\n  });\n\n  it('should add parents as reference', () => {\n    const tree = StartupActuatorService.parseAsTree(data);\n    const child = tree.getById(8);\n    const parent = tree.getById(7);\n\n    expect.assertions(2);\n    expect(child.startupStep.parent).toBe(parent);\n    expect(child.startupStep.depth).toBe(3);\n  });\n\n  it('should add children as reference', () => {\n    const tree = StartupActuatorService.parseAsTree(data);\n    const parent = tree.getById(7);\n    const child = tree.getById(8);\n\n    expect.assertions(1);\n    expect(parent.startupStep.children).toContain(child);\n  });\n\n  it('should extract map from event tags', () => {\n    const tag = {\n      key: 'event',\n      value:\n        'ServletRequestHandledEvent: url=[/applications]; client=[0:0:0:0:0:0:0:1]; method=[GET, POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[13ms]; status=[OK]',\n    };\n\n    const parsedTag = StartupActuatorService.parseTag(tag);\n\n    expect.assertions(1);\n    expect(parsedTag).toStrictEqual({\n      ...tag,\n      parsed: {\n        eventName: 'ServletRequestHandledEvent',\n        url: ['/applications'],\n        client: ['0:0:0:0:0:0:0:1'],\n        method: ['GET', 'POST'],\n        servlet: ['dispatcherServlet'],\n        time: ['13ms'],\n        status: ['OK'],\n        session: ['null'],\n        user: ['null'],\n      },\n    });\n  });\n\n  it('should parse tags when iterating startupSteps', () => {\n    const tree = StartupActuatorService.parseAsTree(data);\n    const children = tree.getByParentId(6);\n\n    expect.assertions(2);\n    expect(children.length).toBe(25);\n    expect(children.map((event) => event.startupStep.id)).toStrictEqual([\n      7, 9, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 24, 25, 26, 27, 28, 31, 32,\n      34, 35, 36, 37, 38, 43,\n    ]);\n  });\n\n  it('should find start and end time', () => {\n    const tree = StartupActuatorService.parseAsTree(data);\n    const startTime = tree.getStartTime();\n    const endTime = tree.getEndTime();\n\n    expect.assertions(2);\n    expect(startTime).toBe(Date.parse('2020-12-10T21:53:41.836728041Z'));\n    expect(endTime).toBe(Date.parse('2020-12-10T22:13:47.495769441Z'));\n  });\n\n  it('should return progress of event in context of whole tree', () => {\n    const tree = StartupActuatorService.parseAsTree(data);\n    const period = tree.getPeriod(tree.getById(1));\n\n    expect.assertions(2);\n    expect(period.start).toBe(0);\n    expect(period.end).toBe(0.000038153408219073554);\n  });\n\n  it('should return the path in tree for a given id (no pun intended)', () => {\n    const tree = StartupActuatorService.parseAsTree(data);\n\n    const path = tree.getPath(10);\n    expect.assertions(1);\n    expect(path).toEqual([10, 9, 6, 5]);\n  });\n\n  it('should parse duration to seconds', () => {\n    const tree = StartupActuatorService.parseAsTree(data);\n    const event = tree.getById(1);\n\n    expect.assertions(1);\n    expect(event.duration).toBe(45.279861);\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/services/startup-actuator.ts",
    "content": "/*\n * Copyright 2014-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { StartupActuatorEventTree } from '@/services/startup-activator-tree';\nimport { parse, toMilliseconds } from '@/utils/iso8601-duration';\n\nconst regex = new RegExp('([^=\\\\s]*)=\\\\[([^\\\\]]*)\\\\]', 'gi');\n\nfunction mapDuration(duration) {\n  if (typeof duration === 'string') {\n    return toMilliseconds(parse(duration));\n  } else if (!isNaN(Number.parseFloat(duration))) {\n    return toMilliseconds(duration);\n  } else {\n    return -1;\n  }\n}\n\nexport const StartupActuatorService = {\n  parseAsTree(data) {\n    const events = data.timeline.events || [];\n    const eventsForTree = events\n      .sort((a, b) => a.startupStep.id - b.startupStep.id)\n      .map((event) => {\n        event.startupStep.parent = this.getById(\n          events,\n          event.startupStep.parentId,\n        );\n        event.startupStep.children = this.getByParentId(\n          events,\n          event.startupStep.id,\n        );\n\n        event.startupStep.tags = event.startupStep.tags.map(this.parseTag);\n        event.duration = mapDuration(event.duration);\n\n        event.startupStep.depth = 0;\n\n        return event;\n      })\n      .map((event) => {\n        let parent = event.startupStep.parent;\n        while (parent !== null && parent !== undefined) {\n          parent = parent.startupStep.parent;\n          event.startupStep.depth++;\n        }\n\n        return event;\n      });\n\n    return new StartupActuatorEventTree(eventsForTree);\n  },\n  getById(events, id) {\n    return (events || []).find((event) => event.startupStep.id === id);\n  },\n  getByParentId(events, id) {\n    return (events || []).filter((event) => event.startupStep.parentId === id);\n  },\n  parseTag(param) {\n    if (param.key === 'event') {\n      const parsed = {};\n      parsed['eventName'] = param.value.split(':')[0];\n\n      const matcher = param.value.matchAll(regex);\n      for (const match of matcher) {\n        parsed[match[1]] = match[2].split(',').map((s) => s.trim());\n      }\n      param.parsed = parsed;\n    }\n\n    return param;\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/shell/i18n.de.json",
    "content": "{\n  \"navbar\": {\n    \"signedInAs\": \"Angemeldet als\",\n    \"logout\": \"Abmelden\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/shell/i18n.en.json",
    "content": "{\n  \"navbar\": {\n    \"signedInAs\": \"Signed in as\",\n    \"logout\": \"Log out\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/shell/i18n.es.json",
    "content": "{\n  \"navbar\": {\n    \"logout\": \"Salir\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/shell/i18n.fr.json",
    "content": "{\n  \"navbar\": {\n    \"logout\": \"Déconnexion\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/shell/i18n.is.json",
    "content": "{\n  \"navbar\": {\n    \"logout\": \"Skrá út\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/shell/i18n.ko.json",
    "content": "{\n  \"navbar\": {\n    \"logout\": \"로그아웃\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/shell/i18n.ru.json",
    "content": "{\n  \"navbar\": {\n    \"logout\": \"Выход\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/shell/i18n.zh-CN.json",
    "content": "{\n  \"navbar\": {\n    \"logout\": \"注销\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/shell/i18n.zh-TW.json",
    "content": "{\n  \"navbar\": {\n    \"signedInAs\": \"目前登入身分\",\n    \"logout\": \"登出\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/shell/index.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div id=\"app\">\n    <SbaNavbar :error=\"error\" />\n    <div class=\"sba-container\">\n      <router-view :applications=\"applications\" :error=\"error\" />\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { useApplicationStore } from '@/composables/useApplicationStore';\nimport SbaNavbar from '@/shell/navbar';\n\ndefineProps({\n  error: {\n    type: Error,\n    default: null,\n  },\n});\n\nconst { applications } = useApplicationStore();\n</script>\n\n<style scoped>\n.sba-container {\n  @apply pt-14 h-full;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/shell/navbar.spec.ts",
    "content": "import { screen } from '@testing-library/vue';\nimport { Mocked, afterEach, describe, expect, it, vi } from 'vitest';\n\nimport { getAvailableLocales } from '@/i18n';\nimport { getCurrentUser } from '@/sba-config';\nimport Navbar from '@/shell/navbar.vue';\nimport { render } from '@/test-utils';\n\nvi.mock('@/sba-config', async () => {\n  const sbaConfig =\n    await vi.importActual<typeof import('@/sba-config')>('@/sba-config');\n\n  return {\n    ...sbaConfig,\n    getCurrentUser: vi.fn(),\n    getAvailableLanguages: vi.fn(),\n  };\n});\n\nvi.mock('@/i18n', async () => {\n  const i18n = await vi.importActual<typeof import('@/i18n')>('@/i18n');\n\n  return {\n    ...i18n,\n    getAvailableLocales: vi.fn().mockReturnValue([]),\n  };\n});\n\ndescribe('Navbar', function () {\n  afterEach(() => {\n    vi.clearAllMocks();\n  });\n\n  it('User menu is visible, when a user is logged in', async function () {\n    (getCurrentUser as Mocked<any>).mockReturnValue({\n      name: 'mail@example.org',\n    });\n\n    render(Navbar);\n\n    expect(screen.getByTestId('usermenu')).toBeVisible();\n    expect(screen.getByText('mail@example.org')).toBeVisible();\n  });\n\n  it.each`\n    user\n    ${null}\n    ${{}}\n  `(\"User menu is hidden, when user object is '$user'\", async function (user) {\n    (getCurrentUser as Mocked<any>).mockReturnValue(user);\n\n    render(Navbar);\n\n    expect(screen.queryByTestId('usermenu')).not.toBeInTheDocument();\n  });\n\n  it('Language menu is visible, when more than one language is available', async function () {\n    (getAvailableLocales as Mocked<any>).mockReturnValue(['de', 'en', 'fr']);\n\n    render(Navbar, { locale: 'de' });\n\n    expect(screen.queryByText('Deutsch')).toBeInTheDocument();\n  });\n\n  it('Language menu is hidden, when just one language is available', async function () {\n    (getAvailableLocales as Mocked<any>).mockReturnValue(['de']);\n\n    render(Navbar, { locale: 'de' });\n\n    expect(screen.queryByText('Deutsch')).not.toBeInTheDocument();\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/shell/navbar.vue",
    "content": "<!--\n  - Copyright 2014-2019 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-navbar :brand=\"brand\" class=\"text-sm lg:text-base\">\n    <sba-navbar-nav>\n      <template v-for=\"item in topLevelViews\" :key=\"item.id\">\n        <template v-if=\"item.children.length === 0\">\n          <sba-nav-item\n            v-if=\"!item.href && item.name\"\n            :to=\"{ name: item.name }\"\n          >\n            <component :is=\"item.handle\" :error=\"error\" />\n          </sba-nav-item>\n          <sba-nav-item\n            v-else-if=\"item.href !== '#'\"\n            :href=\"item.href\"\n            target=\"blank\"\n          >\n            <component :is=\"item.handle\" :error=\"error\" />\n          </sba-nav-item>\n          <sba-nav-item v-else>\n            <component :is=\"item.handle\" :error=\"error\" />\n          </sba-nav-item>\n        </template>\n        <template v-else>\n          <sba-nav-dropdown :href=\"item.href\">\n            <template #label>\n              <component :is=\"item.handle\" />\n            </template>\n            <template #default>\n              <sba-dropdown-item\n                v-for=\"child in item.children\"\n                :key=\"child.name\"\n                :to=\"{ name: child.name }\"\n                v-bind=\"{ ...child }\"\n              >\n                <component :is=\"child.handle\" :error=\"error\" />\n              </sba-dropdown-item>\n            </template>\n          </sba-nav-dropdown>\n        </template>\n      </template>\n    </sba-navbar-nav>\n    <sba-navbar-nav class=\"ml-auto\">\n      <sba-nav-language-selector\n        v-if=\"availableLocales.length > 1\"\n        :available-locales=\"availableLocales\"\n        @locale-changed=\"changeLocale\"\n      />\n      <sba-nav-usermenu v-if=\"showUserMenu\" />\n      <sba-nav-item v-if=\"isAboutEnabled\" :to=\"{ name: 'about' }\">\n        <FontAwesomeIcon\n          :aria-label=\"t('about.label')\"\n          icon=\"question-circle\"\n        />\n      </sba-nav-item>\n    </sba-navbar-nav>\n  </sba-navbar>\n</template>\n\n<script lang=\"ts\" setup>\nimport { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';\nimport moment from 'moment';\nimport { computed } from 'vue';\nimport { useI18n } from 'vue-i18n';\n\nimport SbaDropdownItem from '@/components/sba-dropdown/sba-dropdown-item.vue';\nimport SbaNavDropdown from '@/components/sba-nav/sba-nav-dropdown.vue';\nimport SbaNavItem from '@/components/sba-nav/sba-nav-item.vue';\nimport SbaNavbarNav from '@/components/sba-navbar/sba-navbar-nav.vue';\nimport SbaNavbar from '@/components/sba-navbar/sba-navbar.vue';\n\nimport { useViewRegistry } from '@/composables/ViewRegistry';\nimport { getAvailableLocales } from '@/i18n';\nimport sbaConfig, { getCurrentUser } from '@/sba-config';\nimport SbaNavLanguageSelector from '@/shell/sba-nav-language-selector.vue';\nimport SbaNavUsermenu from '@/shell/sba-nav-usermenu.vue';\nimport { compareBy } from '@/utils/collections';\n\ndefineProps({\n  error: {\n    type: Error,\n    default: null,\n  },\n});\n\nconst availableLocales = getAvailableLocales();\n\nconst currentUser = getCurrentUser();\nconst showUserMenu = !!currentUser && Object.hasOwn(currentUser, 'name');\n\nconst { views, getViewByName } = useViewRegistry();\nconst i18n = useI18n();\nconst t = i18n.t;\n\nconst brand = sbaConfig.uiSettings.brand;\n\nconst topLevelViews = computed(() => {\n  let rootViews = views\n    .filter((view) => {\n      return (\n        !view.parent &&\n        !view.name?.includes('instance') &&\n        !view.name?.includes('about') &&\n        !view.path?.includes('/instance') &&\n        view.isEnabled()\n      );\n    })\n    .sort(compareBy((v) => v.order));\n\n  return rootViews.map((rootView) => {\n    const children = views.filter((v) => v.parent === rootView.name);\n\n    return {\n      ...rootView,\n      children,\n    };\n  });\n});\n\nconst isAboutEnabled = getViewByName('about')?.isEnabled();\nconst changeLocale = (locale) => {\n  i18n.locale.value = locale;\n  moment.locale(locale);\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/shell/sba-dropdown-logout-item.vue",
    "content": "<template>\n  <form action=\"logout\" class=\"w-full\" method=\"post\">\n    <sba-dropdown-item as=\"button\" type=\"submit\">\n      <input\n        v-if=\"csrfToken\"\n        :name=\"csrfParameterName\"\n        :value=\"csrfToken\"\n        type=\"hidden\"\n      />\n      <font-awesome-icon icon=\"sign-out-alt\" />&nbsp;<span\n        v-text=\"$t('navbar.logout')\"\n      />\n    </sba-dropdown-item>\n  </form>\n</template>\n\n<script lang=\"ts\" setup>\nimport SbaDropdownItem from '@/components/sba-dropdown/sba-dropdown-item.vue';\n\nimport sbaConfig from '@/sba-config';\n\nconst readCookie = (name) => {\n  const match = document.cookie.match(\n    new RegExp('(^|;\\\\s*)(' + name + ')=([^;]*)'),\n  );\n  return match ? decodeURIComponent(match[3]) : null;\n};\n\nconst csrfToken = readCookie('XSRF-TOKEN');\nconst csrfParameterName = sbaConfig.csrf.parameterName;\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/shell/sba-nav-language-selector.spec.ts",
    "content": "import userEvent from '@testing-library/user-event';\nimport { screen } from '@testing-library/vue';\nimport { describe, expect, it } from 'vitest';\n\nimport SbaNavLanguageSelector from '@/shell/sba-nav-language-selector.vue';\nimport { render } from '@/test-utils';\n\ndescribe('NavbarItemLanguageSelector', () => {\n  it('should print the locale with the country for selected language/locale', async () => {\n    render(SbaNavLanguageSelector, {\n      locale: 'de',\n      props: {\n        availableLocales: ['de', 'fr'],\n      },\n    });\n\n    const buttons = await screen.findByText('Deutsch');\n    expect(buttons).toBeDefined();\n  });\n\n  it('should print locale with the country for available language in menu', async () => {\n    render(SbaNavLanguageSelector, {\n      locale: 'de',\n      props: {\n        availableLocales: ['de', 'fr'],\n      },\n    });\n\n    const languageButton = await screen.findByText('Deutsch');\n    await userEvent.click(languageButton);\n\n    expect(await screen.findByText('français')).toBeDefined();\n  });\n\n  it('should print the locale as label when it cannot be translated', async () => {\n    render(SbaNavLanguageSelector, {\n      locale: 'zz',\n      props: {\n        availableLocales: ['zz'],\n      },\n    });\n\n    const htmlElement = await screen.findByText('zz');\n    expect(htmlElement).toBeDefined();\n  });\n\n  it('should emit the selected locale', async () => {\n    const wrapper = render(SbaNavLanguageSelector, {\n      locale: 'de',\n      props: {\n        availableLocales: ['de', 'fr'],\n      },\n    });\n\n    await userEvent.click(await screen.findByText('Deutsch'));\n    await userEvent.click(await screen.findByText('français'));\n\n    const emitted = wrapper.emitted();\n    expect(emitted['locale-changed'][0]).toContain('fr');\n  });\n\n  it.each`\n    locale     | expected\n    ${'de'}    | ${'Deutsch'}\n    ${'is'}    | ${'íslenska'}\n    ${'de-DE'} | ${'Deutsch (Deutschland)'}\n    ${'zh-CN'} | ${'简体中文'}\n    ${'zh-TW'} | ${'繁體中文'}\n  `(\n    \"should show '$expected' for given '$locale'\",\n    async ({ locale, expected }) => {\n      render(SbaNavLanguageSelector, {\n        locale,\n        propsData: {\n          availableLocales: ['de'],\n        },\n      });\n\n      await userEvent.click(await screen.findByText(expected));\n    },\n  );\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/shell/sba-nav-language-selector.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-nav-dropdown :text=\"selectedLanguage.label\">\n    <sba-dropdown-item\n      v-for=\"lang in languages\"\n      :key=\"lang.locale\"\n      @click=\"() => localeChanged(lang)\"\n    >\n      {{ lang.label }}\n    </sba-dropdown-item>\n  </sba-nav-dropdown>\n</template>\n\n<script lang=\"ts\" setup>\nimport { computed } from 'vue';\nimport { useI18n } from 'vue-i18n';\n\nimport SbaDropdownItem from '@/components/sba-dropdown/sba-dropdown-item.vue';\nimport SbaNavDropdown from '@/components/sba-nav/sba-nav-dropdown.vue';\n\nconst { locale: currentLocale } = useI18n();\n\nconst props = defineProps({\n  availableLocales: { type: Array, required: true },\n});\n\nconst emit = defineEmits(['locale-changed']);\n\nconst selectedLanguage = computed(() => {\n  return mapLocale(currentLocale.value);\n});\n\nconst languages = computed(() => {\n  return props.availableLocales.map(mapLocale).filter((mappedLocale) => {\n    return mappedLocale.locale !== selectedLanguage.value.locale;\n  });\n});\n\nconst localeChanged = ($event) => {\n  const selectedLocale = $event.locale;\n  if (selectedLocale !== currentLocale) {\n    emit('locale-changed', selectedLocale);\n  }\n};\n\nconst mapLocale = (locale) => {\n  try {\n    let languageTag = locale.split('-').reverse().pop();\n    let regionTag =\n      locale.split('-').length > 1 ? `-${locale.split('-').pop()}` : '';\n\n    if (locale.toLowerCase().startsWith('zh')) {\n      if (locale.endsWith('CN')) {\n        regionTag = '-Hans';\n      }\n      if (locale.endsWith('TW')) {\n        regionTag = '-Hant';\n      }\n    }\n\n    let translatedLanguageNames = new Intl.DisplayNames([locale], {\n      type: 'language',\n    });\n    let label = translatedLanguageNames.of(`${languageTag}${regionTag}`);\n\n    if (label?.toUpperCase() === 'UNKNOWN REGION') {\n      label = locale;\n    }\n\n    return {\n      locale,\n      label,\n    };\n  } catch {\n    return {\n      locale,\n      label: locale,\n    };\n  }\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/shell/sba-nav-usermenu.vue",
    "content": "<template>\n  <sba-nav-dropdown data-testid=\"usermenu\">\n    <template #label>\n      <font-awesome-icon\n        class=\"w-10 rounded-full white mr-2\"\n        color=\"white\"\n        icon=\"user-circle\"\n      />\n      <strong v-text=\"username\" />\n    </template>\n    <sba-dropdown-item>\n      Signed in as: <strong v-text=\"username\" />\n    </sba-dropdown-item>\n\n    <template v-if=\"userSubMenuItems.length > 0\">\n      <sba-dropdown-divider />\n\n      <template v-for=\"item in userSubMenuItems\" :key=\"item.name\">\n        <sba-dropdown-item\n          v-if=\"!item.href && item.name\"\n          :to=\"{ name: item.name }\"\n        >\n          <component :is=\"item.handle\" />\n        </sba-dropdown-item>\n        <sba-dropdown-item\n          v-else-if=\"item.href !== '#'\"\n          :href=\"item.href\"\n          target=\"blank\"\n        >\n          <component :is=\"item.handle\" />\n        </sba-dropdown-item>\n        <sba-dropdown-item v-else>\n          <component :is=\"item.handle\" />\n        </sba-dropdown-item>\n      </template>\n    </template>\n\n    <sba-dropdown-divider />\n\n    <sba-dropdown-logout-item />\n  </sba-nav-dropdown>\n</template>\n\n<script lang=\"ts\" setup>\nimport { computed } from 'vue';\n\nimport SbaDropdownDivider from '@/components/sba-dropdown/sba-dropdown-divider.vue';\nimport SbaDropdownItem from '@/components/sba-dropdown/sba-dropdown-item.vue';\nimport SbaNavDropdown from '@/components/sba-nav/sba-nav-dropdown.vue';\n\nimport { useViewRegistry } from '@/composables/ViewRegistry';\nimport { getCurrentUser } from '@/sba-config';\nimport SbaDropdownLogoutItem from '@/shell/sba-dropdown-logout-item.vue';\n\nconst currentUser = getCurrentUser();\nconst username = currentUser ? currentUser.name : null;\n\nconst { views } = useViewRegistry();\n\nconst userSubMenuItems = computed(() => {\n  return views.filter((v) => v.parent === 'user');\n});\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/store.spec.ts",
    "content": "import { waitFor } from '@testing-library/vue';\nimport { HttpResponse, http } from 'msw';\nimport { ReplaySubject } from 'rxjs';\nimport { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport { registerWithOneInstance } from '@/mocks/fixtures/eventStream/registerWithOneInstance';\nimport { registerWithTwoInstances } from '@/mocks/fixtures/eventStream/registerWithTwoInstances';\nimport { server } from '@/mocks/server';\nimport Application from '@/services/application';\nimport ApplicationStore from '@/store';\n\ndescribe('store', () => {\n  let applicationStore;\n\n  let mockSubject;\n  vi.spyOn(Application, 'getStream').mockImplementation(function () {\n    return mockSubject;\n  });\n\n  let changedListener;\n  let addedListener;\n  let updateListener;\n  let removedListener;\n\n  beforeEach(() => {\n    server.use(\n      http.get('/applications', () => {\n        return HttpResponse.json([]);\n      }),\n    );\n\n    changedListener = vi.fn();\n    addedListener = vi.fn();\n    updateListener = vi.fn();\n    removedListener = vi.fn();\n\n    mockSubject = new ReplaySubject();\n    applicationStore = new ApplicationStore();\n    applicationStore.start();\n    applicationStore.addEventListener('changed', changedListener);\n    applicationStore.addEventListener('added', addedListener);\n    applicationStore.addEventListener('updated', updateListener);\n    applicationStore.addEventListener('removed', removedListener);\n    applicationStore.addEventListener('error', (error) => console.error(error));\n  });\n\n  afterEach(() => {\n    applicationStore.stop();\n  });\n\n  it('registers a new instance', async () => {\n    mockSubject.next({ data: registerWithOneInstance });\n\n    await waitFor(() => {\n      const applications = applicationStore.applications;\n      expect(applications).toHaveLength(1);\n      expect(applications[0].instances).toHaveLength(1);\n    });\n\n    expect(changedListener).toHaveBeenCalled();\n    expect(addedListener).toHaveBeenCalled();\n    expect(updateListener).not.toHaveBeenCalled();\n    expect(removedListener).not.toHaveBeenCalled();\n  });\n\n  it('registers one instance and then another one', async () => {\n    mockSubject.next({ data: registerWithOneInstance });\n    mockSubject.next({ data: registerWithTwoInstances });\n\n    await waitFor(() => {\n      const applications = applicationStore.applications;\n      expect(applications).toHaveLength(1);\n      expect(applications[0].instances).toHaveLength(2);\n    });\n\n    expect(changedListener).toHaveBeenCalled();\n    expect(addedListener).toHaveBeenCalled();\n    expect(updateListener).toHaveBeenCalled();\n    expect(removedListener).not.toHaveBeenCalled();\n  });\n\n  it('deregisters an instance', async () => {\n    mockSubject.next({ data: registerWithTwoInstances });\n    mockSubject.next({ data: registerWithOneInstance });\n\n    await waitFor(() => {\n      const applications = applicationStore.applications;\n      expect(applications).toHaveLength(1);\n      expect(applications[0].instances).toHaveLength(1);\n    });\n\n    expect(changedListener).toHaveBeenCalled();\n    expect(addedListener).toHaveBeenCalled();\n    expect(updateListener).toHaveBeenCalled();\n    expect(removedListener).not.toHaveBeenCalled();\n  });\n\n  it('removes an application', async () => {\n    mockSubject.next({ data: registerWithOneInstance });\n\n    await waitFor(() => {\n      expect(applicationStore.applications).toHaveLength(1);\n    });\n\n    const data = { ...registerWithOneInstance, instances: [] };\n    mockSubject.next({ data: data });\n\n    await waitFor(() => {\n      expect(applicationStore.applications).toHaveLength(0);\n    });\n\n    expect(changedListener).toHaveBeenCalled();\n    expect(addedListener).toHaveBeenCalled();\n    expect(updateListener).not.toHaveBeenCalled();\n    expect(removedListener).toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/store.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n  Subscription,\n  bufferTime,\n  concat,\n  concatMap,\n  defer,\n  delay,\n  filter,\n  map,\n  retryWhen,\n  tap,\n} from 'rxjs';\n\nimport Application from './services/application';\n\nexport const findInstance = (\n  applications: Application[],\n  instanceId: string,\n) => {\n  for (const application of applications) {\n    const instance = application.findInstance(instanceId);\n    if (instance) {\n      return instance;\n    }\n  }\n  return undefined;\n};\n\nexport const findApplicationForInstance = (\n  applications: Application[],\n  instanceId: string,\n) => {\n  return applications.find((application) =>\n    Boolean(application.findInstance(instanceId)),\n  );\n};\n\ntype NoopListener = () => void;\ntype ApplicationAddedListener = (newApplications: Application[]) => void;\ntype ApplicationStoreListener = NoopListener | ApplicationAddedListener;\n\nexport default class ApplicationStore {\n  private _listeners: { [p: string]: Array<ApplicationStoreListener> } = {};\n  private _applications: Map<string, Application> = new Map();\n  private applications: Application[] = [];\n  private subscription: Subscription = null;\n\n  addEventListener(type: string, listener: ApplicationStoreListener) {\n    if (type in this._listeners) {\n      this._listeners[type].push(listener);\n    } else {\n      this._listeners[type] = [listener];\n    }\n  }\n\n  removeEventListener(type: string, listener: ApplicationStoreListener) {\n    if (!(type in this._listeners)) {\n      return;\n    }\n\n    const idx = this._listeners[type].indexOf(listener);\n    if (idx > 0) {\n      this._listeners[type].splice(idx, 1);\n    }\n  }\n\n  _dispatchEvent(type: string, ...args: any[]) {\n    if (!(type in this._listeners)) {\n      return;\n    }\n    this._listeners[type].forEach((listener) => listener.call(this, ...args));\n  }\n\n  start() {\n    // Do not resubscribe when already started\n    if (this.subscription !== null) {\n      return;\n    }\n    const list = defer(() => Application.list()).pipe(\n      tap(() => this._dispatchEvent('connected')),\n      concatMap((message) => message.data),\n    );\n\n    const stream = Application.getStream().pipe(map((message) => message.data));\n\n    this.subscription = concat(list, stream)\n      .pipe(\n        retryWhen((errors) =>\n          errors.pipe(\n            tap((error) => this._dispatchEvent('error', error)),\n            delay(5000),\n          ),\n        ),\n        bufferTime(250),\n        filter((a) => a.length > 0),\n      )\n      .subscribe({\n        next: (applications) => this.updateApplications(applications),\n      });\n  }\n\n  updateApplications(applications: Application[]) {\n    applications.forEach((a) => this.updateApplication(a));\n    this.applications = [...this._applications.values()];\n    this._dispatchEvent('changed', this.applications);\n  }\n\n  updateApplication(application: Application) {\n    const oldApplication = this._applications.get(application.name);\n    if (!oldApplication && application.instances.length > 0) {\n      this._applications.set(application.name, application);\n      this._dispatchEvent('added', application);\n    } else if (oldApplication && application.instances.length > 0) {\n      this._applications.set(application.name, application);\n      this._dispatchEvent('updated', application, oldApplication);\n    } else if (oldApplication && application.instances.length <= 0) {\n      this._applications.delete(application.name);\n      this._dispatchEvent('removed', oldApplication);\n    }\n  }\n\n  stop() {\n    if (this.subscription && !this.subscription.closed) {\n      try {\n        this.subscription.unsubscribe();\n      } finally {\n        this.subscription = null;\n      }\n    }\n  }\n\n  findApplicationByInstanceId(instanceId: string) {\n    return findApplicationForInstance(this.applications, instanceId);\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/test-utils.ts",
    "content": "import NotificationcenterPlugin from '@stekoe/vue-toast-notificationcenter';\nimport { RenderResult, render as tlRender } from '@testing-library/vue';\nimport { RouterLinkStub } from '@vue/test-utils';\nimport { merge } from 'lodash-es';\nimport PrimeVue from 'primevue/config';\nimport Tooltip from 'primevue/tooltip';\nimport { createI18n } from 'vue-i18n';\nimport { createRouter, createWebHashHistory } from 'vue-router';\n\nimport components from './components/index';\nimport SbaModalPlugin from './plugins/modal';\n\nimport { createViewRegistry } from '@/composables/ViewRegistry';\nimport ViewRegistry from '@/viewRegistry';\n\nlet terms = {};\nconst modules: Record<string, any> = import.meta.glob('@/**/i18n.en.json', {\n  eager: true,\n});\nfor (const modulesKey in modules) {\n  terms = { ...terms, ...modules[modulesKey] };\n}\nexport let router;\ncreateViewRegistry();\n\nexport const render = (testComponent, options?): RenderResult => {\n  const routes = [{ path: '/', component: testComponent }];\n  if (testComponent.install) {\n    const viewRegistry = new ViewRegistry();\n    testComponent.install({ viewRegistry });\n    const routeForComponent = viewRegistry._toRoutes(() => true)[0];\n\n    routes.push({\n      ...routeForComponent,\n      path: '/' + routeForComponent.path,\n    });\n  }\n\n  router = createRouter({\n    history: createWebHashHistory(),\n    routes: routes,\n  });\n\n  const renderOptions = merge(\n    {\n      global: {\n        plugins: [\n          router,\n          createI18n({\n            locale: options?.locale || 'en',\n            messages: {\n              en: terms,\n            },\n            legacy: false,\n            fallbackWarn: false,\n            missingWarn: false,\n          }),\n          NotificationcenterPlugin,\n          SbaModalPlugin,\n          [\n            PrimeVue,\n            {\n              theme: {\n                options: {\n                  darkModeSelector: false,\n                },\n              },\n            },\n          ],\n          components,\n        ],\n        directives: {\n          tooltip: Tooltip,\n        },\n        stubs: {\n          RouterLink: RouterLinkStub,\n          'sba-exchanges-chart': true,\n        },\n      },\n    },\n    options,\n  );\n  return tlRender(testComponent, renderOptions);\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/tests/setup.ts",
    "content": "import '@testing-library/jest-dom';\nimport '@testing-library/jest-dom/vitest';\nimport { cleanup } from '@testing-library/vue';\nimport { afterAll, afterEach, beforeAll, vi } from 'vitest';\n\nimport { server } from '@/mocks/server';\nimport sbaConfig from '@/sba-config';\n\nglobal.IntersectionObserver = vi.fn().mockImplementation(function () {\n  return {\n    observe: vi.fn(),\n    unobserve: vi.fn(),\n    disconnect: vi.fn(),\n  };\n});\nglobal.ResizeObserver = vi.fn().mockImplementation(function () {\n  return {\n    observe: vi.fn(),\n    unobserve: vi.fn(),\n    disconnect: vi.fn(),\n  };\n});\nglobal.matchMedia = vi.fn().mockReturnValue({\n  addEventListener: vi.fn(),\n  removeEventListener: vi.fn(),\n});\nglobal.EventSource = class {\n  constructor() {}\n  close() {}\n};\n\nglobal.SBA = sbaConfig;\n\nbeforeAll(() => server.listen({ onUnhandledRequest: 'error' }));\nafterAll(() => server.close());\nafterEach(() => server.resetHandlers());\n\n// runs a cleanup after each test case (e.g. clearing jsdom)\nafterEach(() => {\n  vi.clearAllMocks();\n  cleanup();\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/toast-theme.css",
    "content": "@import '@stekoe/vue-toast-notificationcenter/dist/style.css';\n\n.v-toast-container {\n  z-index: 100;\n  padding: 4em 1em;\n}\n\n.v-toast__text {\n  overflow: hidden;\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/array.ts",
    "content": "/*\n * Copyright 2014-2025 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Adds values to an array and recursively flattens nested arrays.\n * Ignores null and undefined values.\n *\n * @example\n *   const arr: number[] = [];\n *   pushFlattened(arr, 1, [2, [3, null]], undefined, 4);\n *   arr = [1, 2, 3, 4]\n *\n * @template T Type of array elements\n * @param {T[]} target Target array to which values are added\n * @param {...(T | T[] | null | undefined)} values Values or arrays of values to add and flatten\n * @returns {T[]} The target array with added and flattened values\n */\nexport const pushFlattened = <T>(\n  target: T[],\n  ...values: (T | T[] | null | undefined)[]\n): T[] => {\n  for (const value of values) {\n    if (Array.isArray(value)) {\n      // recursively flatten\n      pushFlattened(target, ...value);\n    } else if (value !== null && value !== undefined) {\n      target.push(value as T);\n    }\n  }\n  return target;\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/autolink.spec.ts",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { describe, expect, it } from 'vitest';\n\nimport autolink, { createAutolink } from './autolink';\n\ndescribe('autolink should', () => {\n  it('return the input string for normal text', () => {\n    const str = 'This is just a normal text containing no hyperlinks';\n    expect(autolink(str)).toBe(str);\n  });\n\n  it('return string with anchor tag for the hyperlink', () => {\n    const str = 'Please visit http://example.com.';\n    expect(autolink(str)).toBe(\n      'Please visit <a href=\"http://example.com\" target=\"_blank\" rel=\"noopener noreferrer\">http://example.com</a>.',\n    );\n  });\n\n  it('return string with anchor tag with shortened text for the hyperlink', () => {\n    const str =\n      'Please visit http://extraordinary.com/very/very/log/hyperlink.';\n\n    const customAutolink = createAutolink({\n      truncate: {\n        length: 30,\n        location: 'smart',\n      },\n    });\n    expect(customAutolink(str)).toBe(\n      'Please visit <a href=\"http://extraordinary.com/very/very/log/hyperlink\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"http://extraordinary.com/very/very/log/hyperlink\">extraordinary.com/very&hellip;rlink</a>.',\n    );\n  });\n\n  it('return string with anchor for hyperlinks in dense json', () => {\n    const str =\n      '{\"name\":\"John Smith\",\"links\":[{\"rel\":\"random-link1\",\"href\":\"https://localhost:8000/api/123/query?action=do_something&age=21\",\"hreflang\":null,\"media\":null,\"title\":null,\"type\":null,\"deprecation\":null}]}';\n    expect(autolink(str)).toBe(\n      '{\"name\":\"John Smith\",\"links\":[{\"rel\":\"random-link1\",\"href\":\"<a href=\"https://localhost:8000/api/123/query?action=do_something&age=21\" target=\"_blank\" rel=\"noopener noreferrer\">https://localhost:8000/api/123/query?action=do_something&age=21</a>\",\"hreflang\":null,\"media\":null,\"title\":null,\"type\":null,\"deprecation\":null}]}',\n    );\n  });\n\n  it('does not take version numbers as as link', () => {\n    const str = '1.2.3.4';\n\n    expect(autolink(str)).toBe('1.2.3.4');\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/autolink.ts",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport _Autolinker, { AutolinkerConfig } from 'autolinker';\n\nexport const defaults: AutolinkerConfig = {\n  urls: {\n    schemeMatches: true,\n    tldMatches: false,\n    ipV4Matches: false,\n  },\n  email: false,\n  phone: false,\n  mention: false,\n  hashtag: false,\n\n  stripPrefix: false,\n  stripTrailingSlash: false,\n  newWindow: true,\n\n  className: '',\n};\nconst autolinker = new _Autolinker(defaults);\nexport default (s: any) => {\n  if (typeof s !== 'string') return s;\n\n  try {\n    return autolinker.link(s);\n  } catch {\n    return s;\n  }\n};\n\nexport function createAutolink(cfg) {\n  const autolinker = new _Autolinker({ ...defaults, ...cfg });\n  return (s) => {\n    try {\n      return autolinker.link(s);\n    } catch {\n      return s;\n    }\n  };\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/axios.spec.ts",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport { redirectOn401 } from './axios';\n\ndescribe('redirectOn401', () => {\n  beforeEach(() => {\n    Object.defineProperty(window, 'location', {\n      writable: true,\n      value: {\n        assign: vi.fn(),\n        href: 'http://example.com/',\n      },\n    });\n  });\n\n  it('should not redirect on 500', async () => {\n    const error = {\n      response: {\n        status: 500,\n      },\n    };\n\n    try {\n      await redirectOn401()(error);\n    } catch (e) {\n      expect(e).toBe(error);\n    }\n\n    expect(window.location.assign).not.toBeCalled();\n  });\n\n  it('should redirect on 401', async () => {\n    const error = {\n      response: {\n        status: 401,\n      },\n    };\n\n    try {\n      await redirectOn401()(error);\n    } catch (e) {\n      expect(e).toBe(error);\n    }\n\n    expect(window.location.assign).toBeCalledWith(\n      'login?redirectTo=http%3A%2F%2Fexample.com%2F&error=401',\n    );\n  });\n\n  it('should not redirect on 401 for predicate yields false', async () => {\n    const error = {\n      response: {\n        status: 401,\n      },\n    };\n\n    try {\n      await redirectOn401(() => false)(error);\n    } catch (e) {\n      expect(e).toBe(error);\n    }\n\n    expect(window.location.assign).not.toBeCalled();\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/axios.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { useNotificationCenter } from '@stekoe/vue-toast-notificationcenter';\nimport axios, { type AxiosError, type AxiosInstance } from 'axios';\n\nimport sbaConfig from '../sba-config';\n\nconst nc = useNotificationCenter();\n\naxios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';\naxios.defaults.xsrfHeaderName = sbaConfig.csrf.headerName;\n\nexport const redirectOn401 =\n  (predicate: (error: AxiosError) => boolean = () => true) =>\n  (error: AxiosError) => {\n    if (error.response && error.response.status === 401 && predicate(error)) {\n      window.location.assign(\n        `login?redirectTo=${encodeURIComponent(\n          window.location.href,\n        )}&error=401`,\n      );\n    }\n    return Promise.reject(error);\n  };\n\naxios.defaults.withCredentials = true;\naxios.defaults.headers.common['Accept'] = 'application/json';\naxios.interceptors.response.use((response) => response, redirectOn401());\n\nexport default axios;\n\nexport const registerErrorToastInterceptor = (axios: AxiosInstance): void => {\n  if (sbaConfig.uiSettings.enableToasts) {\n    axios.interceptors.response.use(\n      (response) => response,\n      (error: AxiosError) => {\n        const data = error.request;\n        const message = `\n                Request failed: ${data.statusText}<br>\n                <small>${data.responseURL}</small>\n        `;\n        nc.error(message, {\n          context: data.status ?? 'axios',\n          title: `Error ${data.status}`,\n          duration: 10000,\n        });\n      },\n    );\n  }\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/collections.spec.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { describe, expect, it, vi } from 'vitest';\n\nimport { anyValueMatches } from './collections';\n\ndescribe('anyValueMatches', () => {\n  it('should return predicate value', () => {\n    const predicate = vi.fn(() => true);\n    expect(anyValueMatches('test', predicate)).toBe(true);\n  });\n\n  it('should call predicate for string', () => {\n    const predicate = vi.fn();\n    anyValueMatches('test', predicate);\n    expect(predicate).toHaveBeenCalledWith('test');\n  });\n\n  it('should call predicate for number', () => {\n    const predicate = vi.fn();\n    anyValueMatches(1, predicate);\n    expect(predicate).toHaveBeenCalledWith(1);\n  });\n\n  it('should call predicate for boolean', () => {\n    const predicate = vi.fn();\n    anyValueMatches(true, predicate);\n    expect(predicate).toHaveBeenCalledWith(true);\n  });\n\n  it('should call predicate for null', () => {\n    const predicate = vi.fn();\n    anyValueMatches(null, predicate);\n    expect(predicate).toHaveBeenCalledWith(null);\n  });\n\n  it('should call predicate for undefined', () => {\n    const predicate = vi.fn();\n    anyValueMatches(undefined, predicate);\n    expect(predicate).toHaveBeenCalledWith(undefined);\n  });\n\n  it('should not call predicate for empty object', () => {\n    const predicate = vi.fn();\n    anyValueMatches({}, predicate);\n    expect(predicate).not.toHaveBeenCalled();\n  });\n\n  it('should not call predicate for empty array', () => {\n    const predicate = vi.fn();\n    anyValueMatches([], predicate);\n    expect(predicate).not.toHaveBeenCalled();\n  });\n\n  it('should not call predicate for elements in array', () => {\n    const predicate = vi.fn();\n    anyValueMatches(\n      ['test', 1, true, { value: 'nested-obj' }, ['nested-array'], [], {}],\n      predicate,\n    );\n    expect(predicate).toHaveBeenNthCalledWith(1, 'test');\n    expect(predicate).toHaveBeenNthCalledWith(2, 1);\n    expect(predicate).toHaveBeenNthCalledWith(3, true);\n    expect(predicate).toHaveBeenNthCalledWith(4, 'nested-obj');\n    expect(predicate).toHaveBeenNthCalledWith(5, 'nested-array');\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/collections.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport const compareBy = (mapper) => (a, b) => {\n  const valA = mapper(a);\n  const valB = mapper(b);\n  return valA > valB ? 1 : valA < valB ? -1 : 0;\n};\n\nexport const anyValueMatches = (obj, predicate) => {\n  if (Array.isArray(obj)) {\n    return obj.some((e) => anyValueMatches(e, predicate));\n  } else if (obj !== null && typeof obj === 'object') {\n    return anyValueMatches(Object.values(obj), predicate);\n  }\n  return predicate(obj);\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/d3.ts",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport * as array from 'd3-array';\nimport * as axis from 'd3-axis';\nimport * as brush from 'd3-brush';\nimport * as scale from 'd3-scale';\nimport * as selection from 'd3-selection';\nimport * as shape from 'd3-shape';\nimport * as time from 'd3-time';\n\nexport default {\n  ...array,\n  ...axis,\n  ...brush,\n  ...scale,\n  ...selection,\n  ...shape,\n  ...time,\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/eventsource-polyfill.ts",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport default async () => {\n  if (typeof window.EventSource === 'undefined') {\n    return import(\n      /* webpackChunkName: \"event-source-polyfill\" */ 'event-source-polyfill'\n    );\n  }\n  return Promise.resolve();\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/formatWithDataTypes.spec.ts",
    "content": "/*\n * Copyright 2014-2026 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { describe, expect, it } from 'vitest';\n\nimport { formatWithDataTypes } from './formatWithDataTypes';\n\ndescribe('formatWithDataTypes', () => {\n  it('returns primitive values unchanged', () => {\n    expect(formatWithDataTypes(42)).toBe(42);\n    expect(formatWithDataTypes('foo')).toBe('foo');\n    expect(formatWithDataTypes(null)).toBe(null);\n    expect(formatWithDataTypes(undefined)).toBe(undefined);\n  });\n\n  it('returns object unchanged when no config provided', () => {\n    const input = { a: 1, b: 2 };\n    const output = formatWithDataTypes(input);\n    expect(output).toEqual({ a: 1, b: 2 });\n  });\n\n  it('applies prettyBytes for bytes datatype', () => {\n    const input = { memory: { heap: { committed: 1024 } } };\n    const output = formatWithDataTypes(input, {\n      'memory.heap.committed': 'bytes',\n    });\n    expect(output.memory.heap.committed).toBe('1.02 kB');\n  });\n\n  it('wraps value with type for non-bytes datatypes', () => {\n    const input = { duration: 5000 };\n    const output = formatWithDataTypes(input, { duration: 'milliseconds' });\n    expect(output.duration).toEqual(5000);\n  });\n\n  it('handles multiple config entries', () => {\n    const input = {\n      memory: { heap: { committed: 2048 } },\n      duration: 3000,\n    };\n    const output = formatWithDataTypes(input, {\n      'memory.heap.committed': 'bytes',\n      duration: 'milliseconds',\n    });\n    expect(output.memory.heap.committed).toBe('2.05 kB');\n    expect(output.duration).toEqual(3000);\n  });\n\n  it('ignores missing keys in config', () => {\n    const input = { a: 1 };\n    const output = formatWithDataTypes(input, { 'b.c.d': 'bytes' });\n    expect(output).toEqual({ a: 1 });\n  });\n\n  it('formats date from timestamp', () => {\n    const input = { timestamp: 1717243496000 };\n    const output = formatWithDataTypes(input, { timestamp: 'date' });\n    expect(output.timestamp).toMatch(/01\\.06\\.2024, 14:04:56/);\n  });\n\n  it('formats date from ISO string', () => {\n    const input = { created: '2024-06-01T12:34:56Z' };\n    const output = formatWithDataTypes(input, { created: 'date' });\n    expect(output.created).toMatch(/01\\.06\\.2024, 14:34:56/);\n  });\n\n  it('handles formatting errors gracefully', () => {\n    const input = { invalidDate: 'not-a-date', invalidBytes: 'not-a-number' };\n    const output = formatWithDataTypes(input, {\n      invalidDate: 'date',\n      invalidBytes: 'bytes',\n    });\n    expect(output.invalidDate).toBe('not-a-date');\n    expect(output.invalidBytes).toBe('not-a-number');\n  });\n\n  it('does not format negative bytes', () => {\n    const input = { size: -1024 };\n    const output = formatWithDataTypes(input, { size: 'bytes' });\n    expect(output.size).toBe(-1024);\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/formatWithDataTypes.ts",
    "content": "/*\n * Copyright 2014-2026 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport prettyBytes from 'pretty-bytes';\n\nimport { formatDateTime } from './prettyTime';\n\n/**\n * Configuration object mapping property paths to their data types.\n * Keys are dot-notation paths (e.g., 'memory.heap.committed'), values are data type names.\n */\ntype DataTypeConfig = {\n  [key: string]: string;\n};\n\n/**\n * Formats object values based on their data types.\n *\n * Converts a proxy or regular object to a raw object and applies formatting\n * to specified properties based on their data types.\n *\n * Supported data types:\n * - 'bytes': Formats numeric byte values to human-readable format (e.g., '1.02 kB').\n *            Only formats non-negative values.\n * - 'date': Formats timestamps or ISO strings to localized date-time strings.\n *\n * @template T - The type of the input object\n * @param input - The object to format. Primitive values are returned unchanged.\n * @param config - Optional configuration mapping property paths to data types.\n *                 Uses dot notation for nested properties (e.g., 'memory.heap.committed').\n * @returns A new object with formatted values, or the original input if it's not an object.\n *\n * @example\n * const data = { memory: { heap: { committed: 1024 } }, timestamp: 1717243496000 };\n * const formatted = formatWithDataTypes(data, {\n *   'memory.heap.committed': 'bytes',\n *   'timestamp': 'date'\n * });\n * // Returns: { memory: { heap: { committed: '1.02 kB' } }, timestamp: 'Jun 1, 2024, 12:34:56 PM' }\n */\nexport function formatWithDataTypes<T>(input: T, config?: DataTypeConfig): T {\n  if (typeof input !== 'object' || input === null) {\n    return input;\n  }\n\n  if (!config) {\n    return input;\n  }\n\n  const result = JSON.parse(JSON.stringify(input)) as any;\n\n  // Process each configured property path\n  for (const [path, dataType] of Object.entries(config)) {\n    const keys = path.split('.');\n    let current = result;\n\n    // Navigate through nested objects to find the parent of the target property\n    for (let i = 0; i < keys.length - 1; i++) {\n      if (current[keys[i]] === undefined) {\n        break;\n      }\n      current = current[keys[i]];\n    }\n\n    // Apply formatting to the target property\n    const lastKey = keys[keys.length - 1];\n    if (current && current[lastKey] !== undefined) {\n      try {\n        if (dataType === 'bytes' && current[lastKey] >= 0) {\n          current[lastKey] = prettyBytes(current[lastKey]);\n        } else if (dataType === 'date') {\n          current[lastKey] = formatDateTime(current[lastKey]);\n        }\n      } catch {\n        // Keep original value on error\n      }\n    }\n  }\n\n  return result as T;\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/http-status.ts",
    "content": "/*\n * Copyright 2014-2026 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst HTTP_STATUS_TEXTS: Record<number, string> = {\n  // 1xx Informational\n  100: 'Continue',\n  101: 'Switching Protocols',\n  102: 'Processing',\n  103: 'Early Hints',\n  // 2xx Success\n  200: 'OK',\n  201: 'Created',\n  202: 'Accepted',\n  203: 'Non-Authoritative Information',\n  204: 'No Content',\n  205: 'Reset Content',\n  206: 'Partial Content',\n  207: 'Multi-Status',\n  208: 'Already Reported',\n  226: 'IM Used',\n  // 3xx Redirection\n  300: 'Multiple Choices',\n  301: 'Moved Permanently',\n  302: 'Found',\n  303: 'See Other',\n  304: 'Not Modified',\n  305: 'Use Proxy',\n  307: 'Temporary Redirect',\n  308: 'Permanent Redirect',\n  // 4xx Client Error\n  400: 'Bad Request',\n  401: 'Unauthorized',\n  402: 'Payment Required',\n  403: 'Forbidden',\n  404: 'Not Found',\n  405: 'Method Not Allowed',\n  406: 'Not Acceptable',\n  407: 'Proxy Authentication Required',\n  408: 'Request Timeout',\n  409: 'Conflict',\n  410: 'Gone',\n  411: 'Length Required',\n  412: 'Precondition Failed',\n  413: 'Payload Too Large',\n  414: 'URI Too Long',\n  415: 'Unsupported Media Type',\n  416: 'Range Not Satisfiable',\n  417: 'Expectation Failed',\n  418: \"I'm a teapot\",\n  421: 'Misdirected Request',\n  422: 'Unprocessable Entity',\n  423: 'Locked',\n  424: 'Failed Dependency',\n  425: 'Too Early',\n  426: 'Upgrade Required',\n  428: 'Precondition Required',\n  429: 'Too Many Requests',\n  431: 'Request Header Fields Too Large',\n  451: 'Unavailable For Legal Reasons',\n  // 5xx Server Error\n  500: 'Internal Server Error',\n  501: 'Not Implemented',\n  502: 'Bad Gateway',\n  503: 'Service Unavailable',\n  504: 'Gateway Timeout',\n  505: 'HTTP Version Not Supported',\n  506: 'Variant Also Negotiates',\n  507: 'Insufficient Storage',\n  508: 'Loop Detected',\n  510: 'Not Extended',\n  511: 'Network Authentication Required',\n};\n\n/**\n * Returns the HTTP status text for a given status code.\n * @param statusCode - The HTTP status code\n * @returns The status text (e.g., \"OK\" for 200)\n */\nexport const getHttpStatusText = (statusCode: number): string => {\n  return HTTP_STATUS_TEXTS[statusCode] || '';\n};\n\n/**\n * Returns the formatted HTTP status with code and text.\n * @param statusCode - The HTTP status code\n * @returns Formatted status string (e.g., \"200 OK\")\n */\nexport const getHttpStatus = (statusCode: number): string => {\n  const statusText = HTTP_STATUS_TEXTS[statusCode];\n  return statusText ? `${statusCode} ${statusText}` : `${statusCode}`;\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/iso8601-duration.spec.ts",
    "content": "/*\n * Copyright 2014-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { describe, expect, test } from 'vitest';\n\nimport { toMilliseconds } from '@/utils/iso8601-duration';\n\ndescribe('iso8601-duration', () => {\n  test.each([\n    [1.023, 0, 0, 0, 0, 1_023],\n    [1.023, 1, 1, 0, 0, 3_661_023],\n    [1.023, 1, 1, 1, 0, 90_061_023],\n    [1.023, 1, 1, 1, 1, 694_861_023],\n  ])(\n    'should return the miliseconds of a duration object with %n seconds, %n minutes, %n hours, %n days, %n weeks',\n    (seconds, minutes, hours, days, weeks, expected) => {\n      const duration = {\n        seconds,\n        minutes,\n        hours,\n        days,\n        weeks,\n      };\n\n      const milliseconds = toMilliseconds(duration);\n\n      expect(milliseconds).toBeCloseTo(expected);\n    },\n  );\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/iso8601-duration.ts",
    "content": "/*\n * Copyright 2014-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport { parse } from 'iso8601-duration';\n\n/**\n * Convert ISO8601 duration object to milliseconds\n *\n * Hint: Years and months are ignored.\n * Calculating based on JavaScript date is too imprecise.\n *\n * @param {Object} duration - The duration object\n * @return {Number}\n */\nexport const toMilliseconds = (duration) => {\n  let result = duration.seconds;\n  result += duration.minutes * 60;\n  result += duration.hours * 60 * 60;\n  result += duration.days * 60 * 60 * 24;\n  result += duration.weeks * 60 * 60 * 24 * 7;\n\n  return result * 1000;\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/logtail.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { EMPTY, Observable, catchError, concatMap, of, timer } from './rxjs';\n\nexport default (getFn, interval, initialSize = 300 * 1024) => {\n  let range = `bytes=-${initialSize}`;\n  let size = 0;\n\n  return timer(0, interval).pipe(\n    concatMap(() => {\n      return new Observable((observer) => {\n        getFn({ headers: { range, Accept: 'text/plain' } })\n          .then((response) => {\n            observer.next(response);\n            observer.complete();\n          })\n          .catch((error) => observer.error(error));\n      }).pipe(\n        catchError((error) => of({ data: '', status: error.response.status })),\n      );\n    }),\n    concatMap((response) => {\n      let initial = size === 0;\n      const contentLength = response.data.length;\n\n      if (response.status === 200) {\n        if (!initial) {\n          throw 'Expected 206 - Partial Content on subsequent requests.';\n        }\n        size = contentLength;\n        range = `bytes=${size - 1}-`;\n      } else if (response.status === 206) {\n        size = parseInt(response.headers['content-range'].split('/')[1]);\n        range = `bytes=${size - 1}-`;\n      } else if (response.status === 416) {\n        size = 0;\n        range = `bytes=-${initialSize}`;\n        initial = true;\n      } else {\n        throw 'Unexpected response status: ' + response.status;\n      }\n\n      let addendum = null;\n      let skipped = 0;\n\n      if (initial) {\n        if (contentLength >= size) {\n          addendum = response.data;\n        } else {\n          // In case of a partial response find the first line break.\n          addendum = response.data.substring(response.data.indexOf('\\n') + 1);\n          skipped = size - addendum.length;\n        }\n      } else if (response.data.length > 1) {\n        // Remove the first byte which has been part of the previos response.\n        addendum = response.data.substring(1);\n      }\n\n      return addendum\n        ? of({\n            totalBytes: size,\n            skipped,\n            addendum,\n          })\n        : EMPTY;\n    }),\n  );\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/objToYaml.ts",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { dump } from 'js-yaml';\n\n/**\n * Convert JSON to YAML.\n *\n * Example:\n *   objToYaml({foo: \"bar\"})\n * Output:\n *   foo: bar\n *\n * @param input  A JSON object or a JSON string.\n * @param options Optional js-yaml dump options.\n * @returns YAML string.\n */\nexport function objToYaml(\n  input: object | string,\n  options: Parameters<typeof dump>[1] = {},\n): string {\n  if (typeof input === 'string') {\n    return input;\n  }\n\n  return dump(input, {\n    noRefs: true,\n    indent: 2,\n    lineWidth: -1,\n    ...options,\n  });\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/prettyTime.ts",
    "content": "import moment from 'moment/moment';\nimport { useI18n } from 'vue-i18n';\n\nexport const enum PrettyTimeUnit {\n  years = 'years',\n  months = 'months',\n  weeks = 'weeks',\n  days = 'days',\n  hours = 'hours',\n  minutes = 'minutes',\n  seconds = 'seconds',\n  milliseconds = 'milliseconds',\n}\n\n/**\n * Formats a Date or ISO string to a localized date-time string.\n * @param time - Date object or ISO string to format\n * @param locale - Optional locale string (defaults to browser default)\n * @returns Formatted date-time string\n */\nexport const formatDateTime = (\n  time: Date | string | number,\n  locale?: string,\n) => {\n  return new Intl.DateTimeFormat(locale, {\n    dateStyle: 'medium',\n    timeStyle: 'medium',\n  }).format(new Date(time));\n};\n\n/**\n * usePrettyTime provides utility functions for formatting time durations and date-times.\n *\n * - formatTime: Converts a duration in milliseconds to a human-readable string (e.g., \"2 days 3 hours\").\n * - formatDateTime: Formats a Date or ISO string to a localized date-time string.\n *\n * Example:\n * const { formatTime, formatDateTime } = usePrettyTime();\n * formatTime(90061000); // \"1 days 1 hours 1 minutes 1 seconds\"\n * formatDateTime(\"2024-06-01T12:34:56Z\"); // \"Jun 1, 2024, 12:34:56 PM\" (locale-dependent)\n */\nexport const usePrettyTime = () => {\n  const { t, locale } = useI18n();\n\n  const formatTime = (time: number) => {\n    if (time < 0) {\n      return t('time.unknown');\n    }\n\n    const duration = moment.duration(time);\n\n    const output = {\n      [PrettyTimeUnit.years]: Math.floor(duration.asYears()),\n      [PrettyTimeUnit.months]: Math.floor(duration.asMonths()),\n      [PrettyTimeUnit.weeks]: Math.floor(duration.asWeeks()),\n      [PrettyTimeUnit.days]: Math.floor(duration.asDays()),\n      [PrettyTimeUnit.hours]: duration.hours(),\n      [PrettyTimeUnit.minutes]: duration.minutes(),\n      [PrettyTimeUnit.seconds]: duration.seconds(),\n      [PrettyTimeUnit.milliseconds]: duration.milliseconds(),\n    };\n\n    return Object.entries(output).reduce((acc, [key, value]) => {\n      if (value > 0) {\n        return acc + t(`time_short.${key}`, { count: value }) + ' ';\n      }\n      return acc;\n    }, '');\n  };\n\n  return {\n    formatTime,\n    formatDateTime: (time: Date | string | number) =>\n      formatDateTime(time, locale.value),\n  };\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/rxjs.spec.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { describe, expect, it, vi } from 'vitest';\n\nimport { delay, doOnSubscribe, listen } from './rxjs';\n\nimport { EMPTY, concat, of, throwError } from '@/utils/rxjs';\n\ndescribe('doOnSubscribe', () => {\n  it('should call callback when subscribing', () => {\n    return new Promise((resolve) => {\n      const cb = vi.fn();\n      EMPTY.pipe(doOnSubscribe(cb)).subscribe({\n        complete: () => {\n          expect(cb).toHaveBeenCalledTimes(1);\n          resolve(true);\n        },\n      });\n    });\n  });\n});\n\ndescribe('listen', () => {\n  it('should call callback with complete', () => {\n    return new Promise((resolve) => {\n      const cb = vi.fn();\n      EMPTY.pipe(listen(cb)).subscribe({\n        complete: () => {\n          expect(cb).toHaveBeenCalledTimes(1);\n          expect(cb).toHaveBeenCalledWith('completed');\n          resolve(true);\n        },\n      });\n    });\n  });\n\n  it('should call callback with executing and complete', () => {\n    return new Promise((resolve) => {\n      const cb = vi.fn();\n      of(1)\n        .pipe(delay(10), listen(cb, 1))\n        .subscribe({\n          complete: () => {\n            expect(cb).toHaveBeenCalledTimes(2);\n            expect(cb).toHaveBeenCalledWith('executing');\n            expect(cb).toHaveBeenCalledWith('completed');\n            resolve(true);\n          },\n        });\n    });\n  });\n\n  it('should call callback with failed', () => {\n    return new Promise((resolve) => {\n      const cb = vi.fn();\n      console.warn = vi.fn();\n      throwError(new Error('test'))\n        .pipe(listen(cb))\n        .subscribe({\n          error: () => {\n            expect(cb).toHaveBeenCalledTimes(1);\n            expect(cb).toHaveBeenCalledWith('failed');\n            resolve(true);\n          },\n        });\n    });\n  });\n\n  it('should call callback with executing and failed', () => {\n    return new Promise((done) => {\n      const cb = vi.fn();\n\n      concat(of(1).pipe(delay(10)), throwError(new Error('test')))\n        .pipe(listen(cb, 1))\n        .subscribe({\n          error: () => {\n            expect(cb).toHaveBeenCalledTimes(2);\n            expect(cb).toHaveBeenCalledWith('executing');\n            expect(cb).toHaveBeenCalledWith('failed');\n            done(true);\n          },\n        });\n    });\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/rxjs.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { defer, tap } from 'rxjs';\n\nexport {\n  of,\n  defer,\n  concat,\n  catchError,\n  throwError,\n  EMPTY,\n  from,\n  timer,\n  Observable,\n  Subject,\n  animationFrameScheduler,\n  concatMap,\n  delay,\n  debounceTime,\n  mergeWith,\n  map,\n  retryWhen,\n  tap,\n  filter,\n  concatAll,\n  ignoreElements,\n  bufferTime,\n  finalize,\n} from 'rxjs';\n\nexport const doOnSubscribe = (cb) => (source) =>\n  defer(() => {\n    cb();\n    return source;\n  });\n\nexport const listen =\n  (cb, execDelay = 150) =>\n  (source) => {\n    let handle = null;\n    return source.pipe(\n      doOnSubscribe(\n        () => (handle = setTimeout(() => cb('executing'), execDelay)),\n      ),\n      tap({\n        complete: () => {\n          clearTimeout(handle);\n          cb('completed');\n        },\n        error: (error) => {\n          console.warn('Operation failed:', error);\n          clearTimeout(handle);\n          cb('failed');\n        },\n      }),\n    );\n  };\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/sanitizeHtml.spec.ts",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { describe, expect, it } from 'vitest';\n\nimport { sanitizeHtml } from './sanitizeHtml';\n\ndescribe('sanitizeHtml', () => {\n  it('should return plain text unchanged', () => {\n    const input = 'This is plain text';\n    expect(sanitizeHtml(input)).toBe('This is plain text');\n  });\n\n  it('should preserve safe HTML tags', () => {\n    const input = '<p>Hello <strong>world</strong></p>';\n    expect(sanitizeHtml(input)).toBe('<p>Hello <strong>world</strong></p>');\n  });\n\n  it('should remove script tags', () => {\n    const input = '<p>Hello</p><script>alert(\"XSS\")</script>';\n    expect(sanitizeHtml(input)).toBe('<p>Hello</p>');\n  });\n\n  it('should remove potentially dangerous attributes', () => {\n    const input = '<p onclick=\"alert(\\'XSS\\')\">Click me</p>';\n    expect(sanitizeHtml(input)).toBe('<p>Click me</p>');\n  });\n\n  it('should remove iframe tags', () => {\n    const input = '<p>Content</p><iframe src=\"evil.com\"></iframe>';\n    expect(sanitizeHtml(input)).toBe('<p>Content</p>');\n  });\n\n  it('should transform anchor tags to open in new tab', () => {\n    const input = '<a href=\"https://example.com\">Link</a>';\n    const result = sanitizeHtml(input);\n    expect(result).toContain('target=\"_blank\"');\n    expect(result).toContain('https://example.com');\n  });\n\n  it('should preserve existing anchor href and add target=\"_blank\"', () => {\n    const input = '<a href=\"https://spring.io\">Spring</a>';\n    const result = sanitizeHtml(input);\n    expect(result).toBe(\n      '<a href=\"https://spring.io\" target=\"_blank\">Spring</a>',\n    );\n  });\n\n  it('should handle anchor tags that already have target attribute', () => {\n    const input = '<a href=\"https://example.com\" target=\"_self\">Link</a>';\n    const result = sanitizeHtml(input);\n    // The simpleTransform should override the target attribute\n    expect(result).toContain('target=\"_blank\"');\n  });\n\n  it('should remove javascript: protocol from links', () => {\n    const input = '<a href=\"javascript:alert(\\'XSS\\')\">Click</a>';\n    const result = sanitizeHtml(input);\n    expect(result).not.toContain('javascript:');\n  });\n\n  it('should handle empty string', () => {\n    expect(sanitizeHtml('')).toBe('');\n  });\n\n  it('should handle multiple paragraphs with mixed content', () => {\n    const input = '<p>First paragraph</p><p>Second <em>paragraph</em></p>';\n    expect(sanitizeHtml(input)).toBe(\n      '<p>First paragraph</p><p>Second <em>paragraph</em></p>',\n    );\n  });\n\n  it('should remove style tags', () => {\n    const input = '<style>body { color: red; }</style><p>Content</p>';\n    expect(sanitizeHtml(input)).toBe('<p>Content</p>');\n  });\n\n  it('should handle nested HTML elements', () => {\n    const input = '<div><p>Nested <strong>content</strong></p></div>';\n    const result = sanitizeHtml(input);\n    expect(result).toContain('Nested');\n    expect(result).toContain('content');\n  });\n\n  it('should remove object and embed tags', () => {\n    const input =\n      '<p>Safe content</p><object data=\"evil.swf\"></object><embed src=\"evil.swf\">';\n    expect(sanitizeHtml(input)).toBe('<p>Safe content</p>');\n  });\n\n  it('should preserve multiple links with target=\"_blank\" for each', () => {\n    const input =\n      '<a href=\"https://first.com\">First</a> and <a href=\"https://second.com\">Second</a>';\n    const result = sanitizeHtml(input);\n    expect(result).toBe(\n      '<a href=\"https://first.com\" target=\"_blank\">First</a> and <a href=\"https://second.com\" target=\"_blank\">Second</a>',\n    );\n  });\n\n  it('should handle HTML entities correctly', () => {\n    const input = '<p>Price: &lt;$10&gt;</p>';\n    expect(sanitizeHtml(input)).toBe('<p>Price: &lt;$10&gt;</p>');\n  });\n\n  it('should remove form elements', () => {\n    const input = '<form><input type=\"text\" /></form><p>Content</p>';\n    expect(sanitizeHtml(input)).toBe('<p>Content</p>');\n  });\n\n  it('should handle links with query parameters', () => {\n    const input =\n      '<a href=\"https://example.com?param=value&other=123\">Link</a>';\n    const result = sanitizeHtml(input);\n    expect(result).toContain('https://example.com?param=value&amp;other=123');\n    expect(result).toContain('target=\"_blank\"');\n  });\n\n  it('should preserve list structures', () => {\n    const input = '<ul><li>Item 1</li><li>Item 2</li></ul>';\n    expect(sanitizeHtml(input)).toBe('<ul><li>Item 1</li><li>Item 2</li></ul>');\n  });\n\n  it('should preserve download attribute in anchor tag', () => {\n    const input = '<a href=\"https://example.com\" download>Link</a>';\n    const result = sanitizeHtml(input);\n    expect(result).toContain('https://example.com');\n    expect(result).toContain('download');\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/sanitizeHtml.ts",
    "content": "import _sanitizeHtml from 'sanitize-html';\n\nexport function sanitizeHtml(dirty: string): string {\n  return _sanitizeHtml(dirty, {\n    allowedEmptyAttributes:\n      _sanitizeHtml.defaults.allowedEmptyAttributes.concat('download'),\n    allowedAttributes: {\n      a: _sanitizeHtml.defaults.allowedAttributes['a'].concat('download'),\n    },\n    transformTags: {\n      a: function (tagName, attribs) {\n        let newAttribs = attribs ? attribs : {};\n\n        // When download attribute is not set, set target attribute\n        if (attribs.download === undefined) {\n          newAttribs = {\n            ...newAttribs,\n            target: '_blank',\n          };\n        }\n\n        return {\n          tagName,\n          attribs: {\n            ...newAttribs,\n          },\n        };\n      },\n    },\n  });\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/shortenClassname.spec.ts",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { describe, expect, it } from 'vitest';\n\nimport shortenClassname from './shortenClassname';\n\ndescribe('shortenClassname', () => {\n  it('should shorten when too long', () => {\n    expect(\n      shortenClassname(\n        'de.codecentric.boot.admin.server.config.AdminServerAutoConfiguration',\n        40,\n      ),\n    ).toBe('d.c.b.a.s.config.AdminServerAutoConfiguration');\n  });\n\n  it('should not shorten when string is small enough', () => {\n    expect(\n      shortenClassname(\n        'de.codecentric.boot.admin.server.config.AdminServerAutoConfiguration',\n        300,\n      ),\n    ).toBe(\n      'de.codecentric.boot.admin.server.config.AdminServerAutoConfiguration',\n    );\n  });\n\n  it('should not shorten when no package is present', () => {\n    expect(shortenClassname('AdminServerAutoConfiguration', 1)).toBe(\n      'AdminServerAutoConfiguration',\n    );\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/shortenClassname.ts",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Shortens a fully qualified class name to fit within a target length.\n * Example:\n *   shortenClassname('org.springframework.boot.actuate.health.HealthEndpoint', 20)\n *   // returns: \"o.s.b.a.h.HealthEndpoint\"\n *\n * @param {string} fullName - The fully qualified class name.\n * @param {number} targetLength - The maximum allowed length for the shortened name.\n * @returns {string} The shortened class name.\n */\nexport default (fullName, targetLength) => {\n  if (!fullName || fullName.length < targetLength) {\n    return fullName;\n  }\n\n  const tokens = fullName.split('.');\n  let shortened = tokens.pop();\n  while (tokens.length > 0) {\n    const next = tokens.pop();\n    if (next.length + 1 + shortened.length < targetLength) {\n      shortened = next + '.' + shortened;\n    } else {\n      shortened = next[0] + '.' + shortened;\n    }\n  }\n  return shortened;\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/sortObject.spec.ts",
    "content": "import { describe, expect, it } from 'vitest';\n\nimport { sortObject } from './sortObject';\n\ndescribe('sortObject', () => {\n  it('sorts a flat object alphabetically by keys', () => {\n    const input = { b: 2, a: 1, c: 3 };\n    const output = sortObject(input);\n    expect(Object.keys(output)).toEqual(['a', 'b', 'c']);\n    expect(output).toEqual({ a: 1, b: 2, c: 3 });\n  });\n\n  it('sorts nested objects recursively', () => {\n    const input = { z: 1, a: { d: 4, b: 2, c: 3 }, b: 2 };\n    const output = sortObject(input);\n    expect(Object.keys(output)).toEqual(['a', 'b', 'z']);\n    expect(Object.keys(output.a)).toEqual(['b', 'c', 'd']);\n    expect(output).toEqual({ a: { b: 2, c: 3, d: 4 }, b: 2, z: 1 });\n  });\n\n  it('leaves arrays unchanged', () => {\n    const input = { arr: [3, 1, 2], b: 2, a: 1 };\n    const output = sortObject(input);\n    expect(output.arr).toEqual([3, 1, 2]);\n    expect(Object.keys(output)).toEqual(['a', 'arr', 'b']);\n  });\n\n  it('returns empty objects unchanged', () => {\n    const input = {};\n    const output = sortObject(input);\n    expect(output).toEqual({});\n  });\n\n  it('returns primitive values unchanged', () => {\n    expect(sortObject(42)).toBe(42);\n    expect(sortObject('foo')).toBe('foo');\n    expect(sortObject(null)).toBe(null);\n    expect(sortObject(undefined)).toBe(undefined);\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/sortObject.ts",
    "content": "export function sortObject(input: any): any {\n  if (Array.isArray(input)) {\n    return input.map(sortObject);\n  } else if (input !== null && typeof input === 'object') {\n    return Object.keys(input)\n      .sort()\n      .reduce(\n        (acc, key) => {\n          acc[key] = sortObject(input[key]);\n          return acc;\n        },\n        {} as Record<string, any>,\n      );\n  }\n  return input;\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/toast.ts",
    "content": ""
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/transformToJSON.spec.ts",
    "content": "import { describe, expect, it } from 'vitest';\n\nimport { transformToJSON } from '@/utils/transformToJSON';\n\nconst input = {\n  'service-url': 'http://localhost:8080',\n  'sidebar.links.0.iframe': 'true',\n  'sidebar.links.0.label': '🏠 Home',\n  'sidebar.links.0.url': 'https://codecentric.de',\n  'tags.environment': 'test',\n};\n\ndescribe('transformToNestedPojo', () => {\n  it('transforms simple key', () => {\n    const output = transformToJSON(input);\n\n    expect(output['service-url']).toEqual('http://localhost:8080');\n  });\n\n  it('transforms nested key', () => {\n    const output = transformToJSON(input);\n\n    expect(output.tags.environment).toEqual('test');\n  });\n\n  it('transforms array', () => {\n    const output = transformToJSON(input);\n\n    expect(output.sidebar.links).toHaveLength(1);\n    expect(output.sidebar.links[0]).toEqual({\n      iframe: 'true',\n      label: '🏠 Home',\n      url: 'https://codecentric.de',\n    });\n  });\n\n  it('handles conflict when property is both string and object', () => {\n    const conflictInput = {\n      'my-service-headless.kubernetes': 'namespace',\n      'my-service-headless': 'some-value',\n    };\n\n    const output = transformToJSON(conflictInput, 'LAX');\n    expect(output['my-service-headless']['__']).toEqual('some-value');\n    expect(output['my-service-headless']['kubernetes']).toEqual('namespace');\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/transformToJSON.ts",
    "content": "/**\n * Converts a flat object with dot notation keys into a nested POJO.\n *\n * Supports two modes:\n * - **STRICT** (default): Later values overwrite earlier values for the same path.\n * - **LAX**: Resolves conflicts between primitive values and nested objects by using\n *   a sentinel key `__` to preserve both values.\n *\n * @param input - The flat object to transform.\n * @param mode - Transformation mode: 'STRICT' or 'LAX'. Defaults to 'STRICT'.\n * @returns The nested POJO.\n *\n * @example\n * // Basic transformation with nested objects and arrays\n * transformToJSON({\n *   'user.name': 'Alice',\n *   'user.address.street': 'Main St',\n *   'user.address.zip': '12345',\n *   'items.0.id': 1,\n *   'items.0.name': 'Item 1',\n *   'items.1.id': 2,\n *   'items.1.name': 'Item 2'\n * });\n * // Returns:\n * // {\n * //   user: {\n * //     name: 'Alice',\n * //     address: { street: 'Main St', zip: '12345' }\n * //   },\n * //   items: [\n * //     { id: 1, name: 'Item 1' },\n * //     { id: 2, name: 'Item 2' }\n * //   ]\n * // }\n *\n * @example\n * // LAX mode: Conflict resolution with sentinel key '__'\n * // When a property is both a primitive and has nested properties\n * transformToJSON({\n *   'my-service': 'simple-value',\n *   'my-service.kubernetes': 'namespace'\n * }, 'LAX');\n * // Returns:\n * // {\n * //   'my-service': {\n * //     __: 'simple-value',        // Original primitive value preserved\n * //     kubernetes: 'namespace'     // Nested property added\n * //   }\n * // }\n *\n * @example\n * // LAX mode: Reverse order (deep path first, then shallow)\n * transformToJSON({\n *   'config.server.port': '8080',\n *   'config': 'default-config'\n * }, 'LAX');\n * // Returns:\n * // {\n * //   config: {\n * //     __: 'default-config',       // Later primitive value preserved\n * //     server: { port: '8080' }    // Earlier nested structure kept\n * //   }\n * // }\n *\n * @remarks\n * - The sentinel key `__` is only used in LAX mode when conflicts occur.\n * - Numeric keys (e.g., '0', '1') create arrays automatically.\n * - In STRICT mode, the last value for a given path wins.\n * - Avoid using `__` as a key in your input data when using LAX mode.\n */\nexport function transformToJSON(\n  input: Record<string, any>,\n  mode: 'STRICT' | 'LAX' = 'STRICT',\n): any {\n  const result: any = {};\n  const isPrimitive = (val: any) =>\n    val === null || ['string', 'number', 'boolean'].includes(typeof val);\n\n  for (const [flatKey, value] of Object.entries(input)) {\n    const parts = flatKey.split('.');\n    let current: any = result;\n\n    for (let i = 0; i < parts.length; i++) {\n      const part = parts[i];\n      const nextPart = parts[i + 1];\n      const isNextArrayIndex = nextPart !== undefined && /^\\d+$/.test(nextPart);\n\n      const isLast = i === parts.length - 1;\n\n      if (isLast) {\n        // Final segment: assign the value\n        if (mode === 'LAX') {\n          const existing = current[part];\n          // Handle conflict: existing object receives primitive value at root level\n          if (existing && typeof existing === 'object') {\n            if (!('__' in existing)) {\n              existing.__ = value;\n            }\n            continue; // Preserve existing nested structure\n          }\n        }\n\n        current[part] = value;\n      } else {\n        // Intermediate segment\n        if (/^\\d+$/.test(part)) {\n          const idx = Number(part);\n          if (!Array.isArray(current)) {\n            const arr: any[] = [];\n            Object.assign(arr, current);\n            current = arr;\n          }\n          if (!current[idx]) current[idx] = isNextArrayIndex ? [] : {};\n          current = current[idx];\n        } else {\n          // Object key: navigate or create path\n          if (!current[part]) {\n            current[part] = isNextArrayIndex ? [] : {};\n          } else if (mode === 'LAX' && isPrimitive(current[part])) {\n            current[part] = { __: current[part] };\n          }\n          current = current[part];\n        }\n      }\n    }\n  }\n  return result;\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/uri.spec.ts",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { describe, expect, it } from 'vitest';\n\nimport uri from './uri';\n\ndescribe('uri', () => {\n  it('should escape uris properly', () => {\n    expect(uri`http://app/${'foo/bar'}?q=${'???'}`).toBe(\n      'http://app/foo%2Fbar?q=%3F%3F%3F',\n    );\n    expect(uri`http://app/${'foo/bar'}?q=1`).toBe('http://app/foo%2Fbar?q=1');\n    expect(uri`http://app/${'foo/bar'}`).toBe('http://app/foo%2Fbar');\n    expect(uri`http://app/foo`).toBe('http://app/foo');\n    expect(uri``).toBe('');\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/uri.ts",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * URI template tag function that encodes interpolated values using encodeURIComponent.\n *\n * Usage example:\n *   const userId = 'john/doe';\n *   const url = uri`/api/users/${userId}/profile`;\n *   // url === \"/api/users/john%2Fdoe/profile\"\n *\n * @param strings Template string array\n * @param values Interpolated values to encode\n * @returns Encoded URI string\n */\nexport default (strings, ...values) => {\n  let result = strings[0];\n  for (let i = 0; i < values.length; ++i) {\n    result += encodeURIComponent(values[i]) + strings[i + 1];\n  }\n  return result;\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/useRouterState.spec.ts",
    "content": "import { waitFor } from '@testing-library/vue';\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\nimport { reactive } from 'vue';\n\nimport { useRouterState } from '@/utils/useRouterState';\n\nconst routerReplace = vi.fn();\nconst mockedQuery = vi.fn().mockReturnValue({});\n\nvi.mock('vue-router', () => {\n  return {\n    useRouter: () => ({\n      replace: routerReplace,\n    }),\n    useRoute: () =>\n      reactive({\n        get query() {\n          return mockedQuery();\n        },\n      }),\n  };\n});\n\ndescribe('useRouterState', () => {\n  beforeEach(() => {\n    vi.resetAllMocks();\n  });\n\n  it('should initialize routerState with the initial query parameters', async () => {\n    mockedQuery.mockReturnValue({\n      string: 'foo',\n      boolean: 'true',\n      number: '0',\n      float: '0.123',\n    });\n\n    const routerState = useRouterState();\n\n    await waitFor(() => {\n      expect(routerState).toEqual({\n        string: 'foo',\n        boolean: true,\n        number: 0,\n        float: 0.123,\n      });\n    });\n  });\n\n  it('should call router with initial state', () => {\n    useRouterState({\n      foo: 'bar',\n    });\n\n    waitFor(() => {\n      expect(routerReplace).toHaveBeenCalledWith({\n        query: {\n          foo: 'bar',\n        },\n      });\n    });\n  });\n\n  it('should extend existing query params', () => {\n    mockedQuery.mockReturnValue({\n      foo: 'bar',\n    });\n\n    const routerState = useRouterState({\n      bar: 'baz',\n    });\n\n    waitFor(() => {\n      expect(routerReplace).toHaveBeenCalledWith({\n        query: {\n          foo: 'bar',\n          bar: 'baz',\n        },\n      });\n      expect(routerState).toEqual({\n        foo: 'bar',\n        bar: 'baz',\n      });\n    });\n  });\n\n  it('should override existing query params', () => {\n    mockedQuery.mockReturnValue({\n      foo: 'bar',\n    });\n\n    const routerState = useRouterState({\n      foo: 'baz',\n    });\n\n    waitFor(() => {\n      expect(routerReplace).toHaveBeenCalledWith({\n        query: {\n          foo: 'baz',\n        },\n      });\n      expect(routerState).toEqual({\n        foo: 'baz',\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/useRouterState.ts",
    "content": "import { isEqual } from 'lodash-es';\nimport qs from 'qs';\nimport { UnwrapNestedRefs, reactive, watch } from 'vue';\nimport { LocationQuery, useRoute, useRouter } from 'vue-router';\n\n/**\n * Hook to synchronize the query parameters of the current route with a reactive object.\n *\n * @param initialState The initial state of the reactive object.\n * @returns The reactive object.\n */\nexport function useRouterState<T extends object>(\n  initialState: T = {} as T,\n): UnwrapNestedRefs<T> {\n  const route = useRoute();\n  const router = useRouter();\n\n  let routerState = reactive({\n    ...initialState,\n    ...correctTypesInRouterQuery(route.query),\n  });\n\n  watch(route, (_route) => {\n    const queryParams = JSON.stringify(_route.query);\n    routerState = parseQueryParams(queryParams);\n  });\n\n  watch(routerState, (newValue: any) => {\n    const to = {\n      name: route.name,\n      query: {\n        ...route.query,\n        ...newValue,\n      },\n    };\n    const routerQueryKeys = Object.keys(route.query);\n    const newRouterQueryKeys = Object.keys({ ...route.query, ...newValue });\n    if (isEqual(routerQueryKeys, newRouterQueryKeys)) {\n      router.replace(to);\n    } else {\n      router.push(to);\n    }\n  });\n\n  return routerState;\n}\n\nfunction parseQueryParams(queryParams: string) {\n  return qs.parse(queryParams, {\n    decoder: (str, defaultDecoder, charset, type) => {\n      const bools = {\n        true: true,\n        false: false,\n      };\n      if (type === 'value' && typeof bools[str] === 'boolean') {\n        return bools[str];\n      } else {\n        return defaultDecoder(str);\n      }\n    },\n  });\n}\n\nfunction correctTypesInRouterQuery(query: LocationQuery) {\n  return (\n    query !== undefined &&\n    JSON.parse(JSON.stringify(query), (_, value) => {\n      if (value === 'false') return false;\n      if (value === 'true') return true;\n\n      const float = Number.parseFloat(value);\n      if (!isNaN(float)) return float;\n\n      const number = Number.parseInt(value);\n      if (!isNaN(number)) return number;\n\n      return value;\n    })\n  );\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/utils/useSubscription.ts",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { Subscription } from 'rxjs';\nimport { onBeforeUnmount } from 'vue';\n\n/**\n * When a subscription is passed, it will be unsubscribed on unmount.\n *\n * @param subscription Subscription\n */\nexport const useSubscription = (subscription: Subscription) => {\n  onBeforeUnmount(() => {\n    if (subscription && !subscription.closed) {\n      try {\n        subscription.unsubscribe();\n      } finally {\n        subscription = null;\n      }\n    }\n  });\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/viewRegistry.spec.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { describe, expect, it } from 'vitest';\n\nimport ViewRegistry from './viewRegistry';\n\nimport sbaConfig from '@/sba-config';\n\ndescribe('viewRegistry', () => {\n  it('should replace the existing one', async () => {\n    const viewRegistry = new ViewRegistry();\n\n    viewRegistry.addView(\n      ...[\n        { name: 'view', group: 'group', path: '' },\n        { name: 'duplicateView', group: 'group' },\n        { name: 'duplicateView', group: 'group' },\n      ],\n    );\n\n    expect(viewRegistry.views).toHaveLength(2);\n  });\n\n  it('should create a redirect based on path', () => {\n    const viewRegistry = new ViewRegistry();\n\n    viewRegistry.addRedirect('/', 'asString');\n    viewRegistry.addRedirect('/', { name: 'asObject' });\n\n    expect(viewRegistry.routes).toContainEqual(\n      expect.objectContaining({\n        path: '/',\n        redirect: { name: 'asString' },\n      }),\n    );\n    expect(viewRegistry.routes).toContainEqual(\n      expect.objectContaining({\n        path: '/',\n        redirect: { name: 'asObject' },\n      }),\n    );\n  });\n\n  it('hide or show views depending on their settings', () => {\n    sbaConfig.uiSettings.viewSettings = [\n      { name: 'disabledView', enabled: false },\n      { name: 'explicitlyEnabledView', enabled: true },\n    ];\n\n    const viewRegistry = new ViewRegistry();\n    viewRegistry.addView(\n      ...[\n        { name: 'disabledView', group: 'group' },\n        { name: 'explicitlyEnabledView', group: 'group' },\n        { name: 'implicitlyEnabledView', group: 'group' },\n      ],\n    );\n\n    const disabledView = viewRegistry.getViewByName('disabledView');\n    expect(disabledView).toBeDefined();\n    expect(disabledView.isEnabled()).toBeFalsy();\n\n    const implicitlyEnabledView = viewRegistry.getViewByName(\n      'implicitlyEnabledView',\n    );\n    expect(implicitlyEnabledView).toBeDefined();\n    expect(implicitlyEnabledView.isEnabled()).toBeTruthy();\n\n    const explicitlyEnabledView = viewRegistry.getViewByName(\n      'explicitlyEnabledView',\n    );\n    expect(explicitlyEnabledView).toBeDefined();\n    expect(explicitlyEnabledView.isEnabled()).toBeTruthy();\n  });\n\n  it('should render a translated label', () => {\n    const viewRegistry = new ViewRegistry();\n    viewRegistry.addView(...[{ path: 'parent', label: 'parent.label' }]);\n\n    expect(viewRegistry.views[0].handle.render).toBeDefined();\n  });\n\n  it('derives name from parent and path', () => {\n    const viewRegistry = new ViewRegistry();\n    viewRegistry.addView(\n      ...[{ path: 'parent' }, { parent: 'parent', path: 'path' }],\n    );\n\n    expect(viewRegistry.views).toContainEqual(\n      expect.objectContaining({ name: 'parent' }),\n    );\n    expect(viewRegistry.views).toContainEqual(\n      expect.objectContaining({ name: 'parent/path' }),\n    );\n  });\n\n  it('parent/child routes are generated correctly', () => {\n    const viewRegistry = new ViewRegistry();\n    viewRegistry.addView(\n      ...[\n        { path: 'parent', component: {} },\n        { parent: 'parent', path: 'path', component: {} },\n      ],\n    );\n\n    expect(viewRegistry.routes).toContainEqual(\n      expect.objectContaining({\n        name: 'parent',\n      }),\n    );\n    expect(viewRegistry.routes[0].children).toContainEqual(\n      expect.objectContaining({\n        name: 'parent/path',\n      }),\n    );\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/viewRegistry.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { remove } from 'lodash-es';\nimport { Text, VNode, h, markRaw, reactive, shallowRef, toRaw } from 'vue';\nimport { Router, createRouter, createWebHistory } from 'vue-router';\n\nimport sbaConfig from './sba-config';\nimport { VIEW_GROUP, VIEW_GROUP_ICON } from './views/ViewGroup';\n\nlet router: Router;\n\nconst createI18nTextVNode = (label: string) =>\n  shallowRef({\n    render(): VNode {\n      return h(Text, this.$t(label));\n    },\n  });\n\ntype ViewFilterFunction = (view: SbaView) => boolean;\ntype ViewConfig = {\n  isEnabled?: (obj?) => boolean;\n  [key: string]: any;\n};\n\nexport default class ViewRegistry {\n  private readonly _redirects: any[] = [];\n\n  private _views: SbaView[] = reactive([]);\n\n  get views(): SbaView[] {\n    return this._views;\n  }\n\n  get routes() {\n    const routes = this._toRoutes((view) => view.path && !view.parent);\n    return [...routes, ...this._redirects];\n  }\n\n  get router(): Router {\n    return router;\n  }\n\n  setGroupIcon(name, icon) {\n    VIEW_GROUP_ICON[name] = icon;\n  }\n\n  createRouter() {\n    const routesKnownToBackend = sbaConfig.uiSettings.routes.map(\n      (r) => new RegExp(`^${r.replace('/**', '(/.*)?')}$`),\n    );\n    const unknownRoutes = this.routes.filter(\n      (vr) =>\n        vr.path !== '/' && !routesKnownToBackend.some((br) => br.test(vr.path)),\n    );\n    if (unknownRoutes.length > 0) {\n      console.warn(\n        `The routes ${JSON.stringify(\n          unknownRoutes.map((r) => r.path),\n        )} aren't known to the backend and may be not properly routed!`,\n      );\n    }\n\n    router = createRouter({\n      history: createWebHistory(),\n      linkActiveClass: 'is-active',\n      routes: this.routes,\n    });\n    return router;\n  }\n\n  getViewByName(name) {\n    return Array.prototype.find.call(this._views, (v) => v.name === name);\n  }\n\n  addView(...views: View[]): SbaView[] {\n    return views.map((view) => this._addView(view));\n  }\n\n  addRedirect(path: string, redirect: string | object) {\n    if (typeof redirect === 'string') {\n      this._redirects.push({ path, redirect: { name: redirect } });\n    } else {\n      this._redirects.push({ path, redirect });\n    }\n  }\n\n  _addView(viewConfig: ViewConfig): SbaView {\n    const view = { ...viewConfig } as SbaView;\n    view.hasChildren = !!viewConfig.children;\n\n    if (!viewConfig.name) {\n      view.name = [viewConfig.parent, viewConfig.path]\n        .filter((p) => !!p)\n        .join('/');\n    }\n\n    if (viewConfig.label && !viewConfig.handle) {\n      view.handle = createI18nTextVNode(viewConfig.label);\n    }\n\n    if (viewConfig.handle) {\n      view.handle = markRaw(viewConfig.handle);\n    }\n\n    if (!viewConfig.group) {\n      view.group = VIEW_GROUP.NONE;\n    }\n    if (viewConfig.component) {\n      view.component = markRaw(viewConfig.component);\n    }\n\n    if (!viewConfig.isEnabled) {\n      view.isEnabled = () => {\n        const viewSettings = sbaConfig.uiSettings.viewSettings.find(\n          (vs) => vs.name === viewConfig.name,\n        );\n        return !viewSettings || viewSettings.enabled === true;\n      };\n    } else {\n      view.isEnabled = viewConfig.isEnabled;\n    }\n\n    this._removeExistingView(view);\n    this._views.push(view);\n\n    return view;\n  }\n\n  _removeExistingView(view) {\n    remove(this._views, (v) => {\n      return v.name === view.name && v.group === view.group;\n    });\n  }\n\n  _toRoutes(filter: ViewFilterFunction) {\n    return this._views.filter(filter).map((view) => {\n      const children = this._toRoutes(\n        (childView) => childView.parent === view.name,\n      );\n\n      return {\n        path: view.path,\n        name: view.name,\n        component: view.component,\n        props: view.props,\n        meta: { view: toRaw(view) },\n        children,\n      };\n    });\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/ViewGroup.ts",
    "content": "export const VIEW_GROUP = {\n  WEB: 'web',\n  INSIGHTS: 'insights',\n  DATA: 'data',\n  JVM: 'jvm',\n  LOGGING: 'logging',\n  NONE: 'none',\n  SECURITY: 'security',\n  DEPENDENCIES: 'dependencies',\n};\n\nexport const VIEW_GROUP_ICON = {\n  [VIEW_GROUP.WEB]:\n    '<svg aria-hidden=\"true\" focusable=\"false\" data-prefix=\"fas\" class=\"w-5 h-5 mr-3\" role=\"img\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 496 512\"><path fill=\"currentColor\" d=\"M336.5 160C322 70.7 287.8 8 248 8s-74 62.7-88.5 152h177zM152 256c0 22.2 1.2 43.5 3.3 64h185.3c2.1-20.5 3.3-41.8 3.3-64s-1.2-43.5-3.3-64H155.3c-2.1 20.5-3.3 41.8-3.3 64zm324.7-96c-28.6-67.9-86.5-120.4-158-141.6 24.4 33.8 41.2 84.7 50 141.6h108zM177.2 18.4C105.8 39.6 47.8 92.1 19.3 160h108c8.7-56.9 25.5-107.8 49.9-141.6zM487.4 192H372.7c2.1 21 3.3 42.5 3.3 64s-1.2 43-3.3 64h114.6c5.5-20.5 8.6-41.8 8.6-64s-3.1-43.5-8.5-64zM120 256c0-21.5 1.2-43 3.3-64H8.6C3.2 212.5 0 233.8 0 256s3.2 43.5 8.6 64h114.6c-2-21-3.2-42.5-3.2-64zm39.5 96c14.5 89.3 48.7 152 88.5 152s74-62.7 88.5-152h-177zm159.3 141.6c71.4-21.2 129.4-73.7 158-141.6h-108c-8.8 56.9-25.6 107.8-50 141.6zM19.3 352c28.6 67.9 86.5 120.4 158 141.6-24.4-33.8-41.2-84.7-50-141.6h-108z\"></path></svg>',\n  [VIEW_GROUP.INSIGHTS]:\n    '<svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5 mr-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"></path></svg>',\n  [VIEW_GROUP.DATA]:\n    '<svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5 mr-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4\"></path></svg>',\n  [VIEW_GROUP.JVM]:\n    '<svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5 mr-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z\"></path><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"></path></svg>',\n  [VIEW_GROUP.LOGGING]:\n    '<svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5 mr-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\"></path></svg>',\n  [VIEW_GROUP.DEPENDENCIES]:\n    '<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-5 w-5 mr-3\">' +\n    '  <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M14.25 6.087c0-.355.186-.676.401-.959.221-.29.349-.634.349-1.003 0-1.036-1.007-1.875-2.25-1.875s-2.25.84-2.25 1.875c0 .369.128.713.349 1.003.215.283.401.604.401.959v0a.64.64 0 0 1-.657.643 48.39 48.39 0 0 1-4.163-.3c.186 1.613.293 3.25.315 4.907a.656.656 0 0 1-.658.663v0c-.355 0-.676-.186-.959-.401a1.647 1.647 0 0 0-1.003-.349c-1.036 0-1.875 1.007-1.875 2.25s.84 2.25 1.875 2.25c.369 0 .713-.128 1.003-.349.283-.215.604-.401.959-.401v0c.31 0 .555.26.532.57a48.039 48.039 0 0 1-.642 5.056c1.518.19 3.058.309 4.616.354a.64.64 0 0 0 .657-.643v0c0-.355-.186-.676-.401-.959a1.647 1.647 0 0 1-.349-1.003c0-1.035 1.008-1.875 2.25-1.875 1.243 0 2.25.84 2.25 1.875 0 .369-.128.713-.349 1.003-.215.283-.4.604-.4.959v0c0 .333.277.599.61.58a48.1 48.1 0 0 0 5.427-.63 48.05 48.05 0 0 0 .582-4.717.532.532 0 0 0-.533-.57v0c-.355 0-.676.186-.959.401-.29.221-.634.349-1.003.349-1.035 0-1.875-1.007-1.875-2.25s.84-2.25 1.875-2.25c.37 0 .713.128 1.003.349.283.215.604.401.96.401v0a.656.656 0 0 0 .658-.663 48.422 48.422 0 0 0-.37-5.36c-1.886.342-3.81.574-5.766.689a.578.578 0 0 1-.61-.58v0Z\" />' +\n    '</svg>',\n  [VIEW_GROUP.NONE]: '<div class=\"h-5 w-5 mr-3\">&nbsp;</div>',\n  [VIEW_GROUP.SECURITY]:\n    '<svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5 mr-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"></path></svg>',\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/about/handle.vue",
    "content": "<template>\n  <FontAwesomeIcon icon=\"question-circle\" size=\"lg\" />\n</template>\n\n<script setup>\nimport { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/about/i18n.de.json",
    "content": "{\n  \"about\": {\n    \"title\": \"Über Spring Boot Admin\",\n    \"label\": \"Über\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/about/i18n.en.json",
    "content": "{\n  \"about\": {\n    \"title\": \"About Spring Boot Admin\",\n    \"label\": \"About\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/about/i18n.es.json",
    "content": "{\n  \"about\": {\n    \"title\": \"Acerca de Spring Boot Admin\",\n    \"label\": \"Acerca de SBA\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/about/i18n.fr.json",
    "content": "{\n  \"about\": {\n    \"title\": \"À propos de Spring Boot Admin\",\n    \"label\": \"À propos\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/about/i18n.is.json",
    "content": "{\n  \"about\": {\n    \"title\": \"Um Spring Boot Admin\",\n    \"label\": \"Um\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/about/i18n.ko.json",
    "content": "{\n  \"about\": {\n    \"title\": \"Spring Boot Admin 소개\",\n    \"label\": \"소개\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/about/i18n.pt-BR.json",
    "content": "{\n  \"about\": {\n    \"title\": \"Sobre o Spring Boot Admin\",\n    \"label\": \"Sobre\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/about/i18n.ru.json",
    "content": "{\n  \"about\": {\n    \"title\": \"О Spring Boot Admin\",\n    \"label\": \"О проекте\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/about/i18n.zh-CN.json",
    "content": "{\n  \"about\": {\n    \"title\": \"关于Spring Boot Admin\",\n    \"label\": \"关于我们\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/about/i18n.zh-TW.json",
    "content": "{\n  \"about\": {\n    \"title\": \"關於 Spring Boot Admin\",\n    \"label\": \"關於\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/about/index.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-wave />\n  <div class=\"container prose prose-slate mx-auto pt-10\">\n    <h1 class=\"mb-1\" v-text=\"$t('about.title')\" />\n    <h2 v-if=\"version\" v-text=\"`Version ${version}`\" />\n    <p>This is an administration UI for Spring Boot applications.</p>\n    <p>\n      To monitor applications, they must be registered at this server. This is\n      either done by including the\n      <a\n        :href=\"`${documentationBaseUrl}/getting-started.html#register-clients-via-spring-boot-admin`\"\n        rel=\"noreferrer\"\n        target=\"_blank\"\n      >\n        Spring Boot Admin Client\n      </a>\n      or using a\n      <a\n        :href=\"`${documentationBaseUrl}/getting-started.html#discover-clients-via-spring-cloud-discovery`\"\n        rel=\"noreferrer\"\n        target=\"_blank\"\n      >\n        Spring Cloud Discovery Client\n      </a>\n      implementation.\n    </p>\n    <p>\n      If you have any question please consult the\n      <a :href=\"`${documentationBaseUrl}`\"> Reference Guide </a>, ask on\n      <a\n        href=\"https://stackoverflow.com/questions/tagged/spring-boot-admin\"\n        rel=\"noreferrer\"\n        target=\"_blank\"\n        >Stack Overflow</a\n      >\n      or have a chat on the\n      <a\n        href=\"https://gitter.im/codecentric/spring-boot-admin\"\n        rel=\"noreferrer\"\n        target=\"_blank\"\n        >Gitter</a\n      >\n      channel.\n    </p>\n    <p>\n      If you found a bug, want to propose a feature or submit a pull request\n      please use the\n      <a href=\"https://github.com/codecentric/spring-boot-admin/issues\">\n        issue tracker </a\n      >.\n    </p>\n    <div class=\"flex justify-between\">\n      <sba-button @click=\"openLink(documentationBaseUrl)\">\n        <font-awesome-icon icon=\"book\" size=\"lg\" />&nbsp;Reference Guide\n      </sba-button>\n      <sba-button\n        @click=\"openLink('https://github.com/codecentric/spring-boot-admin')\"\n      >\n        <font-awesome-icon :icon=\"['fab', 'github']\" size=\"lg\" />&nbsp;Sources\n      </sba-button>\n      <sba-button\n        @click=\"\n          openLink(\n            'https://stackoverflow.com/questions/tagged/spring-boot-admin',\n          )\n        \"\n      >\n        <font-awesome-icon\n          :icon=\"['fab', 'stack-overflow']\"\n          size=\"lg\"\n        />&nbsp;Stack Overflow\n      </sba-button>\n      <sba-button\n        @click=\"openLink('https://gitter.im/codecentric/spring-boot-admin')\"\n      >\n        <font-awesome-icon :icon=\"['fab', 'gitter']\" size=\"lg\" />&nbsp;Gitter\n      </sba-button>\n    </div>\n\n    <h1 class=\"mt-5\">Trademarks and Licenses</h1>\n    <p>\n      The source code of Spring Boot Admin is licensed under\n      <a href=\"https://www.apache.org/licenses/LICENSE-2.0\">\n        Apache License 2.0 </a\n      >.\n    </p>\n    <p>\n      Spring, Spring Boot and Spring Cloud are trademarks of\n      <a href=\"https://www.vmware.com/\" rel=\"noreferrer\" target=\"_blank\"\n        >VMware, Inc.</a\n      >\n      or its affiliates in the U.S. and other countries.\n    </p>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\n\nimport SbaButton from '@/components/sba-button';\nimport SbaWave from '@/components/sba-wave';\n\nimport handle from '@/views/about/handle.vue';\n\nexport default defineComponent({\n  components: { SbaWave, SbaButton },\n  data: () => ({\n    version: __PROJECT_VERSION__,\n  }),\n  computed: {\n    documentationBaseUrl() {\n      return `https://codecentric.github.io/spring-boot-admin/${\n        this.version || 'current'\n      }`;\n    },\n  },\n  methods: {\n    openLink(url) {\n      window.open(url, '_blank');\n    },\n  },\n  install({ viewRegistry }: ViewInstallFunctionParams) {\n    viewRegistry.addView({\n      path: '/about',\n      name: 'about',\n      handle: handle,\n      order: Number.MAX_VALUE,\n      component: this,\n    });\n  },\n});\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/applications/ActionHandler.ts",
    "content": "import Application from '@/services/application';\nimport Instance from '@/services/instance';\n\nexport interface ActionHandler {\n  restart(item: any): Promise<void>;\n\n  unregister(item: any): Promise<void>;\n\n  shutdown(item: any): Promise<void>;\n}\n\nexport class InstanceActionHandler implements ActionHandler {\n  constructor(\n    private $sbaModal: any,\n    private t: any,\n    private notificationCenter: any,\n  ) {}\n\n  async unregister(item: Instance) {\n    const isConfirmed = await this.$sbaModal.confirm(\n      this.t('applications.actions.unregister'),\n      this.t('instances.unregister', { name: item.id }),\n    );\n    if (!isConfirmed) {\n      return;\n    }\n\n    try {\n      await item.unregister();\n      this.notificationCenter.success(\n        this.t('instances.unregister_successful', { name: item.id }),\n      );\n    } catch (error) {\n      this.notificationCenter.error(\n        this.t('instances.unregister_failed', {\n          name: item.id || item.name,\n          error: error.response.status,\n        }),\n      );\n    }\n  }\n\n  async shutdown(item: Instance) {\n    const isConfirmed = await this.$sbaModal.confirm(\n      this.t('applications.actions.shutdown'),\n      this.t('instances.shutdown', { name: item.id }),\n    );\n    if (!isConfirmed) {\n      return;\n    }\n\n    try {\n      await item.shutdown();\n      this.notificationCenter.success(\n        this.t('instances.shutdown_successful', { name: item.id }),\n      );\n    } catch (error) {\n      this.notificationCenter.error(\n        this.t('instances.shutdown_failed', {\n          name: item.id || item.name,\n          error: error.response.status,\n        }),\n      );\n    }\n  }\n\n  async restart(item: Instance) {\n    const isConfirmed = await this.$sbaModal.confirm(\n      this.t('applications.actions.restart'),\n      this.t('instances.restart', { name: item.id }),\n    );\n    if (!isConfirmed) {\n      return;\n    }\n\n    try {\n      await item.restart();\n      this.notificationCenter.success(\n        this.t('instances.restarted', { name: item.id }),\n      );\n    } catch (error) {\n      this.notificationCenter.error(\n        this.t('instances.restart_failed', {\n          name: item.id || item.name,\n          error: error.response.status,\n        }),\n      );\n    }\n  }\n}\n\nexport class ApplicationActionHandler implements ActionHandler {\n  constructor(\n    private $sbaModal: any,\n    private t: any,\n    private notificationCenter: any,\n  ) {}\n\n  async restart(application: Application) {\n    const isConfirmed = await this.$sbaModal.confirm(\n      this.t('applications.actions.restart'),\n      this.t('applications.restart', { name: application.name }),\n    );\n    if (!isConfirmed) {\n      return;\n    }\n\n    try {\n      await application.restart();\n      this.notificationCenter.success(\n        this.t('applications.restarted', { name: application.name }),\n      );\n    } catch (error) {\n      this.notificationCenter.error(\n        this.t('applications.restart_failed', {\n          name: application.name,\n          error: error.response.status,\n        }),\n      );\n    }\n  }\n\n  async shutdown(application: Application) {\n    const isConfirmed = await this.$sbaModal.confirm(\n      this.t('applications.actions.shutdown'),\n      this.t('applications.shutdown', { name: application.name }),\n    );\n    if (!isConfirmed) {\n      return;\n    }\n\n    try {\n      await application.shutdown();\n      this.notificationCenter.success(\n        this.t('applications.shutdown_successful', { name: application.name }),\n      );\n    } catch (error) {\n      this.notificationCenter.error(\n        this.t('applications.shutdown_failed', {\n          name: application.name,\n          error: error.response.status,\n        }),\n      );\n    }\n  }\n\n  async unregister(application: Application) {\n    const isConfirmed = await this.$sbaModal.confirm(\n      this.t('applications.actions.unregister'),\n      this.t('applications.unregister', { name: application.name }),\n    );\n    if (!isConfirmed) {\n      return;\n    }\n\n    try {\n      await application.unregister();\n      this.notificationCenter.success(\n        this.t('applications.unregister_successful', {\n          name: application.name,\n        }),\n      );\n    } catch (error) {\n      this.notificationCenter.error(\n        this.t('applications.unregister_failed', {\n          name: application.name,\n          error: error.response.status,\n        }),\n      );\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/applications/ApplicationListItemAction.spec.ts",
    "content": "/*\n * Copyright 2014-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport userEvent from '@testing-library/user-event';\nimport { screen, waitFor } from '@testing-library/vue';\nimport { cloneDeep } from 'lodash-es';\nimport { HttpResponse, http } from 'msw';\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport Application from '../../services/application';\n\nimport { applications } from '@/mocks/applications/data';\nimport { server } from '@/mocks/server';\nimport Instance from '@/services/instance';\nimport { render } from '@/test-utils';\nimport ApplicationsListItem from '@/views/applications/ApplicationListItemAction';\n\nasync function clickConfirmModal() {\n  await waitFor(() => {\n    expect(screen.getByRole('dialog')).toBeInTheDocument();\n  });\n\n  const buttonOK = screen.queryByRole('button', { name: 'term.ok' });\n  await userEvent.click(buttonOK);\n}\n\ndescribe('ApplicationListItemAction', () => {\n  let application: Application;\n  let instance: Instance;\n\n  beforeEach(() => {\n    server.use(\n      // Instances\n      http.delete('/instances/:instanceId', () => {\n        return HttpResponse.json({});\n      }),\n      http.post('/instances/:instanceId/actuator/restart', () => {\n        return HttpResponse.json({});\n      }),\n      http.post('/instances/:instanceId/actuator/shutdown', () => {\n        return HttpResponse.json({});\n      }),\n      // Applications\n      http.delete('/applications/:name', () => {\n        return HttpResponse.json({});\n      }),\n      http.post('/applications/:name/actuator/restart', () => {\n        return HttpResponse.json({});\n      }),\n      http.post('/applications/:name/actuator/shutdown', () => {\n        return HttpResponse.json({});\n      }),\n    );\n  });\n\n  beforeEach(() => {\n    application = new Application(cloneDeep(applications[0]));\n    instance = application.instances[0];\n  });\n\n  describe('unregister', () => {\n    it('on instance', async () => {\n      const spy = vi.spyOn(instance, 'unregister');\n\n      render(ApplicationsListItem, {\n        props: { item: instance },\n      });\n\n      await userEvent.click(screen.getByTitle('Unregister'));\n      await clickConfirmModal();\n\n      expect(spy).toHaveBeenCalledOnce();\n    });\n\n    it('on application', async () => {\n      const spy = vi.spyOn(application, 'unregister');\n\n      render(ApplicationsListItem, {\n        props: { item: application },\n      });\n\n      await userEvent.click(screen.getByTitle('Unregister'));\n      await clickConfirmModal();\n\n      expect(spy).toHaveBeenCalledOnce();\n    });\n  });\n\n  describe('restart', () => {\n    it('on instance', async () => {\n      const spy = vi.spyOn(instance, 'restart');\n\n      render(ApplicationsListItem, {\n        props: { item: instance },\n      });\n\n      await userEvent.click(screen.getByTitle('Restart'));\n      await clickConfirmModal();\n\n      expect(spy).toHaveBeenCalledOnce();\n    });\n\n    it('on application', async () => {\n      const spy = vi.spyOn(application, 'restart');\n      render(ApplicationsListItem, {\n        props: { item: application },\n      });\n\n      await userEvent.click(screen.getByTitle('Restart'));\n      await clickConfirmModal();\n\n      expect(spy).toHaveBeenCalledOnce();\n    });\n  });\n\n  describe('shutdown', () => {\n    it('on application', async () => {\n      const spy = vi.spyOn(application, 'shutdown');\n      render(ApplicationsListItem, {\n        props: { item: application },\n      });\n\n      await userEvent.click(screen.getByTitle('Shutdown'));\n      await clickConfirmModal();\n\n      expect(spy).toHaveBeenCalledOnce();\n    });\n\n    it('on instance', async () => {\n      const spy = vi.spyOn(instance, 'shutdown');\n      render(ApplicationsListItem, {\n        props: { item: instance },\n      });\n\n      await userEvent.click(await screen.getByTitle('Shutdown'));\n      await clickConfirmModal();\n\n      expect(spy).toHaveBeenCalledOnce();\n    });\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/applications/ApplicationListItemAction.vue",
    "content": "<template>\n  <sba-button-group class=\"application-list-item__header__actions text-right\">\n    <router-link v-slot=\"{ navigate }\" :to=\"journalLink\" custom>\n      <sba-button\n        :title=\"$t('applications.actions.journal')\"\n        @click.stop=\"navigate\"\n      >\n        <font-awesome-icon :icon=\"faHistory\" />\n      </sba-button>\n    </router-link>\n    <sba-button\n      v-if=\"hasNotificationFiltersSupport\"\n      :id=\"`nf-settings-${item.name || item.id}`\"\n      :title=\"$t('applications.actions.notification_filters')\"\n      @click.stop=\"$emit('filter-settings', item)\"\n    >\n      <font-awesome-icon\n        :icon=\"hasActiveNotificationFilter ? faBellSlash : faBell\"\n      />\n    </sba-button>\n    <sba-button\n      v-if=\"item.isUnregisterable\"\n      class=\"btn-unregister\"\n      :title=\"$t('applications.actions.unregister')\"\n      @click.stop=\"actionHandler.unregister(item)\"\n    >\n      <font-awesome-icon :icon=\"faTrash\" />\n    </sba-button>\n    <sba-button\n      v-if=\"item.hasEndpoint('restart')\"\n      :title=\"$t('applications.actions.restart')\"\n      @click.stop=\"actionHandler.restart(item)\"\n    >\n      <font-awesome-icon :icon=\"faUndoAlt\" />\n    </sba-button>\n    <sba-button\n      v-if=\"item.hasEndpoint('shutdown')\"\n      :title=\"$t('applications.actions.shutdown')\"\n      class=\"is-danger btn-shutdown\"\n      @click.stop=\"actionHandler.shutdown(item)\"\n    >\n      <font-awesome-icon :icon=\"faPowerOff\" />\n    </sba-button>\n  </sba-button-group>\n</template>\n\n<script lang=\"ts\" setup>\nimport {\n  faBell,\n  faBellSlash,\n  faHistory,\n  faPowerOff,\n  faTrash,\n  faUndoAlt,\n} from '@fortawesome/free-solid-svg-icons';\nimport { useNotificationCenter } from '@stekoe/vue-toast-notificationcenter';\nimport { inject } from 'vue';\nimport { useI18n } from 'vue-i18n';\nimport { RouteLocationNamedRaw } from 'vue-router';\n\nimport Application from '@/services/application';\nimport Instance from '@/services/instance';\nimport {\n  ActionHandler,\n  ApplicationActionHandler,\n  InstanceActionHandler,\n} from '@/views/applications/ActionHandler';\n\nconst $sbaModal = inject('$sbaModal');\nconst { t } = useI18n();\nconst notificationCenter = useNotificationCenter({});\n\nconst props = defineProps({\n  item: {\n    type: [Application, Instance],\n    required: true,\n  },\n  hasActiveNotificationFilter: {\n    type: Boolean,\n    default: false,\n  },\n  hasNotificationFiltersSupport: {\n    type: Boolean,\n    default: false,\n  },\n});\n\ndefineEmits(['filter-settings']);\n\nlet journalLink: RouteLocationNamedRaw;\nlet actionHandler: ActionHandler;\nif (props.item instanceof Application) {\n  actionHandler = new ApplicationActionHandler(\n    $sbaModal,\n    t,\n    notificationCenter,\n  );\n  journalLink = {\n    name: 'journal',\n    query: { application: props.item.name },\n  };\n} else if (props.item instanceof Instance) {\n  actionHandler = new InstanceActionHandler($sbaModal, t, notificationCenter);\n  journalLink = { name: 'journal', query: { instanceId: props.item.id } };\n}\n</script>\n\n<style scoped>\n.application-list-item__header__actions {\n  @apply hidden lg:inline-flex p-1 bg-black/5 rounded-lg;\n}\n\n.btn-shutdown,\n.btn-unregister {\n  @apply ml-1 !important;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/applications/ApplicationNotificationCenter.vue",
    "content": "<template>\n  <Popover class=\"relative\">\n    <PopoverButton\n      :as=\"SbaButton\"\n      :disabled=\"notificationFiltersLength === 0\"\n      :title=\"\n        notificationFiltersLength > 0\n          ? t('applications.actions.notification_filters')\n          : t('applications.notification_filter.none')\n      \"\n      class=\"mr-1 h-full\"\n    >\n      <font-awesome-icon\n        :icon=\"notificationFiltersLength > 0 ? 'bell-slash' : 'bell'\"\n      />\n    </PopoverButton>\n\n    <PopoverPanel\n      v-slot=\"{ close }\"\n      :as=\"SbaPanel\"\n      class=\"absolute left-1/2 z-10 mt-3 w-screen max-w-xl -translate-x-1/2 transform px-4 sm:px-0 shadow-lg text-sm\"\n    >\n      <div\n        class=\"font-semibold leading-5\"\n        v-text=\"t('applications.actions.notification_filters')\"\n      />\n      <div\n        class=\"mt-2 leading-5 text-slate-500\"\n        v-text=\"t('notification_filter_center.description')\"\n      />\n      <div\n        v-for=\"(filter, idx) in notificationFilters\"\n        :key=\"filter.id\"\n        :class=\"{\n          'mt-4': idx === 0,\n          'py-3': idx < notificationFiltersLength - 1,\n          'pt-3': idx >= notificationFiltersLength - 1,\n        }\"\n        class=\"flex items-center border-t border-gray-50\"\n      >\n        <div class=\"w-1/2\">\n          {{ filter.instanceId || filter.applicationName }}\n        </div>\n        <div class=\"flex-1\">\n          <strong\n            v-text=\"\n              filter.expiry\n                ? filter.expiry.locale(momentLocale).fromNow(true)\n                : t('term.ever')\n            \"\n          />\n        </div>\n        <div class=\"flex-none text-right text-red-700\">\n          <button\n            :disabled=\"executing[filter.id]\"\n            @click.stop=\"removeFilter(filter, close)\"\n          >\n            {{ t('term.delete') }}\n          </button>\n        </div>\n      </div>\n    </PopoverPanel>\n  </Popover>\n</template>\n\n<script setup>\nimport { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue';\nimport { computed, ref, watch } from 'vue';\nimport { useI18n } from 'vue-i18n';\n\nimport SbaButton from '@/components/sba-button';\nimport SbaPanel from '@/components/sba-panel';\n\nconst props = defineProps({\n  notificationFilters: {\n    type: Array,\n    default: () => [],\n  },\n});\n\nconst emit = defineEmits(['filter-remove']);\n\nconst i18n = useI18n();\nconst t = i18n.t;\nconst locale = i18n.locale;\nconst executing = ref({});\n\nconst notificationFiltersLength = ref(0);\n\nconst momentLocale = computed({\n  get() {\n    return locale.value;\n  },\n});\n\nwatch(\n  () => props.notificationFilters,\n  (notificationFilters) => {\n    notificationFiltersLength.value = notificationFilters.length;\n  },\n);\n\nconst removeFilter = async (filter, closePopover) => {\n  executing[filter.id] = true;\n  emit('filter-remove', filter);\n\n  if (notificationFiltersLength.value <= 1) {\n    closePopover();\n  }\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/applications/ApplicationStats.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div class=\"hidden md:flex mr-1 gap-1\">\n    <sba-tag\n      :label=\"$t('applications.applications')\"\n      :value=\"applicationsCount\"\n    />\n    <sba-tag :label=\"$t('applications.instances')\" :value=\"instancesCount\" />\n  </div>\n</template>\n\n<script setup>\nimport { computed } from 'vue';\n\nimport { useApplicationStore } from '@/composables/useApplicationStore';\n\nconst { applications } = useApplicationStore();\nconst applicationsCount = computed({\n  get() {\n    return applications.value.length;\n  },\n});\n\nconst instancesCount = computed({\n  get() {\n    return applications.value.reduce(\n      (current, next) => current + next.instances.length,\n      0,\n    );\n  },\n});\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/applications/ApplicationStatusHero.spec.ts",
    "content": "import { screen } from '@testing-library/vue';\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\nimport { Ref, ref } from 'vue';\n\nimport { useApplicationStore } from '@/composables/useApplicationStore';\nimport Application from '@/services/application';\nimport Instance from '@/services/instance';\nimport { render } from '@/test-utils';\nimport ApplicationStatusHero from '@/views/applications/ApplicationStatusHero.vue';\n\nvi.mock('@/composables/useApplicationStore', () => ({\n  useApplicationStore: vi.fn(),\n}));\n\ndescribe('ApplicationStatusHero', () => {\n  let applications: Ref<Application[]>;\n\n  beforeEach(async () => {\n    applications = ref([]);\n\n    // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n    // @ts-ignore\n    useApplicationStore.mockReturnValue({\n      applicationsInitialized: ref(true),\n      applications,\n      error: ref(null),\n    });\n  });\n\n  it.each`\n    instance1Status | instance2Status | expectedMessage                          | expectedIcon\n    ${'UP'}         | ${'UP'}         | ${'all up'}                              | ${'check-circle'}\n    ${'OFFLINE'}    | ${'OFFLINE'}    | ${'all down'}                            | ${'minus-circle'}\n    ${'UNKNOWN'}    | ${'UNKNOWN'}    | ${'all in unknown state'}                | ${'question-circle'}\n    ${'UP'}         | ${'UNKNOWN'}    | ${'some instances are in unknown state'} | ${'question-circle'}\n    ${'UP'}         | ${'DOWN'}       | ${'some instances are down'}             | ${'minus-circle'}\n    ${'UP'}         | ${'OFFLINE'}    | ${'some instances are down'}             | ${'minus-circle'}\n    ${'DOWN'}       | ${'UNKNOWN'}    | ${'some instances are down'}             | ${'minus-circle'}\n    ${'DOWN'}       | ${'OFFLINE'}    | ${'all down'}                            | ${'minus-circle'}\n    ${'OFFLINE'}    | ${'UP'}         | ${'some instances are down'}             | ${'minus-circle'}\n  `(\n    '`$expectedMessage` is shown when `$instance1Status` and `$instance2Status`',\n    ({ instance1Status, instance2Status, expectedMessage, expectedIcon }) => {\n      applications.value = [\n        new Application({\n          name: 'Test Application',\n          statusTimestamp: Date.now(),\n          instances: [\n            new Instance({\n              id: '4711',\n              statusInfo: { status: instance1Status },\n            }),\n            new Instance({\n              id: '4712',\n              statusInfo: { status: instance2Status },\n            }),\n          ],\n        }),\n      ];\n\n      render(ApplicationStatusHero, {\n        global: {\n          stubs: {\n            'font-awesome-icon': {\n              template: '<svg data-testid=\"icon\" />',\n            },\n          },\n        },\n      });\n\n      expect(screen.getByTestId('icon')).toHaveAttribute('icon', expectedIcon);\n      expect(screen.getByText(expectedMessage)).toBeVisible();\n    },\n  );\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/applications/ApplicationStatusHero.vue",
    "content": "<template>\n  <sba-panel>\n    <div class=\"flex flex-row items-center justify-center my-2\">\n      <template v-if=\"applicationsCount > 0\">\n        <template v-if=\"statusInfo.allUp\">\n          <font-awesome-icon icon=\"check-circle\" class=\"text-green-500 icon\" />\n          <div class=\"text-center\">\n            <h1 class=\"status-label\" v-text=\"$t('applications.all_up')\" />\n            <p class=\"text-gray-400\" v-text=\"lastUpdate\" />\n          </div>\n        </template>\n        <template v-else-if=\"statusInfo.allDown\">\n          <font-awesome-icon icon=\"minus-circle\" class=\"text-red-500 icon\" />\n          <div class=\"text-center\">\n            <h1 class=\"status-label\" v-text=\"$t('applications.all_down')\" />\n            <p class=\"text-gray-400\" v-text=\"lastUpdate\" />\n          </div>\n        </template>\n        <template v-if=\"statusInfo.allUnknown\">\n          <font-awesome-icon\n            icon=\"question-circle\"\n            class=\"text-gray-300 icon\"\n          />\n          <div class=\"text-center\">\n            <h1 class=\"status-label\" v-text=\"$t('applications.all_unknown')\" />\n            <p class=\"text-gray-400\" v-text=\"lastUpdate\" />\n          </div>\n        </template>\n        <template v-else-if=\"someInstancesDown\">\n          <font-awesome-icon icon=\"minus-circle\" class=\"text-red-500 icon\" />\n          <div class=\"text-center\">\n            <h1 class=\"status-label\" v-text=\"$t('applications.some_down')\" />\n            <p class=\"text-gray-400\" v-text=\"lastUpdate\" />\n          </div>\n        </template>\n\n        <template v-else-if=\"someInstancesUnknown\">\n          <font-awesome-icon\n            icon=\"question-circle\"\n            class=\"text-gray-300 icon\"\n          />\n          <div class=\"text-center\">\n            <h1 class=\"status-label\" v-text=\"$t('applications.some_unknown')\" />\n            <p class=\"text-gray-400\" v-text=\"lastUpdate\" />\n          </div>\n        </template>\n      </template>\n      <template v-else>\n        <font-awesome-icon icon=\"frown-open\" class=\"text-gray-500 icon\" />\n        <h1\n          class=\"status-label\"\n          v-text=\"$t('applications.no_applications_registered')\"\n        />\n      </template>\n    </div>\n  </sba-panel>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue';\n\nimport { useApplicationStore } from '@/composables/useApplicationStore';\nimport { useDateTimeFormatter } from '@/composables/useDateTimeFormatter';\nimport { getStatusInfo } from '@/services/application';\n\nconst { applications } = useApplicationStore();\nconst { formatDateTime } = useDateTimeFormatter();\n\nconst lastUpdate = ref(formatDateTime(new Date()));\n\nconst statusInfo = computed(() => {\n  return getStatusInfo(applications.value);\n});\n\nwatch(statusInfo, () => {\n  lastUpdate.value = formatDateTime(new Date());\n});\n\nconst applicationsCount = computed(() => {\n  return applications.value.length;\n});\n\nconst someInstancesDown = computed(() => {\n  return statusInfo.value.someDown;\n});\n\nconst someInstancesUnknown = computed(() => {\n  return statusInfo.value.someUnknown;\n});\n</script>\n\n<style scoped>\n.status-label {\n  @apply font-bold text-2xl;\n}\n.icon {\n  @apply text-6xl pr-4;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/applications/InstancesList.spec.ts",
    "content": "import { screen } from '@testing-library/vue';\nimport { deepMerge } from '@vitest/utils';\n\nimport Instance from '@/services/instance';\nimport { render } from '@/test-utils';\nimport InstancesList from '@/views/applications/InstancesList.vue';\n\ndescribe('InstancesList', () => {\n  describe('Metadata: hide-url', () => {\n    it('should show service url when hide-url is set to false', async () => {\n      render(InstancesList, {\n        props: {\n          instances: [\n            createInstance({\n              registration: {\n                metadata: { 'hide-url': 'false' },\n              },\n            }),\n          ],\n        },\n      });\n\n      expect(await screen.queryByText('Spring Boot Admin')).toBeVisible();\n      expect(await screen.queryByText('http://localhost:8080')).toBeVisible();\n    });\n\n    it('should show service url when hide-url not set', async () => {\n      render(InstancesList, {\n        props: {\n          instances: [createInstance()],\n        },\n      });\n\n      expect(await screen.queryByText('Spring Boot Admin')).toBeVisible();\n      expect(await screen.queryByText('http://localhost:8080')).toBeVisible();\n    });\n\n    it('should hide service url when hide-url is set to true', async () => {\n      render(InstancesList, {\n        props: {\n          instances: [\n            createInstance({\n              registration: {\n                metadata: { 'hide-url': 'true' },\n              },\n            }),\n          ],\n        },\n      });\n\n      expect(await screen.queryByText('Spring Boot Admin')).toBeVisible();\n      expect(\n        await screen.queryByText('http://localhost:8080'),\n      ).not.toBeInTheDocument();\n    });\n  });\n\n  describe('Metadata: disable-url', () => {\n    it('should show service url homepage button when diable-url is set to false', async () => {\n      render(InstancesList, {\n        props: {\n          instances: [\n            createInstance({\n              registration: {\n                metadata: { 'disable-url': 'false' },\n              },\n            }),\n          ],\n        },\n      });\n\n      expect(\n        await screen.queryByRole('link', { name: 'Homepage' }),\n      ).toBeVisible();\n    });\n\n    it('should show service url homepage button when diable-url is not set', async () => {\n      render(InstancesList, {\n        props: {\n          instances: [createInstance()],\n        },\n      });\n\n      expect(\n        await screen.queryByRole('link', { name: 'Homepage' }),\n      ).toBeVisible();\n    });\n\n    it('should show service url homepage button when diable-url is set to true', async () => {\n      render(InstancesList, {\n        props: {\n          instances: [\n            createInstance({\n              registration: {\n                metadata: { 'disable-url': 'true' },\n              },\n            }),\n          ],\n        },\n      });\n\n      expect(\n        await screen.queryByRole('link', { name: 'Homepage' }),\n      ).not.toBeInTheDocument();\n    });\n  });\n});\n\n// Utility functions\n\nfunction createInstance(options: InstanceType<typeof Instance> = {}): Instance {\n  const defaultData = {\n    id: 'Spring Boot Admin',\n    statusInfo: { status: 'UP' },\n    registration: {\n      serviceUrl: 'http://localhost:8080',\n    },\n  };\n\n  const instance = deepMerge(defaultData, options);\n  return new Instance(instance);\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/applications/InstancesList.vue",
    "content": "<!--\n  - Copyright 2014-2024 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <ul>\n    <li\n      v-for=\"instance in instances\"\n      :key=\"instance.id\"\n      :data-testid=\"instance.id\"\n      class=\"flex p-2 pr-4 hover:bg-gray-100 gap-2 odd:bg-gray-50 items-center\"\n      @click.stop=\"showDetails(instance)\"\n    >\n      <div class=\"pt-1 md:w-16 text-center\">\n        <sba-status\n          :date=\"instance.statusTimestamp\"\n          :status=\"instance.statusInfo.status\"\n        />\n      </div>\n      <div class=\"flex-1\">\n        <div\n          class=\"flex gap-2 items-center instance-item\"\n          :class=\"{ 'instance--show-url': instance.showUrl() }\"\n        >\n          <ItemInformation\n            class=\"instance-item-information\"\n            :instance=\"instance\"\n          />\n          <div class=\"hidden lg:block\">\n            <slot :instance=\"instance\" name=\"actions\" />\n          </div>\n        </div>\n        <ItemTags :instance=\"instance\" />\n      </div>\n    </li>\n  </ul>\n</template>\n\n<script lang=\"ts\" setup>\nimport { PropType } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport SbaStatus from '@/components/sba-status.vue';\n\nimport Instance from '@/services/instance';\nimport ItemInformation from '@/views/applications/listItem/ItemInformation.vue';\nimport ItemTags from '@/views/applications/listItem/ItemTags.vue';\n\nconst router = useRouter();\n\ndefineProps({\n  instances: {\n    type: Object as PropType<Array<Instance>>,\n    default: () => [] as Instance[],\n  },\n  showNotificationSettings: {\n    type: Boolean,\n    default: false,\n  },\n  hasActiveNotificationFilter: {\n    type: Function,\n    default: () => false,\n  },\n});\n\nconst showDetails = (instance: Instance) => {\n  router.push({\n    name: 'instances/details',\n    params: { instanceId: instance.id },\n  });\n};\n</script>\n\n<style scoped>\n.instance--show-url {\n  .instance-item-information {\n    @apply gap-1;\n    grid-area: 1 / 1 / 1 / 3;\n  }\n}\n</style>\n\n<style>\n.instance-item.instance--show-url {\n  .instance-id {\n    @apply text-xs font-light;\n  }\n  .instance-version {\n    @apply text-xs font-light;\n  }\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/applications/NotificationFilterSettings.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-panel class=\"shadow-xl\">\n    <template v-if=\"!activeFilter\">\n      <div class=\"field\">\n        <p class=\"control has-inline-text\">\n          <span\n            v-html=\"\n              t('applications.suppress_notifications_on', {\n                name: object.id || object.name,\n              })\n            \"\n          />&nbsp;\n          <sba-select\n            v-model=\"ttl\"\n            class=\"inline-flex\"\n            name=\"ttl\"\n            :options=\"ttlOptions\"\n            @click.stop\n          />\n        </p>\n      </div>\n      <div class=\"field is-grouped is-grouped-right\">\n        <div class=\"control\">\n          <sba-button\n            :class=\"{ 'is-loading': actionState === 'executing' }\"\n            @click.stop=\"addFilter\"\n          >\n            <font-awesome-icon icon=\"bell-slash\" />&nbsp;<span\n              v-text=\"t('term.suppress')\"\n            />\n          </sba-button>\n        </div>\n      </div>\n    </template>\n    <template v-else>\n      <div class=\"field\">\n        <p class=\"control has-inline-text\">\n          <span\n            v-html=\"\n              t('applications.notifications_suppressed_for', {\n                name: object.id || object.name,\n              })\n            \"\n          />&nbsp;\n          <strong\n            v-text=\"\n              activeFilter.expiry\n                ? activeFilter.expiry.locale(currentLocale).fromNow(true)\n                : t('term.ever')\n            \"\n          />.\n        </p>\n      </div>\n      <div class=\"field is-grouped is-grouped-right\">\n        <div class=\"control\">\n          <sba-button\n            :class=\"{ 'is-loading': actionState === 'executing' }\"\n            @click.stop=\"deleteActiveFilter\"\n          >\n            <font-awesome-icon icon=\"bell\" />&nbsp;<span\n              v-text=\"t('term.unsuppress')\"\n            />\n          </sba-button>\n        </div>\n      </div>\n    </template>\n  </sba-panel>\n</template>\n<script>\nimport { useI18n } from 'vue-i18n';\n\nexport default {\n  props: {\n    object: {\n      type: Object,\n      required: true,\n    },\n    notificationFilters: {\n      type: Array,\n      required: true,\n    },\n  },\n  emits: ['filter-deleted', 'filter-added', 'filter-remove', 'filter-add'],\n  setup() {\n    const i18n = useI18n();\n    return {\n      t: i18n.t,\n      currentLocale: i18n.locale,\n    };\n  },\n  data() {\n    return {\n      ttl: 5 * 60 * 1000,\n      ttlOptions: [\n        { label: this.t('term.minutes', { count: 5 }), value: 5 * 60 * 1000 },\n        { label: this.t('term.minutes', { count: 15 }), value: 15 * 60 * 1000 },\n        { label: this.t('term.minutes', { count: 30 }), value: 30 * 60 * 1000 },\n        { label: this.t('term.hours', { count: 1 }), value: 60 * 60 * 1000 },\n        {\n          label: this.t('term.hours', { count: 3 }),\n          value: 3 * 60 * 60 * 1000,\n        },\n        {\n          label: this.t('term.hours', { count: 8 }),\n          value: 8 * 60 * 60 * 1000,\n        },\n        {\n          label: this.t('term.hours', { count: 24 }),\n          value: 24 * 60 * 60 * 1000,\n        },\n        { label: this.t('term.ever'), value: -1 },\n      ],\n      actionState: null,\n    };\n  },\n  computed: {\n    activeFilter() {\n      return this.notificationFilters.find((f) => f.affects(this.object));\n    },\n  },\n  methods: {\n    async addFilter() {\n      this.$emit('filter-add', {\n        object: this.object,\n        ttl: this.ttl,\n      });\n    },\n    async deleteActiveFilter() {\n      this.$emit('filter-remove', this.activeFilter);\n    },\n  },\n};\n</script>\n\n<style>\n.control.has-inline-text {\n  line-height: 2.25em;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/applications/applications.spec.ts",
    "content": "import userEvent from '@testing-library/user-event';\nimport { screen, waitFor } from '@testing-library/vue';\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\nimport { Ref, ref } from 'vue';\n\nimport { useApplicationStore } from '@/composables/useApplicationStore';\nimport Application from '@/services/application';\nimport Instance, { Registration } from '@/services/instance';\nimport { render } from '@/test-utils';\nimport Applications from '@/views/applications/index.vue';\n\nvi.mock('@/composables/useApplicationStore', () => ({\n  useApplicationStore: vi.fn(),\n}));\n\ndescribe('Applications', () => {\n  let applicationsInitialized: Ref<boolean>;\n  let applications: Ref<Application[]>;\n  let error: Ref<any>;\n\n  beforeEach(async () => {\n    applicationsInitialized = ref(false);\n    applications = ref([]);\n    error = ref(null);\n\n    // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n    // @ts-ignore\n    useApplicationStore.mockReturnValue({\n      applicationStore: {\n        findApplicationByInstanceId: (id: string) => {\n          return applications.value.find((a) => {\n            return a.instances.some((i) => i.id === id);\n          });\n        },\n      },\n      applicationsInitialized,\n      applications,\n      error,\n    });\n\n    render(Applications);\n  });\n\n  it('when applications are loading, a hint should be shown', async () => {\n    expect(await screen.findByText('Loading applications...')).toBeVisible();\n  });\n\n  it('when there are no applications, a corresponding text is shown', async () => {\n    applicationsInitialized.value = true;\n\n    await waitFor(() => {\n      expect(screen.getByText('No applications registered.')).toBeVisible();\n    });\n  });\n\n  describe('when there are applications', () => {\n    beforeEach(async () => {\n      const instance = new Instance({\n        id: 'id',\n        statusInfo: {\n          status: 'UP',\n        },\n        registration: {\n          name: 'spring-boot-admin-sample-servlet',\n          serviceUrl: 'serviceUrl',\n          metadata: {},\n        } as Registration,\n      });\n\n      applicationsInitialized.value = true;\n      applications.value = [\n        new Application({\n          id: 'app-id',\n          name: 'spring-boot-admin-sample-servlet',\n          instances: [instance],\n          status: 'UP',\n        }),\n      ];\n    });\n\n    it('name of applications are shown', async () => {\n      await waitFor(() => {\n        expect(\n          screen.getByRole('button', {\n            name: /spring-boot-admin-sample-servlet/i,\n          }),\n        ).toBeVisible();\n      });\n    });\n\n    it('when the search does not match, a corresponding text is shown', async () => {\n      const filterInput = screen.getByLabelText('Filter');\n      await userEvent.type(filterInput, 'does-not-match');\n\n      expect(\n        await screen.findByText('No results matching your filter.'),\n      ).toBeVisible();\n    });\n\n    it('when the search matches, the application is shown', async () => {\n      const filterInput = screen.getByLabelText('Filter');\n      await userEvent.type(filterInput, 'sample');\n\n      await waitFor(() => {\n        expect(\n          screen.getByRole('button', {\n            name: /spring-boot-admin-sample-servlet/i,\n          }),\n        ).toBeVisible();\n      });\n    });\n\n    it('clicking on the name opens list of instances', async () => {\n      const applicationElement = await screen.findByRole('button', {\n        name: /spring-boot-admin-sample-servlet/i,\n      });\n\n      await userEvent.click(applicationElement);\n\n      expect(await screen.findByText('serviceUrl')).toBeVisible();\n    });\n\n    describe('application list', () => {\n      beforeEach(async () => {\n        applications.value = [\n          new Application({\n            id: 'app-id',\n            name: 'spring-boot-admin-sample-servlet-up',\n            instances: [\n              {\n                id: 'id',\n                statusInfo: {\n                  status: 'UP',\n                },\n                registration: {\n                  name: 'spring-boot-admin-sample-servlet-up',\n                  serviceUrl: 'serviceUrl',\n                  metadata: {},\n                } as Registration,\n              },\n            ],\n            status: 'UP',\n          }),\n          new Application({\n            id: 'app-id2',\n            name: 'spring-boot-admin-sample-servlet-down',\n            instances: [\n              {\n                id: 'id2',\n                statusInfo: {\n                  status: 'DOWN',\n                },\n                registration: {\n                  name: 'spring-boot-admin-sample-servlet-down',\n                  serviceUrl: 'serviceUrl',\n                  metadata: {},\n                } as Registration,\n              },\n            ],\n            status: 'DOWN',\n          }),\n          new Application({\n            id: 'app-id3',\n            name: 'spring-boot-admin-sample-servlet-up2',\n            instances: [\n              {\n                id: 'id5',\n                statusInfo: {\n                  status: 'UP',\n                },\n                registration: {\n                  name: 'spring-boot-admin-sample-servlet-up2',\n                  serviceUrl: 'serviceUrl',\n                  metadata: {},\n                } as Registration,\n              },\n            ],\n            status: 'UP',\n          }),\n          new Application({\n            id: 'app-id4',\n            name: 'spring-boot-admin-sample-servlet-restricted',\n            instances: [\n              {\n                id: 'id6',\n                statusInfo: {\n                  status: 'UP',\n                },\n                registration: {\n                  name: 'spring-boot-admin-sample-servlet-restricted',\n                  serviceUrl: 'serviceUrl',\n                  metadata: {},\n                } as Registration,\n              },\n              {\n                id: 'id7',\n                statusInfo: {\n                  status: 'DOWN',\n                },\n                registration: {\n                  name: 'spring-boot-admin-sample-servlet-restricted',\n                  serviceUrl: 'serviceUrl',\n                  metadata: {},\n                } as Registration,\n              },\n            ],\n            status: 'RESTRICTED',\n          }),\n        ];\n      });\n\n      it('should show applications ordered by status DOWN, RESTRICTED and UP', () => {\n        const allByRole = screen.getAllByRole('button');\n\n        const getIndex = (status: string) =>\n          allByRole.findIndex((element: HTMLElement) =>\n            element.textContent.startsWith(\n              `${status}spring-boot-admin-sample-servlet`,\n            ),\n          );\n\n        const indexDown = getIndex('DOWN');\n        const indexRestricted = getIndex('RESTRICTED');\n        const indexUp = getIndex('UP');\n\n        expect(indexDown).toBeGreaterThan(-1);\n        expect(indexDown).toBeLessThan(indexRestricted);\n        expect(indexRestricted).toBeLessThan(indexUp);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/applications/handle.vue",
    "content": "<!--\n  - Copyright 2014-2019 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <span>\n    <span v-if=\"downCount > 0\" class=\"mr-2\">\n      <font-awesome-icon icon=\"exclamation-triangle\" />\n    </span>\n    <span\n      :class=\"{ 'has-badge has-badge-rounded has-badge-danger': downCount > 0 }\"\n      :data-badge=\"downCount > 0 ? downCount : undefined\"\n      v-text=\"$t('applications.label')\"\n    />\n  </span>\n</template>\n\n<script lang=\"ts\" setup>\nimport { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';\nimport { computed, watch } from 'vue';\n\nimport { useApplicationStore } from '@/composables/useApplicationStore';\nimport sbaConfig from '@/sba-config';\n\nconst favicon = sbaConfig.uiSettings.favicon;\nconst faviconDanger = sbaConfig.uiSettings.faviconDanger;\n\nconst { applications } = useApplicationStore();\nconst downCount = computed(() => {\n  return applications.value.reduce((current, next) => {\n    return (\n      current +\n      next.instances.filter((instance) => instance.statusInfo.status !== 'UP')\n        .length\n    );\n  }, 0);\n});\n\nwatch(downCount, (newVal: number) => {\n  updateFavicon(newVal === 0);\n});\n\nconst updateFavicon = (up) =>\n  ((document.querySelector('link[rel*=\"icon\"]') as HTMLLinkElement).href = up\n    ? favicon\n    : faviconDanger);\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/applications/i18n.de.json",
    "content": "{\n  \"applications\": {\n    \"actions\": {\n      \"journal\": \"Journal\",\n      \"notification_filters\": \"Benachrichtigungsfilter\",\n      \"refresh_applications\": \"Anwendungen aktualisieren\",\n      \"restart\": \"Neu starten\",\n      \"shutdown\": \"Herunterfahren\",\n      \"switch_to_grouping_by_application\": \"Aggregiere Instanzen zu Applikationen (gleiche Namen)\",\n      \"switch_to_grouping_by_group\": \"Gruppiere Instanzen anhand des Gruppennamens\",\n      \"unregister\": \"Deregistrieren\"\n    },\n    \"all_up\": \"Alle verfügbar\",\n    \"all_down\": \"Alle Instanzen sind ausgefallen\",\n    \"all_unknown\": \"Alle Instanzen haben unbekannten Status\",\n    \"some_unknown\": \"Einige Instanzen haben unbekannten Status\",\n    \"some_down\": \"Einige Instanzen sind ausgefallen\",\n    \"applications\": \"Anwendungen\",\n    \"fetching_notification_filters_failed\": \"Der Abruf der Benachrichtigungseinstellungen ist fehlgeschlagen.\",\n    \"notification_filter\": {\n      \"none\": \"Keine Benachrichtigungseinstellungen gesetzt.\",\n      \"removed\": \"Benachrichtigungseinstellungen wurden geändert.\"\n    },\n    \"instances\": \"Instanzen\",\n    \"loading_applications\": \"Anwendungen werden geladen...\",\n    \"no_applications_registered\": \"Keine Anwendungen registriert.\",\n    \"notifications_suppressed_for\": \"Benachrichtigungen für <code>{name}</code> werden unterdrückt für\",\n    \"restricted\": \"eingeschränkt\",\n    \"server_connection_failed\": \"Verbindung zum Server fehlgeschlagen.\",\n    \"suppress_notifications_on\": \"Benachrichtungen für <code>{name}</code> für\",\n    \"status\": \"Status\",\n    \"label\": \"Anwendungen\",\n    \"shutdown\": \"Möchten Sie die Anwendung <code>{name}</code> herunterfahren?\",\n    \"refreshed\": \"Anwendungen wurden aktualisiert.\",\n    \"restart\": \"Möchten Sie Anwendung <code>{name}</code> neu starten?\",\n    \"restarted\": \"Die Anwendung wurde neu gestartet.\",\n    \"unregister\": \"Möchten Sie die Anwendung <code>{name}</code> deregistrieren?\",\n    \"unregister_successful\": \"Die Anwendung {name} wurde erfolgreich deregistriert.\",\n    \"unregister_failed\": \"Deregistration der Anwendung <code>{name}</code> fehlgeschlagen ({error}).\"\n  },\n  \"instances\": {\n    \"shutdown\": \"Möchten Sie die Instanz <code>{name}</code> herunterfahren?\",\n    \"restart\": \"Möchten Sie die Instanz <code>{name}</code> neu starten?\",\n    \"restarted\": \"Die Instanz {name} wurde neu gestartet.\",\n    \"unregister\": \"Möchten Sie die Instanz <code>{name}</code> deregistrieren?\",\n    \"unregister_successful\": \"Die Instanz {name} wurde erfolgreich deregistriert.\",\n    \"unregister_failed\": \"Deregistration der Instanz <code>{name}</code> fehlgeschlagen ({error}).\"\n  },\n  \"notification_filter_center\": {\n    \"description\": \"Benachrichtigungen für folgende Applikationen / Instanzen werden unterdrückt.\"\n  },\n  \"filter\": {\n    \"no_results\": \"Keine Ergebnisse für den eingestellten Filter.\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/applications/i18n.en.json",
    "content": "{\n  \"applications\": {\n    \"actions\": {\n      \"journal\": \"Journal\",\n      \"notification_filters\": \"Notification filters\",\n      \"refresh_applications\": \"Refresh applications\",\n      \"restart\": \"Restart\",\n      \"shutdown\": \"Shutdown\",\n      \"switch_to_grouping_by_application\": \"Aggregate same instances as application\",\n      \"switch_to_grouping_by_group\": \"Group same instance by same group name\",\n      \"unregister\": \"Unregister\"\n    },\n    \"all_up\": \"all up\",\n    \"all_down\": \"all down\",\n    \"all_unknown\": \"all in unknown state\",\n    \"some_unknown\": \"some instances are in unknown state\",\n    \"some_down\": \"some instances are down\",\n    \"applications\": \"Applications\",\n    \"fetching_notification_filters_failed\": \"Fetching notification filters failed.\",\n    \"notification_filter\": {\n      \"none\": \"No notification filters set.\",\n      \"removed\": \"Notification filter removed.\"\n    },\n    \"instances\": \"Instances\",\n    \"loading_applications\": \"Loading applications...\",\n    \"no_applications_registered\": \"No applications registered.\",\n    \"notifications_suppressed_for\": \"Notifications on <code>{name}</code> are suppressed for\",\n    \"restricted\": \"restricted\",\n    \"server_connection_failed\": \"Server connection failed.\",\n    \"suppress_notifications_on\": \"Suppress notifications on <code>{name}</code> for\",\n    \"status\": \"Status\",\n    \"label\": \"Applications\",\n    \"up\": \"up\",\n    \"down\": \"down\",\n    \"offline\": \"offline\",\n    \"shutdown\": \"Shutdown application <code>{name}</code>?\",\n    \"shutdown_successful\": \"Successfully shutdown application {name}.\",\n    \"refreshed\": \"Applications refreshed.\",\n    \"restart\": \"Restart application <code>{name}</code>?\",\n    \"restarted\": \"Successfully restarted application <code>{name}</code>.\",\n    \"unregister\": \"Deregister application <code>{name}</code>?\",\n    \"unregister_successful\": \"Successfully deregistered application <code>{name}</code>.\",\n    \"unregister_failed\": \"Deregistration of application <code>{name}</code> failed ({error}).\"\n  },\n  \"instances\": {\n    \"shutdown\": \"Shutdown instance <code>{name}</code>?\",\n    \"shutdown_successful\": \"Successfully shutdown instances {name}.\",\n    \"shutdown_failed\": \"Failed to shutdown instances {name}.\",\n    \"restart\": \"Restart instance <code>{name}</code>?\",\n    \"restarted\": \"Successfully restarted instance {name}.\",\n    \"unregister\": \"Deregister instance <code>{name}</code>?\",\n    \"unregister_successful\": \"Successfully deregistered instance <code>{name}</code>.\",\n    \"unregister_failed\": \"Deregistration of instance <code>{name}</code> failed ({error}).\"\n  },\n  \"notification_filter_center\": {\n    \"description\": \"Notifications on following applications or instances will be suppressed.\"\n  },\n  \"filter\": {\n    \"no_results\": \"No results matching your filter.\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/applications/i18n.es.json",
    "content": "{\n  \"applications\": {\n    \"all_up\": \"Todo arriba\",\n    \"applications\": \"Aplicaciones\",\n    \"instances\": \"Instancias\",\n    \"some_down\": \"instancias caídas\",\n    \"loading_applications\": \"Cargando aplicaciones...\",\n    \"no_applications_registered\": \"No hay aplicaciones registradas.\",\n    \"notifications_suppressed_for\": \"Notificaciones para <code>{name}</code> estás suspendidas por\",\n    \"restricted\": \"restringido\",\n    \"server_connection_failed\": \"Conexión al servidor fallida.\",\n    \"suppress_notifications_on\": \"Suspender notificaciones para <code>{name}</code> por\",\n    \"status\": \"Estado\",\n    \"label\": \"Aplicaciones\",\n    \"up\": \"Arriba\",\n    \"shutdown\": \"Detener aplicación <code>{name}</code>?\",\n    \"restart\": \"Reiniciar aplicación <code>{name}</code>?\",\n    \"restarted\": \"Aplicación <code>{name}</code> reiniciada exitosamente\"\n  },\n  \"instances\": {\n    \"shutdown\": \"Detener instancia <code>{name}</code>?\",\n    \"restart\": \"Reinciar instancia <code>{name}</code>?\",\n    \"restarted\": \"Instancia reiniciada exitosamente\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/applications/i18n.fr.json",
    "content": "{\n  \"applications\": {\n    \"all_up\": \"tout est disponible\",\n    \"applications\": \"Applications\",\n    \"instances\": \"Instances\",\n    \"some_down\": \"instances inaccessibles\",\n    \"loading_applications\": \"Chargement des applications...\",\n    \"no_applications_registered\": \"Aucune application enregistrée.\",\n    \"notifications_suppressed_for\": \"Les notifications sur <code>{name}</code> sont supprimées\",\n    \"restricted\": \"limité\",\n    \"server_connection_failed\": \"Echec de la connexion au serveur.\",\n    \"suppress_notifications_on\": \"Supression des notifications sur <code>{name}</code>\",\n    \"status\": \"Statut\",\n    \"label\": \"Applications\",\n    \"up\": \"OK\",\n    \"shutdown\": \"Shutdown application <code>{name}</code>?\",\n    \"restart\": \"Restart application <code>{name}</code>?\",\n    \"restarted\": \"Successfully restarted application <code>{name}</code>\"\n  },\n  \"instances\": {\n    \"shutdown\": \"Shutdown instance <code>{name}</code>?\",\n    \"restart\": \"Restart instance <code>{name}</code>?\",\n    \"restarted\": \"Successfully restarted instance\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/applications/i18n.is.json",
    "content": "{\n  \"applications\": {\n    \"all_up\": \"Öll fáanleg\",\n    \"applications\": \"Forrit\",\n    \"instances\": \"Eintök\",\n    \"some_down\": \"Eintök ekki fáanleg\",\n    \"loading_applications\": \"Forrit eru að ræsa…\",\n    \"no_applications_registered\": \"Engin forrit skráð.\",\n    \"notifications_suppressed_for\": \"Tilkynningar fyrir <code>{name}</code> eru hunsuð fyrir\",\n    \"restricted\": \"skert\",\n    \"server_connection_failed\": \"Mistókst sambandið við þjón.\",\n    \"suppress_notifications_on\": \"Tilkynningar fyrir <code>{name}</code> fyrir\",\n    \"status\": \"Staða\",\n    \"label\": \"Forrit\",\n    \"up\": \"OK\",\n    \"shutdown\": \"Shutdown application <code>{name}</code>?\",\n    \"restart\": \"Restart application <code>{name}</code>?\",\n    \"restarted\": \"Successfully restarted application <code>{name}</code>\"\n  },\n  \"instances\": {\n    \"shutdown\": \"Shutdown instance <code>{name}</code>?\",\n    \"restart\": \"Restart instance <code>{name}</code>?\",\n    \"restarted\": \"Successfully restarted instance\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/applications/i18n.ko.json",
    "content": "{\n  \"applications\": {\n    \"actions\": {\n      \"journal\": \"일지\",\n      \"notification_filters\": \"알림 필터\",\n      \"unregister\": \"등록 해제\",\n      \"shutdown\": \"종료\",\n      \"restart\": \"재시작\"\n    },\n    \"all_up\": \"전체 인스턴스 가동중\",\n    \"all_down\": \"전체 인스턴스 중단됨\",\n    \"all_unknown\": \"전체 인스턴스 상태 불명\",\n    \"some_unknown\": \"일부 인스턴스 상태 불명\",\n    \"some_down\": \"일부 인스턴스 중단됨\",\n    \"applications\": \"애플리케이션\",\n    \"fetching_notification_filters_failed\": \"알림 필터를 가져오지 못하였습니다.\",\n    \"notification_filter\": {\n      \"none\": \"설정된 알림 필터가 없습니다.\",\n      \"removed\": \"알림 필터가 제거되었습니다.\"\n    },\n    \"instances\": \"인스턴스\",\n    \"loading_applications\": \"애플리케이션 불러오는 중...\",\n    \"no_applications_registered\": \"등록된 애플리케이션이 없습니다.\",\n    \"notifications_suppressed_for\": \"<code>{name}</code>에 대한 알림이 억제됩니다.\",\n    \"restricted\": \"제한됨\",\n    \"server_connection_failed\": \"서버 연결에 실패하였습니다.\",\n    \"suppress_notifications_on\": \"<code>{name}</code>에 대한 알림\",\n    \"status\": \"상태\",\n    \"label\": \"애플리케이션\",\n    \"up\": \"가동\",\n    \"down\": \"중단\",\n    \"offline\": \"오프라인\",\n    \"shutdown\": \"<code>{name}</code> 애플리케이션을 종료할까요?\",\n    \"shutdown_successful\": \"{name} 애플리케이션을 종료하였습니다.\",\n    \"refreshed\": \"애플리케이션 새로고침됨\",\n    \"restart\": \"<code>{name}</code> 애플리케이션을 재시작할까요?\",\n    \"restarted\": \"<code>{name}</code> 애플리케이션을 재시작하였습니다.\",\n    \"unregister\": \"<<code>{name}</code> 애플리케이션 등록을 해제할까요?\",\n    \"unregister_successful\": \"<code>{name}</code> 애플리케이션 등록을 해제하였습니다.\",\n    \"unregister_failed\": \"<code>{name}</code> 애플리케이션 등록을 해제하지 못하였습니다. ({error})\"\n  },\n  \"instances\": {\n    \"shutdown\": \"<code>{name}</code> 인스턴스를 종료할까요?\",\n    \"shutdown_successful\": \"{name} 인스턴스를 종료하였습니다.\",\n    \"shutdown_failed\": \"{name} 인스턴스를 종료하지 못하였습니다.\",\n    \"restart\": \"<code>{name}</code> 인스턴스를 재시작할까요?\",\n    \"restarted\": \"{name} 인스턴스를 재시작하였습니다.\",\n    \"unregister\": \"<code>{name}</code> 인스턴스 등록을 해제할까요?\",\n    \"unregister_successful\": \"<code>{name}</code> 인스턴스 등록을 해제하였습니다.\",\n    \"unregister_failed\": \"<code>{name}</code> 인스턴스 등록을 해제하지 못하였습니다. ({error})\"\n  },\n  \"notification_filter_center\": {\n    \"description\": \"다음 애플리케이션 또는 인스턴스에 대한 알림이 억제됩니다.\"\n  },\n  \"filter\": {\n    \"no_results\": \"필터에 일치하는 결과가 없습니다.\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/applications/i18n.pt-BR.json",
    "content": "{\n  \"applications\": {\n    \"all_up\": \"Todas up\",\n    \"applications\": \"Aplicações\",\n    \"instances\": \"Instâncias\",\n    \"some_down\": \"Instâncias down\",\n    \"loading_applications\": \"Carregando aplicações...\",\n    \"no_applications_registered\": \"Nenhuma aplicação registrada.\",\n    \"notifications_suppressed_for\": \"As notificações de <code>{name}</code> estão suspensas por\",\n    \"restricted\": \"restrito\",\n    \"server_connection_failed\": \"Falha na conexão do servidor.\",\n    \"suppress_notifications_on\": \"Suspenda notificações de <code>{name}</code> por\",\n    \"status\": \"Estado\",\n    \"label\": \"Aplicações\",\n    \"up\": \"up\",\n    \"shutdown\": \"Shutdown application <code>{name}</code>?\",\n    \"restart\": \"Restart application <code>{name}</code>?\",\n    \"restarted\": \"Successfully restarted application <code>{name}</code>\"\n  },\n  \"instances\": {\n    \"shutdown\": \"Shutdown instance <code>{name}</code>?\",\n    \"restart\": \"Restart instance <code>{name}</code>?\",\n    \"restarted\": \"Successfully restarted instance\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/applications/i18n.ru.json",
    "content": "{\n  \"applications\": {\n    \"actions\": {\n      \"journal\": \"Журнал\",\n      \"notification_filters\": \"Фильтры уведомлений\",\n      \"unregister\": \"Удалить регистрацию\",\n      \"shutdown\": \"Остановить\",\n      \"restart\": \"Перезапустить\"\n    },\n    \"all_up\": \"все доступны\",\n    \"all_down\": \"все недоступны\",\n    \"all_unknown\": \"все в неизвестном состоянии\",\n    \"some_unknown\": \"некоторые экземпляры в неизвестном состоянии\",\n    \"some_down\": \"экземпляры недоступны\",\n    \"applications\": \"Приложения\",\n    \"fetching_notification_filters_failed\": \"Ошибка загрузки фильтров уведомлений.\",\n    \"notification_filter\": {\n      \"none\": \"Фильтры уведомлений не установлены.\",\n      \"removed\": \"Фильтр уведомлений удалён.\"\n    },\n    \"instances\": \"Экземпляры\",\n    \"loading_applications\": \"Загрузка приложений...\",\n    \"no_applications_registered\": \"Нет зарегистрированных приложений.\",\n    \"notifications_suppressed_for\": \"Уведомления для <code>{name}</code> отключены на\",\n    \"restricted\": \"ограничен\",\n    \"server_connection_failed\": \"Ошибка при подключении к серверу.\",\n    \"suppress_notifications_on\": \"Скрыть уведомления для <code>{name}</code> на\",\n    \"status\": \"Статус\",\n    \"label\": \"Приложения\",\n    \"up\": \"ОК\",\n    \"down\": \"недоступен\",\n    \"offline\": \"оффлайн\",\n    \"shutdown\": \"Остановить приложение <code>{name}</code>?\",\n    \"shutdown_successful\": \"Приложение {name} успешно остановлено.\",\n    \"refreshed\": \"Приложения обновлены.\",\n    \"restart\": \"Перезапустить приложение <code>{name}</code>?\",\n    \"restarted\": \"Приложение <code>{name}</code> успешно перезапущено.\",\n    \"unregister\": \"Удалить регистрацию приложения <code>{name}</code>?\",\n    \"unregister_successful\": \"Регистрация приложения <code>{name}</code> успешно удалена.\",\n    \"unregister_failed\": \"Ошибка удаления регистрации приложения <code>{name}</code> ({error}).\"\n  },\n  \"instances\": {\n    \"shutdown\": \"Остановить экземпляр <code>{name}</code>?\",\n    \"shutdown_successful\": \"Экземпляр {name} успешно остановлен.\",\n    \"shutdown_failed\": \"Ошибка остановки экземпляра {name}.\",\n    \"restart\": \"Перезапустить экземпляр <code>{name}</code>?\",\n    \"restarted\": \"Экземпляр {name} успешно перезапущен.\",\n    \"unregister\": \"Удалить регистрацию экземпляра <code>{name}</code>?\",\n    \"unregister_successful\": \"Регистрация экземпляра <code>{name}</code> успешно удалена.\",\n    \"unregister_failed\": \"Ошибка удаления регистрации экземпляра <code>{name}</code> ({error}).\"\n  },\n  \"notification_filter_center\": {\n    \"description\": \"Уведомления для следующих приложений или экземпляров будут отключены.\"\n  },\n  \"filter\": {\n    \"no_results\": \"Нет результатов, соответствующих вашему фильтру.\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/applications/i18n.zh-CN.json",
    "content": "{\n  \"applications\": {\n    \"all_up\": \"全部在线\",\n    \"applications\": \"应用数\",\n    \"instances\": \"实例数\",\n    \"some_down\": \"离线实例\",\n    \"loading_applications\": \"应用加载中...\",\n    \"no_applications_registered\": \"暂无应用注册。\",\n    \"notifications_suppressed_for\": \"Notifications on <code>{name}</code> are suppressed for\",\n    \"restricted\": \"受限的\",\n    \"server_connection_failed\": \"服务连接失败。\",\n    \"suppress_notifications_on\": \"Suppress notifications on <code>{name}</code> for\",\n    \"status\": \"实例状态\",\n    \"label\": \"应用\",\n    \"up\": \"在线\",\n    \"shutdown\": \"Shutdown application <code>{name}</code>?\",\n    \"restart\": \"Restart application <code>{name}</code>?\",\n    \"restarted\": \"Successfully restarted application <code>{name}</code>\"\n  },\n  \"instances\": {\n    \"shutdown\": \"Shutdown instance <code>{name}</code>?\",\n    \"restart\": \"Restart instance <code>{name}</code>?\",\n    \"restarted\": \"Successfully restarted instance\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/applications/i18n.zh-TW.json",
    "content": "{\n  \"applications\": {\n    \"actions\": {\n      \"journal\": \"日誌\",\n      \"notification_filters\": \"通知篩選器\",\n      \"unregister\": \"取消註冊\",\n      \"shutdown\": \"關閉\",\n      \"restart\": \"重新啟動\"\n    },\n    \"all_up\": \"全部正常\",\n    \"all_down\": \"全部停止\",\n    \"all_unknown\": \"全部狀態未知\",\n    \"some_unknown\": \"部分執行個體狀態未知\",\n    \"some_down\": \"部分執行個體已停止\",\n    \"applications\": \"應用程式\",\n    \"fetching_notification_filters_failed\": \"取得通知篩選器失敗。\",\n    \"notification_filter\": {\n      \"none\": \"未設定通知篩選器。\",\n      \"removed\": \"已移除通知篩選器。\"\n    },\n    \"instances\": \"執行個體\",\n    \"loading_applications\": \"正在載入應用程式...\",\n    \"no_applications_registered\": \"目前沒有已註冊的應用程式。\",\n    \"notifications_suppressed_for\": \"<code>{name}</code> 的通知已隱藏，期間為\",\n    \"restricted\": \"受限\",\n    \"server_connection_failed\": \"伺服器連線失敗。\",\n    \"suppress_notifications_on\": \"隱藏 <code>{name}</code> 的通知，期間為\",\n    \"status\": \"狀態\",\n    \"label\": \"應用程式\",\n    \"up\": \"正常\",\n    \"down\": \"停止\",\n    \"offline\": \"離線\",\n    \"shutdown\": \"確定要關閉應用程式 <code>{name}</code>？\",\n    \"shutdown_successful\": \"應用程式 {name} 已成功關閉。\",\n    \"refreshed\": \"應用程式已重新整理。\",\n    \"restart\": \"確定要重新啟動應用程式 <code>{name}</code>？\",\n    \"restarted\": \"應用程式 <code>{name}</code> 已成功重新啟動。\",\n    \"unregister\": \"確定要取消註冊應用程式 <code>{name}</code>？\",\n    \"unregister_successful\": \"應用程式 <code>{name}</code> 已成功取消註冊。\",\n    \"unregister_failed\": \"取消註冊應用程式 <code>{name}</code> 失敗 ({error})。\"\n  },\n  \"instances\": {\n    \"shutdown\": \"確定要關閉執行個體 <code>{name}</code>？\",\n    \"shutdown_successful\": \"執行個體 {name} 已成功關閉。\",\n    \"shutdown_failed\": \"關閉執行個體 {name} 失敗。\",\n    \"restart\": \"確定要重新啟動執行個體 <code>{name}</code>？\",\n    \"restarted\": \"執行個體 {name} 已成功重新啟動。\",\n    \"unregister\": \"確定要取消註冊執行個體 <code>{name}</code>？\",\n    \"unregister_successful\": \"執行個體 <code>{name}</code> 已成功取消註冊。\",\n    \"unregister_failed\": \"取消註冊執行個體 <code>{name}</code> 失敗 ({error})。\"\n  },\n  \"notification_filter_center\": {\n    \"description\": \"以下應用程式或執行個體的通知將被隱藏。\"\n  },\n  \"filter\": {\n    \"no_results\": \"沒有符合篩選條件的結果。\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/applications/index.vue",
    "content": "<!--\n  - Copyright 2014-2024 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div>\n    <sba-wave />\n    <section>\n      <sba-sticky-subnav>\n        <div class=\"container mx-auto flex\">\n          <ApplicationStats />\n          <sba-confirm-button\n            class=\"mr-1\"\n            :title=\"$t('applications.actions.refresh_applications')\"\n            @click=\"refreshContext\"\n          >\n            <font-awesome-icon :icon=\"'rotate-left'\" />\n          </sba-confirm-button>\n          <template v-if=\"groupNames.length > 1\">\n            <sba-button-group class=\"mr-1\">\n              <sba-button\n                :title=\"\n                  $t('applications.actions.switch_to_grouping_by_application')\n                \"\n                :disabled=\"isGroupingFunctionActive('application')\"\n                @click=\"() => setGroupingFunction('application')\"\n              >\n                <font-awesome-icon icon=\"list\" />\n              </sba-button>\n              <sba-button\n                :title=\"$t('applications.actions.switch_to_grouping_by_group')\"\n                :disabled=\"isGroupingFunctionActive('group')\"\n                @click=\"() => setGroupingFunction('group')\"\n              >\n                <font-awesome-icon icon=\"expand\" />\n              </sba-button>\n            </sba-button-group>\n          </template>\n          <ApplicationNotificationCenter\n            v-if=\"hasNotificationFiltersSupport\"\n            :notification-filters=\"notificationFilters\"\n            @filter-remove=\"removeFilter\"\n          />\n          <div class=\"flex-1\">\n            <sba-input\n              v-model=\"routerState.q\"\n              :placeholder=\"t('term.filter')\"\n              name=\"filter\"\n              type=\"search\"\n            >\n              <template #prepend>\n                <font-awesome-icon icon=\"filter\" />\n              </template>\n            </sba-input>\n          </div>\n        </div>\n      </sba-sticky-subnav>\n\n      <div class=\"container mx-auto py-6\">\n        <sba-alert\n          v-if=\"error\"\n          :error=\"error\"\n          :title=\"t('applications.server_connection_failed')\"\n          class-names=\"mb-6\"\n          severity=\"WARN\"\n        />\n        <sba-panel v-if=\"!applicationsInitialized\">\n          <p\n            class=\"is-muted is-loading\"\n            v-text=\"t('applications.loading_applications')\"\n          />\n        </sba-panel>\n\n        <ApplicationStatusHero v-if=\"applicationsInitialized\" />\n\n        <template v-if=\"applicationsInitialized\">\n          <sba-panel\n            v-if=\"hasActiveFilter && grouped.length === 0\"\n            class=\"text-center\"\n          >\n            {{ t('filter.no_results') }}\n          </sba-panel>\n\n          <template v-else>\n            <sba-panel\n              v-for=\"group in grouped\"\n              :id=\"group.name\"\n              :key=\"group.name\"\n              v-on-clickaway=\"(event: Event) => deselect(event, group.name)\"\n              :seamless=\"true\"\n              class=\"application-group\"\n              :aria-expanded=\"isExpanded(group.name)\"\n              @title-click=\"\n                () => {\n                  select(group.name);\n                  toggleGroup(group.name);\n                }\n              \"\n            >\n              <template #title>\n                <div class=\"items-center inline-flex flex-row min-w-[29rem]\">\n                  <font-awesome-icon\n                    icon=\"chevron-down\"\n                    :class=\"{\n                      '-rotate-90': !isExpanded(group.name),\n                      'mr-2 transition-[transform]': true,\n                    }\"\n                  />\n                  <sba-status-badge\n                    v-if=\"isGroupedByApplication\"\n                    class=\"mr-2\"\n                    :status=\"\n                      applicationStore.findApplicationByInstanceId(\n                        group.instances[0].id,\n                      )?.status\n                    \"\n                  />\n\n                  <span v-text=\"t(group.name)\" />\n                  <span\n                    class=\"ml-2 text-sm text-gray-500 self-end\"\n                    v-text=\"\n                      t('term.instances_tc', {\n                        count: group.instances?.length ?? 0,\n                      })\n                    \"\n                  />\n                </div>\n\n                <span\n                  class=\"hidden lg:inline ml-4\"\n                  v-text=\"group.instances[0].buildVersion\"\n                />\n              </template>\n\n              <template v-if=\"isGroupedByApplication\" #actions>\n                <ApplicationListItemAction\n                  :has-notification-filters-support=\"\n                    hasNotificationFiltersSupport\n                  \"\n                  :item=\"\n                    applicationStore.findApplicationByInstanceId(\n                      group.instances[0].id,\n                    )\n                  \"\n                  @filter-settings=\"toggleNotificationFilterSettings\"\n                />\n              </template>\n\n              <template v-if=\"isExpanded(group.name)\" #default>\n                <InstancesList :instances=\"group.instances\">\n                  <template #actions=\"{ instance }\">\n                    <ApplicationListItemAction\n                      :has-notification-filters-support=\"\n                        hasNotificationFiltersSupport\n                      \"\n                      :item=\"instance\"\n                      class=\"md:hidden\"\n                      @filter-settings=\"toggleNotificationFilterSettings\"\n                    />\n                  </template>\n                </InstancesList>\n              </template>\n            </sba-panel>\n          </template>\n\n          <NotificationFilterSettings\n            v-if=\"showNotificationFilterSettingsObject\"\n            v-on-clickaway=\"() => toggleNotificationFilterSettings(null)\"\n            v-popper=\"\n              `nf-settings-${\n                showNotificationFilterSettingsObject.id ||\n                showNotificationFilterSettingsObject.name\n              }`\n            \"\n            :notification-filters=\"notificationFilters\"\n            :object=\"showNotificationFilterSettingsObject\"\n            @filter-add=\"addFilter\"\n            @filter-remove=\"removeFilter\"\n          />\n        </template>\n      </div>\n    </section>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';\nimport { useNotificationCenter } from '@stekoe/vue-toast-notificationcenter';\nimport { groupBy, sortBy, transform } from 'lodash-es';\nimport { computed, nextTick, ref } from 'vue';\nimport { useI18n } from 'vue-i18n';\nimport { useRoute, useRouter } from 'vue-router';\n\nimport SbaButton from '@/components/sba-button.vue';\nimport SbaConfirmButton from '@/components/sba-confirm-button.vue';\nimport SbaStickySubnav from '@/components/sba-sticky-subnav.vue';\nimport SbaWave from '@/components/sba-wave.vue';\n\nimport { useApplicationStore } from '@/composables/useApplicationStore';\nimport Application from '@/services/application';\nimport Instance from '@/services/instance';\nimport NotificationFilter from '@/services/notification-filter';\nimport axios from '@/utils/axios';\nimport { anyValueMatches } from '@/utils/collections';\nimport { Subject, concatMap, mergeWith, timer } from '@/utils/rxjs';\nimport { useRouterState } from '@/utils/useRouterState';\nimport { useSubscription } from '@/utils/useSubscription';\nimport ApplicationListItemAction from '@/views/applications/ApplicationListItemAction.vue';\nimport ApplicationNotificationCenter from '@/views/applications/ApplicationNotificationCenter.vue';\nimport ApplicationStats from '@/views/applications/ApplicationStats.vue';\nimport ApplicationStatusHero from '@/views/applications/ApplicationStatusHero.vue';\nimport InstancesList from '@/views/applications/InstancesList.vue';\nimport NotificationFilterSettings from '@/views/applications/NotificationFilterSettings.vue';\n\nconst props = defineProps({\n  error: {\n    type: Error,\n    default: null,\n  },\n  selected: {\n    type: String,\n    default: null,\n  },\n});\n\nconst instanceMatchesFilter = (term: string, instance: Instance) => {\n  const predicate = (value: string | number) =>\n    String(value).toLowerCase().includes(term);\n\n  return (\n    anyValueMatches(instance.registration, predicate) ||\n    anyValueMatches(instance.buildVersion, predicate) ||\n    anyValueMatches(instance.id, predicate) ||\n    anyValueMatches(instance.tags, predicate)\n  );\n};\n\ntype NotificationFilterSettingsObject = { id: string; name: string };\n\ntype InstancesListType = {\n  name?: string;\n  statusKey?: string;\n  status?: string;\n  instances?: Instance[];\n  applications?: Application[];\n};\n\nconst groupingFunctions = {\n  application: (instance: Instance) => instance.registration.name,\n  group: (instance: Instance) =>\n    instance.registration.metadata?.['group'] ?? 'term.no_group',\n};\n\nconst { t } = useI18n();\nconst router = useRouter();\nconst route = useRoute();\nconst { applications, applicationsInitialized, applicationStore } =\n  useApplicationStore();\nconst notificationCenter = useNotificationCenter({});\nconst expandedGroups = ref([props.selected]);\nconst groupingFunction = ref(groupingFunctions.application);\n\nconst routerState = useRouterState({\n  q: '',\n});\nconst hasActiveFilter = computed(() => {\n  return routerState.q?.length > 0;\n});\nconst notificationFilterSubject = new Subject();\nconst hasNotificationFiltersSupport = NotificationFilter.isSupported();\nconst notificationFilters = ref([]);\n\nuseSubscription(\n  timer(0, 60000)\n    .pipe(\n      mergeWith(notificationFilterSubject),\n      concatMap(fetchNotificationFilters),\n    )\n    .subscribe({\n      next: (data) => {\n        notificationFilters.value = data;\n      },\n      error: (error) => {\n        console.warn('Fetching notification filters failed with error:', error);\n        notificationCenter.error(\n          t('applications.fetching_notification_filters_failed'),\n        );\n      },\n    }),\n);\n\nasync function fetchNotificationFilters() {\n  if (hasNotificationFiltersSupport) {\n    const response = await NotificationFilter.getFilters();\n    return response.data;\n  }\n  return [];\n}\n\nconst groupNames = computed(() => {\n  return [\n    ...new Set(\n      applications.value\n        .flatMap((application: Application) => application.instances)\n        .map(\n          (instance: Instance) =>\n            instance.registration.metadata?.['group'] ?? 'Ungrouped',\n        ),\n    ),\n  ];\n});\n\nconst grouped = computed(() => {\n  const filteredApplications = filterInstances(applications.value);\n\n  const instances = filteredApplications.flatMap(\n    (application: Application) => application.instances,\n  );\n\n  const grouped = groupBy<Instance>(instances, groupingFunction.value);\n\n  const list = transform<Instance[], InstancesListType[]>(\n    grouped,\n    (result, instances, name) => {\n      result.push({\n        name,\n        instances: sortBy(instances, [\n          (instance) => instance.registration.name,\n        ]),\n      });\n    },\n    [],\n  );\n\n  return sortBy(list, [(item) => getApplicationStatus(item)]);\n});\n\nconst refreshContext = () => {\n  axios.post('/applications').then(() => {\n    notificationCenter.success(t('applications.refreshed'));\n  });\n};\n\nfunction getApplicationStatus(item: InstancesListType): string {\n  return applicationStore.findApplicationByInstanceId(item.instances[0].id)\n    ?.status;\n}\n\nfunction filterInstances(applications: Application[]) {\n  if (!routerState.q) {\n    return applications;\n  }\n\n  return applications\n    .map((application) =>\n      application.filterInstances((i) =>\n        instanceMatchesFilter(routerState.q.toLowerCase(), i),\n      ),\n    )\n    .filter((application) => application.instances.length > 0);\n}\n\nconst isGroupedByApplication = computed(() => {\n  return groupingFunction.value === groupingFunctions.application;\n});\n\nif (props.selected) {\n  scrollIntoView(props.selected);\n}\n\nasync function scrollIntoView(id) {\n  if (id) {\n    await nextTick();\n    const el = document.getElementById(id);\n    if (el) {\n      el.scrollIntoView({\n        behavior: 'smooth',\n        block: 'end',\n        inline: 'nearest',\n      });\n    }\n  }\n}\n\nconst showNotificationFilterSettingsObject = ref(\n  null as unknown as NotificationFilterSettingsObject,\n);\nconst setGroupingFunction = (key: keyof typeof groupingFunctions) => {\n  groupingFunction.value = groupingFunctions[key];\n  expandedGroups.value = [];\n};\n\nconst isGroupingFunctionActive = (key: keyof typeof groupingFunctions) => {\n  return groupingFunction.value === groupingFunctions[key];\n};\n\nfunction isExpanded(name: string) {\n  return expandedGroups.value.includes(name);\n}\n\nfunction toggleGroup(name: string) {\n  if (expandedGroups.value.includes(name)) {\n    expandedGroups.value = expandedGroups.value.filter((n) => n !== name);\n  } else {\n    expandedGroups.value = [...expandedGroups.value, name];\n  }\n}\n\nfunction select(name: string) {\n  router.push({\n    name: 'applications',\n    params: { selected: name },\n    query: { ...route.query },\n  });\n}\n\nfunction deselect(event: Event, expectedSelected: string) {\n  if (event && event.target instanceof HTMLAnchorElement) {\n    return;\n  }\n  toggleNotificationFilterSettings(null);\n  if (!expectedSelected || props.selected === expectedSelected) {\n    router.push({ name: 'applications' });\n  }\n}\n\nasync function addFilter({ object, ttl }) {\n  try {\n    const response = await NotificationFilter.addFilter(object, ttl);\n    let notificationFilter = response.data;\n    notificationFilterSubject.next(notificationFilter);\n    notificationCenter.success(\n      `${t('applications.notifications_suppressed_for', {\n        name:\n          notificationFilter.applicationName || notificationFilter.instanceId,\n      })} <strong>${notificationFilter.expiry.fromNow(true)}</strong>.`,\n    );\n  } catch (error) {\n    console.warn('Adding notification filter failed:', error);\n  } finally {\n    toggleNotificationFilterSettings(null);\n  }\n}\n\nasync function removeFilter(activeFilter) {\n  try {\n    await activeFilter.delete();\n    notificationFilterSubject.next(activeFilter.id);\n    notificationCenter.success(t('applications.notification_filter.removed'));\n  } catch (error) {\n    console.warn('Deleting notification filter failed:', error);\n  } finally {\n    toggleNotificationFilterSettings(null);\n  }\n}\n\nfunction toggleNotificationFilterSettings(obj) {\n  showNotificationFilterSettingsObject.value = obj ? obj : null;\n}\n</script>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\nimport { directive as onClickaway } from 'vue3-click-away';\n\nimport Popper from '@/directives/popper';\nimport handle from '@/views/applications/handle.vue';\n\nexport default defineComponent({\n  directives: { Popper, onClickaway },\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      path: '/applications/:selected?',\n      props: true,\n      name: 'applications',\n      handle,\n      order: 0,\n      component: this,\n    });\n    viewRegistry.addRedirect('/', 'applications');\n  },\n});\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/applications/listItem/ItemInformation.vue",
    "content": "<!--\n  - Copyright 2014-2024 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <section\n    class=\"grid grid-cols-2 md:grid-cols-[26.5rem_1fr] items-center w-full\"\n  >\n    <div class=\"flex\" style=\"grid-area: 1 / 1 / 1 / 3\">\n      <template v-if=\"instance.showUrl()\">\n        <div\n          v-tooltip.top=\"{\n            disabled: instanceUrlToShow.length <= 50,\n            value: instanceUrlToShow,\n            pt: {},\n          }\"\n          class=\"text-ellipsis overflow-hidden whitespace-nowrap\"\n          v-text=\"instanceUrlToShow\"\n        />\n        <div class=\"ml-1 flex gap-1 items-start\">\n          <sba-tag\n            v-if=\"instance.registration.metadata?.['group']\"\n            class=\"ml-2\"\n            :value=\"instance.registration.metadata?.['group']\"\n            small\n          />\n\n          <template v-if=\"!instance.isUrlDisabled()\">\n            <sba-button\n              as=\"a\"\n              :href=\"instance.registration.serviceUrl\"\n              size=\"2xs\"\n              referrerpolicy=\"no-referrer\"\n              target=\"_blank\"\n              :aria-label=\"t('term.homepage')\"\n            >\n              <font-awesome-icon :icon=\"faHome\" size=\"xs\" />\n            </sba-button>\n          </template>\n          <sba-button\n            as=\"a\"\n            :href=\"instance.registration.managementUrl\"\n            size=\"2xs\"\n            referrerpolicy=\"no-referrer\"\n            target=\"_blank\"\n            :aria-label=\"t('term.actuator_endpoint')\"\n          >\n            <font-awesome-icon :icon=\"faClipboardList\" size=\"xs\" />\n          </sba-button>\n          <sba-button\n            as=\"a\"\n            :href=\"instance.registration.healthUrl\"\n            size=\"2xs\"\n            referrerpolicy=\"no-referrer\"\n            target=\"_blank\"\n            :aria-label=\"t('health.label')\"\n          >\n            <font-awesome-icon :icon=\"faHeart\" size=\"xs\" />\n          </sba-button>\n        </div>\n      </template>\n\n      <template v-else>\n        <sba-tag\n          v-if=\"instance.registration.metadata?.['group']\"\n          class=\"ml-2\"\n          :value=\"instance.registration.metadata?.['group']\"\n          small\n        />\n      </template>\n    </div>\n\n    <span class=\"instance-id\" v-text=\"instance.id\" />\n    <span\n      class=\"instance-version text-right lg:text-left\"\n      v-text=\"instance.buildVersion\"\n    />\n  </section>\n</template>\n\n<script setup lang=\"ts\">\nimport {\n  faClipboardList,\n  faHeart,\n  faHome,\n} from '@fortawesome/free-solid-svg-icons';\nimport { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';\nimport { useI18n } from 'vue-i18n';\n\nimport SbaButton from '@/components/sba-button.vue';\nimport SbaTag from '@/components/sba-tag.vue';\n\nimport Instance from '@/services/instance';\n\nconst { t } = useI18n();\n\nconst { instance } = defineProps<{\n  instance: Instance;\n}>();\n\nconst instanceUrlToShow =\n  instance.registration.serviceUrl || instance.registration.healthUrl;\n</script>\n\n<style>\n.p-tooltip {\n  max-width: fit-content !important;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/applications/listItem/ItemTags.vue",
    "content": "<!--\n  - Copyright 2014-2024 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div\n    v-if=\"Object.keys(instance.tags ?? {}).length > 0\"\n    class=\"mt-2 hidden lg:block overflow-x-auto\"\n  >\n    <sba-tags :small=\"true\" :tags=\"instance.tags\" />\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport SbaTags from '@/components/sba-tags.vue';\n\nimport Instance from '@/services/instance';\n\ndefineProps<{\n  instance: Instance;\n}>();\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/external/index.spec.ts",
    "content": "import { beforeEach, describe, expect, it } from 'vitest';\n\nimport ViewRegistry from '@/viewRegistry';\nimport { addExternalLink, addIframeView } from '@/views/external/index';\n\ndescribe('External View', () => {\n  let viewRegistry: ViewRegistry;\n\n  beforeEach(() => {\n    viewRegistry = new ViewRegistry();\n  });\n\n  describe('External Link', () => {\n    it('will be added to view registry', () => {\n      addExternalLink(viewRegistry, {\n        label: 'External Link',\n        url: 'https://www.codecentric.de',\n      });\n\n      expect(viewRegistry.views).toHaveLength(1);\n      expect(viewRegistry.views.map((v) => v.label)).toContain('External Link');\n    });\n\n    it('will be added to view registry with children', () => {\n      addExternalLink(viewRegistry, {\n        label: 'External Link',\n        url: 'https://www.codecentric.de',\n        children: [\n          {\n            label: 'External Link Child',\n            url: 'https://www.codecentric.de',\n          },\n        ],\n      });\n\n      expect(viewRegistry.views).toHaveLength(2);\n      expect(viewRegistry.views.map((v) => v.label)).toContain('External Link');\n      expect(viewRegistry.views.map((v) => v.label)).toContain(\n        'External Link Child',\n      );\n    });\n\n    it('will add multiple external links', () => {\n      addExternalLink(viewRegistry, {\n        label: 'External Link 1',\n        url: 'https://www.codecentric.de',\n      });\n      addExternalLink(viewRegistry, {\n        label: 'External Link 2',\n        url: 'https://www.codecentric.de',\n      });\n\n      expect(viewRegistry.views).toHaveLength(2);\n      expect(viewRegistry.views.map((v) => v.label)).toContain(\n        'External Link 1',\n      );\n      expect(viewRegistry.views.map((v) => v.label)).toContain(\n        'External Link 2',\n      );\n    });\n  });\n\n  describe('External Iframe', () => {\n    it('will be added to view registry', () => {\n      addIframeView(viewRegistry, {\n        label: 'External Iframe',\n        url: 'https://www.codecentric.de',\n        iframe: true,\n      });\n\n      expect(viewRegistry.views).toHaveLength(1);\n      expect(viewRegistry.views.map((v) => v.label)).toContain(\n        'External Iframe',\n      );\n    });\n\n    it('will add multiple iframes', () => {\n      addIframeView(viewRegistry, {\n        label: 'External Link 1',\n        url: 'https://www.codecentric.de',\n      });\n      addIframeView(viewRegistry, {\n        label: 'External Link 2',\n        url: 'https://www.codecentric.de',\n      });\n\n      expect(viewRegistry.views).toHaveLength(2);\n      expect(viewRegistry.views.map((v) => v.label)).toContain(\n        'External Link 1',\n      );\n      expect(viewRegistry.views.map((v) => v.label)).toContain(\n        'External Link 2',\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/external/index.ts",
    "content": "/*\n * Copyright 2014-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { h } from 'vue';\n\nimport './style.css';\n\nimport sbaConfig from '@/sba-config';\nimport ViewRegistry from '@/viewRegistry';\n\nfunction addExternalView(\n  viewRegistry: ViewRegistry,\n  view: ExternalView,\n  parent?: string,\n) {\n  if (view.iframe) {\n    addIframeView(viewRegistry, view, parent);\n  } else {\n    addExternalLink(viewRegistry, view, parent);\n  }\n}\n\nfunction getViewOpts(view: ExternalView, parent?: string) {\n  const safeLabel = view.label.replace(/[^a-zA-Z0-9-_]/g, '');\n  const name = `/external/${safeLabel}`;\n\n  return {\n    name,\n    path: name,\n    parent,\n    label: view.label,\n    order: view.order,\n  };\n}\n\nexport const addIframeView = (\n  viewRegistry: ViewRegistry,\n  view: ExternalView,\n  parent?: string,\n) => {\n  const viewOpts = {\n    ...getViewOpts(view, parent),\n    component: {\n      inheritAttrs: false,\n      render() {\n        return h('div', { class: 'external-view' }, [\n          h('iframe', { src: view.url }),\n        ]);\n      },\n    },\n  } as ComponentView;\n\n  viewRegistry.addView(viewOpts);\n\n  view.children?.forEach((view) => {\n    addExternalView(viewRegistry, view, viewOpts.name);\n  });\n};\n\nexport const addExternalLink = (\n  viewRegistry: ViewRegistry,\n  view: ExternalView,\n  parent?: string,\n) => {\n  const viewOpts = {\n    ...getViewOpts(view, parent),\n    href: view.url,\n  } as LinkView;\n\n  viewRegistry.addView(viewOpts);\n\n  view.children?.forEach((view) => {\n    addExternalView(viewRegistry, view, viewOpts.name);\n  });\n};\n\nexport default {\n  install({ viewRegistry }) {\n    const views = sbaConfig.uiSettings.externalViews;\n    views.forEach((view) => {\n      addExternalView(viewRegistry, view);\n    });\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/external/style.css",
    "content": "/*!\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n.external-view {\n  display: flex;\n  width: 100%;\n  height: calc(100vh - 52px);\n  flex-direction: column;\n}\n.external-view > * {\n  flex-grow: 1;\n  border: none;\n  margin: 0;\n  padding: 0;\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/index.ts",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst isStorybook = Object.prototype.hasOwnProperty.call(window, 'STORIES');\n\nconst views = [];\n\nif (!isStorybook) {\n  const context: Record<string, any> = import.meta.glob(\n    './**/index.(js|vue|ts)',\n    { eager: true },\n  );\n  Object.keys(context)\n    .filter((key) => {\n      const contextElement = context[key];\n      return 'default' in contextElement;\n    })\n    .forEach(function (key) {\n      const defaultExport = context[key].default;\n      if (defaultExport && defaultExport.install) {\n        views.push(defaultExport);\n      }\n    });\n}\n\nexport default views;\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/auditevents/auditevents-list.stories.ts",
    "content": "import { applications } from '../../../mocks/applications/data';\nimport Instance from '../../../services/instance';\nimport Index from './index.vue';\n\nexport default {\n  component: Index,\n  title: 'SBA View/AuditeventsList',\n};\n\nconst Template = (args, { argTypes }) => ({\n  components: { Index },\n  props: Object.keys(argTypes),\n  template: '<index v-bind=\"$props\" />',\n});\n\nexport const Test = {\n  render: Template,\n\n  args: {\n    instance: new Instance({\n      id: 'bba333956ae6',\n      ...applications[0].instances[0],\n    }),\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/auditevents/auditevents-list.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <table class=\"auditevents table w-full\">\n    <thead>\n      <tr>\n        <th v-html=\"$t('instances.auditevents.timestamp')\" />\n        <th v-html=\"$t('instances.auditevents.event')\" />\n        <th v-html=\"$t('instances.auditevents.principal')\" />\n        <th v-html=\"$t('instances.auditevents.remote_address')\" />\n        <th v-html=\"$t('instances.auditevents.session_id')\" />\n      </tr>\n    </thead>\n    <tbody>\n      <template v-for=\"event in events\" :key=\"event.key\">\n        <tr\n          class=\"is-selectable\"\n          :class=\"{ 'auditevents__event--is-detailed': showDetails[event.key] }\"\n          @click=\"\n            showDetails[event.key]\n              ? delete showDetails[event.key]\n              : (showDetails[event.key] = true)\n          \"\n        >\n          <td v-text=\"formatDate(event.timestamp)\" />\n          <td>\n            <span\n              class=\"tag\"\n              :class=\"{\n                'is-success': event.isSuccess(),\n                'is-danger': event.isFailure(),\n              }\"\n              v-text=\"event.type\"\n            />\n          </td>\n          <td v-if=\"hasSessionEndpoint && event.principal\">\n            <router-link\n              :to=\"{\n                name: 'instances/sessions',\n                params: { instanceId: instance.id },\n                query: { username: event.principal },\n              }\"\n              v-text=\"event.principal\"\n            />\n          </td>\n          <td v-else v-text=\"event.principal\" />\n          <td v-text=\"event.remoteAddress\" />\n          <td v-if=\"hasSessionEndpoint && event.sessionId\">\n            <router-link\n              :to=\"{\n                name: 'instances/sessions',\n                params: { instanceId: instance.id },\n                query: { sessionId: event.sessionId },\n              }\"\n              v-text=\"event.sessionId\"\n            />\n          </td>\n          <td v-else v-text=\"event.sessionId\" />\n        </tr>\n        <tr v-if=\"showDetails[event.key]\" :key=\"`${event.key}-detail`\">\n          <td colspan=\"5\">\n            <pre\n              class=\"auditevents__event-detail\"\n              v-text=\"toJson(event.data)\"\n            />\n          </td>\n        </tr>\n      </template>\n      <tr v-if=\"events.length === 0\">\n        <td class=\"is-muted\" colspan=\"5\">\n          <p\n            v-if=\"isLoading\"\n            class=\"is-loading\"\n            v-html=\"$t('instances.auditevents.loading_audit_events')\"\n          />\n          <p\n            v-else\n            v-html=\"$t('instances.auditevents.no_audit_events_found')\"\n          />\n        </td>\n      </tr>\n    </tbody>\n  </table>\n</template>\n\n<script>\nimport prettyBytes from 'pretty-bytes';\n\nimport { useDateTimeFormatter } from '@/composables/useDateTimeFormatter';\nimport Instance from '@/services/instance';\n\nexport default {\n  props: {\n    events: {\n      type: Array,\n      default: () => [],\n    },\n    instance: {\n      type: Instance,\n      required: true,\n    },\n    isLoading: {\n      type: Boolean,\n      default: false,\n    },\n  },\n  setup() {\n    const { formatDateTime } = useDateTimeFormatter();\n\n    return {\n      formatDate: formatDateTime,\n    };\n  },\n  data: () => ({\n    showDetails: {},\n  }),\n  computed: {\n    hasSessionEndpoint() {\n      return this.instance.hasEndpoint('sessions');\n    },\n  },\n  methods: {\n    prettyBytes,\n    toJson(obj) {\n      return JSON.stringify(obj, null, 4);\n    },\n  },\n};\n</script>\n\n<style lang=\"css\">\n.auditevents {\n  table-layout: fixed;\n}\n.auditevents td {\n  vertical-align: middle;\n}\n.auditevents__event--is-detailed td {\n  border: none !important;\n}\n.auditevents__event-detail {\n  overflow: auto;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/auditevents/i18n.de.json",
    "content": "{\n  \"instances\": {\n    \"auditevents\": {\n      \"audit_log_not_supported_spring_boot_1\": \"Spring Boot 1.x Anwendungen unterstützen keine Audit-Logs.\",\n      \"event\": \"Ereignis\",\n      \"loading_audit_events\": \"Audit-Events werden geladen...\",\n      \"no_audit_events_found\": \"Keine Audit-Events vorhanden.\",\n      \"principal\": \"Prinzipal\",\n      \"remote_address\": \"Remote address\",\n      \"session_id\": \"Session Id\",\n      \"timestamp\": \"Zeitstempel\",\n      \"type\": \"Typ\",\n      \"label\": \"Audit-Protokoll\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/auditevents/i18n.en.json",
    "content": "{\n  \"instances\": {\n    \"auditevents\": {\n      \"audit_log_not_supported_spring_boot_1\": \"Audit Log is not supported for Spring Boot 1.x applications.\",\n      \"event\": \"Event\",\n      \"loading_audit_events\": \"Loading Audit Events...\",\n      \"no_audit_events_found\": \"No Audit Events found.\",\n      \"principal\": \"Principal\",\n      \"remote_address\": \"Remote address\",\n      \"session_id\": \"Session Id\",\n      \"timestamp\": \"Timestamp\",\n      \"type\": \"Type\",\n      \"label\": \"Audit Log\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/auditevents/i18n.es.json",
    "content": "{\n  \"instances\": {\n    \"auditevents\": {\n      \"audit_log_not_supported_spring_boot_1\": \"Log de auditoría no soportado para aplicaciones Spring Boot 1.x .\",\n      \"event\": \"Evento\",\n      \"loading_audit_events\": \"Cargando Eventos de Auditoría...\",\n      \"no_audit_events_found\": \"No se encontraron eventos de auditoría.\",\n      \"principal\": \"Principal\",\n      \"remote_address\": \"Dirección remota\",\n      \"session_id\": \"Session Id\",\n      \"timestamp\": \"Horario\",\n      \"type\": \"Tipo\",\n      \"label\": \"Log de Auditoría\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/auditevents/i18n.fr.json",
    "content": "{\n  \"instances\": {\n    \"auditevents\": {\n      \"audit_log_not_supported_spring_boot_1\": \"Les Audit de Log ne sont pas supportés par les applications Spring Boot 1.x.\",\n      \"event\": \"Événement\",\n      \"loading_audit_events\": \"Chargement des Audit d'évènements...\",\n      \"no_audit_events_found\": \"Aucun Audit d'évènements trouvés.\",\n      \"principal\": \"Principal\",\n      \"remote_address\": \"Adresse distante\",\n      \"session_id\": \"Session Id\",\n      \"timestamp\": \"Timestamp\",\n      \"type\": \"Type\",\n      \"label\": \"Audit Log\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/auditevents/i18n.is.json",
    "content": "{\n  \"instances\": {\n    \"auditevents\": {\n      \"audit_log_not_supported_spring_boot_1\": \"Spring Boot 1.x forrit bjóða enga endurskoðunarannála (audit logs).\",\n      \"event\": \"Atburður\",\n      \"loading_audit_events\": \"Endurskoðunaratburðir eru að hlaða…\",\n      \"no_audit_events_found\": \"Engir endurskoðunaratburðir eru í boði.\",\n      \"principal\": \"Principal\",\n      \"remote_address\": \"Remote address\",\n      \"session_id\": \"Seta Id\",\n      \"timestamp\": \"Tímapunktur\",\n      \"type\": \"Gerð\",\n      \"label\": \"Endurskoðunarannáll\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/auditevents/i18n.ko.json",
    "content": "{\n  \"instances\": {\n    \"auditevents\": {\n      \"audit_log_not_supported_spring_boot_1\": \"감사(Audit) 로그는 Spring Boot 1.x 버전의 애플리케이션은 지원하지 않습니다.\",\n      \"event\": \"이벤트\",\n\n      \"loading_audit_events\": \"감사(Audit) 이벤트 불러오는 중...\",\n      \"no_audit_events_found\": \"감사(Audit) 이벤트를 찾을 수 없습니다.\",\n      \"principal\": \"접근 주체\",\n      \"remote_address\": \"원격 주소\",\n      \"session_id\": \"Session Id\",\n      \"timestamp\": \"Timestamp\",\n      \"type\": \"Type\",\n      \"label\": \"Audit Log\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/auditevents/i18n.pt-BR.json",
    "content": "{\n  \"instances\": {\n    \"auditevents\": {\n      \"audit_log_not_supported_spring_boot_1\": \"O log de auditoria não é suportado em aplicações Spring Boot 1.x.\",\n      \"event\": \"Evento\",\n      \"loading_audit_events\": \"Carregando Eventos de Auditoria...\",\n      \"no_audit_events_found\": \"Nenhum Evento de Auditoria encontrado.\",\n      \"principal\": \"Principal\",\n      \"remote_address\": \"Endereço Remoto\",\n      \"session_id\": \"Id de sessão\",\n      \"timestamp\": \"Timestamp\",\n      \"type\": \"Tipo\",\n      \"label\": \"Log de Auditoria\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/auditevents/i18n.ru.json",
    "content": "{\n  \"instances\": {\n    \"auditevents\": {\n      \"audit_log_not_supported_spring_boot_1\": \"Аудирование не поддерживается для приложений на Spring Boot 1.x.\",\n      \"event\": \"Событие\",\n      \"loading_audit_events\": \"Загрузка событий аудита...\",\n      \"no_audit_events_found\": \"События аудита не найдены.\",\n      \"principal\": \"Принципал\",\n      \"remote_address\": \"Адрес\",\n      \"session_id\": \"Идентификатор сессии\",\n      \"timestamp\": \"Время\",\n      \"type\": \"Тип\",\n      \"label\": \"Журнал аудита\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/auditevents/i18n.zh-CN.json",
    "content": "{\n  \"instances\": {\n    \"auditevents\": {\n      \"audit_log_not_supported_spring_boot_1\": \"Spring Boot 1.x的应用不支持审计日志。\",\n      \"event\": \"事件\",\n      \"loading_audit_events\": \"加载审计事件中...\",\n      \"no_audit_events_found\": \"没有审计事件。\",\n      \"principal\": \"当事人\",\n      \"remote_address\": \"远程地址\",\n      \"session_id\": \"会话ID\",\n      \"timestamp\": \"时间戳\",\n      \"type\": \"类型\",\n      \"label\": \"审计日志\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/auditevents/i18n.zh-TW.json",
    "content": "{\n  \"instances\": {\n    \"auditevents\": {\n      \"audit_log_not_supported_spring_boot_1\": \"Spring Boot 1.x 的應用程式不支援稽核日誌。\",\n      \"event\": \"事件\",\n      \"loading_audit_events\": \"正在載入稽核事件...\",\n      \"no_audit_events_found\": \"找不到稽核事件。\",\n      \"principal\": \"主體\",\n      \"remote_address\": \"遠端位址\",\n      \"session_id\": \"Session ID\",\n      \"timestamp\": \"時間戳記\",\n      \"type\": \"類型\",\n      \"label\": \"稽核日誌\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/auditevents/index.spec.ts",
    "content": "import userEvent from '@testing-library/user-event';\nimport { screen, waitFor } from '@testing-library/vue';\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport Instance from '@/services/instance';\nimport { render } from '@/test-utils';\nimport Auditevents from '@/views/instances/auditevents/index.vue';\n\ndescribe('Auditevents', () => {\n  const fetchAuditevents = vi.fn().mockResolvedValue({\n    data: {\n      events: [],\n    },\n  });\n\n  beforeEach(async () => {\n    render(Auditevents, {\n      props: {\n        instance: createInstance(fetchAuditevents),\n      },\n    });\n  });\n\n  it('fetches data on startup', async () => {\n    await waitFor(() => expect(fetchAuditevents).toHaveBeenCalled());\n  });\n\n  it('fetches data when filter for Principal is changed', async () => {\n    const input = await screen.findByPlaceholderText(\n      'instances.auditevents.principal',\n    );\n    await userEvent.type(input, 'Abc');\n\n    await waitFor(() => {\n      const calls = fetchAuditevents.mock.calls;\n      expect(calls[calls.length - 1][0].principal).toEqual('Abc');\n    });\n  });\n\n  it('fetches data when filter for Type is changed', async () => {\n    const input = await screen.findByPlaceholderText(\n      'instances.auditevents.type',\n    );\n    await userEvent.type(input, 'AUTHENTICATION_FAILURE');\n\n    await waitFor(() => {\n      const calls = fetchAuditevents.mock.calls;\n      expect(calls[calls.length - 1][0].type).toEqual('AUTHENTICATION_FAILURE');\n    });\n  });\n\n  it('handles error when fetching data', async () => {\n    render(Auditevents, {\n      props: {\n        instance: createInstance(\n          vi.fn().mockRejectedValue({\n            response: {\n              headers: {\n                'content-type': 'application/vnd.spring-boot.actuator.v2',\n              },\n            },\n          }),\n        ),\n      },\n    });\n\n    await screen.findByText('Fetching of data failed.');\n  });\n\n  function createInstance(fetchAuditevents) {\n    const instance = new Instance({ id: 4711 });\n    instance.fetchAuditevents = fetchAuditevents;\n    return instance;\n  }\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/auditevents/index.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-instance-section :error=\"error\" :loading=\"isLoading\">\n    <template #before>\n      <sba-sticky-subnav>\n        <div class=\"flex gap-2\">\n          <sba-input\n            v-model.trim=\"filter.principal\"\n            :placeholder=\"$t('instances.auditevents.principal')\"\n            name=\"filter_principal\"\n            type=\"search\"\n          />\n          <sba-input\n            v-model=\"filter.type\"\n            :list=\"[\n              'AUTHENTICATION_FAILURE',\n              'AUTHENTICATION_SUCCESS',\n              'AUTHENTICATION_SWITCH',\n              'AUTHORIZATION_FAILURE',\n            ]\"\n            :placeholder=\"$t('instances.auditevents.type')\"\n            name=\"filter_type\"\n            type=\"search\"\n          />\n\n          <sba-input\n            :value=\"formatDate(filter.after)\"\n            name=\"filter_datetime\"\n            placeholder=\"Date\"\n            type=\"datetime-local\"\n            @input=\"filter.after = parseDate($event.target.value)\"\n          />\n        </div>\n      </sba-sticky-subnav>\n    </template>\n\n    <sba-panel :seamless=\"true\">\n      <auditevents-list\n        :events=\"events\"\n        :instance=\"instance\"\n        :is-loading=\"isLoading\"\n      />\n    </sba-panel>\n  </sba-instance-section>\n</template>\n\n<script>\nimport { uniqBy } from 'lodash-es';\nimport moment from 'moment';\nimport { Subject, concatMap, debounceTime, mergeWith, tap, timer } from 'rxjs';\n\nimport SbaInput from '@/components/sba-input';\nimport SbaPanel from '@/components/sba-panel';\nimport SbaStickySubnav from '@/components/sba-sticky-subnav';\n\nimport subscribing from '@/mixins/subscribing';\nimport Instance from '@/services/instance';\nimport { VIEW_GROUP } from '@/views/ViewGroup';\nimport AuditeventsList from '@/views/instances/auditevents/auditevents-list';\nimport SbaInstanceSection from '@/views/instances/shell/sba-instance-section';\n\nclass Auditevent {\n  constructor({ timestamp, ...event }) {\n    Object.assign(this, event);\n    this.zonedTimestamp = timestamp;\n    this.timestamp = new Date(timestamp);\n  }\n\n  get key() {\n    return `${this.zonedTimestamp}-${this.type}-${this.principal}`;\n  }\n\n  get remoteAddress() {\n    return (\n      (this.data && this.data.details && this.data.details.remoteAddress) ||\n      null\n    );\n  }\n\n  get sessionId() {\n    return (\n      (this.data && this.data.details && this.data.details.sessionId) || null\n    );\n  }\n\n  isSuccess() {\n    return this.type.toLowerCase().includes('success');\n  }\n\n  isFailure() {\n    return this.type.toLowerCase().includes('failure');\n  }\n}\n\nexport default {\n  components: {\n    SbaInput,\n    SbaInstanceSection,\n    SbaStickySubnav,\n    SbaPanel,\n    AuditeventsList,\n  },\n  mixins: [subscribing],\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  data: () => ({\n    isLoading: false,\n    error: null,\n    events: [],\n    filter: {\n      after: moment().startOf('day'),\n      type: null,\n      principal: null,\n    },\n  }),\n  watch: {\n    filter: {\n      deep: true,\n      handler() {\n        this.filterChanged.next();\n      },\n    },\n  },\n  methods: {\n    formatDate(value) {\n      return value.format(moment.HTML5_FMT.DATETIME_LOCAL);\n    },\n    parseDate(value) {\n      return moment(value, moment.HTML5_FMT.DATETIME_LOCAL, true).toDate();\n    },\n    async fetchAuditevents() {\n      this.isLoading = true;\n      const response = await this.instance.fetchAuditevents(this.filter);\n      const converted = response.data.events.map(\n        (event) => new Auditevent(event),\n      );\n      converted.reverse();\n      this.isLoading = false;\n      return converted;\n    },\n    createSubscription() {\n      this.filterChanged = new Subject();\n      this.error = null;\n\n      return timer(0, 5000)\n        .pipe(\n          mergeWith(\n            this.filterChanged.pipe(\n              debounceTime(250),\n              tap({\n                next: () => (this.events = []),\n              }),\n            ),\n          ),\n          concatMap(this.fetchAuditevents),\n        )\n        .subscribe({\n          next: (events) => {\n            this.addEvents(events);\n          },\n          error: (error) => {\n            console.warn('Fetching audit events failed:', error);\n            this.error = error;\n          },\n        });\n    },\n    addEvents(events) {\n      this.events = uniqBy(\n        this.events ? events.concat(this.events) : events,\n        (event) => event.key,\n      );\n    },\n  },\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      name: 'instances/auditevents',\n      parent: 'instances',\n      path: 'auditevents',\n      component: this,\n      label: 'instances.auditevents.label',\n      group: VIEW_GROUP.SECURITY,\n      order: 600,\n      isEnabled: ({ instance }) => instance.hasEndpoint('auditevents'),\n    });\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/beans/beans-list-details.vue",
    "content": "<template>\n  <table class=\"w-full table-auto\">\n    <colgroup>\n      <col class=\"text-gray-500\" />\n      <col class=\"py-3\" />\n    </colgroup>\n    <tbody>\n      <!-- NAME -->\n      <tr v-if=\"bean.name !== bean.shortName\">\n        <th class=\"label\" v-text=\"$t('instances.beans.name')\" />\n        <td class=\"value\" v-text=\"bean.name\" />\n      </tr>\n\n      <!-- ALIAS -->\n      <tr v-for=\"(alias, idx) in bean.aliases\" :key=\"alias\">\n        <th\n          v-if=\"idx === 0\"\n          :rowspan=\"bean.aliases.length\"\n          class=\"label\"\n          v-text=\"$t('instances.beans.aliases')\"\n        />\n        <td class=\"value\" v-text=\"alias\" />\n      </tr>\n\n      <!-- BEAN TYPE -->\n      <tr v-if=\"bean.type !== bean.shortType\">\n        <th class=\"label\" v-text=\"$t('instances.beans.type')\" />\n        <td class=\"value\" v-text=\"bean.type\" />\n      </tr>\n\n      <!-- BEAN RESOURCE -->\n      <tr v-if=\"bean.resource\">\n        <th class=\"label\" v-text=\"$t('instances.beans.resource')\" />\n        <td class=\"value\" v-text=\"bean.resource\" />\n      </tr>\n\n      <!-- BEAN DEPENDENCIES -->\n      <tr v-if=\"bean.dependencies\">\n        <th class=\"label\" v-text=\"$t('instances.beans.dependencies')\" />\n        <td class=\"value px-4\">\n          <ul>\n            <li\n              v-for=\"dependency in bean.dependencies\"\n              :key=\"dependency\"\n              class=\"list-disc break-all\"\n              v-text=\"dependency\"\n            />\n          </ul>\n        </td>\n      </tr>\n    </tbody>\n  </table>\n</template>\n\n<script>\nexport default {\n  name: 'BeansListDetails',\n  props: {\n    bean: {\n      type: Object,\n      required: true,\n    },\n  },\n};\n</script>\n\n<style>\n.label {\n  @apply text-gray-500 text-sm text-right px-2 align-top w-40;\n}\n.value {\n  @apply break-all;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/beans/beans-list.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div class=\"-mx-4 -my-3\">\n    <template v-for=\"(bean, index) in beans\" :key=\"bean.name\">\n      <div\n        :class=\"{\n          'm-1 border rounded shadow-sm': showDetails[bean.name] === true,\n        }\"\n      >\n        <div\n          :key=\"bean.name\"\n          class=\"flex items-center\"\n          :class=\"{\n            'bg-gray-50': index % 2 === 0 || showDetails[bean.name] === true,\n            'px-3 py-2': showDetails[bean.name] === true,\n            'px-4 py-3': showDetails[bean.name] !== true,\n          }\"\n          @click=\"toggle(bean.name)\"\n        >\n          <div class=\"flex-1 sm:break-all\">\n            <div\n              :class=\"{ 'font-bold': showDetails[bean.name] === true }\"\n              :title=\"bean.name\"\n              v-text=\"bean.shortName\"\n            />\n            <small\n              class=\"sm:break-all\"\n              :title=\"bean.type\"\n              v-text=\"bean.shortType\"\n            />\n          </div>\n          <div>\n            <span v-text=\"bean.scope\" />\n          </div>\n        </div>\n        <div\n          v-if=\"showDetails[bean.name] === true\"\n          :key=\"`${bean.name}-detail`\"\n          class=\"text-sm\"\n        >\n          <beans-list-details :bean=\"bean\" />\n        </div>\n      </div>\n    </template>\n  </div>\n</template>\n\n<script>\nimport BeansListDetails from '@/views/instances/beans/beans-list-details';\n\nexport default {\n  components: { BeansListDetails },\n  props: {\n    beans: {\n      type: Array,\n      default: () => [],\n    },\n  },\n  data() {\n    return {\n      showDetails: {},\n    };\n  },\n  methods: {\n    toggle(name) {\n      if (this.showDetails[name]) {\n        this.showDetails = {\n          ...this.showDetails,\n          [name]: null,\n        };\n      } else {\n        this.showDetails = {\n          ...this.showDetails,\n          [name]: true,\n        };\n      }\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/beans/i18n.de.json",
    "content": "{\n  \"instances\": {\n    \"beans\": {\n      \"label\": \"Beans\",\n      \"name\": \"Name\",\n      \"aliases\": \"Aliase\",\n      \"type\": \"Typ\",\n      \"resource\": \"Ressource\",\n      \"dependencies\": \"Abhängigkeiten\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/beans/i18n.en.json",
    "content": "{\n  \"instances\": {\n    \"beans\": {\n      \"label\": \"Beans\",\n      \"name\": \"Name\",\n      \"aliases\": \"Aliases\",\n      \"type\": \"Type\",\n      \"resource\": \"Resource\",\n      \"dependencies\": \"Dependencies\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/beans/i18n.es.json",
    "content": "{\n  \"instances\": {\n    \"beans\": {\n      \"label\": \"Beans\",\n      \"name\": \"Nombre\",\n      \"aliases\": \"Alias\",\n      \"type\": \"Tipo\",\n      \"resource\": \"Recurso\",\n      \"dependencies\": \"Dependencias\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/beans/i18n.fr.json",
    "content": "{\n  \"instances\": {\n    \"beans\": {\n      \"label\": \"Beans\",\n      \"name\": \"Nom\",\n      \"aliases\": \"Alias\",\n      \"type\": \"Type\",\n      \"resource\": \"Ressource\",\n      \"dependencies\": \"Dépendances\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/beans/i18n.is.json",
    "content": "{\n  \"instances\": {\n    \"beans\": {\n      \"label\": \"Baunir\",\n      \"name\": \"Nafn\",\n      \"aliases\": \"Samnefni\",\n      \"type\": \"Gerð\",\n      \"resource\": \"Resource\",\n      \"dependencies\": \"Fylgni\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/beans/i18n.ko.json",
    "content": "{\n  \"instances\": {\n    \"beans\": {\n      \"label\": \"빈(Bean)\",\n      \"name\": \"이름\",\n      \"aliases\": \"별칭\",\n      \"type\": \"타입\",\n      \"resource\": \"리소스\",\n      \"dependencies\": \"의존성\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/beans/i18n.pt-BR.json",
    "content": "{\n  \"instances\": {\n    \"beans\": {\n      \"label\": \"Beans\",\n      \"name\": \"Nome\",\n      \"aliases\": \"Aliases\",\n      \"type\": \"Tipo\",\n      \"resource\": \"Recurso\",\n      \"dependencies\": \"Dependências\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/beans/i18n.ru.json",
    "content": "{\n  \"instances\": {\n    \"beans\": {\n      \"label\": \"Бины\",\n      \"name\": \"Имя\",\n      \"aliases\": \"Псевдонимы\",\n      \"type\": \"Тип\",\n      \"resource\": \"Ресурс\",\n      \"dependencies\": \"Зависимости\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/beans/i18n.zh-CN.json",
    "content": "{\n  \"instances\": {\n    \"beans\": {\n      \"label\": \"类\",\n      \"name\": \"名称\",\n      \"aliases\": \"别名\",\n      \"type\": \"类型\",\n      \"resource\": \"资源\",\n      \"dependencies\": \"依赖关系\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/beans/i18n.zh-TW.json",
    "content": "{\n  \"instances\": {\n    \"beans\": {\n      \"label\": \"Bean\",\n      \"name\": \"名稱\",\n      \"aliases\": \"別名\",\n      \"type\": \"類型\",\n      \"resource\": \"資源\",\n      \"dependencies\": \"相依元件\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/beans/index.vue",
    "content": "<!--\n  - Copyright 2014-2019 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-instance-section :error=\"error\" :loading=\"isLoading\">\n    <template #before>\n      <sba-sticky-subnav>\n        <sba-input\n          v-model=\"filter\"\n          :placeholder=\"$t('term.filter')\"\n          name=\"filter\"\n          type=\"search\"\n        >\n          <template #prepend>\n            <font-awesome-icon icon=\"filter\" />\n          </template>\n          <template #append>\n            {{ filterResultString }}\n          </template>\n        </sba-input>\n      </sba-sticky-subnav>\n    </template>\n\n    <template v-for=\"context in filteredContexts\" :key=\"context.name\">\n      <sba-panel :header-sticks-below=\"'#subnavigation'\" :title=\"context.name\">\n        <beans-list :key=\"`${context.name}-beans`\" :beans=\"context.beans\" />\n      </sba-panel>\n    </template>\n  </sba-instance-section>\n</template>\n\n<script>\nimport { isEmpty } from 'lodash-es';\n\nimport Instance from '@/services/instance';\nimport { compareBy } from '@/utils/collections';\nimport shortenClassname from '@/utils/shortenClassname';\nimport { VIEW_GROUP } from '@/views/ViewGroup';\nimport BeansList from '@/views/instances/beans/beans-list';\nimport SbaInstanceSection from '@/views/instances/shell/sba-instance-section';\n\nclass Bean {\n  constructor(name, bean) {\n    Object.assign(this, bean);\n    this.name = name;\n    this.shortName = shortenClassname(this.name, 80);\n    this.shortType = shortenClassname(this.type, 80);\n  }\n}\n\nconst flattenBeans = (beans) => {\n  return Object.keys(beans).map((key) => {\n    return new Bean(key, beans[key]);\n  });\n};\n\nconst flattenContexts = (beanData) => {\n  if (isEmpty(beanData.contexts)) {\n    return [];\n  }\n  return Object.keys(beanData.contexts).map((key) => ({\n    beans: flattenBeans(beanData.contexts[key].beans),\n    name: key,\n    parent: beanData.contexts[key].parentId,\n  }));\n};\n\nexport default {\n  components: { SbaInstanceSection, BeansList },\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  data: () => ({\n    isLoading: false,\n    error: null,\n    contexts: [],\n    filter: '',\n  }),\n  computed: {\n    filterResultString() {\n      const totalBeans = this.contexts.reduce((count, ctx) => {\n        return count + ctx.beans?.length;\n      }, 0);\n      const filteredBeansLength = this.filteredContexts.reduce((count, ctx) => {\n        return count + ctx.beans?.length;\n      }, 0);\n\n      return `${filteredBeansLength}/${totalBeans}`;\n    },\n    filteredContexts() {\n      const filterFn = this.getFilterFn();\n      return this.contexts.map((ctx) => ({\n        ...ctx,\n        beans: ctx.beans.filter(filterFn).sort(compareBy((bean) => bean.name)),\n      }));\n    },\n  },\n  created() {\n    this.fetchBeans();\n  },\n  methods: {\n    getFilterFn() {\n      if (!this.filter) {\n        return () => true;\n      }\n      const regex = new RegExp(this.filter, 'i');\n      return (bean) =>\n        bean.name.match(regex) ||\n        (bean.aliases && bean.aliases.some((alias) => alias.match(regex)));\n    },\n    async fetchBeans() {\n      this.error = null;\n      this.isLoading = true;\n      try {\n        const res = await this.instance.fetchBeans();\n        this.contexts = flattenContexts(res.data);\n      } catch (error) {\n        console.warn('Fetching beans failed:', error);\n        this.error = error;\n      }\n      this.isLoading = false;\n    },\n  },\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      name: 'instances/beans',\n      parent: 'instances',\n      path: 'beans',\n      label: 'instances.beans.label',\n      group: VIEW_GROUP.INSIGHTS,\n      component: this,\n      order: 110,\n      isEnabled: ({ instance }) => instance.hasEndpoint('beans'),\n    });\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/caches/caches-list.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <table class=\"table-auto w-full\">\n    <thead>\n      <tr>\n        <th v-html=\"$t('instances.caches.name')\" />\n        <th v-html=\"$t('instances.caches.cache_manager')\" />\n        <th>&nbsp;</th>\n      </tr>\n    </thead>\n    <tbody>\n      <tr v-for=\"cache in caches\" :key=\"cache.key\">\n        <td>\n          <span class=\"is-breakable\" v-text=\"cache.name\" />\n        </td>\n        <td>\n          <span class=\"is-breakable\" v-text=\"cache.cacheManager\" />\n        </td>\n        <td class=\"is-narrow text-right\">\n          <sba-button\n            class=\"button\"\n            :class=\"{\n              'is-loading': clearing[cache.key] === 'executing',\n              'is-info': clearing[cache.key] === 'completed',\n              'is-danger': clearing[cache.key] === 'failed',\n            }\"\n            :disabled=\"cache.key in clearing\"\n            @click=\"clearCache(cache)\"\n          >\n            <span\n              v-if=\"clearing[cache.key] === 'completed'\"\n              v-text=\"$t('term.cleared')\"\n            />\n            <span\n              v-else-if=\"clearing[cache.key] === 'failed'\"\n              v-text=\"$t('term.failed')\"\n            />\n            <span v-else>\n              <font-awesome-icon icon=\"trash\" class=\"mr-2\" />\n              <span v-text=\"$t('term.clear')\" />\n            </span>\n          </sba-button>\n        </td>\n      </tr>\n      <tr v-if=\"caches.length === 0\">\n        <td class=\"is-muted\" colspan=\"3 \">\n          <p\n            v-if=\"isLoading\"\n            class=\"is-loading\"\n            v-text=\"$t('instances.caches.loading')\"\n          />\n          <p v-else v-text=\"$t('instances.caches.no_caches_found')\" />\n        </td>\n      </tr>\n    </tbody>\n  </table>\n</template>\n<script>\nimport { ActionScope } from '@/components/ActionScope';\n\nimport Application from '@/services/application';\nimport Instance from '@/services/instance';\nimport { concatMap, listen, of, tap } from '@/utils/rxjs';\n\nexport default {\n  name: 'CachesList',\n  props: {\n    caches: {\n      type: Array,\n      default: () => [],\n    },\n    instance: {\n      type: Instance,\n      required: true,\n    },\n    application: {\n      type: Application,\n      required: true,\n    },\n    isLoading: {\n      type: Boolean,\n      default: false,\n    },\n    scope: {\n      type: ActionScope,\n      required: true,\n    },\n  },\n  emits: ['cleared'],\n  data: () => ({\n    clearing: {},\n    clearingAll: null,\n    clearTimeouts: {},\n  }),\n  beforeUnmount() {\n    Object.values(this.clearTimeouts).forEach((timeoutId) => {\n      if (timeoutId) {\n        clearTimeout(timeoutId);\n      }\n    });\n    this.clearTimeouts = {};\n  },\n  methods: {\n    clearCache(cache) {\n      this._clearCache(cache)\n        .pipe(\n          listen((status) => {\n            this.clearing = {\n              ...this.clearing,\n              [cache.key]: status,\n            };\n          }),\n        )\n        .subscribe({\n          complete: () => {\n            this.clearTimeouts[cache.key] = setTimeout(() => {\n              delete this.clearing[cache.key];\n              delete this.clearTimeouts[cache.key];\n              this.clearing = { ...this.clearing };\n            }, 2500);\n            return this.$emit('cleared', cache.key);\n          },\n        });\n    },\n    _clearCache(cache) {\n      let scope = this.scope;\n      return of(cache).pipe(\n        concatMap(async (cache) => {\n          if (scope === ActionScope.APPLICATION) {\n            await this.application.clearCache(cache.name, cache.cacheManager);\n          } else {\n            await this.instance.clearCache(cache.name, cache.cacheManager);\n          }\n          return cache.key;\n        }),\n        tap({\n          error: (error) => {\n            console.warn(`Clearing cache ${cache.key} failed:`, error);\n          },\n        }),\n      );\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/caches/i18n.de.json",
    "content": "{\n  \"instances\": {\n    \"caches\": {\n      \"label\": \"Caches\",\n      \"cache_manager\": \"Cache Manager\",\n      \"loading_caches\": \"Cache-Informationen werden geladen...\",\n      \"name\": \"Name\",\n      \"no_caches_found\": \"Keine Cache-Informationen gefunden.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/caches/i18n.en.json",
    "content": "{\n  \"instances\": {\n    \"caches\": {\n      \"label\": \"Caches\",\n      \"cache_manager\": \"Cache Manager\",\n      \"loading\": \"Loading Caches...\",\n      \"name\": \"Name\",\n      \"no_caches_found\": \"No caches found.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/caches/i18n.es.json",
    "content": "{\n  \"instances\": {\n    \"caches\": {\n      \"label\": \"Caches\",\n      \"cache_manager\": \"Cache Manager\",\n\n      \"loading\": \"Cargando Caches...\",\n      \"name\": \"Nombre\",\n      \"no_caches_found\": \"No se encontraron caches.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/caches/i18n.fr.json",
    "content": "{\n  \"instances\": {\n    \"caches\": {\n      \"label\": \"Caches\",\n      \"cache_manager\": \"Manager de cache\",\n      \"loading\": \"Chargement des Caches...\",\n      \"name\": \"Nom\",\n      \"no_caches_found\": \"Aucun caches trouvés.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/caches/i18n.is.json",
    "content": "{\n  \"instances\": {\n    \"caches\": {\n      \"label\": \"Skyndiminni\",\n      \"cache_manager\": \"Skyndiminnastjórnandi\",\n      \"loading_caches\": \"Skyndiminnaupplýsingar eru að hlaða…\",\n      \"name\": \"Nafn\",\n      \"no_caches_found\": \"Engar skyndiminnaupplýsingar eru í boði.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/caches/i18n.ko.json",
    "content": "{\n  \"instances\": {\n    \"caches\": {\n      \"label\": \"캐시\",\n      \"cache_manager\": \"캐시 관리자\",\n      \"loading\": \"캐시 정보 불러오는 중...\",\n      \"name\": \"이름\",\n      \"no_caches_found\": \"캐시 정보를 찾을 수 없습니다.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/caches/i18n.pt-BR.json",
    "content": "{\n  \"instances\": {\n    \"caches\": {\n      \"label\": \"Caches\",\n      \"cache_manager\": \"Gerenciador de Cache\",\n      \"loading\": \"Carregando Caches...\",\n      \"name\": \"Name\",\n      \"no_caches_found\": \"Nenhum cache encontrado.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/caches/i18n.ru.json",
    "content": "{\n  \"instances\": {\n    \"caches\": {\n      \"label\": \"Кэши\",\n      \"cache_manager\": \"Кэш-менеджер\",\n      \"loading\": \"Загрузка кэшей...\",\n      \"name\": \"Имя\",\n      \"no_caches_found\": \"Кэши не найдены.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/caches/i18n.zh-CN.json",
    "content": "{\n  \"instances\": {\n    \"caches\": {\n      \"label\": \"缓存\",\n      \"cache_manager\": \"缓存管理\",\n      \"loading\": \"加载缓存中...\",\n      \"name\": \"名称\",\n      \"no_caches_found\": \"未找到缓存。\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/caches/i18n.zh-TW.json",
    "content": "{\n  \"instances\": {\n    \"caches\": {\n      \"label\": \"快取\",\n      \"cache_manager\": \"快取管理\",\n      \"loading\": \"正在載入快取...\",\n      \"name\": \"名稱\",\n      \"no_caches_found\": \"找不到快取。\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/caches/index.vue",
    "content": "<!--\n  - Copyright 2014-2019 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-instance-section :error=\"error\">\n    <template #before>\n      <sba-sticky-subnav>\n        <div class=\"flex gap-2\">\n          <sba-action-button-scoped\n            :action-fn=\"clearCaches\"\n            :instance-count=\"application.instances.length\"\n            :show-info=\"false\"\n            @scope-changed=\"changeScope\"\n          >\n            <template #completed>\n              <span v-text=\"$t('term.execution_successful')\" />\n            </template>\n            <template #failed>\n              <span v-text=\"$t('term.execution_failed')\" />\n            </template>\n            <template #default>\n              <font-awesome-icon icon=\"trash\" />&nbsp;\n              <span v-text=\"$t('term.clear')\" />\n            </template>\n          </sba-action-button-scoped>\n\n          <div class=\"flex-1\">\n            <sba-input\n              v-model=\"filter\"\n              :placeholder=\"$t('term.filter')\"\n              name=\"filter\"\n              type=\"search\"\n            >\n              <template #prepend>\n                <font-awesome-icon icon=\"filter\" />\n              </template>\n              <template #append>\n                <span class=\"button is-static\">\n                  <span v-text=\"filteredCaches.length\" />\n                  /\n                  <span v-text=\"caches.length\" />\n                </span>\n              </template>\n            </sba-input>\n          </div>\n        </div>\n      </sba-sticky-subnav>\n    </template>\n\n    <sba-panel>\n      <caches-list\n        :application=\"application\"\n        :caches=\"filteredCaches\"\n        :instance=\"instance\"\n        :scope=\"scope\"\n        :is-loading=\"isLoading\"\n      />\n    </sba-panel>\n  </sba-instance-section>\n</template>\n\n<script>\nimport { flatMap, isEmpty } from 'lodash-es';\n\nimport { ActionScope } from '@/components/ActionScope';\n\nimport Application from '@/services/application';\nimport Instance from '@/services/instance';\nimport { VIEW_GROUP } from '@/views/ViewGroup';\nimport CachesList from '@/views/instances/caches/caches-list';\nimport SbaInstanceSection from '@/views/instances/shell/sba-instance-section';\n\nconst flattenCaches = (cacheData) => {\n  if (isEmpty(cacheData.cacheManagers)) {\n    return [];\n  }\n  const mappend = flatMap(\n    Object.entries(cacheData.cacheManagers),\n    ([cacheManagerName, v]) =>\n      Object.keys(v.caches).map((cacheName) => ({\n        cacheManager: cacheManagerName,\n        name: cacheName,\n        key: `${cacheManagerName}:${cacheName}`,\n      })),\n  );\n  return mappend.sort((c1, c2) => c1.key.localeCompare(c2.key));\n};\n\nexport default {\n  components: { SbaInstanceSection, CachesList },\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n    application: {\n      type: Application,\n      required: true,\n    },\n  },\n  data: () => ({\n    isLoading: false,\n    error: null,\n    caches: [],\n    filter: '',\n    scope: ActionScope.INSTANCE,\n  }),\n  computed: {\n    filteredCaches() {\n      let filterFn = this.getFilterFn();\n      return filterFn ? this.caches.filter(filterFn) : this.caches;\n    },\n  },\n  created() {\n    this.fetchCaches();\n  },\n  methods: {\n    clearCaches(scope) {\n      if (scope === 'instance') {\n        return this.instance.clearCaches();\n      } else {\n        return this.application.clearCaches();\n      }\n    },\n    async fetchCaches() {\n      this.error = null;\n      this.isLoading = true;\n      try {\n        const res = await this.instance.fetchCaches();\n        this.caches = flattenCaches(res.data);\n      } catch (error) {\n        console.warn('Fetching caches failed:', error);\n        this.error = error;\n      }\n      this.isLoading = false;\n    },\n    getFilterFn() {\n      let filterFn = null;\n\n      if (this.filter) {\n        const normalizedFilter = this.filter.toLowerCase();\n        filterFn = (cache) =>\n          cache.name.toLowerCase().includes(normalizedFilter);\n      }\n\n      return filterFn;\n    },\n    changeScope(scope) {\n      this.scope = scope;\n    },\n  },\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      name: 'instances/caches',\n      parent: 'instances',\n      path: 'caches',\n      label: 'instances.caches.label',\n      group: VIEW_GROUP.DATA,\n      component: this,\n      order: 970,\n      isEnabled: ({ instance }) => instance.hasEndpoint('caches'),\n    });\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/conditions/conditions-list-details.spec.ts",
    "content": "import { screen } from '@testing-library/vue';\nimport { describe, expect, it } from 'vitest';\n\nimport { render } from '@/test-utils';\nimport ConditionsListDetails from '@/views/instances/conditions/conditions-list-details.vue';\n\ndescribe('ConditionsListDetails', () => {\n  it('should display condition', async () => {\n    render(ConditionsListDetails, {\n      props: {\n        condition: {\n          condition: 'SpringBootAdminServerEnabledCondition',\n        },\n      },\n    });\n    expect(\n      await screen.findByLabelText('instances.conditions.condition'),\n    ).toHaveTextContent('SpringBootAdminServerEnabledCondition');\n  });\n\n  it('should display message', async () => {\n    render(ConditionsListDetails, {\n      props: {\n        condition: {\n          message: 'matched',\n        },\n      },\n    });\n    expect(\n      await screen.findByLabelText('instances.conditions.message'),\n    ).toHaveTextContent('matched');\n  });\n\n  it.each`\n    condition\n    ${undefined}\n    ${null}\n    ${''}\n  `(\n    'should not display condition if condition is $condition',\n    async ({ condition }) => {\n      render(ConditionsListDetails, {\n        props: {\n          condition: {\n            condition,\n          },\n        },\n      });\n      expect(\n        screen.queryByLabelText('instances.conditions.condition'),\n      ).not.toBeInTheDocument();\n    },\n  );\n\n  it.each`\n    message\n    ${undefined}\n    ${null}\n    ${''}\n  `(\n    'should not display message if message is $message',\n    async ({ message }) => {\n      render(ConditionsListDetails, {\n        props: {\n          condition: {\n            message,\n          },\n        },\n      });\n      expect(\n        screen.queryByLabelText('instances.conditions.message'),\n      ).not.toBeInTheDocument();\n    },\n  );\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/conditions/conditions-list-details.vue",
    "content": "<!--\n  - Copyright 2014-2024 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <table>\n    <colgroup>\n      <col class=\"text-gray-500\" />\n      <col class=\"py-3\" />\n    </colgroup>\n    <tbody>\n      <!-- Condition -->\n      <tr v-if=\"condition.condition\">\n        <th\n          :id=\"`conditions-label-${id}`\"\n          class=\"label\"\n          v-text=\"$t('instances.conditions.condition')\"\n        />\n        <td\n          :aria-labelledby=\"`conditions-label-${id}`\"\n          class=\"value\"\n          v-text=\"condition.condition\"\n        />\n      </tr>\n\n      <!-- Message -->\n      <tr v-if=\"condition.message\">\n        <th\n          :id=\"`message-label-${id}`\"\n          class=\"label\"\n          v-text=\"$t('instances.conditions.message')\"\n        />\n        <td\n          :aria-labelledby=\"`message-label-${id}`\"\n          class=\"value\"\n          v-text=\"condition.message\"\n        />\n      </tr>\n    </tbody>\n  </table>\n</template>\n\n<script setup lang=\"ts\">\ndefineProps<{\n  condition: {\n    condition: string;\n    message: string;\n  };\n}>();\nconst id = Math.floor(Math.random() * 1_000_000);\n</script>\n\n<style>\n.label {\n  @apply text-gray-500 text-sm text-right px-2 align-top w-32;\n}\n.value {\n  @apply text-sm break-all;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/conditions/conditions-list.vue",
    "content": "<!--\n  - Copyright 2014-2024 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div class=\"-mx-4 -my-3\">\n    <template\n      v-for=\"(conditionalBean, index) in conditionalBeans\"\n      :key=\"conditionalBean.name\"\n    >\n      <div class=\"m-1 border rounded shadow-sm\">\n        <div\n          :key=\"conditionalBean.name\"\n          class=\"flex items-center\"\n          :class=\"{\n            'bg-gray-50':\n              index % 2 === 0 || showDetails[conditionalBean.name] === true,\n            'px-3 pt-2': showDetails[conditionalBean.name] === true,\n            'px-4 py-3': showDetails[conditionalBean.name] !== true,\n          }\"\n        >\n          <div class=\"flex-1 sm:break-all\">\n            <h4\n              :class=\"{\n                'font-bold': showDetails[conditionalBean.name] === true,\n              }\"\n              :title=\"conditionalBean.name\"\n              @click=\"toggle(conditionalBean.name)\"\n              v-text=\"conditionalBean.name\"\n            />\n            <template v-if=\"showDetails[conditionalBean.name] === true\">\n              <h5\n                v-if=\"\n                  conditionalBean.matched.length &&\n                  conditionalBean.notMatched.length\n                \"\n                class=\"px-2 py-1\"\n              >\n                {{ $t('instances.conditions.matched') }}\n              </h5>\n              <template\n                v-for=\"matchedCondition in conditionalBean.matched\"\n                :key=\"matchedCondition.condition\"\n              >\n                <div class=\"py-2 m-1 border rounded\">\n                  <conditions-list-details :condition=\"matchedCondition\" />\n                </div>\n              </template>\n              <h5 v-if=\"conditionalBean.notMatched.length\" class=\"px-2 py-1\">\n                {{ $t('instances.conditions.not-matched') }}\n              </h5>\n              <template\n                v-for=\"notMatchedCondition in conditionalBean.notMatched\"\n                :key=\"notMatchedCondition.condition\"\n              >\n                <div class=\"py-2 m-1 border rounded\">\n                  <conditions-list-details :condition=\"notMatchedCondition\" />\n                </div>\n              </template>\n            </template>\n          </div>\n        </div>\n      </div>\n    </template>\n  </div>\n</template>\n\n<script>\nimport ConditionsListDetails from '@/views/instances/conditions/conditions-list-details.vue';\n\nexport default {\n  components: { ConditionsListDetails },\n  props: {\n    conditionalBeans: {\n      type: Array,\n      default: () => [],\n    },\n  },\n  data() {\n    return {\n      showDetails: {},\n    };\n  },\n  methods: {\n    toggle(name) {\n      if (this.showDetails[name]) {\n        this.showDetails = {\n          ...this.showDetails,\n          [name]: null,\n        };\n      } else {\n        this.showDetails = {\n          ...this.showDetails,\n          [name]: true,\n        };\n      }\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/conditions/i18n.de.json",
    "content": "{\n  \"instances\": {\n    \"conditions\": {\n      \"label\": \"Bedingungen\",\n      \"positive-matches\": \"Übereinstimmungen\",\n      \"negative-matches\": \"Keine Übereinstimmungen\",\n      \"condition\": \"Bedingung\",\n      \"message\": \"Hinweis\",\n      \"not-matched\": \"Nicht übereinstimmend\",\n      \"matched\": \"Übereinstimmend\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/conditions/i18n.en.json",
    "content": "{\n  \"instances\": {\n    \"conditions\": {\n      \"label\": \"Conditions\",\n      \"positive-matches\": \"Positive matches\",\n      \"negative-matches\": \"Negative matches\",\n      \"condition\": \"Condition\",\n      \"message\": \"Message\",\n      \"not-matched\": \"Not matched\",\n      \"matched\": \"Matched\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/conditions/i18n.zh-TW.json",
    "content": "{\n  \"instances\": {\n    \"conditions\": {\n      \"label\": \"條件評估\",\n      \"positive-matches\": \"符合條件\",\n      \"negative-matches\": \"不符合條件\",\n      \"condition\": \"條件\",\n      \"message\": \"訊息\",\n      \"not-matched\": \"不符合\",\n      \"matched\": \"符合\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/conditions/index.vue",
    "content": "<!--\n  - Copyright 2014-2024 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-instance-section :error=\"error\" :loading=\"isLoading\">\n    <template #before>\n      <sba-sticky-subnav>\n        <sba-input\n          v-model=\"filter\"\n          :placeholder=\"$t('term.filter')\"\n          name=\"filter\"\n          type=\"search\"\n        >\n          <template #prepend>\n            <font-awesome-icon icon=\"filter\" />\n          </template>\n          <template #append>\n            {{ filterResultString }}\n          </template>\n        </sba-input>\n      </sba-sticky-subnav>\n    </template>\n\n    <template v-for=\"context in filteredContexts\" :key=\"context.name\">\n      <sba-panel\n        :id=\"`${context.name}-context`\"\n        :header-sticks-below=\"'#subnavigation'\"\n        :title=\"context.name\"\n      >\n        <sba-panel\n          v-if=\"context.positiveMatches.length\"\n          :title=\"$t('instances.conditions.positive-matches')\"\n        >\n          <conditions-list\n            :key=\"`${context.name}-positiveMatches`\"\n            :conditional-beans=\"context.positiveMatches\"\n          />\n        </sba-panel>\n        <sba-panel\n          v-if=\"context.negativeMatches.length\"\n          :title=\"$t('instances.conditions.negative-matches')\"\n        >\n          <conditions-list\n            :key=\"`${context.name}-negativeMatches`\"\n            :conditional-beans=\"context.negativeMatches\"\n          />\n        </sba-panel>\n      </sba-panel>\n    </template>\n  </sba-instance-section>\n</template>\n\n<script>\nimport { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';\nimport { isEmpty } from 'lodash-es';\n\nimport SbaStickySubnav from '@/components/sba-sticky-subnav.vue';\n\nimport Instance from '@/services/instance';\nimport { compareBy } from '@/utils/collections';\nimport { VIEW_GROUP } from '@/views/ViewGroup';\nimport ConditionsList from '@/views/instances/conditions/conditions-list.vue';\nimport SbaInstanceSection from '@/views/instances/shell/sba-instance-section';\n\nclass ConditionalBean {\n  constructor(name, positiveMatches, negativeMatches) {\n    this.name = name;\n    this.matched = positiveMatches.map((condition) => new Condition(condition));\n    this.notMatched = negativeMatches.map(\n      (condition) => new Condition(condition),\n    );\n  }\n}\n\nclass Condition {\n  constructor(condition) {\n    this.condition = condition.condition;\n    this.message = condition.message;\n  }\n}\n\nconst mapPositiveMatches = (positiveMatches) => {\n  return Object.keys(positiveMatches).map(\n    (matchedBeanName) =>\n      new ConditionalBean(\n        matchedBeanName,\n        positiveMatches[matchedBeanName],\n        [],\n      ),\n  );\n};\n\nconst mapNegativeMatches = (negativeMatches) => {\n  return Object.keys(negativeMatches).map(\n    (matchedBeanName) =>\n      new ConditionalBean(\n        matchedBeanName,\n        negativeMatches[matchedBeanName].matched,\n        negativeMatches[matchedBeanName].notMatched,\n      ),\n  );\n};\n\nconst mapContexts = (conditionsData) => {\n  if (isEmpty(conditionsData.contexts)) {\n    return [];\n  }\n  return Object.keys(conditionsData.contexts).map((contextName) => ({\n    positiveMatches: mapPositiveMatches(\n      conditionsData.contexts[contextName].positiveMatches,\n    ),\n    negativeMatches: mapNegativeMatches(\n      conditionsData.contexts[contextName].negativeMatches,\n    ),\n    name: contextName,\n    parent: conditionsData.contexts[contextName].parentId,\n  }));\n};\n\nexport default {\n  components: {\n    FontAwesomeIcon,\n    SbaStickySubnav,\n    ConditionsList,\n    SbaInstanceSection,\n  },\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  data: () => ({\n    isLoading: false,\n    error: null,\n    contexts: [],\n    filter: '',\n  }),\n  computed: {\n    filterResultString() {\n      const totalMatches = this.contexts.reduce((count, ctx) => {\n        return (\n          count + ctx.positiveMatches?.length + ctx.negativeMatches?.length\n        );\n      }, 0);\n\n      const filteredMatches = this.filteredContexts.reduce((count, ctx) => {\n        return (\n          count + ctx.positiveMatches?.length + ctx.negativeMatches?.length\n        );\n      }, 0);\n\n      return `${filteredMatches}/${totalMatches}`;\n    },\n    filteredContexts() {\n      const filterFn = this.getFilterFn();\n      return this.contexts.map((ctx) => ({\n        ...ctx,\n        positiveMatches: ctx.positiveMatches\n          .filter(filterFn)\n          .sort(compareBy((conditionalBean) => conditionalBean.name)),\n        negativeMatches: ctx.negativeMatches\n          .filter(filterFn)\n          .sort(compareBy((conditionalBean) => conditionalBean.name)),\n      }));\n    },\n  },\n  created() {\n    this.fetchConditions();\n  },\n  methods: {\n    getFilterFn() {\n      if (!this.filter) {\n        return () => true;\n      }\n      const regex = new RegExp(this.filter, 'i');\n      return (conditionalBean) => conditionalBean.name.match(regex);\n    },\n    async fetchConditions() {\n      this.error = null;\n      this.isLoading = true;\n      try {\n        const res = await this.instance.fetchConditions();\n        this.contexts = mapContexts(res.data);\n      } catch (error) {\n        console.warn('Fetching conditions failed:', error);\n        this.error = error;\n      }\n      this.isLoading = false;\n    },\n  },\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      name: 'instances/conditions',\n      parent: 'instances',\n      path: 'conditions',\n      label: 'instances.conditions.label',\n      group: VIEW_GROUP.INSIGHTS,\n      component: this,\n      order: 120,\n      isEnabled: ({ instance }) => instance.hasEndpoint('conditions'),\n    });\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/configprops/i18n.de.json",
    "content": "{\n  \"instances\": {\n    \"configprops\": {\n      \"label\": \"Configuration Properties\",\n      \"no_properties_set\": \"Keine Konfiguration gefunden.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/configprops/i18n.en.json",
    "content": "{\n  \"instances\": {\n    \"configprops\": {\n      \"label\": \"Configuration Properties\",\n\n      \"no_properties_set\": \"No properties set\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/configprops/i18n.es.json",
    "content": "{\n  \"instances\": {\n    \"configprops\": {\n      \"label\": \"Configuración\",\n\n      \"no_properties_set\": \"No hay propiedades configuradas\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/configprops/i18n.fr.json",
    "content": "{\n  \"instances\": {\n    \"configprops\": {\n      \"label\": \"Propriétés de configuration\",\n\n      \"no_properties_set\": \"Aucune propriétés définies\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/configprops/i18n.is.json",
    "content": "{\n  \"instances\": {\n    \"configprops\": {\n      \"label\": \"Stillingareiginleikar\",\n\n      \"no_properties_set\": \"Engin stilling í boði.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/configprops/i18n.ko.json",
    "content": "{\n  \"instances\": {\n    \"configprops\": {\n      \"label\": \"구성 속성(Configuration Properties)\",\n\n      \"no_properties_set\": \"설정된 구성 속성 정보가 없습니다.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/configprops/i18n.pt-BR.json",
    "content": "{\n  \"instances\": {\n    \"configprops\": {\n      \"label\": \"Propriedades de Configuração\",\n\n      \"no_properties_set\": \"Nenhuma propriedade definida\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/configprops/i18n.ru.json",
    "content": "{\n  \"instances\": {\n    \"configprops\": {\n      \"label\": \"Свойства конфигураций\",\n\n      \"no_properties_set\": \"Нет установленных конфигураций\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/configprops/i18n.zh-CN.json",
    "content": "{\n  \"instances\": {\n    \"configprops\": {\n      \"label\": \"配置属性\",\n\n      \"no_properties_set\": \"未设置属性\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/configprops/i18n.zh-TW.json",
    "content": "{\n  \"instances\": {\n    \"configprops\": {\n      \"label\": \"設定屬性\",\n\n      \"no_properties_set\": \"未設定屬性\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/configprops/index.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-instance-section :error=\"error\" :loading=\"!hasLoaded\">\n    <template #before>\n      <sba-sticky-subnav>\n        <sba-input\n          v-model=\"filter\"\n          :placeholder=\"$t('term.filter')\"\n          name=\"filter\"\n          type=\"search\"\n        >\n          <template #prepend>\n            <font-awesome-icon icon=\"filter\" />\n          </template>\n        </sba-input>\n      </sba-sticky-subnav>\n    </template>\n\n    <sba-panel\n      v-for=\"bean in configurationPropertiesBeans\"\n      :key=\"bean.name\"\n      :header-sticks-below=\"'#subnavigation'\"\n      :title=\"bean.name\"\n    >\n      <div class=\"-mx-4 -my-3\">\n        <table\n          v-if=\"Object.keys(bean.properties).length > 0\"\n          class=\"table-auto w-full\"\n        >\n          <tr\n            v-for=\"(value, name, idx) in bean.properties\"\n            :key=\"`${bean.name}-${name}`\"\n            :class=\"{ 'bg-gray-50': idx % 2 === 0 }\"\n          >\n            <td class=\"w-1/2 px-4 py-3\" v-text=\"name\" />\n            <td class=\"px-4 py-3\" v-text=\"value\" />\n          </tr>\n        </table>\n      </div>\n    </sba-panel>\n  </sba-instance-section>\n</template>\n\n<script>\nimport { isEmpty, mapKeys, pickBy } from 'lodash-es';\n\nimport Instance from '@/services/instance';\nimport { VIEW_GROUP } from '@/views/ViewGroup';\nimport SbaInstanceSection from '@/views/instances/shell/sba-instance-section';\n\nconst filterProperty = (needle) => (value, name) => {\n  return (\n    name.toString().toLowerCase().includes(needle) ||\n    (value && value.toString().toLowerCase().includes(needle))\n  );\n};\nconst filterProperties = (needle, properties) =>\n  pickBy(properties, filterProperty(needle));\nconst filterConfigurationProperties = (needle) => (propertySource) => {\n  if (!propertySource || !propertySource.properties) {\n    return null;\n  }\n  return {\n    ...propertySource,\n    properties: filterProperties(needle, propertySource.properties),\n  };\n};\n\nfunction flattenBean(obj, prefix = '') {\n  if (Object(obj) !== obj) {\n    return { [prefix]: obj };\n  }\n\n  if (Array.isArray(obj)) {\n    if (obj.length === 0) {\n      return { [prefix]: [] };\n    } else {\n      return obj\n        .map((value, idx) => flattenBean(value, `${prefix}[${idx}]`))\n        .reduce((c, n) => ({ ...c, ...n }), {});\n    }\n  } else {\n    if (isEmpty(obj)) {\n      return { [prefix]: {} };\n    } else {\n      return Object.entries(obj)\n        .map(([name, value]) =>\n          flattenBean(value, prefix ? `${prefix}.${name}` : name),\n        )\n        .reduce((c, n) => ({ ...c, ...n }), {});\n    }\n  }\n}\n\nconst flattenConfigurationPropertiesBeans = (configprops) => {\n  const propertySources = [];\n  const contextNames = Object.keys(configprops.contexts);\n\n  for (const contextName of contextNames) {\n    const context = configprops.contexts[contextName];\n    const beanNames = Object.keys(context.beans);\n\n    for (const beanName of beanNames) {\n      const bean = context.beans[beanName];\n      const properties = mapKeys(\n        flattenBean(bean.properties),\n        (value, key) => `${bean.prefix}.${key}`,\n      );\n      propertySources.push({\n        name:\n          contextNames.length > 1 ? `${contextName}: ${beanName}` : beanName,\n        properties,\n      });\n    }\n  }\n\n  return propertySources;\n};\n\nexport default {\n  components: { SbaInstanceSection },\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  data: () => ({\n    hasLoaded: false,\n    error: null,\n    configprops: null,\n    filter: null,\n  }),\n  computed: {\n    configurationPropertiesBeans() {\n      if (!this.configprops) {\n        return [];\n      }\n      const configurationProperties = flattenConfigurationPropertiesBeans(\n        this.configprops,\n      );\n      if (!this.filter) {\n        return configurationProperties;\n      }\n      return configurationProperties\n        .map(filterConfigurationProperties(this.filter.toLowerCase()))\n        .filter((ps) => ps && Object.keys(ps.properties).length > 0);\n    },\n  },\n  created() {\n    this.fetchConfigprops();\n  },\n  methods: {\n    async fetchConfigprops() {\n      this.error = null;\n      try {\n        const configprops = await this.instance.fetchConfigprops();\n        this.configprops = configprops.data;\n      } catch (error) {\n        console.warn('Fetching configuration properties failed:', error);\n        this.error = error;\n      }\n      this.hasLoaded = true;\n    },\n  },\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      name: 'instances/configprops',\n      parent: 'instances',\n      path: 'configprops',\n      component: this,\n      label: 'instances.configprops.label',\n      group: VIEW_GROUP.INSIGHTS,\n      order: 110,\n      isEnabled: ({ instance }) => instance.hasEndpoint('configprops'),\n    });\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/dependencies/SbomList.vue",
    "content": "<template>\n  <sba-instance-section :error=\"error\" :loading=\"!hasLoaded\">\n    <sba-panel\n      :key=\"sbomId\"\n      :header-sticks-below=\"'#subnavigation'\"\n      :title=\"`${sbomId} (${filterResultString})`\"\n    >\n      <div class=\"-mx-4 -my-3\">\n        <table class=\"table table-full table-sm\">\n          <thead data-testid=\"sbom-table-header\">\n            <tr>\n              <template v-for=\"column in columns\" :key=\"column.property\">\n                <th\n                  v-if=\"column.sortable\"\n                  class=\"table-header-clickable\"\n                  @click=\"toggleSort(column.property)\"\n                >\n                  {{\n                    $t(`instances.dependencies.list.header.${column.property}`)\n                  }}\n                  <font-awesome-icon\n                    v-if=\"sortedBy[column.property]\"\n                    :data-testid=\"`sorted-icon-${column.property}-${sortedBy[column.property]}`\"\n                    icon=\"chevron-down\"\n                    :class=\"{\n                      '-rotate-180': sortedBy[column.property] === 'DESC',\n                      'transition-[transform]': true,\n                    }\"\n                  />\n                </th>\n                <th v-else>\n                  {{\n                    $t(`instances.dependencies.list.header.${column.property}`)\n                  }}\n                </th>\n              </template>\n            </tr>\n          </thead>\n          <tbody data-testid=\"sbom-table-body\">\n            <template\n              v-for=\"component in filteredAndSortedComponents\"\n              :key=\"`${component.group}:${component.name}:${component.version}`\"\n            >\n              <tr data-testid=\"sbom-table-body-row\">\n                <td>{{ component.group }}</td>\n                <td>{{ component.name }}</td>\n                <td>{{ component.version }}</td>\n                <td>\n                  <dl\n                    v-if=\"component.licenses\"\n                    class=\"divide-y divide-gray-200\"\n                  >\n                    <template\n                      v-for=\"licenseItem in component.licenses\n                        .filter((license) => license.license)\n                        .map((license) => license.license)\"\n                      :key=\"licenseItem.id ? licenseItem.id : licenseItem.name\"\n                    >\n                      <dt v-if=\"licenseItem.url\">\n                        <a :href=\"licenseItem.url\">{{\n                          licenseItem.id ? licenseItem.id : licenseItem.name\n                        }}</a>\n                      </dt>\n                      <dt v-else>\n                        {{ licenseItem.id ? licenseItem.id : licenseItem.name }}\n                      </dt>\n                    </template>\n                  </dl>\n                </td>\n                <td>{{ component.publisher }}</td>\n                <td>{{ component.description }}</td>\n              </tr>\n            </template>\n          </tbody>\n        </table>\n      </div>\n    </sba-panel>\n  </sba-instance-section>\n</template>\n<script>\nimport { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';\n\nimport Instance from '@/services/instance';\nimport SbaInstanceSection from '@/views/instances/shell/sba-instance-section.vue';\n\nexport default {\n  name: 'SbomList',\n  components: {\n    FontAwesomeIcon,\n    SbaInstanceSection,\n  },\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n    sbomId: {\n      type: String,\n      required: true,\n    },\n    filter: {\n      type: String,\n      required: true,\n    },\n  },\n  data: () => ({\n    hasLoaded: false,\n    error: null,\n    components: [],\n    sortedBy: {\n      group: 'ASC',\n      name: 'ASC',\n      version: 'ASC',\n    },\n    columns: [\n      {\n        property: 'group',\n        sortable: true,\n      },\n      {\n        property: 'name',\n        sortable: true,\n      },\n      {\n        property: 'version',\n        sortable: true,\n      },\n      {\n        property: 'licenses',\n        sortable: false,\n      },\n      {\n        property: 'publisher',\n        sortable: true,\n      },\n      {\n        property: 'description',\n        sortable: false,\n      },\n    ],\n  }),\n  computed: {\n    filterResultString() {\n      return `${this.filteredAndSortedComponents.length}/${this.components.length}`;\n    },\n    filteredAndSortedComponents() {\n      const filterFn = this.getFilterFn();\n      return this.sortFn(this.components.filter(filterFn));\n    },\n  },\n  created() {\n    this.fetchSbom(this.sbomId);\n  },\n  methods: {\n    sortFn(components) {\n      if (Object.keys(this.sortedBy).length === 0) {\n        return components;\n      }\n\n      const sortFunctions = {};\n      for (const property in this.sortedBy) {\n        const order = this.sortedBy[property];\n        sortFunctions[property] = function (a, b) {\n          if (a[property] && !b[property]) {\n            return 1;\n          } else if (!a[property] && b[property]) {\n            return -1;\n          } else if (!a[property] && !b[property]) {\n            return 0;\n          }\n\n          const compareResult = a[property].localeCompare(b[property]);\n          return order === 'ASC' ? compareResult : compareResult * -1;\n        };\n      }\n      return components.sort((a, b) => {\n        for (const property in sortFunctions) {\n          const compareResult = sortFunctions[property](a, b);\n          if (compareResult !== 0) {\n            return compareResult;\n          }\n        }\n        return 0;\n      });\n    },\n    toggleSort(field) {\n      if (this.sortedBy[field] && this.sortedBy[field] === 'DESC') {\n        delete this.sortedBy[field];\n      } else if (this.sortedBy[field]) {\n        this.sortedBy[field] = 'DESC';\n      } else {\n        this.sortedBy[field] = 'ASC';\n      }\n    },\n    getFilterFn() {\n      if (!this.filter) {\n        return () => true;\n      }\n      const regex = new RegExp(this.filter, 'i');\n      return (component) =>\n        (component.name && component.name.match(regex)) ||\n        (component.group && component.group.match(regex)) ||\n        (component.version && component.version.match(regex)) ||\n        (component.publisher && component.publisher.match(regex)) ||\n        (component.licenses &&\n          component.licenses.some(\n            (license) =>\n              license.license && JSON.stringify(license.license).match(regex),\n          ));\n    },\n    async fetchSbom(sbomId) {\n      this.error = null;\n      try {\n        const res = await this.instance.fetchSbom(sbomId);\n        this.components = res.data.components;\n      } catch (error) {\n        console.warn('Fetching sbom failed:', error);\n        this.error = error;\n      }\n      this.hasLoaded = true;\n    },\n  },\n};\n</script>\n<style lang=\"scss\" scoped>\n.table-header-clickable {\n  cursor: pointer;\n}\n\ndt > a:hover {\n  text-decoration: underline;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/dependencies/i18n.de.json",
    "content": "{\n  \"instances\": {\n    \"dependencies\": {\n      \"label\": \"Abhängigkeiten\",\n      \"no_data_provided\": \"Es wurden keine Daten bereitgestellt. Bitte überprüfen Sie, ob Sie die software bill of materials (SBOM) korrekt konfiguriert haben.\",\n      \"list\": {\n        \"header\": {\n          \"group\": \"Gruppe\",\n          \"name\": \"Name\",\n          \"version\": \"Version\",\n          \"publisher\": \"Veröffentlicht von\",\n          \"description\": \"Beschreibung\",\n          \"licenses\": \"Lizenzen\"\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/dependencies/i18n.en.json",
    "content": "{\n  \"instances\": {\n    \"dependencies\": {\n      \"label\": \"Dependencies\",\n      \"no_data_provided\": \"No data provided. Please check if you have configured software bill of materials (SBOM) correctly.\",\n      \"list\": {\n        \"header\": {\n          \"group\": \"Group\",\n          \"name\": \"Name\",\n          \"version\": \"Version\",\n          \"publisher\": \"Publisher\",\n          \"description\": \"Description\",\n          \"licenses\": \"Licenses\"\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/dependencies/i18n.zh-TW.json",
    "content": "{\n  \"instances\": {\n    \"dependencies\": {\n      \"label\": \"相依元件\",\n      \"list\": {\n        \"header\": {\n          \"group\": \"群組\",\n          \"name\": \"名稱\",\n          \"version\": \"版本\",\n          \"publisher\": \"發布者\",\n          \"description\": \"說明\",\n          \"licenses\": \"授權條款\"\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/dependencies/index.spec.ts",
    "content": "import userEvent from '@testing-library/user-event';\nimport { screen } from '@testing-library/vue';\nimport { HttpResponse, http } from 'msw';\nimport { beforeEach, describe, expect, it } from 'vitest';\n\nimport { applications } from '@/mocks/applications/data';\nimport { server } from '@/mocks/server';\nimport Application from '@/services/application';\nimport { render } from '@/test-utils';\nimport Dependencies from '@/views/instances/dependencies/index.vue';\n\ndescribe('Dependencies', () => {\n  const GROUP_TABLE_COLUMN_INDEX = 0;\n  const NAME_TABLE_COLUMN_INDEX = 1;\n  const VERSION_TABLE_COLUMN_INDEX = 2;\n  const LICENSES_TABLE_COLUMN_INDEX = 3;\n  const PUBLISHER_TABLE_COLUMN_INDEX = 4;\n  const DESCRIPTION_TABLE_COLUMN_INDEX = 5;\n\n  const application = new Application(applications[0]);\n  const instance = application.instances[0];\n\n  describe('Render correctly', () => {\n    beforeEach(() => {\n      render(Dependencies, {\n        props: {\n          instance,\n        },\n      });\n    });\n\n    it('both sbom ids', async () => {\n      expect(await screen.findByText(/application \\(63\\/63\\)/)).toBeVisible();\n\n      expect(await screen.findByText(/system \\(63\\/63\\)/)).toBeVisible();\n    });\n\n    it('table shows correct information', async () => {\n      // header order\n      const tableHeaders = await screen.findAllByTestId('sbom-table-header');\n      tableHeaders.forEach((tableHeader) => {\n        // only one header row\n        expect(tableHeader.children.length).toBe(1);\n\n        // having exactly this order\n        expect(\n          tableHeader.children[0].children[GROUP_TABLE_COLUMN_INDEX]\n            .textContent,\n        ).toContain('instances.dependencies.list.header.group');\n        expect(\n          tableHeader.children[0].children[NAME_TABLE_COLUMN_INDEX].textContent,\n        ).toContain('instances.dependencies.list.header.name');\n        expect(\n          tableHeader.children[0].children[VERSION_TABLE_COLUMN_INDEX]\n            .textContent,\n        ).toContain('instances.dependencies.list.header.version');\n        expect(\n          tableHeader.children[0].children[LICENSES_TABLE_COLUMN_INDEX]\n            .textContent,\n        ).toContain('instances.dependencies.list.header.licenses');\n        expect(\n          tableHeader.children[0].children[PUBLISHER_TABLE_COLUMN_INDEX]\n            .textContent,\n        ).toContain('instances.dependencies.list.header.publisher');\n        expect(\n          tableHeader.children[0].children[DESCRIPTION_TABLE_COLUMN_INDEX]\n            .textContent,\n        ).toContain('instances.dependencies.list.header.description');\n      });\n\n      // columns should be filled with values matching headers\n      const log4jRows = await screen.findAllByText('log4j-api');\n      log4jRows.forEach((row) => {\n        const log4jRow = row.parentElement;\n        expect(\n          log4jRow.children[GROUP_TABLE_COLUMN_INDEX].textContent,\n        ).toContain('org.apache.logging.log4j');\n        expect(\n          log4jRow.children[NAME_TABLE_COLUMN_INDEX].textContent,\n        ).toContain('log4j-api');\n        expect(\n          log4jRow.children[VERSION_TABLE_COLUMN_INDEX].textContent,\n        ).toContain('2.23.1');\n        expect(\n          log4jRow.children[LICENSES_TABLE_COLUMN_INDEX].textContent,\n        ).toContain('Apache-2.0');\n        expect(\n          log4jRow.children[PUBLISHER_TABLE_COLUMN_INDEX].textContent,\n        ).toContain('The Apache Software Foundation');\n        expect(\n          log4jRow.children[DESCRIPTION_TABLE_COLUMN_INDEX].textContent,\n        ).toContain('The Apache Log4j API');\n      });\n    });\n  });\n\n  describe('filter correctly', () => {\n    beforeEach(() => {\n      render(Dependencies, {\n        props: {\n          instance,\n        },\n      });\n    });\n\n    it('for dependency name', async () => {\n      const filterInput = screen.getByLabelText('Filter');\n      await userEvent.type(filterInput, 'log4j-api');\n\n      expect(await screen.findByText(/application \\(1\\/63\\)/)).toBeVisible();\n\n      expect(await screen.findByText(/system \\(1\\/63\\)/)).toBeVisible();\n\n      (await screen.findAllByTestId('sbom-table-body')).forEach((tbody) => {\n        expect(tbody.children.length).toBe(1);\n      });\n    });\n\n    it('for dependency group', async () => {\n      const filterInput = screen.getByLabelText('Filter');\n      await userEvent.type(filterInput, 'org.apache.logging.log4j');\n\n      expect(await screen.findByText(/application \\(2\\/63\\)/)).toBeVisible();\n\n      expect(await screen.findByText(/system \\(2\\/63\\)/)).toBeVisible();\n\n      (await screen.findAllByTestId('sbom-table-body')).forEach((tbody) => {\n        expect(tbody.children.length).toBe(2);\n      });\n    });\n\n    it('for dependency version', async () => {\n      const filterInput = screen.getByLabelText('Filter');\n      await userEvent.type(filterInput, '2.17.0');\n\n      expect(await screen.findByText(/application \\(6\\/63\\)/)).toBeVisible();\n\n      expect(await screen.findByText(/system \\(6\\/63\\)/)).toBeVisible();\n\n      (await screen.findAllByTestId('sbom-table-body')).forEach((tbody) => {\n        expect(tbody.children.length).toBe(6);\n      });\n    });\n\n    it('for dependency license', async () => {\n      const filterInput = screen.getByLabelText('Filter');\n      await userEvent.type(filterInput, 'BSD-3-Clause');\n\n      expect(await screen.findByText(/application \\(3\\/63\\)/)).toBeVisible();\n\n      expect(await screen.findByText(/system \\(3\\/63\\)/)).toBeVisible();\n\n      (await screen.findAllByTestId('sbom-table-body')).forEach((tbody) => {\n        expect(tbody.children.length).toBe(3);\n      });\n    });\n\n    it('for dependency publisher', async () => {\n      const filterInput = screen.getByLabelText('Filter');\n      await userEvent.type(filterInput, 'Eclipse Foundation');\n\n      expect(await screen.findByText(/application \\(4\\/63\\)/)).toBeVisible();\n\n      expect(await screen.findByText(/system \\(4\\/63\\)/)).toBeVisible();\n\n      (await screen.findAllByTestId('sbom-table-body')).forEach((tbody) => {\n        expect(tbody.children.length).toBe(4);\n      });\n    });\n  });\n\n  describe('sort correctly', () => {\n    beforeEach(() => {\n      server.use(\n        http.get('/instances/:instanceId/actuator/sbom', () => {\n          return HttpResponse.json({\n            ids: ['application'],\n          });\n        }),\n        http.get('/instances/:instanceId/actuator/sbom/application', () => {\n          return HttpResponse.json({\n            components: [\n              {\n                publisher: 'CCC',\n                group: 'c.cccc.ccc',\n                name: 'ccc',\n                version: '3.0.0',\n                description: 'C description',\n                licenses: [\n                  {\n                    license: {\n                      id: 'C',\n                    },\n                  },\n                ],\n              },\n              {\n                publisher: 'AAA',\n                group: 'a.aaaa.aaa',\n                name: 'aaa',\n                version: '1.0.0',\n                description: 'A description',\n                licenses: [\n                  {\n                    license: {\n                      id: 'Apache-2.0',\n                    },\n                  },\n                ],\n              },\n              {\n                publisher: 'BBB',\n                group: 'b.bbbb.bbb',\n                name: 'bbb',\n                version: '2.0.0',\n                description: 'B description',\n                licenses: [\n                  {\n                    license: {\n                      id: 'BSD',\n                    },\n                  },\n                ],\n              },\n            ],\n          });\n        }),\n      );\n\n      render(Dependencies, {\n        props: {\n          instance,\n        },\n      });\n    });\n\n    const resetDefaultSort = async () => {\n      for (const header of await screen.findAllByTestId('sbom-table-header')) {\n        const tableHeaderColumns = header.children[0].children;\n        await userEvent.click(tableHeaderColumns[GROUP_TABLE_COLUMN_INDEX]);\n        await userEvent.click(tableHeaderColumns[GROUP_TABLE_COLUMN_INDEX]);\n        await userEvent.click(tableHeaderColumns[NAME_TABLE_COLUMN_INDEX]);\n        await userEvent.click(tableHeaderColumns[NAME_TABLE_COLUMN_INDEX]);\n        await userEvent.click(tableHeaderColumns[VERSION_TABLE_COLUMN_INDEX]);\n        await userEvent.click(tableHeaderColumns[VERSION_TABLE_COLUMN_INDEX]);\n      }\n\n      expect(\n        await screen.queryByTestId('sorted-icon-group-ASC'),\n      ).not.toBeInTheDocument();\n      expect(\n        await screen.queryByTestId('sorted-icon-name-ASC'),\n      ).not.toBeInTheDocument();\n      expect(\n        await screen.queryByTestId('sorted-icon-version-ASC'),\n      ).not.toBeInTheDocument();\n    };\n\n    it('initial sort by group, name and version', async () => {\n      expect(\n        (await screen.findAllByTestId('sorted-icon-group-ASC'))[0],\n      ).toBeVisible();\n      expect(\n        (await screen.findAllByTestId('sorted-icon-name-ASC'))[0],\n      ).toBeVisible();\n      expect(\n        (await screen.findAllByTestId('sorted-icon-version-ASC'))[0],\n      ).toBeVisible();\n\n      const dependencyRows = await screen.findAllByTestId(\n        'sbom-table-body-row',\n      );\n      expect(\n        dependencyRows[0].children[GROUP_TABLE_COLUMN_INDEX].textContent,\n      ).toContain('a.aaaa.aaa');\n      expect(\n        dependencyRows[0].children[NAME_TABLE_COLUMN_INDEX].textContent,\n      ).toContain('aaa');\n      expect(\n        dependencyRows[0].children[VERSION_TABLE_COLUMN_INDEX].textContent,\n      ).toContain('1.0.0');\n\n      expect(\n        dependencyRows[1].children[GROUP_TABLE_COLUMN_INDEX].textContent,\n      ).toContain('b.bbbb.bbb');\n      expect(\n        dependencyRows[1].children[NAME_TABLE_COLUMN_INDEX].textContent,\n      ).toContain('bbb');\n      expect(\n        dependencyRows[1].children[VERSION_TABLE_COLUMN_INDEX].textContent,\n      ).toContain('2.0.0');\n\n      expect(\n        dependencyRows[2].children[GROUP_TABLE_COLUMN_INDEX].textContent,\n      ).toContain('c.cccc.ccc');\n      expect(\n        dependencyRows[2].children[NAME_TABLE_COLUMN_INDEX].textContent,\n      ).toContain('ccc');\n      expect(\n        dependencyRows[2].children[VERSION_TABLE_COLUMN_INDEX].textContent,\n      ).toContain('3.0.0');\n    });\n\n    it.each`\n      property       | tableColumnIndex                | expectedValues\n      ${'group'}     | ${GROUP_TABLE_COLUMN_INDEX}     | ${['a.aaaa.aaa', 'b.bbbb.bbb', 'c.cccc.ccc']}\n      ${'name'}      | ${NAME_TABLE_COLUMN_INDEX}      | ${['aaa', 'bbb', 'ccc']}\n      ${'version'}   | ${VERSION_TABLE_COLUMN_INDEX}   | ${['1.0.0', '2.0.0', '3.0.0']}\n      ${'publisher'} | ${PUBLISHER_TABLE_COLUMN_INDEX} | ${['AAA', 'BBB', 'CCC']}\n    `(\n      'by $property ASC',\n      async ({ property, tableColumnIndex, expectedValues }) => {\n        await resetDefaultSort();\n\n        // activate sort ASC\n        const tableHeaderColumns = (\n          await screen.findAllByTestId('sbom-table-header')\n        )[0].children[0].children;\n        await userEvent.click(tableHeaderColumns[tableColumnIndex]);\n\n        expect(\n          (await screen.findAllByTestId(`sorted-icon-${property}-ASC`))[0],\n        ).toBeVisible();\n\n        // check sort\n        const dependencyRows = await screen.findAllByTestId(\n          'sbom-table-body-row',\n        );\n\n        expect(\n          dependencyRows[0].children[tableColumnIndex].textContent,\n        ).toContain(expectedValues[0]);\n        expect(\n          dependencyRows[1].children[tableColumnIndex].textContent,\n        ).toContain(expectedValues[1]);\n        expect(\n          dependencyRows[2].children[tableColumnIndex].textContent,\n        ).toContain(expectedValues[2]);\n      },\n    );\n\n    it.each`\n      property       | tableColumnIndex                | expectedValues\n      ${'group'}     | ${GROUP_TABLE_COLUMN_INDEX}     | ${['a.aaaa.aaa', 'b.bbbb.bbb', 'c.cccc.ccc'].reverse()}\n      ${'name'}      | ${NAME_TABLE_COLUMN_INDEX}      | ${['aaa', 'bbb', 'ccc'].reverse()}\n      ${'version'}   | ${VERSION_TABLE_COLUMN_INDEX}   | ${['1.0.0', '2.0.0', '3.0.0'].reverse()}\n      ${'publisher'} | ${PUBLISHER_TABLE_COLUMN_INDEX} | ${['AAA', 'BBB', 'CCC'].reverse()}\n    `(\n      'by $property DESC',\n      async ({ property, tableColumnIndex, expectedValues }) => {\n        await resetDefaultSort();\n\n        // activate sort DESC\n        const tableHeaderColumns = (\n          await screen.findAllByTestId('sbom-table-header')\n        )[0].children[0].children;\n        await userEvent.click(tableHeaderColumns[tableColumnIndex]);\n        await userEvent.click(tableHeaderColumns[tableColumnIndex]);\n\n        expect(\n          (await screen.findAllByTestId(`sorted-icon-${property}-DESC`))[0],\n        ).toBeVisible();\n\n        // check sort\n        const dependencyRows = await screen.findAllByTestId(\n          'sbom-table-body-row',\n        );\n\n        expect(\n          dependencyRows[0].children[tableColumnIndex].textContent,\n        ).toContain(expectedValues[0]);\n        expect(\n          dependencyRows[1].children[tableColumnIndex].textContent,\n        ).toContain(expectedValues[1]);\n        expect(\n          dependencyRows[2].children[tableColumnIndex].textContent,\n        ).toContain(expectedValues[2]);\n      },\n    );\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/dependencies/index.vue",
    "content": "<template>\n  <sba-instance-section :error=\"error\" :loading=\"!hasLoaded\">\n    <template #before>\n      <sba-sticky-subnav>\n        <sba-input\n          v-model=\"filter\"\n          :placeholder=\"$t('term.filter')\"\n          name=\"filter\"\n          type=\"search\"\n        >\n          <template #prepend>\n            <font-awesome-icon icon=\"filter\" />\n          </template>\n        </sba-input>\n      </sba-sticky-subnav>\n    </template>\n    <template v-if=\"sboms.length === 0\">\n      <sba-alert\n        severity=\"WARN\"\n        :error=\"$t('instances.dependencies.no_data_provided')\"\n      />\n    </template>\n    <template v-for=\"sbomId in sboms\" v-else :key=\"sbomId\">\n      <sbom-list :instance=\"instance\" :sbom-id=\"sbomId\" :filter=\"filter\" />\n    </template>\n  </sba-instance-section>\n</template>\n<script setup>\nimport { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';\nimport { onMounted, ref } from 'vue';\n\nimport SbaAlert from '@/components/sba-alert.vue';\nimport SbaStickySubnav from '@/components/sba-sticky-subnav.vue';\n\nimport Instance from '@/services/instance';\nimport SbomList from '@/views/instances/dependencies/SbomList.vue';\nimport SbaInstanceSection from '@/views/instances/shell/sba-instance-section.vue';\n\nconst props = defineProps({\n  instance: {\n    type: Instance,\n    required: true,\n  },\n});\n\nconst hasLoaded = ref(false);\nconst error = ref(null);\nconst sboms = ref([]);\nconst filter = ref('');\n\nconst fetchSbomIds = async () => {\n  error.value = null;\n  try {\n    const res = await props.instance.fetchSbomIds();\n    sboms.value = res.data.ids;\n  } catch (err) {\n    console.warn('Fetching sbom ids failed:', err);\n    error.value = err;\n  }\n  hasLoaded.value = true;\n};\n\nonMounted(() => {\n  fetchSbomIds();\n});\n</script>\n\n<script>\nimport { VIEW_GROUP } from '@/views/ViewGroup';\n\nexport default {\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      name: 'instances/dependencies',\n      parent: 'instances',\n      path: 'dependencies',\n      label: 'instances.dependencies.label',\n      group: VIEW_GROUP.DEPENDENCIES,\n      order: 1,\n      component: this,\n      isEnabled: ({ instance }) => instance.hasEndpoint('sbom'),\n    });\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/LineChart.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <canvas id=\"chart\" ref=\"chart\" />\n</template>\n\n<script>\nimport Chart from 'chart.js/auto';\nimport { merge } from 'lodash-es';\nimport { shallowRef } from 'vue';\nimport { useI18n } from 'vue-i18n';\n\nexport default {\n  props: {\n    data: {\n      type: Array,\n      required: true,\n    },\n    label: {\n      type: String,\n      required: true,\n    },\n    datasets: {\n      type: Object,\n      required: true,\n    },\n    config: {\n      type: Object,\n      required: true,\n    },\n  },\n  setup(props) {\n    const { t } = useI18n();\n    return { ...props, t };\n  },\n  data() {\n    return {\n      chart: undefined,\n      colors: [\n        {\n          borderColor: 'rgb(255, 224, 138)',\n          backgroundColor: 'rgba(255, 224, 138, .1)',\n        },\n        {\n          borderColor: 'rgb(62, 142, 208)',\n          backgroundColor: 'rgba(62, 142, 208, .1)',\n        },\n        {\n          borderColor: 'rgb(0, 209, 178)',\n          backgroundColor: 'rgba(0, 209, 178, .1)',\n        },\n      ],\n    };\n  },\n  watch: {\n    '$i18n.locale': function () {\n      this.chart.update();\n    },\n    data: {\n      handler(newData) {\n        if (this.chart) {\n          const data = newData[newData.length - 1];\n          const chartData = this.chart.data;\n          const datasets = chartData.datasets;\n\n          Object.keys(this.datasets).forEach((id) => {\n            datasets.find((dataset) => dataset.id === id).data.push(data[id]);\n          });\n\n          chartData.labels.push(data[this.label]);\n\n          this.chart.update();\n        }\n      },\n      deep: true,\n    },\n  },\n  mounted() {\n    const _data = this.data;\n    const labels = _data.map((d) => d[this.label]);\n    const minTimestamp = Math.min(...labels);\n\n    const datasets = Object.keys(this.datasets).map((id, idx) => {\n      const config = this.datasets[id];\n\n      return {\n        id,\n        borderColor: config.borderColor ?? this.colors[idx].borderColor,\n        backgroundColor:\n          config.backgroundColor ?? this.colors[idx].backgroundColor,\n        pointHoverRadius: 5,\n        label: config.label,\n        data: [..._data.map((d) => d[id])],\n      };\n    });\n\n    const config = {\n      type: 'line',\n      data: {\n        labels,\n        datasets,\n      },\n      options: {\n        animation: {\n          duration: 0,\n        },\n        plugins: {\n          filler: {\n            propagate: true,\n          },\n          legend: {\n            labels: {\n              generateLabels: (chart) => {\n                const data = chart.data;\n                if (data.datasets.length) {\n                  const {\n                    labels: { pointStyle },\n                  } = chart.legend.options;\n\n                  return data.datasets.map((dataset, i) => {\n                    const style = this.colors[i];\n\n                    return {\n                      text: this.t(dataset.label),\n                      fillStyle: style.backgroundColor,\n                      strokeStyle: style.borderColor,\n                      lineWidth: 2,\n                      pointStyle,\n                      hidden: !chart.getDataVisibility(i),\n\n                      // Extra data used for toggling the correct item\n                      index: i,\n                    };\n                  });\n                }\n                return [];\n              },\n            },\n          },\n        },\n        elements: {\n          line: {\n            tension: 0,\n            borderWidth: 2,\n          },\n          point: {\n            radius: 0,\n            hitRadius: 30,\n          },\n        },\n        scales: {\n          y: {\n            min: 0,\n          },\n          x: {\n            type: 'linear',\n            min: minTimestamp,\n            time: {\n              displayFormats: {\n                quarter: 'HH:mm:ss',\n              },\n            },\n            ticks: {\n              autoSkip: false,\n              minRotation: 45,\n            },\n          },\n        },\n      },\n    };\n\n    this.chart = shallowRef(\n      new Chart(this.$refs.chart, merge(config, this.config)),\n    );\n  },\n  beforeUnmount() {\n    this.chart.destroy();\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/__snapshots__/health-details.spec.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`HealthDetails > Health .details > should format object details correctly 1`] = `\n<dd\n  aria-labelledby=\"health-detail-v-7__validChains\"\n  data-v-4727d85f=\"\"\n>\n  <pre\n    class=\"formatted overflow-auto !whitespace-pre\"\n    data-v-4727d85f=\"\"\n    data-v-4fb4748a=\"\"\n  >\n    - status: VALID\n  chain:\n    - subject: CN=example.com, OU=IT, O=Example Corp, L=San Francisco, ST=CA, C=US\n      issuer: CN=R3, O=Let's Encrypt, C=US\n\n  </pre>\n</dd>\n`;\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/cache-chart.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div class=\"cache-chart\">\n    <svg class=\"cache-chart__svg\" />\n  </div>\n</template>\n\n<script>\nimport moment from 'moment';\n\nimport d3 from '@/utils/d3';\n\nexport default {\n  props: {\n    data: {\n      type: Array,\n      default: () => [],\n    },\n  },\n  data: () => ({}),\n  watch: {\n    data: {\n      handler: 'drawChart',\n      deep: true,\n    },\n  },\n  mounted() {\n    const margin = {\n      top: 5,\n      right: 5,\n      bottom: 30,\n      left: 50,\n    };\n\n    this.width =\n      this.$el.getBoundingClientRect().width - margin.left - margin.right;\n    this.height =\n      this.$el.getBoundingClientRect().height - margin.top - margin.bottom;\n\n    this.chartLayer = d3\n      .select(this.$el.querySelector('.cache-chart__svg'))\n      .append('g')\n      .attr('transform', `translate(${margin.left},${margin.top})`);\n\n    this.xAxis = this.chartLayer\n      .append('g')\n      .attr('class', 'cache-chart__axis-x')\n      .attr('transform', `translate(0,${this.height})`);\n\n    this.yAxis = this.chartLayer\n      .append('g')\n      .attr('class', 'cache-chart__axis-y')\n      .attr('stroke', null);\n\n    this.areas = this.chartLayer.append('g');\n\n    this.drawChart(this.data);\n  },\n  methods: {\n    drawChart(_data) {\n      const data =\n        _data.length === 1\n          ? _data.concat([{ ..._data[0], timestamp: _data[0].timestamp + 1 }])\n          : _data;\n\n      ///setup x and y scale\n      const extent = d3.extent(data, (d) => d.timestamp);\n      const x = d3.scaleTime().range([0, this.width]).domain(extent);\n\n      const y = d3\n        .scaleLinear()\n        .range([this.height, 0])\n        .domain([0, d3.max(data, (d) => d.totalPerInterval) * 1.05]);\n\n      //draw areas\n      const miss = this.areas\n        .selectAll('.cache-chart__area--miss')\n        .data([data]);\n      miss\n        .enter()\n        .append('path')\n        .merge(miss)\n        .attr('class', 'cache-chart__area--miss')\n        .attr(\n          'd',\n          d3\n            .area()\n            .x((d) => x(d.timestamp))\n            .y0((d) => y(d.hitsPerInterval))\n            .y1((d) => y(d.totalPerInterval)),\n        );\n      miss.exit().remove();\n\n      const hit = this.areas.selectAll('.cache-chart__area--hit').data([data]);\n      hit\n        .enter()\n        .append('path')\n        .merge(hit)\n        .attr('class', 'cache-chart__area--hit')\n        .attr(\n          'd',\n          d3\n            .area()\n            .x((d) => x(d.timestamp))\n            .y0(y(0))\n            .y1((d) => y(d.hitsPerInterval)),\n        );\n      hit.exit().remove();\n\n      //draw axis\n      this.xAxis.call(\n        d3\n          .axisBottom(x)\n          .ticks(5)\n          .tickFormat((d) => moment(d).format('HH:mm:ss')),\n      );\n\n      this.yAxis.call(\n        d3\n          .axisLeft(y)\n          .ticks(5)\n          .tickFormat((d) => (d <= 1000 ? d : (d / 1000).toFixed(1) + 'K')),\n      );\n    },\n  },\n};\n</script>\n\n<style lang=\"css\">\n.cache-chart__svg {\n  height: 159px;\n  width: 100%;\n}\n.cache-chart__area--miss {\n  fill: #ffe08a;\n  opacity: 0.8;\n}\n.cache-chart__area--hit {\n  fill: #3e8ed0;\n  opacity: 0.8;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/datasource-chart.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div class=\"datasource-chart\">\n    <svg class=\"datasource-chart__svg\" />\n  </div>\n</template>\n\n<script>\nimport moment from 'moment';\n\nimport d3 from '@/utils/d3';\n\nexport default {\n  props: {\n    data: {\n      type: Array,\n      default: () => [],\n    },\n  },\n  watch: {\n    data: {\n      handler: 'drawChart',\n      deep: true,\n    },\n  },\n  mounted() {\n    const margin = {\n      top: 5,\n      right: 5,\n      bottom: 30,\n      left: 50,\n    };\n\n    this.width =\n      this.$el.getBoundingClientRect().width - margin.left - margin.right;\n    this.height =\n      this.$el.getBoundingClientRect().height - margin.top - margin.bottom;\n\n    this.chartLayer = d3\n      .select(this.$el.querySelector('.datasource-chart__svg'))\n      .append('g')\n      .attr('transform', `translate(${margin.left},${margin.top})`);\n\n    this.xAxis = this.chartLayer\n      .append('g')\n      .attr('class', 'datasource-chart__axis-x')\n      .attr('transform', `translate(0,${this.height})`);\n\n    this.yAxis = this.chartLayer\n      .append('g')\n      .attr('class', 'datasource-chart__axis-y')\n      .attr('stroke', null);\n\n    this.areas = this.chartLayer.append('g');\n\n    this.drawChart(this.data);\n  },\n  methods: {\n    drawChart(_data) {\n      const data =\n        _data.length === 1\n          ? _data.concat([{ ..._data[0], timestamp: _data[0].timestamp + 1 }])\n          : _data;\n\n      ///setup x and y scale\n      const extent = d3.extent(data, (d) => d.timestamp);\n      const x = d3.scaleTime().range([0, this.width]).domain(extent);\n\n      const y = d3\n        .scaleLinear()\n        .range([this.height, 0])\n        .domain([0, d3.max(data, (d) => d.active) * 1.05]);\n\n      //draw max\n      const max = this.areas\n        .selectAll('.datasource-chart__line--max')\n        .data([data]);\n      max\n        .enter()\n        .append('path')\n        .merge(max)\n        .attr('class', 'datasource-chart__line--max')\n        .attr(\n          'd',\n          d3\n            .line()\n            .x((d) => x(d.timestamp))\n            .y((d) => y(d.max)),\n        );\n      max.exit().remove();\n\n      //draw areas\n      const active = this.areas\n        .selectAll('.datasource-chart__area--active')\n        .data([data]);\n      active\n        .enter()\n        .append('path')\n        .merge(active)\n        .attr('class', 'datasource-chart__area--active')\n        .attr(\n          'd',\n          d3\n            .area()\n            .x((d) => x(d.timestamp))\n            .y0(y(0))\n            .y1((d) => y(d.active)),\n        );\n      active.exit().remove();\n\n      //draw axis\n      this.xAxis.call(\n        d3\n          .axisBottom(x)\n          .ticks(5)\n          .tickFormat((d) => moment(d).format('HH:mm:ss')),\n      );\n\n      this.yAxis.call(d3.axisLeft(y).ticks(5));\n    },\n  },\n};\n</script>\n\n<style lang=\"css\">\n.datasource-chart__svg {\n  height: 159px;\n  width: 100%;\n}\n.datasource-chart__area--active {\n  fill: #3e8ed0;\n  opacity: 0.8;\n}\n.datasource-chart__line--max {\n  stroke: #3e8ed0;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/details-cache.spec.ts",
    "content": "import { screen, waitFor } from '@testing-library/vue';\nimport { enableAutoUnmount } from '@vue/test-utils';\nimport { HttpResponse, http } from 'msw';\nimport { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport { applications } from '@/mocks/applications/data';\nimport { server } from '@/mocks/server';\nimport Application from '@/services/application';\nimport { render } from '@/test-utils';\nimport DetailsCache from '@/views/instances/details/details-cache.vue';\n\nvi.mock('@/sba-config', async () => {\n  const sbaConfig: any = await vi.importActual('@/sba-config');\n  return {\n    default: {\n      ...sbaConfig.default,\n      uiSettings: {\n        pollTimer: {\n          cache: 100,\n        },\n      },\n    },\n  };\n});\n\ndescribe('DetailsCache', () => {\n  enableAutoUnmount(afterEach);\n\n  const CACHE_NAME = 'foo-cache';\n  const HITS = [24.0, 32.0, 48.0];\n  const MISSES = [8.0, 8.0, 16.0];\n  const TOTAL = [32, 40, 64];\n  const HITS_PER_INTERVAL = [0, 8, 16];\n  const MISSES_PER_INTERVAL = [0, 0, 8];\n  const TOTAL_PER_INTERVAL = [0, 8, 24];\n\n  const stubChart = {\n    props: ['data'],\n    template: `\n        <div data-test=\"chart\">\n          {{ JSON.stringify($props.data) }}\n        </div>\n      `,\n  };\n\n  const renderComponent = async (stubs = {}) => {\n    const application = new Application(applications[0]);\n    const instance = application.instances[0];\n    return render(DetailsCache, {\n      global: {\n        stubs,\n      },\n      props: {\n        instance,\n        cacheName: CACHE_NAME,\n        index: 0,\n      },\n    });\n  };\n\n  beforeEach(() => {\n    const hitsGenerator = (function* () {\n      yield* HITS;\n    })();\n    const missesGenerator = (function* () {\n      yield* MISSES;\n    })();\n\n    server.use(\n      http.get(\n        '/instances/:instanceId/actuator/metrics/cache.gets',\n        ({ request }) => {\n          const url = new URL(request.url);\n          const tags = url.searchParams.getAll('tag');\n\n          if (tags.includes('result:hit')) {\n            return HttpResponse.json({\n              measurements: [{ value: hitsGenerator.next()?.value }],\n            });\n          } else if (tags.includes('result:miss')) {\n            return HttpResponse.json({\n              measurements: [{ value: missesGenerator.next()?.value }],\n            });\n          }\n        },\n      ),\n      http.get('/instances/:instanceId/actuator/metrics/cache.size', () => {\n        return HttpResponse.json({\n          measurements: [{ value: '1337' }],\n        });\n      }),\n    );\n  });\n\n  it('should render cache name', async () => {\n    await renderComponent();\n\n    expect(\n      await screen.findByRole('heading', { name: `Cache: ${CACHE_NAME}` }),\n    ).toBeVisible();\n  });\n\n  it('should render hits and misses', async () => {\n    await renderComponent();\n\n    expect(\n      await screen.findByLabelText('instances.details.cache.hits'),\n    ).toHaveTextContent(`${HITS[0]}`);\n    expect(\n      await screen.findByLabelText('instances.details.cache.misses'),\n    ).toHaveTextContent(`${MISSES[0]}`);\n  });\n\n  it('should calculate and render hit/miss ratio', async () => {\n    await renderComponent();\n\n    expect(\n      await screen.findByLabelText('instances.details.cache.hit_ratio'),\n    ).toHaveTextContent('75.00%'); // 24 hits, 8 misses\n  });\n\n  it('should calculate total', async () => {\n    const application = new Application(applications[0]);\n    const instance = application.instances[0];\n\n    const { container } = await render(DetailsCache, {\n      global: { stubs: { cacheChart: stubChart } },\n      props: {\n        instance,\n        cacheName: CACHE_NAME,\n        index: 0,\n      },\n    });\n\n    // wait until chart stub receives at least 3 data points\n    await waitFor(\n      () => {\n        const el = container.querySelector('[data-test=\"chart\"]');\n        expect(el).toBeTruthy();\n        const text = (el?.textContent as string) || '[]';\n        const parsed = JSON.parse(text);\n        expect(parsed).toHaveLength(3);\n      },\n      { timeout: 2000 },\n    );\n\n    const chartText =\n      (container.querySelector('[data-test=\"chart\"]')?.textContent as string) ||\n      '[]';\n    const chartData = JSON.parse(chartText);\n\n    for (let index = 0; index < chartData.length; index++) {\n      expect(chartData[index].total).toEqual(TOTAL[index]);\n    }\n  });\n\n  it('should calculate hits, misses and total per interval', async () => {\n    const application = new Application(applications[0]);\n    const instance = application.instances[0];\n\n    const { container } = await render(DetailsCache, {\n      global: { stubs: { cacheChart: stubChart } },\n      props: {\n        instance,\n        cacheName: CACHE_NAME,\n        index: 0,\n      },\n    });\n\n    // wait until chart stub receives at least 3 data points\n    await waitFor(\n      () => {\n        const el = container.querySelector('[data-test=\"chart\"]');\n        expect(el).toBeTruthy();\n        const text = (el?.textContent as string) || '[]';\n        const parsed = JSON.parse(text);\n        expect(parsed).toHaveLength(3);\n      },\n      { timeout: 2000 },\n    );\n\n    const chartText2 =\n      (container.querySelector('[data-test=\"chart\"]')?.textContent as string) ||\n      '[]';\n    const chartData = JSON.parse(chartText2);\n\n    for (let index = 0; index < chartData.length; index++) {\n      expect(chartData[index].hitsPerInterval).toEqual(\n        HITS_PER_INTERVAL[index],\n      );\n      expect(chartData[index].missesPerInterval).toEqual(\n        MISSES_PER_INTERVAL[index],\n      );\n      expect(chartData[index].totalPerInterval).toEqual(\n        TOTAL_PER_INTERVAL[index],\n      );\n    }\n  });\n\n  it('should reinitialize metrics when instance changes', async () => {\n    const application = new Application(applications[0]);\n    const instance = application.instances[0];\n\n    const { getByText, rerender, queryByText } = await render(DetailsCache, {\n      props: {\n        instance,\n        cacheName: CACHE_NAME,\n        index: 0,\n      },\n    });\n\n    // wait until initial fetch rendered a numeric value\n    await waitFor(() => {\n      expect(getByText(`${HITS[0]}`)).toBeTruthy();\n    });\n\n    // simulate switching to a different instance\n    const newApp = new Application({\n      name: 'Other',\n      statusTimestamp: Date.now(),\n      instances: [{ id: 'other-1', statusInfo: { status: 'UP' } }],\n    });\n    const newInstance = newApp.instances[0];\n\n    await rerender({ instance: newInstance, cacheName: CACHE_NAME, index: 0 });\n\n    // component should have reset its rendered data\n    await waitFor(() => {\n      expect(queryByText(`${HITS[0]}`)).not.toBeInTheDocument();\n    });\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/details-cache.vue",
    "content": "<!--\n  - Copyright 2014-2019 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-accordion\n    v-if=\"hasLoaded\"\n    :id=\"`cache-details-panel__${instance.id}`\"\n    :title=\"`Cache: ${cacheName}`\"\n  >\n    <sba-alert v-if=\"error\" :error=\"error\" :title=\"$t('term.fetch_failed')\" />\n    <dl\n      v-if=\"current\"\n      class=\"px-4 py-3 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6\"\n    >\n      <template v-if=\"current.hit !== undefined\">\n        <dt\n          :id=\"`metrics.cache.${index}.hits`\"\n          class=\"text-sm font-medium text-gray-500 sm:col-span-4\"\n          v-text=\"$t('instances.details.cache.hits')\"\n        />\n        <dd\n          :aria-labelledby=\"`metrics.cache.${index}.hits`\"\n          class=\"mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2\"\n          v-text=\"current.hit\"\n        />\n      </template>\n      <template v-if=\"current.miss !== undefined\">\n        <dt\n          :id=\"`metrics.cache.${index}.misses`\"\n          class=\"text-sm font-medium text-gray-500 sm:col-span-4\"\n          v-text=\"$t('instances.details.cache.misses')\"\n        />\n        <dd\n          :aria-labelledby=\"`metrics.cache.${index}.misses`\"\n          class=\"mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2\"\n          v-text=\"current.miss\"\n        />\n      </template>\n      <template v-if=\"ratio !== undefined\">\n        <dt\n          :id=\"`metrics.cache.${index}.ratio`\"\n          class=\"text-sm font-medium text-gray-500 sm:col-span-4\"\n          v-text=\"$t('instances.details.cache.hit_ratio')\"\n        />\n        <dd\n          :aria-labelledby=\"`metrics.cache.${index}.ratio`\"\n          class=\"mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2\"\n          v-text=\"ratio\"\n        />\n      </template>\n      <template v-if=\"current.size !== undefined\">\n        <dt\n          :id=\"`metrics.cache.${index}.size`\"\n          class=\"sm:col-span-4\"\n          v-text=\"$t('instances.details.cache.size')\"\n        />\n        <dd\n          :aria-labelledby=\"`metrics.cache.${index}.size`\"\n          v-text=\"current.size\"\n        />\n      </template>\n    </dl>\n    <cache-chart v-if=\"chartData.length > 0\" :data=\"chartData\" />\n  </sba-accordion>\n</template>\n\n<script>\nimport moment from 'moment';\nimport { take } from 'rxjs/operators';\n\nimport SbaAccordion from '@/components/sba-accordion.vue';\nimport sbaAlert from '@/components/sba-alert.vue';\n\nimport subscribing from '@/mixins/subscribing';\nimport sbaConfig from '@/sba-config';\nimport Instance from '@/services/instance';\nimport { concatMap, delay, map, retryWhen, timer } from '@/utils/rxjs';\nimport cacheChart from '@/views/instances/details/cache-chart';\n\nexport default {\n  components: { SbaAccordion, sbaAlert, cacheChart },\n  mixins: [subscribing],\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n    cacheName: {\n      type: String,\n      required: true,\n    },\n    index: {\n      type: Number,\n      required: true,\n    },\n  },\n  data: () => ({\n    hasLoaded: false,\n    error: null,\n    current: null,\n    shouldFetchCacheSize: true,\n    shouldFetchCacheHits: true,\n    shouldFetchCacheMisses: true,\n    chartData: [],\n    currentInstanceId: null,\n  }),\n  computed: {\n    ratio() {\n      if (\n        Number.isFinite(this.current.hit) &&\n        Number.isFinite(this.current.miss)\n      ) {\n        const total = this.current.hit + this.current.miss;\n        return total > 0\n          ? ((this.current.hit / total) * 100).toFixed(2) + '%'\n          : '-';\n      }\n      return undefined;\n    },\n  },\n  watch: {\n    instance: {\n      handler: 'initCacheMetrics',\n      immediate: true,\n    },\n  },\n  methods: {\n    initCacheMetrics() {\n      if (this.instance.id !== this.currentInstanceId) {\n        this.currentInstanceId = this.instance.id;\n        this.hasLoaded = false;\n        this.error = null;\n        this.current = null;\n        this.chartData = [];\n        this.shouldFetchCacheSize = true;\n        this.shouldFetchCacheHits = true;\n        this.shouldFetchCacheMisses = true;\n      }\n    },\n    async fetchMetrics() {\n      const [hit, miss, size] = await Promise.all([\n        this.fetchCacheHits(),\n        this.fetchCacheMisses(),\n        this.fetchCacheSize(),\n      ]);\n      return {\n        hit: hit,\n        miss: miss,\n        total: hit + (miss || 0),\n        size,\n      };\n    },\n    async fetchCacheHits() {\n      if (this.shouldFetchCacheHits) {\n        try {\n          const response = await this.instance.fetchMetric('cache.gets', {\n            name: this.cacheName,\n            result: 'hit',\n          });\n          return response.data.measurements[0].value;\n        } catch (error) {\n          this.shouldFetchCacheHits = false;\n          console.warn(\n            `Fetching cache ${this.cacheName} hits failed - error is ignored`,\n            error,\n          );\n          return undefined;\n        }\n      }\n    },\n    async fetchCacheMisses() {\n      if (this.shouldFetchCacheMisses) {\n        try {\n          const response = await this.instance.fetchMetric('cache.gets', {\n            name: this.cacheName,\n            result: 'miss',\n          });\n          return response.data.measurements[0].value;\n        } catch (error) {\n          this.shouldFetchCacheMisses = false;\n          console.warn(\n            `Fetching cache ${this.cacheName} misses failed - error is ignored`,\n            error,\n          );\n          return undefined;\n        }\n      }\n    },\n    async fetchCacheSize() {\n      if (this.shouldFetchCacheSize) {\n        try {\n          const response = await this.instance.fetchMetric('cache.size', {\n            name: this.cacheName,\n          });\n          return response.data.measurements[0].value;\n        } catch (error) {\n          this.shouldFetchCacheSize = false;\n          console.warn(\n            `Fetching cache ${this.cacheName} size failed - error is ignored`,\n            error,\n          );\n          return undefined;\n        }\n      }\n    },\n    calculateMetricsPerInterval(data) {\n      let hitsPerInterval = 0;\n      let missesPerInterval = 0;\n      let totalPerInterval = 0;\n\n      if (this.chartData.length > 0) {\n        const previousChartData = this.chartData[this.chartData.length - 1];\n        hitsPerInterval = data.hit - previousChartData.hit;\n        missesPerInterval = data.miss - previousChartData.miss;\n        totalPerInterval = data.total - previousChartData.total;\n      }\n\n      return { ...data, hitsPerInterval, missesPerInterval, totalPerInterval };\n    },\n    createSubscription() {\n      return timer(0, sbaConfig.uiSettings.pollTimer.cache)\n        .pipe(\n          concatMap(this.fetchMetrics),\n          map(this.calculateMetricsPerInterval),\n          retryWhen((err) => {\n            return err.pipe(delay(1000), take(5));\n          }),\n        )\n        .subscribe({\n          next: (data) => {\n            this.hasLoaded = true;\n            this.current = data;\n            this.chartData.push({ ...data, timestamp: moment().valueOf() });\n          },\n          error: (error) => {\n            this.hasLoaded = true;\n            console.warn(\n              `Fetching cache ${this.cacheName} metrics failed:`,\n              error,\n            );\n            this.error = error;\n          },\n        });\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/details-caches.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div>\n    <details-cache\n      v-for=\"(cache, index) in caches\"\n      :key=\"cache\"\n      :index=\"index\"\n      :cache-name=\"cache\"\n      :instance=\"instance\"\n    />\n  </div>\n</template>\n\n<script>\nimport { uniq } from 'lodash-es';\nimport { take } from 'rxjs/operators';\n\nimport subscribing from '@/mixins/subscribing';\nimport sbaConfig from '@/sba-config';\nimport Instance from '@/services/instance';\nimport { concatMap, delay, retryWhen, timer } from '@/utils/rxjs';\nimport detailsCache from '@/views/instances/details/details-cache';\n\nexport default {\n  components: { detailsCache },\n  mixins: [subscribing],\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  data: () => ({\n    caches: [],\n  }),\n  methods: {\n    async fetchCaches() {\n      const response = await this.instance.fetchMetric('cache.gets');\n      return uniq(\n        response.data.availableTags.filter((tag) => tag.tag === 'name')[0]\n          .values,\n      );\n    },\n    createSubscription() {\n      return timer(0, sbaConfig.uiSettings.pollTimer.cache)\n        .pipe(\n          concatMap(this.fetchCaches),\n          retryWhen((err) => {\n            return err.pipe(delay(1000), take(5));\n          }),\n        )\n        .subscribe({\n          next: (names) => {\n            this.caches = names;\n          },\n          error: (error) => {\n            console.warn('Fetching caches failed:', error);\n          },\n        });\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/details-datasource.spec.ts",
    "content": "import { screen, waitFor } from '@testing-library/vue';\nimport { enableAutoUnmount } from '@vue/test-utils';\nimport { HttpResponse, http } from 'msw';\nimport { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport { applications } from '@/mocks/applications/data';\nimport { server } from '@/mocks/server';\nimport Application from '@/services/application';\nimport { render } from '@/test-utils';\nimport DetailsDatasource from '@/views/instances/details/details-datasource.vue';\n\nvi.mock('@/sba-config', async () => {\n  const sbaConfig: any = await vi.importActual('@/sba-config');\n  return {\n    default: {\n      ...sbaConfig.default,\n      uiSettings: {\n        pollTimer: {\n          datasource: 100,\n        },\n      },\n    },\n  };\n});\n\ndescribe('DetailsDatasource', () => {\n  enableAutoUnmount(afterEach);\n\n  const DATA_SOURCE = 'my-ds';\n  const ACTIVE = [2.0, 3.0, 4.0];\n  const MIN = [1.0, 1.0, 1.0];\n  const MAX = [5.0, -1.0, 10.0]; // include unlimited (-1) case\n\n  beforeEach(() => {\n    const activeGenerator = (function* () {\n      yield* ACTIVE;\n    })();\n    const minGenerator = (function* () {\n      yield* MIN;\n    })();\n    const maxGenerator = (function* () {\n      yield* MAX;\n    })();\n\n    server.use(\n      http.get('/instances/:instanceId/actuator/metrics', () => {\n        return HttpResponse.json({\n          names: [\n            'jdbc.connections.active',\n            'jdbc.connections.min',\n            'jdbc.connections.max',\n          ],\n        });\n      }),\n      http.get(\n        '/instances/:instanceId/actuator/metrics/jdbc.connections.active',\n        () => {\n          return HttpResponse.json({\n            measurements: [{ value: activeGenerator.next()?.value }],\n          });\n        },\n      ),\n      http.get(\n        '/instances/:instanceId/actuator/metrics/jdbc.connections.min',\n        () => {\n          return HttpResponse.json({\n            measurements: [{ value: minGenerator.next()?.value }],\n          });\n        },\n      ),\n      http.get(\n        '/instances/:instanceId/actuator/metrics/jdbc.connections.max',\n        () => {\n          return HttpResponse.json({\n            measurements: [{ value: maxGenerator.next()?.value }],\n          });\n        },\n      ),\n    );\n  });\n\n  async function renderComponent(stubs = {}) {\n    const application = new Application(applications[0]);\n    const instance = application.instances[0];\n    return render(DetailsDatasource, {\n      global: {\n        stubs,\n      },\n      props: {\n        instance,\n        dataSource: DATA_SOURCE,\n      },\n    });\n  }\n\n  it('renders panel title with datasource name', async () => {\n    await renderComponent();\n\n    // title uses i18n keys in the test environment; ensure heading is present\n    const heading = await screen.findByRole('heading');\n    expect(heading).toBeVisible();\n  });\n\n  it('renders active, min and max values', async () => {\n    await renderComponent();\n\n    // labels use i18n keys; assert numeric values are rendered\n    expect(await screen.findByText(`${ACTIVE[0]}`)).toBeVisible();\n    expect(await screen.findByText(`${MIN[0]}`)).toBeVisible();\n    expect(await screen.findByText(`${MAX[0]}`)).toBeVisible();\n  });\n\n  it('handles unlimited max (-1) and pushes chartData points', async () => {\n    const application = new Application(applications[0]);\n    const instance = application.instances[0];\n\n    await render(DetailsDatasource, {\n      props: {\n        instance,\n        dataSource: DATA_SOURCE,\n      },\n    });\n\n    // wait until initial metric value is visible\n    await waitFor(() => {\n      expect(screen.getByText(`${ACTIVE[0]}`)).toBeVisible();\n    });\n\n    // second measurement MAX[1] === -1 should be displayed as 'unlimited' in rendered markup\n    expect(\n      await screen.findByText('instances.details.datasource.unlimited'),\n    ).toBeVisible();\n  });\n\n  it('should reinitialize metrics when instance changes', async () => {\n    const application = new Application(applications[0]);\n    const instance = application.instances[0];\n\n    const { rerender } = await render(DetailsDatasource, {\n      props: {\n        instance,\n        dataSource: DATA_SOURCE,\n      },\n    });\n\n    // Wait for the component to poll and populate chartData (3 samples)\n    await waitFor(() => {\n      // find any of the numeric texts to ensure initial fetch happened\n      expect(screen.getByText(`${ACTIVE[0]}`)).toBeVisible();\n    });\n\n    const newApp = new Application({\n      name: 'Other',\n      statusTimestamp: Date.now(),\n      instances: [{ id: 'other-1', statusInfo: { status: 'UP' } }],\n    });\n    const newInstance = newApp.instances[0];\n\n    await rerender({ instance: newInstance, dataSource: DATA_SOURCE });\n\n    // After rerender with a different instance, the component should reset\n    await waitFor(() => {\n      expect(screen.queryByText(`${ACTIVE[0]}`)).not.toBeInTheDocument();\n    });\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/details-datasource.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-accordion\n    v-if=\"hasLoaded\"\n    :id=\"`health-datasource-panel__${instance.id}`\"\n    :title=\"\n      $t('instances.details.datasource.title', { dataSource: dataSource })\n    \"\n  >\n    <sba-alert v-if=\"error\" :error=\"error\" :title=\"$t('term.fetch_failed')\" />\n\n    <dl\n      v-if=\"current\"\n      class=\"px-4 py-3 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6\"\n    >\n      <dt\n        class=\"text-sm font-medium text-gray-500 sm:col-span-4\"\n        v-text=\"$t('instances.details.datasource.active_connections')\"\n      />\n      <dd\n        class=\"mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2\"\n        v-text=\"current.active\"\n      />\n\n      <dt\n        class=\"text-sm font-medium text-gray-500 sm:col-span-4\"\n        v-text=\"$t('instances.details.datasource.min_connections')\"\n      />\n      <dd\n        class=\"mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2\"\n        v-text=\"current.min\"\n      />\n\n      <dt\n        class=\"text-sm font-medium text-gray-500 sm:col-span-4\"\n        v-text=\"$t('instances.details.datasource.max_connections')\"\n      />\n      <dd\n        v-if=\"current.max >= 0\"\n        class=\"mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2\"\n        v-text=\"current.max\"\n      />\n      <dd\n        v-else\n        class=\"mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2\"\n        v-text=\"$t('instances.details.datasource.unlimited')\"\n      />\n    </dl>\n    <datasource-chart v-if=\"chartData.length > 0\" :data=\"chartData\" />\n  </sba-accordion>\n</template>\n\n<script>\nimport moment from 'moment';\nimport { concatMap, delay, retryWhen, timer } from 'rxjs';\nimport { take } from 'rxjs/operators';\n\nimport SbaAccordion from '@/components/sba-accordion.vue';\n\nimport subscribing from '@/mixins/subscribing';\nimport sbaConfig from '@/sba-config';\nimport Instance from '@/services/instance';\nimport datasourceChart from '@/views/instances/details/datasource-chart';\n\nexport default {\n  components: { SbaAccordion, datasourceChart },\n  mixins: [subscribing],\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n    dataSource: {\n      type: String,\n      required: true,\n    },\n  },\n  data: () => ({\n    hasLoaded: false,\n    error: null,\n    current: null,\n    chartData: [],\n    currentInstanceId: null,\n  }),\n  watch: {\n    instance: {\n      handler: 'initDatasourceMetrics',\n      immediate: true,\n    },\n  },\n  methods: {\n    initDatasourceMetrics() {\n      if (this.instance.id !== this.currentInstanceId) {\n        this.currentInstanceId = this.instance.id;\n        this.hasLoaded = false;\n        this.error = null;\n        this.current = null;\n        this.chartData = [];\n      }\n    },\n    async fetchMetrics() {\n      const responseActive = this.instance.fetchMetric(\n        'jdbc.connections.active',\n        { name: this.dataSource },\n      );\n      const responseMin = this.instance.fetchMetric('jdbc.connections.min', {\n        name: this.dataSource,\n      });\n      const responseMax = this.instance.fetchMetric('jdbc.connections.max', {\n        name: this.dataSource,\n      });\n\n      return {\n        active: (await responseActive).data.measurements[0].value,\n        min: (await responseMin).data.measurements[0].value,\n        max: (await responseMax).data.measurements[0].value,\n      };\n    },\n    createSubscription() {\n      return timer(0, sbaConfig.uiSettings.pollTimer.datasource)\n        .pipe(\n          concatMap(this.fetchMetrics),\n          retryWhen((err) => {\n            return err.pipe(delay(1000), take(5));\n          }),\n        )\n        .subscribe({\n          next: (data) => {\n            this.hasLoaded = true;\n            this.current = data;\n            this.chartData.push({ ...data, timestamp: moment().valueOf() });\n          },\n          error: (error) => {\n            this.hasLoaded = true;\n            console.warn(\n              `Fetching datasource ${this.dataSource} metrics failed:`,\n              error,\n            );\n            this.error = error;\n          },\n        });\n    },\n  },\n};\n</script>\n\n<style lang=\"css\">\n.datasource-current {\n  margin-bottom: 0 !important;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/details-datasources.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div>\n    <details-datasource\n      v-for=\"dataSource in dataSources\"\n      :key=\"dataSource\"\n      :data-source=\"dataSource\"\n      :instance=\"instance\"\n    />\n  </div>\n</template>\n\n<script>\nimport { take } from 'rxjs/operators';\n\nimport subscribing from '@/mixins/subscribing';\nimport sbaConfig from '@/sba-config';\nimport Instance from '@/services/instance';\nimport { concatMap, delay, retryWhen, timer } from '@/utils/rxjs';\nimport detailsDatasource from '@/views/instances/details/details-datasource';\n\nexport default {\n  components: { detailsDatasource },\n  mixins: [subscribing],\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  data: () => ({\n    dataSources: [],\n  }),\n  methods: {\n    async fetchDataSources() {\n      const response = await this.instance.fetchMetric(\n        'jdbc.connections.active',\n      );\n      return response.data.availableTags.filter((tag) => tag.tag === 'name')[0]\n        .values;\n    },\n    createSubscription() {\n      return timer(0, sbaConfig.uiSettings.pollTimer.datasource)\n        .pipe(\n          concatMap(this.fetchDataSources),\n          retryWhen((err) => {\n            return err.pipe(delay(1000), take(5));\n          }),\n        )\n        .subscribe({\n          next: (names) => {\n            this.dataSources = names;\n          },\n          error: (error) => {\n            console.warn('Fetching datasources failed:', error);\n          },\n        });\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/details-gc.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-accordion\n    v-if=\"hasLoaded\"\n    :id=\"`gc-details-panel__${instance.id}`\"\n    v-model=\"panelOpen\"\n    :title=\"$t('instances.details.gc.title')\"\n  >\n    <template #title>\n      <div\n        class=\"ml-2 text-xs font-mono transition-opacity flex-1 justify-items-end\"\n        :class=\"{ 'opacity-0': panelOpen }\"\n      >\n        <ul class=\"flex gap-4\">\n          <li>\n            <span class=\"block 2xl:inline\">\n              {{ $t('instances.details.gc.count_short') }}:\n            </span>\n            {{ current.count }}\n          </li>\n          <li>\n            <span class=\"block 2xl:inline\">\n              {{ $t('instances.details.gc.time_spent_total_short') }}:\n            </span>\n            {{ current.total_time.asSeconds().toFixed(0) }}s\n          </li>\n          <li>\n            <span class=\"block 2xl:inline\">\n              {{ $t('instances.details.gc.time_spent_max_short') }}:\n            </span>\n            {{ current.max.asSeconds().toFixed(0) }}s\n          </li>\n        </ul>\n      </div>\n    </template>\n\n    <sba-alert v-if=\"error\" :error=\"error\" :title=\"$t('term.fetch_failed')\" />\n\n    <div v-if=\"current\" class=\"flex w-full\">\n      <div class=\"flex-1 text-center\">\n        <p class=\"font-bold\" v-text=\"$t('instances.details.gc.count')\" />\n        <p v-text=\"current.count\" />\n      </div>\n      <div class=\"flex-1 text-center\">\n        <p\n          class=\"font-bold\"\n          v-text=\"$t('instances.details.gc.time_spent_total')\"\n        />\n        <p v-text=\"`${current.total_time.asSeconds().toFixed(4)}s`\" />\n      </div>\n      <div class=\"flex-1 text-center\">\n        <p\n          class=\"font-bold\"\n          v-text=\"$t('instances.details.gc.time_spent_max')\"\n        />\n        <p v-text=\"`${current.max.asSeconds().toFixed(4)}s`\" />\n      </div>\n    </div>\n  </sba-accordion>\n</template>\n\n<script>\nimport moment from 'moment';\nimport { take } from 'rxjs/operators';\n\nimport SbaAccordion from '@/components/sba-accordion.vue';\n\nimport subscribing from '@/mixins/subscribing';\nimport sbaConfig from '@/sba-config';\nimport Instance from '@/services/instance';\nimport { concatMap, delay, retryWhen, timer } from '@/utils/rxjs';\nimport { toMillis } from '@/views/instances/metrics/metric';\n\nexport default {\n  components: { SbaAccordion },\n  mixins: [subscribing],\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  data: () => ({\n    panelOpen: true,\n    hasLoaded: false,\n    error: null,\n    current: null,\n  }),\n  methods: {\n    async fetchMetrics() {\n      const response = await this.instance.fetchMetric('jvm.gc.pause');\n      const measurements = response.data.measurements.reduce(\n        (current, measurement) => ({\n          ...current,\n          [measurement.statistic.toLowerCase()]: measurement.value,\n        }),\n        {},\n      );\n      return {\n        ...measurements,\n        total_time: moment.duration(\n          toMillis(measurements.total_time, response.baseUnit),\n        ),\n        max: moment.duration(toMillis(measurements.max, response.baseUnit)),\n      };\n    },\n    createSubscription() {\n      return timer(0, sbaConfig.uiSettings.pollTimer.gc)\n        .pipe(\n          concatMap(this.fetchMetrics),\n          retryWhen((err) => {\n            return err.pipe(delay(1000), take(5));\n          }),\n        )\n        .subscribe({\n          next: (data) => {\n            this.hasLoaded = true;\n            this.current = data;\n          },\n          error: (error) => {\n            this.hasLoaded = true;\n            console.warn('Fetching GC metrics failed:', error);\n            this.error = error;\n          },\n        });\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/details-health.spec.ts",
    "content": "import userEvent from '@testing-library/user-event';\nimport { screen, waitFor } from '@testing-library/vue';\nimport { HttpResponse, http } from 'msw';\nimport { beforeEach, describe, expect, it } from 'vitest';\n\nimport { applications } from '@/mocks/applications/data';\nimport { server } from '@/mocks/server';\nimport Application from '@/services/application';\nimport { render } from '@/test-utils';\nimport DetailsHealth from '@/views/instances/details/details-health.vue';\n\ndescribe('DetailsHealth', () => {\n  beforeEach(() => {\n    server.use(\n      http.get('/instances/:instanceId/actuator/health', () => {\n        return HttpResponse.json({\n          instance: 'UP',\n          groups: ['liveness'],\n        });\n      }),\n      http.get('/instances/:instanceId/actuator/health/liveness', () => {\n        return HttpResponse.json({\n          status: 'UP',\n          details: {\n            disk: { status: 'UNKNOWN' },\n            database: { status: 'UNKNOWN' },\n          },\n        });\n      }),\n    );\n  });\n\n  it('should display groups as part of health section', async () => {\n    const application = new Application(applications[0]);\n    const instance = application.instances[0];\n\n    render(DetailsHealth, {\n      props: {\n        instance,\n      },\n    });\n\n    await waitFor(() =>\n      expect(screen.queryByRole('status')).not.toBeInTheDocument(),\n    );\n\n    expect(\n      await screen.findByRole('button', {\n        name: /instances.details.health_group.title: liveness/,\n      }),\n    ).toBeVisible();\n  });\n\n  it('health groups are toggleable, when details are available', async () => {\n    const application = new Application(applications[0]);\n    const instance = application.instances[0];\n    instance.statusInfo = { status: 'UP', details: {} };\n\n    render(DetailsHealth, {\n      props: {\n        instance,\n      },\n    });\n\n    await waitFor(() =>\n      expect(screen.queryByRole('status')).not.toBeInTheDocument(),\n    );\n\n    const button = screen.queryByRole('button', {\n      name: /instances.details.health_group.title: liveness/,\n    });\n    expect(button).toBeVisible();\n\n    expect(screen.queryByLabelText('disk')).toBeNull();\n    expect(screen.queryByLabelText('database')).toBeNull();\n\n    await userEvent.click(button);\n\n    expect(screen.queryByLabelText('disk')).toBeDefined();\n    expect(screen.queryByLabelText('database')).toBeDefined();\n  });\n\n  it('should update health details when instance prop changes (watch)', async () => {\n    const application = new Application(applications[0]);\n    const instance1 = application.instances[0];\n    const instance2 = {\n      ...instance1,\n      id: 'other-id',\n      statusInfo: { status: 'DOWN', details: {} },\n    };\n\n    const { rerender } = render(DetailsHealth, {\n      props: {\n        instance: instance1,\n      },\n    });\n\n    await waitFor(() =>\n      expect(screen.queryByRole('status')).not.toBeInTheDocument(),\n    );\n\n    // Simulate prop change\n    await rerender({ instance: instance2 });\n\n    // Wait for the component to react to the prop change\n    await waitFor(() =>\n      expect(\n        screen.queryByRole('button', {\n          name: /instances.details.health_group.title: liveness/,\n        }),\n      ).toBeVisible(),\n    );\n  });\n\n  it('should not display health group button if no groups are present', async () => {\n    server.use(\n      http.get('/instances/:instanceId/actuator/health', () => {\n        return HttpResponse.json({\n          instance: 'UP',\n          groups: [],\n        });\n      }),\n    );\n    const application = new Application(applications[0]);\n    const instance = application.instances[0];\n\n    render(DetailsHealth, {\n      props: {\n        instance,\n      },\n    });\n\n    await waitFor(() =>\n      expect(screen.queryByRole('status')).not.toBeInTheDocument(),\n    );\n\n    expect(\n      screen.queryByRole('button', {\n        name: /instances.details.health_group.title: liveness/,\n      }),\n    ).toBeNull();\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/details-health.vue",
    "content": "<!--\n  - Copyright 2014-2019 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-accordion\n    :id=\"`health-details-panel__${instance.id}`\"\n    v-model=\"panelOpen\"\n    :title=\"$t('instances.details.health.title')\"\n    :loading=\"loading\"\n  >\n    <template #title>\n      <sba-status-badge\n        v-if=\"health.status\"\n        :status=\"health.status\"\n        class=\"ml-2 transition-opacity\"\n        :class=\"{ 'opacity-0': panelOpen }\"\n      />\n    </template>\n\n    <template #actions>\n      <router-link\n        :to=\"{ name: 'journal', query: { instanceId: instance.id } }\"\n        class=\"text-sm inline-flex items-center leading-sm border border-gray-400 bg-white text-gray-700 rounded overflow-hidden px-3 py-1 hover:bg-gray-200 ml-1\"\n      >\n        <font-awesome-icon icon=\"history\" />\n      </router-link>\n    </template>\n\n    <template #default>\n      <sba-alert\n        :error=\"error\"\n        class=\"border-l-4\"\n        :title=\"$t('term.fetch_failed')\"\n      />\n      <div class=\"-mx-4 -my-3\">\n        <health-details :health=\"health\" name=\"Instance\" />\n\n        <template v-for=\"healthGroup in healthGroups\" :key=\"healthGroup.name\">\n          <div\n            class=\"px-4 py-2 border-t sm:px-6\"\n            :class=\"{ 'border-b': isHealthGroupOpen(healthGroup.name) }\"\n          >\n            <h4 class=\"leading-6 font-medium text-gray-900\">\n              <button\n                class=\"flex items-center\"\n                :aria-label=\"\n                  $t('instances.details.health_group.title') +\n                  ': ' +\n                  healthGroup.name\n                \"\n                @click=\"toggleHealthGroup(healthGroup.name)\"\n              >\n                <font-awesome-icon\n                  v-if=\"isHealthGroupCollapsible(healthGroup.name)\"\n                  icon=\"chevron-down\"\n                  class=\"transition-[transform] mr-2 h-4\"\n                  :class=\"{\n                    '-rotate-90': !isHealthGroupOpen(healthGroup.name),\n                  }\"\n                />\n                <span v-text=\"$t('instances.details.health_group.title')\"></span\n                >:&nbsp;\n                <span v-text=\"healthGroup.name\"></span>\n              </button>\n            </h4>\n          </div>\n          <div v-if=\"isHealthGroupOpen(healthGroup.name)\">\n            <health-details\n              :health=\"healthGroup.data\"\n              :name=\"healthGroup.name\"\n            />\n          </div>\n        </template>\n      </div>\n    </template>\n  </sba-accordion>\n</template>\n\n<script lang=\"ts\">\nimport { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';\n\nimport SbaAccordion from '@/components/sba-accordion.vue';\n\nimport Instance from '@/services/instance';\nimport healthDetails from '@/views/instances/details/health-details';\n\nexport default {\n  components: { SbaAccordion, FontAwesomeIcon, healthDetails },\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  data: () => ({\n    error: null,\n    loading: false,\n    liveHealth: null,\n    healthGroups: [],\n    panelOpen: true,\n    healthGroupOpenStatus: {} as {\n      isOpen: boolean;\n      collapsible: boolean;\n    },\n    currentInstanceId: null,\n  }),\n  computed: {\n    health() {\n      return this.liveHealth || this.instance.statusInfo;\n    },\n  },\n  watch: {\n    instance: {\n      handler: 'reloadHealth',\n      immediate: true,\n    },\n  },\n  created() {\n    this.fetchHealth();\n  },\n  methods: {\n    reloadHealth() {\n      if (this.instance.id !== this.currentInstanceId) {\n        this.fetchHealth();\n      }\n    },\n    isHealthGroupOpen(groupName: string) {\n      return this.healthGroupOpenStatus[groupName].isOpen === true;\n    },\n    isHealthGroupCollapsible(groupName: string) {\n      return this.healthGroupOpenStatus[groupName].collapsible;\n    },\n    toggleHealthGroup(groupName: string) {\n      if (this.isHealthGroupCollapsible(groupName)) {\n        this.healthGroupOpenStatus[groupName].isOpen =\n          !this.healthGroupOpenStatus[groupName].isOpen;\n      }\n    },\n    async fetchHealth() {\n      this.error = null;\n      this.loading = true;\n      try {\n        const res = await this.instance.fetchHealth();\n        this.currentInstanceId = this.instance.id;\n        this.liveHealth = res.data;\n\n        if (Array.isArray(res.data.groups)) {\n          this.healthGroups = (\n            await Promise.allSettled(\n              res.data.groups.map(async (group: string) => {\n                return {\n                  name: group,\n                  data: (await this.instance.fetchHealthGroup(group)).data,\n                };\n              }),\n            )\n          ).map((group) =>\n            group.status === 'fulfilled' ? group.value : group.reason,\n          );\n\n          this.healthGroupOpenStatus = this.healthGroups\n            .map(\n              (group: {\n                name: string;\n                data: { details: string } | undefined;\n              }) => {\n                return {\n                  [group.name]: {\n                    isOpen: group.data?.details === undefined,\n                    collapsible: group.data?.details !== undefined,\n                  },\n                };\n              },\n            )\n            .reduce((acc, curr) => ({ ...acc, ...curr }), {});\n        }\n      } catch (error) {\n        console.warn('Fetching live health failed:', error);\n        this.error = error;\n      } finally {\n        this.loading = false;\n      }\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/details-hero.vue",
    "content": "<template>\n  <div class=\"px-6 pt-10 pb-5\">\n    <div class=\"m-auto text-center\">\n      <h1\n        class=\"text-2xl md:text-4xl text-center font-bold text-gray-900 tracking-tight\"\n        v-text=\"instance.registration.name\"\n      />\n      <small><em v-text=\"instance.id\" /></small>\n    </div>\n    <div class=\"mt-4 text-center\">\n      <sba-tags v-if=\"instance\" :tags=\"instance.tags\" class=\"justify-center\" />\n    </div>\n  </div>\n</template>\n\n<script>\nimport Instance from '@/services/instance';\n\nexport default {\n  name: 'DetailsHero',\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n};\n</script>\n\n<style scoped></style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/details-info.spec.ts",
    "content": "import { screen, waitFor } from '@testing-library/vue';\nimport { AxiosHeaders } from 'axios';\nimport { HttpResponse, http } from 'msw';\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport DetailsInfo from './details-info.vue';\n\nimport { applications } from '@/mocks/applications/data';\nimport { server } from '@/mocks/server';\nimport Application from '@/services/application';\nimport { render } from '@/test-utils';\n\ndescribe('DetailsInfo', () => {\n  beforeEach(() => {\n    server.use(\n      http.get('/instances/:instanceId/actuator/info', () => {\n        return HttpResponse.json({\n          app: { version: '1.0.0', name: 'TestApp' },\n          java: { version: '17' },\n        });\n      }),\n    );\n  });\n\n  it('should render info table with keys and values', async () => {\n    const application = new Application(applications[0]);\n    const instance = application.instances[0];\n    instance.hasEndpoint = () => true;\n\n    instance.fetchInfo = async () => ({\n      data: {\n        app: { version: '1.0.0', name: 'TestApp' },\n        java: { version: '17' },\n      },\n      status: 200,\n      statusText: 'OK',\n      headers: new AxiosHeaders(),\n      config: { headers: new AxiosHeaders() },\n    });\n\n    render(DetailsInfo, {\n      props: { instance },\n    });\n\n    await waitFor(() => {\n      expect(screen.queryByRole('status')).not.toBeInTheDocument();\n    });\n\n    expect(await screen.findByText('app')).toBeVisible();\n    expect(await screen.findByText('java')).toBeVisible();\n    // YAML-formatted output is rendered in a <pre> block, so match the full string\n    expect(\n      await screen.findByText(/version: 1.0.0[\\s\\S]*name: TestApp/),\n    ).toBeVisible();\n    expect(await screen.findByText(/version: '17'/)).toBeVisible();\n  });\n\n  it('should show no info message if info is empty', async () => {\n    const application = new Application(applications[0]);\n    const instance = application.instances[0];\n    instance.hasEndpoint = () => true;\n    instance.fetchInfo = async () => ({\n      data: {},\n      status: 200,\n      statusText: 'OK',\n      headers: new AxiosHeaders(),\n      config: { headers: new AxiosHeaders() },\n    });\n\n    render(DetailsInfo, {\n      props: { instance },\n    });\n\n    await waitFor(() => {\n      expect(screen.queryByRole('status')).not.toBeInTheDocument();\n    });\n\n    expect(\n      await screen.findByText('instances.details.info.no_info_provided'),\n    ).toBeVisible();\n  });\n\n  it('should show error alert if fetch fails', async () => {\n    const application = new Application(applications[0]);\n    const instance = application.instances[0];\n    instance.hasEndpoint = () => true;\n    instance.fetchInfo = async () => {\n      throw new Error('fail');\n    };\n\n    render(DetailsInfo, {\n      props: { instance },\n    });\n\n    await waitFor(() => {\n      expect(screen.getByRole('alert')).toBeVisible();\n    });\n    expect(screen.getByRole('alert')).toHaveTextContent(\n      'Fetching of data failed.',\n    );\n  });\n\n  it('should call fetchInfo when instance changes (watcher)', async () => {\n    const application = new Application(applications[0]);\n    const instance1 = application.instances[0];\n    const instance2 = {\n      ...instance1,\n      id: 'other-id',\n      hasEndpoint: () => true,\n      fetchInfo: async () => ({ data: { foo: 'bar' } }),\n    };\n    instance1.hasEndpoint = () => true;\n    const fetchInfoSpy = vi\n      .fn()\n      .mockResolvedValue({ data: { app: { version: '1.0.0' } } });\n    instance1.fetchInfo = fetchInfoSpy;\n\n    const { rerender } = render(DetailsInfo, {\n      props: { instance: instance1 },\n    });\n\n    await waitFor(() => {\n      expect(fetchInfoSpy).toHaveBeenCalled();\n    });\n\n    // Now rerender with a new instance (different id)\n    await rerender({ instance: instance2 });\n\n    // Should show the new info from instance2\n    expect(await screen.findByText('foo')).toBeVisible();\n    expect(await screen.findByText('bar')).toBeVisible();\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/details-info.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-accordion\n    :id=\"`info-details-panel__${instance.id}`\"\n    v-model=\"panelOpen\"\n    :title=\"$t('instances.details.info.title')\"\n    :loading=\"loading\"\n  >\n    <template #title>\n      <div class=\"ml-2 transition-opacity\" :class=\"{ 'opacity-0': panelOpen }\">\n        ({{ Object.keys(info).length }})\n      </div>\n    </template>\n\n    <sba-alert\n      v-if=\"error\"\n      :error=\"error\"\n      class=\"border-l-4\"\n      :title=\"$t('term.fetch_failed')\"\n    />\n\n    <div class=\"content info -mx-4 -my-3\">\n      <sba-key-value-table v-if=\"!isEmptyInfo\" :map=\"info\" />\n      <p\n        v-else\n        class=\"mx-4 my-3\"\n        v-text=\"$t('instances.details.info.no_info_provided')\"\n      />\n    </div>\n  </sba-accordion>\n</template>\n\n<script setup>\nimport { computed, ref, watch } from 'vue';\n\nimport SbaAccordion from '@/components/sba-accordion.vue';\nimport SbaKeyValueTable from '@/components/sba-key-value-table.vue';\n\nimport Instance from '@/services/instance';\nimport { formatWithDataTypes } from '@/utils/formatWithDataTypes';\n\nconst props = defineProps({\n  instance: {\n    type: Instance,\n    required: true,\n  },\n});\n\nconst panelOpen = ref(true);\nconst error = ref(null);\nconst loading = ref(false);\nconst liveInfo = ref(null);\nconst currentInstanceId = ref(null);\n\nconst info = computed(() => formatInfo(liveInfo.value || props.instance.info));\nconst isEmptyInfo = computed(() => Object.keys(info.value).length <= 0);\n\nasync function fetchInfo() {\n  if (props.instance.hasEndpoint('info')) {\n    currentInstanceId.value = props.instance.id;\n    loading.value = true;\n    error.value = null;\n    try {\n      const res = await props.instance.fetchInfo();\n      liveInfo.value = res.data;\n    } catch (err) {\n      error.value = err;\n\n      console.warn('Fetching info failed:', err);\n    } finally {\n      loading.value = false;\n    }\n  }\n}\n\nfunction reloadInfo() {\n  if (props.instance.id !== currentInstanceId.value) {\n    fetchInfo();\n  }\n}\n\nfunction formatInfo(info) {\n  return formatWithDataTypes(info, {\n    'build.time': 'date',\n    'process.memory.heap.committed': 'bytes',\n    'process.memory.heap.init': 'bytes',\n    'process.memory.heap.max': 'bytes',\n    'process.memory.heap.used': 'bytes',\n    'process.memory.nonHeap.committed': 'bytes',\n    'process.memory.nonHeap.init': 'bytes',\n    'process.memory.nonHeap.max': 'bytes',\n    'process.memory.nonHeap.used': 'bytes',\n  });\n}\n\nwatch(\n  () => props.instance,\n  () => reloadInfo(),\n  { immediate: true },\n);\n\nfetchInfo();\n</script>\n\n<style lang=\"css\">\n.info {\n  overflow: auto;\n}\n\n.info__key {\n  vertical-align: top;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/details-memory.spec.ts",
    "content": "import { screen } from '@testing-library/vue';\nimport rxjs from 'rxjs';\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport { render } from '@/test-utils';\nimport DetailsMemory from '@/views/instances/details/details-memory.vue';\n\nvi.mock('@/sba-config', async () => {\n  const sbaConfig: any = await vi.importActual('@/sba-config');\n  return {\n    default: {\n      ...sbaConfig.default,\n      uiSettings: {\n        pollTimer: {\n          memory: 1234,\n        },\n      },\n    },\n  };\n});\n\ndescribe('DetailsMemory', () => {\n  it('should call timer with configured amount', async () => {\n    const timer = vi.spyOn(rxjs, 'timer');\n\n    const Instance = (await import('@/services/instance')).default;\n    render(DetailsMemory, {\n      stubs: {\n        MemChart: true,\n      },\n      props: {\n        instance: new Instance({ id: '1' }),\n        type: 'heap',\n      },\n    });\n\n    expect(timer).toHaveBeenCalledWith(0, 1234);\n  });\n\n  describe('when type is heap', async () => {\n    const Instance = (await import('@/services/instance')).default;\n\n    beforeEach(() => {\n      render(DetailsMemory, {\n        stubs: {\n          MemChart: true,\n        },\n        props: {\n          instance: new Instance({\n            id: '1',\n            availableMetrics: [\n              'jvm.memory.used',\n              'jvm.memory.max',\n              'jvm.memory.committed',\n            ],\n          }),\n          type: 'heap',\n        },\n      });\n    });\n\n    it('should render memory used', async () => {\n      expect(\n        await screen.findByLabelText('instances.details.memory.used'),\n      ).toHaveTextContent('115 MB');\n    });\n\n    it('should render memory size', async () => {\n      expect(\n        await screen.findByLabelText('instances.details.memory.size'),\n      ).toHaveTextContent('197 MB');\n    });\n\n    it('should render memory max', async () => {\n      expect(\n        await screen.findByLabelText('instances.details.memory.max'),\n      ).toHaveTextContent('8.59 GB');\n    });\n\n    it('should not render memory metaspace', async () => {\n      expect(\n        screen.queryByLabelText('instances.details.memory.metaspace'),\n      ).not.toBeInTheDocument();\n    });\n  });\n\n  describe('when type is nonheap', async () => {\n    const Instance = (await import('@/services/instance')).default;\n\n    beforeEach(() => {\n      render(DetailsMemory, {\n        stubs: {\n          MemChart: true,\n        },\n        props: {\n          instance: new Instance({\n            id: '1',\n            availableMetrics: [\n              'jvm.memory.used',\n              'jvm.memory.max',\n              'jvm.memory.committed',\n            ],\n          }),\n          type: 'nonheap',\n        },\n      });\n    });\n\n    it('should render memory used', async () => {\n      expect(\n        await screen.findByLabelText('instances.details.memory.used'),\n      ).toHaveTextContent('115 MB');\n    });\n\n    it('should render memory size', async () => {\n      expect(\n        await screen.findByLabelText('instances.details.memory.size'),\n      ).toHaveTextContent('197 MB');\n    });\n\n    it('should render memory max', async () => {\n      expect(\n        await screen.findByLabelText('instances.details.memory.max'),\n      ).toHaveTextContent('8.59 GB');\n    });\n\n    it('should render memory metaspace', async () => {\n      expect(\n        await screen.findByLabelText('instances.details.memory.metaspace'),\n      ).toHaveTextContent('115 MB');\n    });\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/details-memory.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-accordion\n    v-if=\"hasLoaded\"\n    :id=\"`memory-details-panel__${type}__${instance.id}`\"\n    v-model=\"panelOpen\"\n    :title=\"$t('instances.details.memory.title') + `: ${name}`\"\n  >\n    <template #title>\n      <div\n        class=\"ml-2 text-xs font-mono transition-opacity flex-1 justify-items-end\"\n        :class=\"{ 'opacity-0': panelOpen }\"\n      >\n        <div class=\"flex\">\n          {{ prettyBytes(current.used) }} /\n          {{ prettyBytes(current.committed) }} /\n          {{ prettyBytes(current.max) }}\n        </div>\n      </div>\n    </template>\n\n    <div>\n      <sba-alert v-if=\"error\" :error=\"error\" :title=\"$t('term.fetch_failed')\" />\n\n      <div v-if=\"current\" class=\"flex w-full\">\n        <div v-if=\"current.metaspace\" class=\"flex-1 text-center\">\n          <p\n            id=\"metrics.metaspace\"\n            class=\"font-bold\"\n            v-text=\"$t('instances.details.memory.metaspace')\"\n          />\n          <p\n            aria-labelledby=\"metrics.metaspace\"\n            v-text=\"prettyBytes(current.metaspace)\"\n          />\n        </div>\n        <div class=\"flex-1 text-center\">\n          <p\n            id=\"metrics.memory.used\"\n            class=\"font-bold\"\n            v-text=\"$t('instances.details.memory.used')\"\n          />\n          <p\n            aria-labelledby=\"metrics.memory.used\"\n            v-text=\"prettyBytes(current.used)\"\n          />\n        </div>\n        <div class=\"flex-1 text-center\">\n          <p\n            id=\"metrics.memory.size\"\n            class=\"font-bold\"\n            v-text=\"$t('instances.details.memory.size')\"\n          />\n          <p\n            aria-labelledby=\"metrics.memory.size\"\n            v-text=\"prettyBytes(current.committed)\"\n          />\n        </div>\n        <div v-if=\"current.max >= 0\" class=\"flex-1 text-center\">\n          <p\n            id=\"metrics.memory.max\"\n            class=\"font-bold\"\n            v-text=\"$t('instances.details.memory.max')\"\n          />\n          <p\n            aria-labelledby=\"metrics.memory.max\"\n            v-text=\"prettyBytes(current.max)\"\n          />\n        </div>\n      </div>\n\n      <MemChart v-if=\"chartData.length > 0\" :data=\"chartData\" />\n    </div>\n  </sba-accordion>\n</template>\n\n<script lang=\"ts\">\nimport moment from 'moment';\nimport prettyBytes from 'pretty-bytes';\nimport { concatMap, delay, retryWhen, timer } from 'rxjs';\nimport { take } from 'rxjs/operators';\nimport { defineComponent } from 'vue';\n\nimport SbaAccordion from '@/components/sba-accordion.vue';\n\nimport subscribing from '@/mixins/subscribing';\nimport sbaConfig from '@/sba-config';\nimport Instance from '@/services/instance';\nimport MemChart from '@/views/instances/details/mem-chart.vue';\n\nexport default defineComponent({\n  name: 'DetailsMemory',\n  components: { SbaAccordion, MemChart },\n  mixins: [subscribing],\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n    type: {\n      type: String,\n      required: true,\n    },\n  },\n  data: () => ({\n    panelOpen: true,\n    hasLoaded: false,\n    error: null,\n    current: null,\n    chartData: [],\n    currentInstanceId: null,\n  }),\n  computed: {\n    name() {\n      switch (this.type) {\n        case 'heap':\n          return 'Heap';\n        case 'nonheap':\n          return 'Non heap';\n        default:\n          return this.type;\n      }\n    },\n  },\n  watch: {\n    instance: {\n      handler: 'initMetrics',\n      immediate: true,\n    },\n  },\n  methods: {\n    initMetrics() {\n      if (this.instance.id !== this.currentInstanceId) {\n        this.currentInstanceId = this.instance.id;\n        this.hasLoaded = false;\n        this.error = null;\n        this.current = null;\n        this.chartData = [];\n      }\n    },\n    prettyBytes,\n    async fetchMetrics() {\n      const responseMax = this.instance.fetchMetric('jvm.memory.max', {\n        area: this.type,\n      });\n      const responseUsed = this.instance.fetchMetric('jvm.memory.used', {\n        area: this.type,\n      });\n      const hasMetaspace = (await responseUsed).data.availableTags.some(\n        (tag) => tag.tag === 'id' && tag.values.includes('Metaspace'),\n      );\n      const responeMetaspace =\n        this.type === 'nonheap' && hasMetaspace\n          ? this.instance.fetchMetric('jvm.memory.used', {\n              area: this.type,\n              id: 'Metaspace',\n            })\n          : null;\n      const responseCommitted = this.instance.fetchMetric(\n        'jvm.memory.committed',\n        { area: this.type },\n      );\n      return {\n        max: (await responseMax).data.measurements[0].value,\n        used: (await responseUsed).data.measurements[0].value,\n        metaspace: responeMetaspace\n          ? (await responeMetaspace).data.measurements[0].value\n          : null,\n        committed: (await responseCommitted).data.measurements[0].value,\n      };\n    },\n    createSubscription() {\n      return timer(0, sbaConfig.uiSettings.pollTimer.memory)\n        .pipe(\n          concatMap(this.fetchMetrics),\n          retryWhen((err) => {\n            return err.pipe(delay(1000), take(5));\n          }),\n        )\n        .subscribe({\n          next: (data) => {\n            this.hasLoaded = true;\n            this.current = data;\n            this.chartData.push({ ...data, timestamp: moment().valueOf() });\n          },\n          error: (error) => {\n            this.hasLoaded = true;\n            console.warn('Fetching memory metrics failed:', error);\n            this.error = error;\n          },\n        });\n    },\n  },\n});\n</script>\n\n<style lang=\"css\">\n.memory-current {\n  margin-bottom: 0 !important;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/details-metadata.spec.ts",
    "content": "import { screen } from '@testing-library/vue';\nimport { describe, expect, it } from 'vitest';\n\nimport DetailsMetadata from './details-metadata.vue';\n\nimport { applications } from '@/mocks/applications/data';\nimport Application from '@/services/application';\nimport Instance from '@/services/instance';\nimport { render } from '@/test-utils';\n\ndescribe('DetailsMetadata', () => {\n  it('should render metadata table with keys and values', () => {\n    const application = new Application(applications[0]);\n    const instance = application.instances[0];\n\n    render(DetailsMetadata, {\n      props: { instance },\n    });\n\n    expect(screen.getByText('startup')).toBeVisible();\n    expect(screen.getByText('2021-10-29T08:50:07.486289+02:00')).toBeVisible();\n    expect(screen.getByText('tags.environment')).toBeVisible();\n    expect(screen.getByText('test')).toBeVisible();\n  });\n\n  it('should show metadata count in title', () => {\n    const application = new Application(applications[0]);\n    const instance = application.instances[0];\n\n    render(DetailsMetadata, {\n      props: { instance },\n    });\n\n    expect(screen.getByText('(2)')).toBeVisible();\n  });\n\n  it('should show no metadata message if metadata is empty', () => {\n    const application = new Application(applications[0]);\n    const instance = new Instance({\n      ...application.instances[0],\n      registration: {\n        ...application.instances[0].registration,\n        metadata: {},\n      },\n    });\n\n    render(DetailsMetadata, {\n      props: { instance },\n    });\n\n    expect(\n      screen.getByText('instances.details.metadata.no_data_provided'),\n    ).toBeVisible();\n  });\n\n  it('should show count as (0) when metadata is empty', () => {\n    const application = new Application(applications[0]);\n    const instance = new Instance({\n      ...application.instances[0],\n      registration: {\n        ...application.instances[0].registration,\n        metadata: {},\n      },\n    });\n\n    render(DetailsMetadata, {\n      props: { instance },\n    });\n\n    expect(screen.getByText('(0)')).toBeVisible();\n  });\n\n  it('should sort metadata keys alphabetically', () => {\n    const application = new Application(applications[0]);\n    const instance = new Instance({\n      ...application.instances[0],\n      registration: {\n        ...application.instances[0].registration,\n        metadata: {\n          zebra: 'value1',\n          apple: 'value2',\n          banana: 'value3',\n        },\n      },\n    });\n\n    render(DetailsMetadata, {\n      props: { instance },\n    });\n\n    const keys = screen.getAllByRole('term');\n    expect(keys[0]).toHaveTextContent('apple');\n    expect(keys[1]).toHaveTextContent('banana');\n    expect(keys[2]).toHaveTextContent('zebra');\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/details-metadata.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-accordion\n    :id=\"`metadata-details-panel__${instance.id}`\"\n    v-model=\"panelOpen\"\n    :title=\"$t('instances.details.metadata.title')\"\n    :seamless=\"true\"\n  >\n    <template #title>\n      <div class=\"ml-2 transition-opacity\" :class=\"{ 'opacity-0': panelOpen }\">\n        ({{ Object.keys(metadata).length }})\n      </div>\n    </template>\n    <sba-key-value-table v-if=\"hasMetadata\" :map=\"metadata\" />\n    <p\n      v-else\n      class=\"mx-4 my-3\"\n      v-text=\"$t('instances.details.metadata.no_data_provided')\"\n    />\n  </sba-accordion>\n</template>\n\n<script lang=\"ts\" setup>\nimport { computed, ref } from 'vue';\n\nimport SbaAccordion from '@/components/sba-accordion.vue';\n\nimport Instance from '@/services/instance';\nimport { sortObject } from '@/utils/sortObject';\n\nconst { instance } = defineProps<{\n  instance: Instance;\n}>();\n\nconst panelOpen = ref(true);\n\nconst metadata = computed(() => {\n  return sortObject(instance.registration.metadata);\n});\n\nconst hasMetadata = computed(() => {\n  return Object.keys(metadata.value).length > 0;\n});\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/details-nav.vue",
    "content": "<template>\n  <sba-sticky-subnav>\n    <div class=\"flex\">\n      <div class=\"flex-2\">\n        <instance-switcher\n          :current-instance=\"instance\"\n          :instances=\"application.instances\"\n        />\n      </div>\n      <div class=\"flex-1 text-right\">\n        <sba-button-group>\n          <sba-button\n            v-if=\"instance.showUrl() && !instance.isUrlDisabled()\"\n            as=\"a\"\n            referrerpolicy=\"no-referrer\"\n            target=\"_blank\"\n            :title=\"instance.registration.serviceUrl\"\n            :href=\"instance.registration.serviceUrl\"\n            class=\"border-gray-400 ml-1\"\n          >\n            <font-awesome-icon :icon=\"faHome\" />\n          </sba-button>\n\n          <sba-button\n            v-if=\"instance.showUrl()\"\n            as=\"a\"\n            referrerpolicy=\"no-referrer\"\n            target=\"_blank\"\n            :title=\"instance.registration.managementUrl\"\n            :href=\"instance.registration.managementUrl\"\n            class=\"border-gray-400 ml-1\"\n          >\n            <font-awesome-icon :icon=\"faClipboardList\" />\n          </sba-button>\n\n          <sba-button\n            v-if=\"instance.showUrl()\"\n            as=\"a\"\n            referrerpolicy=\"no-referrer\"\n            target=\"_blank\"\n            :title=\"instance.registration.healthUrl\"\n            :href=\"instance.registration.healthUrl\"\n            class=\"border-gray-400 ml-1\"\n          >\n            <font-awesome-icon :icon=\"faHeart\" />\n          </sba-button>\n        </sba-button-group>\n      </div>\n    </div>\n  </sba-sticky-subnav>\n</template>\n\n<script setup lang=\"ts\">\nimport {\n  faClipboardList,\n  faHeart,\n  faHome,\n} from '@fortawesome/free-solid-svg-icons';\nimport { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';\n\nimport SbaButtonGroup from '@/components/sba-button-group';\nimport SbaButton from '@/components/sba-button.vue';\nimport SbaStickySubnav from '@/components/sba-sticky-subnav.vue';\n\nimport Application from '@/services/application';\nimport Instance from '@/services/instance';\nimport InstanceSwitcher from '@/views/instances/details/instance-switcher';\n\ndefineProps<{\n  application: Application;\n  instance: Instance;\n}>();\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/details-process.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-accordion\n    v-if=\"hasLoaded\"\n    :id=\"`process-details-panel__${instance.id}`\"\n    v-model=\"panelOpen\"\n    :title=\"$t('instances.details.process.title')\"\n  >\n    <template #title>\n      <div\n        class=\"ml-2 text-xs font-mono transition-opacity flex-1 justify-items-end\"\n        :class=\"{ 'opacity-0': panelOpen }\"\n      >\n        <ul class=\"flex gap-4\">\n          <li>\n            <span class=\"block 2xl:inline\">\n              {{ t('instances.details.process.uptime_short') }}:\n            </span>\n            <process-uptime :value=\"tableData.uptime.value\" />\n          </li>\n          <li>\n            <span class=\"block 2xl:inline\">\n              {{ t('instances.details.process.system_cpu_usage_short') }}:\n            </span>\n            {{ tableData.systemCpuLoad.value }}\n          </li>\n          <li>\n            <span class=\"block 2xl:inline\">\n              {{ t('instances.details.process.process_cpu_usage_short') }}:\n            </span>\n            {{ tableData.processCpuLoad.value }}\n          </li>\n        </ul>\n      </div>\n    </template>\n    <div>\n      <sba-alert v-if=\"error\" :error=\"error\" :title=\"$t('term.fetch_failed')\" />\n      <div v-else class=\"-mx-4 -my-3\">\n        <sba-key-value-table :map=\"tableData\" skip-null-values>\n          <template #uptime=\"value\">\n            <process-uptime :value=\"value.value\" />\n          </template>\n        </sba-key-value-table>\n      </div>\n    </div>\n  </sba-accordion>\n</template>\n\n<script setup lang=\"ts\">\nimport { Subscription } from 'rxjs';\nimport { take } from 'rxjs/operators';\nimport { computed, onMounted, onUnmounted, ref } from 'vue';\nimport { useI18n } from 'vue-i18n';\n\nimport SbaAccordion from '@/components/sba-accordion.vue';\n\nimport sbaConfig from '@/sba-config';\nimport Instance from '@/services/instance';\nimport { concatMap, delay, retryWhen, timer } from '@/utils/rxjs';\nimport processUptime from '@/views/instances/details/process-uptime';\nimport { toMillis } from '@/views/instances/metrics/metric';\n\n// Typdefinitionen\n\n// Typdefinitionen\ninterface UptimeData {\n  value: number | null;\n  baseUnit: string | null;\n}\n\ninterface MetricResponse {\n  measurements: Array<{ value: number }>;\n  baseUnit: string;\n}\n\ninterface CpuLoadMetrics {\n  processCpuLoad: number | null;\n  systemCpuLoad: number | null;\n}\n\ninterface TableData {\n  label: string;\n  value: number | string | null;\n}\n\ninterface TableDataMap {\n  pid: TableData;\n  parentPid: TableData;\n  owner: TableData;\n  uptime: TableData;\n  processCpuLoad: TableData;\n  systemCpuLoad: TableData;\n  cpus: TableData;\n}\n\n// Props Definition\nconst props = defineProps<{\n  instance: Instance;\n}>();\n\nconst { t, locale } = useI18n();\n\n// Reaktive Zustandsvariablen\nconst hasLoaded = ref<boolean>(false);\nconst error = ref<Error | null>(null);\n\nconst pid = ref<number | null>(null);\nconst parentPid = ref<number | null>(null);\nconst owner = ref<string | null>(null);\nconst uptime = ref<UptimeData>({ value: null, baseUnit: null });\nconst systemCpuLoad = ref<number | null>(null);\nconst processCpuLoad = ref<number | null>(null);\nconst systemCpuCount = ref<number | null>(null);\nconst panelOpen = ref<boolean>(true);\n\n// Berechnete Eigenschaften\nconst tableData = computed<TableDataMap>(() => {\n  const formatNumber = Intl.NumberFormat(locale.value, {\n    maximumFractionDigits: 2,\n  });\n\n  return {\n    pid: {\n      label: t('instances.details.process.pid'),\n      value: pid.value,\n    },\n    parentPid: {\n      label: t('instances.details.process.parent_pid'),\n      value: parentPid.value,\n    },\n    owner: {\n      label: t('instances.details.process.owner'),\n      value: owner.value,\n    },\n    uptime: {\n      label: t('instances.details.process.uptime'),\n      value: toMillis(uptime.value.value, uptime.value.baseUnit),\n    },\n    processCpuLoad: {\n      label: t('instances.details.process.process_cpu_usage'),\n      value: isNaN(processCpuLoad.value)\n        ? '-'\n        : `${formatNumber.format(processCpuLoad.value * 100)}%`,\n    },\n    systemCpuLoad: {\n      label: t('instances.details.process.system_cpu_usage'),\n      value: isNaN(systemCpuLoad.value)\n        ? '-'\n        : `${formatNumber.format(systemCpuLoad.value * 100)}%`,\n    },\n    cpus: {\n      label: t('instances.details.process.cpus'),\n      value: systemCpuCount.value,\n    },\n  };\n});\n\n// Hilfsfunktionen\nconst fetchMetric = async (name: string): Promise<MetricResponse> => {\n  const response = await props.instance.fetchMetric(name);\n  return response.data;\n};\n\nconst fetchUptime = async (): Promise<void> => {\n  try {\n    const response = await fetchMetric('process.uptime');\n    uptime.value = {\n      value: response.measurements[0].value,\n      baseUnit: response.baseUnit,\n    };\n  } catch (err) {\n    error.value = err instanceof Error ? err : new Error('Unknown error');\n    console.warn('Fetching Uptime failed:', err);\n  }\n};\n\nconst fetchPid = async (): Promise<void> => {\n  if (props.instance.hasEndpoint('env')) {\n    try {\n      const response = await props.instance.fetchEnv('PID');\n      pid.value = response.data.property.value;\n    } catch (err) {\n      console.warn('Fetching PID failed:', err);\n    }\n  }\n};\n\nconst fetchCpuCount = async (): Promise<void> => {\n  try {\n    const response = await fetchMetric('system.cpu.count');\n    systemCpuCount.value = response.measurements[0].value;\n  } catch (err) {\n    console.warn('Fetching Cpu Count failed:', err);\n  }\n};\n\nconst fetchProcessInfo = async (): Promise<void> => {\n  try {\n    const response = await props.instance.fetchInfo();\n    const processInfo = response.data.process;\n    if (processInfo) {\n      pid.value = processInfo.pid;\n      parentPid.value = processInfo.parentPid;\n      owner.value = processInfo.owner;\n    }\n  } catch (err) {\n    console.warn('Fetching Process Info failed:', err);\n  }\n};\n\nconst fetchCpuLoadMetrics = async (): Promise<CpuLoadMetrics> => {\n  const fetchProcessCpuLoad = fetchMetric('process.cpu.usage');\n  const fetchSystemCpuLoad = fetchMetric('system.cpu.usage');\n\n  let processCpuLoadValue: number | null = null;\n  let systemCpuLoadValue: number | null = null;\n\n  try {\n    const response = await fetchProcessCpuLoad;\n    processCpuLoadValue = response.measurements[0].value;\n  } catch (err) {\n    console.warn('Fetching Process CPU Load failed:', err);\n  }\n\n  try {\n    const response = await fetchSystemCpuLoad;\n    systemCpuLoadValue = response.measurements[0].value;\n  } catch (err) {\n    console.warn('Fetching System CPU Load failed:', err);\n  }\n\n  return {\n    processCpuLoad: processCpuLoadValue,\n    systemCpuLoad: systemCpuLoadValue,\n  };\n};\n\nlet subscription: Subscription;\nonMounted(async () => {\n  try {\n    await Promise.allSettled([\n      fetchPid(),\n      fetchUptime(),\n      fetchCpuCount(),\n      fetchProcessInfo(),\n    ]);\n  } finally {\n    hasLoaded.value = true;\n  }\n\n  subscription = timer(0, sbaConfig.uiSettings.pollTimer.process)\n    .pipe(\n      concatMap(fetchCpuLoadMetrics),\n      retryWhen((err) => err.pipe(delay(1000, take(5)))),\n    )\n    .subscribe({\n      next: (data: CpuLoadMetrics) => {\n        processCpuLoad.value = data.processCpuLoad;\n        systemCpuLoad.value = data.systemCpuLoad;\n      },\n      error: (err: Error) => {\n        hasLoaded.value = true;\n        console.warn('Fetching CPU Usage metrics failed:', err);\n        error.value = err;\n      },\n    });\n});\n\nonUnmounted(() => {\n  subscription.unsubscribe();\n});\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/details-threads.spec.ts",
    "content": "import { screen } from '@testing-library/vue';\nimport { HttpResponse, http } from 'msw';\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport { applications } from '@/mocks/applications/data';\nimport { server } from '@/mocks/server';\nimport Application from '@/services/application';\nimport { render } from '@/test-utils';\nimport DetailsThreads from '@/views/instances/details/details-threads.vue';\n\nvi.mock('@/sba-config', async () => {\n  const sbaConfig: any = await vi.importActual('@/sba-config');\n  return {\n    default: {\n      ...sbaConfig.default,\n      uiSettings: {\n        pollTimer: {\n          threads: 100,\n        },\n      },\n    },\n  };\n});\n\ndescribe('DetailsThreads', () => {\n  const LIVE = [10.0, 12.0, 14.0];\n  const PEAK = [15.0, 16.0, 18.0];\n  const DAEMON = [4.0, 5.0, 6.0];\n\n  beforeEach(() => {\n    const liveGen = (function* () {\n      yield* LIVE;\n    })();\n    const peakGen = (function* () {\n      yield* PEAK;\n    })();\n    const daemonGen = (function* () {\n      yield* DAEMON;\n    })();\n\n    server.use(\n      http.get('/instances/:instanceId/actuator/metrics', () => {\n        return HttpResponse.json({\n          names: ['jvm.threads.live', 'jvm.threads.peak', 'jvm.threads.daemon'],\n        });\n      }),\n      http.get(\n        '/instances/:instanceId/actuator/metrics/jvm.threads.live',\n        () => {\n          return HttpResponse.json({\n            measurements: [{ value: liveGen.next()?.value }],\n          });\n        },\n      ),\n      http.get(\n        '/instances/:instanceId/actuator/metrics/jvm.threads.peak',\n        () => {\n          return HttpResponse.json({\n            measurements: [{ value: peakGen.next()?.value }],\n          });\n        },\n      ),\n      http.get(\n        '/instances/:instanceId/actuator/metrics/jvm.threads.daemon',\n        () => {\n          return HttpResponse.json({\n            measurements: [{ value: daemonGen.next()?.value }],\n          });\n        },\n      ),\n    );\n  });\n\n  const renderComponent = async () => {\n    const stubChart = {\n      props: ['data'],\n      template: `\n        <div data-testid=\"chart\">{{ JSON.stringify($props.data) }}</div>\n      `,\n    };\n\n    const application = new Application(applications[0]);\n    const instance = application.instances[0];\n\n    return render(DetailsThreads, {\n      global: { stubs: { threadsChart: stubChart } },\n      props: { instance },\n    });\n  };\n\n  it('pushes chartData points and exposes them via stub', async () => {\n    await renderComponent();\n\n    const text = (await screen.findByTestId('chart')).textContent;\n    const data = JSON.parse(text);\n\n    // first sample matches generators\n    expect(data[0].live).toEqual(LIVE[0]);\n    expect(data[0].peak).toEqual(PEAK[0]);\n    expect(data[0].daemon).toEqual(DAEMON[0]);\n  });\n\n  it('should reinitialize metrics when instance changes', async () => {\n    const { rerender } = await renderComponent();\n\n    const newApp = new Application({\n      name: 'Other',\n      statusTimestamp: Date.now(),\n      instances: [{ id: 'other-1', statusInfo: { status: 'UP' } }],\n    });\n    const newInstance = newApp.instances[0];\n\n    await rerender({ instance: newInstance });\n\n    const text = (await screen.findByTestId('chart')).textContent;\n    const data = JSON.parse(text);\n\n    // first sample matches generators\n    expect(data[0].live).toEqual(LIVE[0]);\n    expect(data[0].peak).toEqual(PEAK[0]);\n    expect(data[0].daemon).toEqual(DAEMON[0]);\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/details-threads.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-accordion\n    v-if=\"hasLoaded\"\n    :id=\"`threads-details-panel__${instance.id}`\"\n    v-model=\"panelOpen\"\n    :title=\"$t('instances.details.threads.title')\"\n  >\n    <template #title>\n      <div\n        class=\"ml-2 text-xs font-mono transition-opacity flex-1 justify-items-end\"\n        :class=\"{ 'opacity-0': panelOpen }\"\n      >\n        <ul class=\"flex gap-4\">\n          <li>\n            <span class=\"block 2xl:inline\">\n              {{ $t('instances.details.threads.live') }}:\n            </span>\n            {{ current.live }}\n          </li>\n          <li>\n            <span class=\"block 2xl:inline\">\n              {{ $t('instances.details.threads.daemon') }}:\n            </span>\n            {{ current.daemon }}\n          </li>\n          <li>\n            <span class=\"block 2xl:inline\">\n              {{ $t('instances.details.threads.peak_live') }}:\n            </span>\n            {{ current.peak }}\n          </li>\n        </ul>\n      </div>\n    </template>\n\n    <div>\n      <sba-alert v-if=\"error\" :error=\"error\" :title=\"$t('term.fetch_failed')\" />\n\n      <div v-if=\"current\" class=\"flex w-full\">\n        <div class=\"flex-1 text-center\">\n          <p class=\"font-bold\" v-text=\"$t('instances.details.threads.live')\" />\n          <p v-text=\"current.live\" />\n        </div>\n        <div class=\"flex-1 text-center\">\n          <p\n            class=\"font-bold\"\n            v-text=\"$t('instances.details.threads.daemon')\"\n          />\n          <p v-text=\"current.daemon\" />\n        </div>\n        <div class=\"flex-1 text-center\">\n          <p\n            class=\"font-bold\"\n            v-text=\"$t('instances.details.threads.peak_live')\"\n          />\n          <p v-text=\"current.peak\" />\n        </div>\n      </div>\n\n      <threads-chart v-if=\"chartData.length > 0\" :data=\"chartData\" />\n    </div>\n  </sba-accordion>\n</template>\n\n<script>\nimport moment from 'moment';\nimport { take } from 'rxjs/operators';\n\nimport SbaAccordion from '@/components/sba-accordion.vue';\n\nimport subscribing from '@/mixins/subscribing';\nimport sbaConfig from '@/sba-config';\nimport Instance from '@/services/instance';\nimport { concatMap, delay, retryWhen, timer } from '@/utils/rxjs';\nimport threadsChart from '@/views/instances/details/threads-chart';\n\nexport default {\n  components: { SbaAccordion, threadsChart },\n  mixins: [subscribing],\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  data: () => ({\n    panelOpen: true,\n    hasLoaded: false,\n    error: null,\n    current: null,\n    chartData: [],\n    currentInstanceId: null,\n  }),\n  watch: {\n    instance: {\n      handler: 'initMetrics',\n      immediate: true,\n    },\n  },\n  methods: {\n    initMetrics() {\n      if (this.instance.id !== this.currentInstanceId) {\n        this.currentInstanceId = this.instance.id;\n        this.error = null;\n        this.hasLoaded = false;\n        this.current = null;\n        this.chartData = [];\n      }\n    },\n    async fetchMetrics() {\n      const responseLive = this.instance.fetchMetric('jvm.threads.live');\n      const responsePeak = this.instance.fetchMetric('jvm.threads.peak');\n      const responseDaemon = this.instance.fetchMetric('jvm.threads.daemon');\n\n      return {\n        live: (await responseLive).data.measurements[0].value,\n        peak: (await responsePeak).data.measurements[0].value,\n        daemon: (await responseDaemon).data.measurements[0].value,\n      };\n    },\n    createSubscription() {\n      return timer(0, sbaConfig.uiSettings.pollTimer.threads)\n        .pipe(\n          concatMap(this.fetchMetrics),\n          retryWhen((err) => {\n            return err.pipe(delay(1000), take(5));\n          }),\n        )\n        .subscribe({\n          next: (data) => {\n            this.hasLoaded = true;\n            this.current = data;\n            this.chartData.push({ ...data, timestamp: moment().valueOf() });\n          },\n          error: (error) => {\n            this.hasLoaded = true;\n            console.warn('Fetching threads metrics failed:', error);\n            this.error = error;\n          },\n        });\n    },\n  },\n};\n</script>\n\n<style lang=\"css\">\n.threads-current {\n  margin-bottom: 0 !important;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/health-details.spec.ts",
    "content": "import { screen, within } from '@testing-library/vue';\nimport { beforeEach, describe, expect, it } from 'vitest';\n\nimport { render } from '@/test-utils';\nimport HealthDetails from '@/views/instances/details/health-details.vue';\n\ndescribe('HealthDetails', () => {\n  describe('Health .details', () => {\n    beforeEach(() => {\n      const healthMock = {\n        status: 'UP',\n        details: {\n          clientConfigServer: {\n            status: 'UNKNOWN',\n            details: { error: 'no property sources located' },\n          },\n          db: {\n            status: 'UP',\n            details: {\n              database: 'HSQL Database Engine',\n              validationQuery: 'isValid()',\n            },\n          },\n          discoveryComposite: {\n            description: 'Discovery Client not initialized',\n            status: 'UNKNOWN',\n            details: {\n              discoveryClient: {\n                description: 'Discovery Client not initialized',\n                status: 'DOWN',\n              },\n            },\n          },\n          diskSpace: {\n            status: 'UP',\n            details: {\n              total: 994662584320,\n              free: 300063879168,\n              threshold: 10485760,\n              exists: true,\n            },\n          },\n          diskSpace2: {\n            status: 'UP',\n            details: {\n              total: 1024,\n              free: 2048,\n              threshold: 4096,\n              exists: false,\n            },\n          },\n          ssl: {\n            status: 'UP',\n            details: {\n              validChains: JSON.parse(\n                '[{\"status\": \"VALID\", \"chain\": [{\"subject\": \"CN=example.com, OU=IT, O=Example Corp, L=San Francisco, ST=CA, C=US\", \"issuer\": \"CN=R3, O=Let\\'s Encrypt, C=US\"}]}]',\n              ),\n            },\n          },\n        },\n      };\n\n      render(HealthDetails, {\n        props: {\n          name: 'Name',\n          health: healthMock,\n        },\n      });\n    });\n\n    it.each`\n      componentId             | status\n      ${'clientConfigServer'} | ${'UNKNOWN'}\n      ${'discoveryComposite'} | ${'UNKNOWN'}\n      ${'discoveryClient'}    | ${'DOWN'}\n      ${'diskSpace'}          | ${'UP'}\n    `(\n      'should display health components status',\n      async ({ componentId, status }) => {\n        const clientConfigServer = await screen.findByRole('definition', {\n          name: componentId,\n        });\n        expect(\n          await within(clientConfigServer).findByRole('status'),\n        ).toHaveTextContent(status);\n      },\n    );\n\n    it('should format diskSpace details correctly', async () => {\n      const diskSpaceInfo = await screen.findByRole('definition', {\n        name: 'diskSpace2',\n      });\n\n      // Assert pretty-printed numbers via pretty-bytes and other primitive values\n      const dsi = within(diskSpaceInfo);\n      // total: 994662584320 bytes -> 995 GB (rounded)\n      expect(\n        await dsi.findByRole('definition', { name: 'total' }),\n      ).toHaveTextContent('1.02 kB');\n\n      // free: 300063879168 bytes -> 300 GB (rounded)\n      expect(\n        await dsi.findByRole('definition', { name: 'free' }),\n      ).toHaveTextContent('2.05 kB');\n\n      // threshold: 10485760 bytes -> 10 MB\n      expect(\n        await dsi.findByRole('definition', { name: 'threshold' }),\n      ).toHaveTextContent('4.1 kB');\n\n      // exists: boolean unchanged\n      expect(\n        await dsi.findByRole('definition', { name: 'exists' }),\n      ).toHaveTextContent('false');\n    });\n\n    it('should format object details correctly', async () => {\n      const sslInfo = await screen.findByRole('definition', {\n        name: 'validChains',\n      });\n      expect(sslInfo).toMatchSnapshot();\n    });\n  });\n\n  describe('Health .components', () => {\n    beforeEach(() => {\n      const healthMock = {\n        status: 'UP',\n        components: {\n          clientConfigServer: {\n            status: 'UNKNOWN',\n            details: { error: 'no property sources located' },\n          },\n          discoveryComposite: {\n            description: 'Discovery Client not initialized',\n            status: 'UNKNOWN',\n            components: {\n              discoveryClient: {\n                description: 'Discovery Client not initialized',\n                status: 'DOWN',\n              },\n            },\n          },\n          diskSpace: {\n            status: 'UP',\n            details: {\n              total: 994662584320,\n              free: 116363821056,\n              threshold: 10485760,\n              exists: true,\n            },\n          },\n        },\n      };\n\n      render(HealthDetails, {\n        props: {\n          health: healthMock,\n        },\n      });\n    });\n\n    it('should display health status', async () => {\n      expect(\n        screen.getByRole('definition', { name: 'clientConfigServer' }),\n      ).toBeInTheDocument();\n    });\n\n    it.each`\n      componentId             | status\n      ${'clientConfigServer'} | ${'UNKNOWN'}\n      ${'discoveryComposite'} | ${'UNKNOWN'}\n      ${'discoveryClient'}    | ${'DOWN'}\n      ${'diskSpace'}          | ${'UP'}\n    `(\n      'should display health components status',\n      async ({ componentId, status }) => {\n        const clientConfigServer = await screen.findByRole('definition', {\n          name: componentId,\n        });\n        expect(\n          await within(clientConfigServer).findByRole('status'),\n        ).toHaveTextContent(status);\n      },\n    );\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/health-details.vue",
    "content": "<!--\n  - Copyright 2014-2020 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <dl\n    class=\"px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6\"\n    :class=\"{ 'bg-white': index % 2 === 0, 'bg-gray-50': index % 2 !== 0 }\"\n  >\n    <dt :id=\"`health-${id}__${name}`\" class=\"text-sm font-medium text-gray-500\">\n      {{ name }}\n    </dt>\n    <dd\n      class=\"mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2\"\n      :aria-labelledby=\"`health-${id}__` + name\"\n    >\n      <sba-status-badge v-if=\"health.status\" :status=\"health.status\" />\n\n      <dl v-if=\"details && details.length > 0\" class=\"grid grid-cols-2 mt-2\">\n        <template v-for=\"detail in details\" :key=\"detail.name\">\n          <dt\n            :id=\"`health-detail-${id}__${detail.name}`\"\n            class=\"font-medium\"\n            v-text=\"detail.name\"\n          />\n          <dd\n            v-if=\"\n              name.toLowerCase().startsWith('diskspace') &&\n              typeof detail.value === 'number'\n            \"\n            :aria-labelledby=\"`health-detail-${id}__${detail.name}`\"\n            v-text=\"prettyBytes(detail.value)\"\n          />\n          <dd\n            v-else-if=\"typeof detail.value === 'object'\"\n            :aria-labelledby=\"`health-detail-${id}__${detail.name}`\"\n          >\n            <sba-formatted-obj\n              class=\"overflow-auto !whitespace-pre\"\n              :value=\"detail.value\"\n            />\n          </dd>\n          <dd\n            v-else\n            :aria-labelledby=\"`health-detail-${id}__${detail.name}`\"\n            class=\"break-words whitespace-pre-wrap\"\n            v-html=\"autolink(detail.value)\"\n          />\n        </template>\n      </dl>\n    </dd>\n  </dl>\n\n  <health-details\n    v-for=\"(child, idx) in childHealth\"\n    :key=\"child.name\"\n    :index=\"idx + 1\"\n    :name=\"child.name\"\n    :health=\"child.value\"\n  />\n</template>\n\n<script lang=\"ts\" setup>\nimport prettyBytes from 'pretty-bytes';\nimport { computed, useId } from 'vue';\n\nimport SbaFormattedObj from '@/components/sba-formatted-obj.vue';\n\nimport autolink from '@/utils/autolink';\n\nconst id = useId();\n\nconst isChildHealth = (value) => {\n  return value !== null && typeof value === 'object' && 'status' in value;\n};\n\nconst {\n  health,\n  name,\n  index = 0,\n} = defineProps<{\n  name: string;\n  health: Record<string, any>;\n  index?: number;\n}>();\n\nconst details = computed(() => {\n  if (health.details || health.components) {\n    return Object.entries(health.details || health.components)\n      .filter(([, value]) => !isChildHealth(value))\n      .map(([name, value]) => ({ name, value }));\n  }\n  return [];\n});\n\nconst childHealth = computed(() => {\n  if (health.details || health.components) {\n    return Object.entries(health.details || health.components)\n      .filter(([, value]) => isChildHealth(value))\n      .map(([name, value]) => ({ name, value }));\n  }\n  return [];\n});\n</script>\n\n<style scoped>\n:deep(a[href]) {\n  @apply underline;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/i18n.de.json",
    "content": "{\n  \"instances\": {\n    \"details\": {\n      \"label\": \"Details\",\n      \"cache\": {\n        \"hit_ratio\": \"Treffer-Rate\",\n        \"hits\": \"Treffer\",\n        \"misses\": \"Fehltreffer\",\n        \"size\": \"Größe\"\n      },\n      \"datasource\": {\n        \"active_connections\": \"Aktive Verbindungen\",\n        \"min_connections\": \"Min. Verbindungen\",\n        \"max_connections\": \"Max. Verbindungen\",\n        \"title\": \"Datenquelle: {dataSource}\",\n        \"unlimited\": \"unlimitiert\"\n      },\n      \"gc\": {\n        \"count\": \"Anzahl\",\n        \"time_spent_max\": \"Max. Zeitdauer\",\n        \"time_spent_total\": \"Gesamtzeit\",\n        \"title\": \"GC Pausen\"\n      },\n      \"health\": {\n        \"title\": \"Zustand\"\n      },\n      \"health_group\": {\n        \"title\": \"Zustandsgruppe\"\n      },\n      \"info\": {\n        \"no_info_provided\": \"Keine Informationen verfügbar.\",\n        \"title\": \"Info\"\n      },\n      \"memory\": {\n        \"max\": \"Max\",\n        \"metaspace\": \"Metaspace\",\n        \"size\": \"Größe\",\n        \"title\": \"Speicher\",\n        \"used\": \"In Verwendung\",\n        \"committed\": \"Zugesichert\"\n      },\n      \"metadata\": {\n        \"no_data_provided\": \"Keine Metadaten vorhanden\",\n        \"title\": \"Metadaten\"\n      },\n      \"process\": {\n        \"cpus\": \"CPUs\",\n        \"pid\": \"PID\",\n        \"parent_pid\": \"Eltern-PID\",\n        \"owner\": \"Besitzer\",\n        \"process_cpu_usage\": \"CPU-Auslastung (%)\",\n        \"system_cpu_usage\": \"System-Auslastung (%)\",\n        \"title\": \"Prozess\",\n        \"uptime\": \"Uptime\"\n      },\n      \"threads\": {\n        \"daemon\": \"Daemon\",\n        \"live\": \"Live\",\n        \"peak_live\": \"Peak Live\",\n        \"title\": \"Threads\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/i18n.en.json",
    "content": "{\n  \"instances\": {\n    \"details\": {\n      \"label\": \"Details\",\n      \"cache\": {\n        \"hit_ratio\": \"Hit ratio\",\n        \"hits\": \"Hits\",\n        \"misses\": \"Misses\",\n        \"size\": \"Size\"\n      },\n      \"datasource\": {\n        \"active_connections\": \"Active connections\",\n        \"min_connections\": \"Min connections\",\n        \"max_connections\": \"Max connections\",\n        \"title\": \"Datasource: {dataSource}\",\n        \"unlimited\": \"unlimited\"\n      },\n      \"gc\": {\n        \"count\": \"Count\",\n        \"count_short\": \"#\",\n        \"time_spent_max\": \"Max time spent\",\n        \"time_spent_max_short\": \"Max\",\n        \"time_spent_total\": \"Total time spent\",\n        \"time_spent_total_short\": \"Total\",\n        \"title\": \"Garbage Collection Pauses\"\n      },\n      \"health\": {\n        \"title\": \"Health\"\n      },\n      \"health_group\": {\n        \"title\": \"Health Group\"\n      },\n      \"info\": {\n        \"no_info_provided\": \"No info provided.\",\n        \"title\": \"Info\"\n      },\n      \"memory\": {\n        \"max\": \"Max\",\n        \"metaspace\": \"Metaspace\",\n        \"size\": \"Size\",\n        \"title\": \"Memory\",\n        \"used\": \"Used\",\n        \"committed\": \"Committed\"\n      },\n      \"metadata\": {\n        \"no_data_provided\": \"No metadata provided.\",\n        \"title\": \"Metadata\"\n      },\n      \"process\": {\n        \"cpus\": \"CPUs\",\n        \"pid\": \"PID\",\n        \"parent_pid\": \"Parent PID\",\n        \"owner\": \"Owner\",\n        \"process_cpu_usage\": \"Process CPU Usage\",\n        \"process_cpu_usage_short\": \"CPU\",\n        \"system_cpu_usage\": \"System CPU Usage\",\n        \"system_cpu_usage_short\": \"Sys\",\n        \"title\": \"Process\",\n        \"uptime\": \"Uptime\",\n        \"uptime_short\": \"Up\"\n      },\n      \"threads\": {\n        \"daemon\": \"Daemon\",\n        \"live\": \"Live\",\n        \"peak_live\": \"Peak Live\",\n        \"title\": \"Threads\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/i18n.es.json",
    "content": "{\n  \"instances\": {\n    \"details\": {\n      \"label\": \"Detalles\",\n\n      \"cache\": {\n        \"hit_ratio\": \"Índice de aciertos\",\n        \"hits\": \"Aciertos\",\n        \"misses\": \"Fallas\",\n        \"size\": \"Tamaño\"\n      },\n      \"datasource\": {\n        \"active_connections\": \"Conexiones activas\",\n        \"min_connections\": \"Conexiones Min\",\n        \"max_connections\": \"Conexiones Max \",\n        \"title\": \"Fuente de datos: {dataSource}\",\n        \"unlimited\": \"Sin límites\"\n      },\n      \"gc\": {\n        \"count\": \"Cantidad\",\n        \"time_spent_max\": \"Tiempo máximo utilizado\",\n        \"time_spent_total\": \"Tiempo total utilizado\",\n        \"title\": \"Pausas en la recolección de basura\"\n      },\n      \"health\": {\n        \"title\": \"Salud\"\n      },\n      \"health_group\": {\n        \"title\": \"Grupo de Salud\"\n      },\n      \"info\": {\n        \"no_info_provided\": \"No hay información provista.\",\n        \"title\": \"Info\"\n      },\n      \"memory\": {\n        \"max\": \"Max\",\n        \"metaspace\": \"Metaspace\",\n        \"size\": \"Tamaño\",\n        \"title\": \"Memoria\",\n        \"used\": \"Usada\",\n        \"committed\": \"Comprometida\"\n      },\n      \"metadata\": {\n        \"no_data_provided\": \"No hay metadatos provistos.\",\n        \"title\": \"Metadatos\"\n      },\n      \"process\": {\n        \"cpus\": \"CPUs\",\n        \"pid\": \"PID\",\n        \"parent_pid\": \"PID Padre\",\n        \"owner\": \"Propietario\",\n        \"process_cpu_usage\": \"Uso CPU proceso\",\n        \"system_cpu_usage\": \"Uso CPU sistema\",\n        \"title\": \"Proceso\",\n        \"uptime\": \"Tiempo de actividad\"\n      },\n      \"threads\": {\n        \"daemon\": \"Daemon\",\n\n        \"live\": \"En vivo\",\n        \"peak_live\": \"Pico en vivo\",\n        \"title\": \"Hilos\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/i18n.fr.json",
    "content": "{\n  \"instances\": {\n    \"details\": {\n      \"label\": \"Détails\",\n      \"cache\": {\n        \"hit_ratio\": \"Hit ratio\",\n        \"hits\": \"Hits\",\n        \"misses\": \"Échecs\",\n        \"size\": \"Taille\"\n      },\n      \"datasource\": {\n        \"active_connections\": \"Connexions actives\",\n        \"min_connections\": \"Connexions min\",\n        \"max_connections\": \"Connexions max\",\n        \"title\": \"Datasource: {dataSource}\",\n        \"unlimited\": \"illimité\"\n      },\n      \"gc\": {\n        \"count\": \"Count\",\n        \"time_spent_max\": \"Temps max passé\",\n        \"time_spent_total\": \"Total du temps passé\",\n        \"title\": \"Pauses du Garbage Collector\"\n      },\n      \"health\": {\n        \"title\": \"État\"\n      },\n      \"health_group\": {\n        \"title\": \"Groupe d'État\"\n      },\n      \"info\": {\n        \"no_info_provided\": \"Aucune infos fournies.\",\n        \"title\": \"Info\"\n      },\n      \"memory\": {\n        \"max\": \"Max\",\n        \"metaspace\": \"Métaspace\",\n        \"size\": \"Taille\",\n        \"title\": \"Memoire\",\n        \"used\": \"Utilisé\",\n        \"committed\": \"Engagé\"\n      },\n      \"metadata\": {\n        \"no_data_provided\": \"Aucune métadonnées fournies.\",\n        \"title\": \"Métadonnées\"\n      },\n      \"process\": {\n        \"cpus\": \"CPUs\",\n        \"pid\": \"PID\",\n        \"parent_pid\": \"PID Parent\",\n        \"owner\": \"Propriétaire\",\n        \"process_cpu_usage\": \"Usage processus du CPU\",\n        \"system_cpu_usage\": \"Usage Système du CPU\",\n        \"title\": \"Processus\",\n        \"uptime\": \"Disponibilité\"\n      },\n      \"threads\": {\n        \"daemon\": \"Démon\",\n        \"live\": \"Actifs\",\n        \"peak_live\": \"max actifs\",\n        \"title\": \"Threads\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/i18n.is.json",
    "content": "{\n  \"instances\": {\n    \"details\": {\n      \"label\": \"Smáatriði\",\n      \"cache\": {\n        \"hit_ratio\": \"Hit ratio\",\n        \"hits\": \"Hits\",\n        \"misses\": \"Misses\",\n        \"size\": \"Stærð\"\n      },\n      \"datasource\": {\n        \"active_connections\": \"Virk tengsl\",\n        \"min_connections\": \"Lágmarksfjöldi af tengslum\",\n        \"max_connections\": \"Hámarksfjöldi af tengslunum\",\n        \"title\": \"Gagnagjafi: {dataSource}\",\n        \"unlimited\": \"ótakmarkaður\"\n      },\n      \"gc\": {\n        \"count\": \"Fjöldi\",\n        \"time_spent_max\": \"Hámarkstímalengd\",\n        \"time_spent_total\": \"Heildartími\",\n        \"title\": \"GC hlé\"\n      },\n      \"health\": {\n        \"title\": \"Staða\"\n      },\n      \"health_group\": {\n        \"title\": \"Stöðuhópur\"\n      },\n      \"info\": {\n        \"no_info_provided\": \"Engar upplýsingar eru í boði.\",\n        \"title\": \"Upplýsingar\"\n      },\n      \"memory\": {\n        \"max\": \"Hámark\",\n        \"metaspace\": \"Metaspace\",\n        \"size\": \"Stærð\",\n        \"title\": \"Geymir\",\n        \"used\": \"I notkun\",\n        \"committed\": \"Skuldbundið\"\n      },\n      \"metadata\": {\n        \"no_data_provided\": \"Engin lysigögn i boði.\",\n        \"title\": \"Lysigögn\"\n      },\n      \"process\": {\n        \"cpus\": \"CPUs\",\n        \"pid\": \"PID\",\n        \"parent_pid\": \"Foreldri PID\",\n        \"owner\": \"Eigandi\",\n        \"process_cpu_usage\": \"CPU notkun (%)\",\n        \"system_cpu_usage\": \"Kerfi notkun (%)\",\n        \"title\": \"Ferli\",\n        \"uptime\": \"Uptime\"\n      },\n      \"threads\": {\n        \"daemon\": \"Þjónn (daemon)\",\n        \"live\": \"Live\",\n        \"peak_live\": \"Peak Live\",\n        \"title\": \"Þræðir\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/i18n.ko.json",
    "content": "{\n  \"instances\": {\n    \"details\": {\n      \"label\": \"상세\",\n      \"cache\": {\n        \"hit_ratio\": \"히트율\",\n        \"hits\": \"히트\",\n        \"misses\": \"미스\",\n        \"size\": \"사이즈\"\n      },\n      \"datasource\": {\n        \"active_connections\": \"활성 커넥션\",\n        \"min_connections\": \"최소 커넥션\",\n        \"max_connections\": \"최대 커넥션\",\n        \"title\": \"데이터소스: {dataSource}\",\n        \"unlimited\": \"무제한\"\n      },\n      \"gc\": {\n        \"count\": \"횟수\",\n        \"time_spent_max\": \"최대 소요 시간\",\n        \"time_spent_total\": \"전체 소요 시간\",\n        \"title\": \"가비지 컬렉션\"\n      },\n      \"health\": {\n        \"title\": \"상태\"\n      },\n      \"health_group\": {\n        \"title\": \"상태 그룹\"\n      },\n      \"info\": {\n        \"no_info_provided\": \"정보가 제공되지 않습니다.\",\n        \"title\": \"정보\"\n      },\n      \"memory\": {\n        \"max\": \"최대\",\n        \"metaspace\": \"메타스페이스\",\n        \"size\": \"사이즈\",\n        \"title\": \"메모리\",\n        \"used\": \"사용됨\",\n        \"committed\": \"커밋됨\"\n      },\n      \"metadata\": {\n        \"no_data_provided\": \"메타데이터가 제공되지 않습니다.\",\n        \"title\": \"메타데이터\"\n      },\n      \"process\": {\n        \"cpus\": \"CPU\",\n        \"pid\": \"PID\",\n        \"parent_pid\": \"부모 PID\",\n        \"owner\": \"소유자\",\n        \"process_cpu_usage\": \"프로세스 CPU 사용량\",\n        \"system_cpu_usage\": \"시스템 CPU 사용량\",\n        \"title\": \"프로세스\",\n        \"uptime\": \"가동시간\"\n      },\n      \"threads\": {\n        \"daemon\": \"데몬\",\n        \"live\": \"활성\",\n        \"peak_live\": \"최대 활성\",\n        \"title\": \"스레드\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/i18n.pt-BR.json",
    "content": "{\n  \"instances\": {\n    \"details\": {\n      \"label\": \"Detalhes\",\n      \"cache\": {\n        \"hit_ratio\": \"Hit ratio\",\n        \"hits\": \"Hits\",\n        \"misses\": \"Falhas\",\n        \"size\": \"Tamanho\"\n      },\n      \"datasource\": {\n        \"active_connections\": \"Conexões ativas\",\n        \"min_connections\": \"Conexões mínimas\",\n        \"max_connections\": \"Conexões máximas\",\n        \"title\": \"Datasource: {dataSource}\",\n        \"unlimited\": \"ilimitado\"\n      },\n      \"gc\": {\n        \"count\": \"Contador\",\n        \"time_spent_max\": \"Tempo máximo gasto\",\n        \"time_spent_total\": \"Tempo total gasto\",\n        \"title\": \"Pausas do Garbage Collection\"\n      },\n      \"health\": {\n        \"title\": \"Integridade\"\n      },\n      \"health_group\": {\n        \"title\": \"Grupo de Integridade\"\n      },\n      \"info\": {\n        \"no_info_provided\": \"Nenhuma informação fornecida.\",\n        \"title\": \"Info\"\n      },\n      \"memory\": {\n        \"max\": \"Máximo\",\n        \"metaspace\": \"Metaspace\",\n        \"size\": \"Tamanho\",\n        \"title\": \"Memoria\",\n        \"used\": \"Usado\",\n        \"committed\": \"Comprometido\"\n      },\n      \"metadata\": {\n        \"no_data_provided\": \"Nenhum metadado fornecido.\",\n        \"title\": \"Metadado\"\n      },\n      \"process\": {\n        \"cpus\": \"CPUs\",\n        \"pid\": \"PID\",\n        \"parent_pid\": \"PID Pai\",\n        \"owner\": \"Proprietário\",\n        \"process_cpu_usage\": \"Uso de Process CPU\",\n        \"system_cpu_usage\": \"Uso de System CPU\",\n        \"title\": \"Processo\",\n        \"uptime\": \"Uptime\"\n      },\n      \"threads\": {\n        \"daemon\": \"Daemon\",\n        \"live\": \"Ativos\",\n        \"peak_live\": \"Peak Live\",\n        \"title\": \"Threads\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/i18n.ru.json",
    "content": "{\n  \"instances\": {\n    \"details\": {\n      \"label\": \"Подробности\",\n      \"cache\": {\n        \"hit_ratio\": \"Коэффициент попадания\",\n        \"hits\": \"Попадания\",\n        \"misses\": \"Промахи\",\n        \"size\": \"Размер\"\n      },\n      \"datasource\": {\n        \"active_connections\": \"Активные подключения\",\n        \"min_connections\": \"Минимальные подключения\",\n        \"max_connections\": \"Максимальные подключения\",\n        \"title\": \"Datasource: {dataSource}\",\n        \"unlimited\": \"неограниченный\"\n      },\n      \"gc\": {\n        \"count\": \"Количество\",\n        \"time_spent_max\": \"Максимум потраченного времени\",\n        \"time_spent_total\": \"Общее потраченное время\",\n        \"title\": \"Паузы сборщика мусора\"\n      },\n      \"health\": {\n        \"title\": \"Здоровье\"\n      },\n      \"health_group\": {\n        \"title\": \"Группа здоровья\"\n      },\n      \"info\": {\n        \"no_info_provided\": \"Информация не предоставлена.\",\n        \"title\": \"Информация\"\n      },\n      \"memory\": {\n        \"max\": \"Максимум\",\n        \"metaspace\": \"Metaspace\",\n        \"size\": \"Размер\",\n        \"title\": \"Память\",\n        \"used\": \"Используется\",\n        \"committed\": \"Зарезервировано\"\n      },\n      \"metadata\": {\n        \"no_data_provided\": \"Метаданные не предоставлены.\",\n        \"title\": \"Метаданные\"\n      },\n      \"process\": {\n        \"cpus\": \"Процессоры\",\n        \"pid\": \"PID (Идентификатор процесса)\",\n        \"parent_pid\": \"Родительский PID\",\n        \"owner\": \"Владелец\",\n        \"process_cpu_usage\": \"Использование процессами\",\n        \"system_cpu_usage\": \"Использование системой\",\n        \"title\": \"Процесс\",\n        \"uptime\": \"Время работы\"\n      },\n      \"threads\": {\n        \"daemon\": \"Демоны\",\n        \"live\": \"Живые потоки\",\n        \"peak_live\": \"Максимум живых потоков\",\n        \"title\": \"Потоки\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/i18n.zh-CN.json",
    "content": "{\n  \"instances\": {\n    \"details\": {\n      \"label\": \"细节\",\n      \"cache\": {\n        \"hit_ratio\": \"命中率\",\n        \"hits\": \"命中次数\",\n        \"misses\": \"未命中\",\n        \"size\": \"粒度\"\n      },\n      \"datasource\": {\n        \"active_connections\": \"活动连接数\",\n        \"min_connections\": \"最小连接数\",\n        \"max_connections\": \"最大连接数\",\n        \"title\": \"数据源: {dataSource}\",\n        \"unlimited\": \"unlimited\"\n      },\n      \"gc\": {\n        \"count\": \"总计\",\n        \"time_spent_max\": \"最大耗时\",\n        \"time_spent_total\": \"总耗时\",\n        \"title\": \"垃圾回收\"\n      },\n      \"health\": {\n        \"title\": \"健康\"\n      },\n      \"health_group\": {\n        \"title\": \"健康组\"\n      },\n      \"info\": {\n        \"no_info_provided\": \"未提供任何信息。\",\n        \"title\": \"信息\"\n      },\n      \"memory\": {\n        \"max\": \"最大\",\n        \"metaspace\": \"初始空间\",\n        \"size\": \"当前可用\",\n        \"title\": \"内存\",\n        \"used\": \"已用\",\n        \"committed\": \"已提交\"\n      },\n      \"metadata\": {\n        \"no_data_provided\": \"未提供元数据。\",\n        \"title\": \"元数据\"\n      },\n      \"process\": {\n        \"cpus\": \"CPU核心数\",\n        \"pid\": \"进程ID\",\n        \"parent_pid\": \"父进程ID\",\n        \"owner\": \"所有者\",\n        \"process_cpu_usage\": \"进程CPU使用率\",\n        \"system_cpu_usage\": \"系统CPU使用率\",\n        \"title\": \"进程\",\n        \"uptime\": \"运行时间\"\n      },\n      \"threads\": {\n        \"daemon\": \"守护线程\",\n        \"live\": \"活动线程\",\n        \"peak_live\": \"线程峰值\",\n        \"title\": \"线程\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/i18n.zh-TW.json",
    "content": "{\n  \"instances\": {\n    \"details\": {\n      \"label\": \"詳細資訊\",\n      \"cache\": {\n        \"hit_ratio\": \"命中率\",\n        \"hits\": \"命中次數\",\n        \"misses\": \"未命中次數\",\n        \"size\": \"大小\"\n      },\n      \"datasource\": {\n        \"active_connections\": \"使用中連線數\",\n        \"min_connections\": \"最小連線數\",\n        \"max_connections\": \"最大連線數\",\n        \"title\": \"資料來源：{dataSource}\",\n        \"unlimited\": \"無限制\"\n      },\n      \"gc\": {\n        \"count\": \"次數\",\n        \"count_short\": \"#\",\n        \"time_spent_max\": \"最大耗時\",\n        \"time_spent_max_short\": \"最大\",\n        \"time_spent_total\": \"總耗時\",\n        \"time_spent_total_short\": \"總計\",\n        \"title\": \"垃圾回收暫停\"\n      },\n      \"health\": {\n        \"title\": \"健康狀態\"\n      },\n      \"health_group\": {\n        \"title\": \"健康群組\"\n      },\n      \"info\": {\n        \"no_info_provided\": \"未提供任何資訊。\",\n        \"title\": \"資訊\"\n      },\n      \"memory\": {\n        \"max\": \"最大值\",\n        \"metaspace\": \"Metaspace\",\n        \"size\": \"大小\",\n        \"title\": \"記憶體\",\n        \"used\": \"已使用\",\n        \"committed\": \"已配置\"\n      },\n      \"metadata\": {\n        \"no_data_provided\": \"未提供中繼資料。\",\n        \"title\": \"中繼資料\"\n      },\n      \"process\": {\n        \"cpus\": \"CPU 核心數\",\n        \"pid\": \"PID\",\n        \"parent_pid\": \"父程序 PID\",\n        \"owner\": \"擁有者\",\n        \"process_cpu_usage\": \"程序 CPU 使用率\",\n        \"process_cpu_usage_short\": \"CPU\",\n        \"system_cpu_usage\": \"系統 CPU 使用率\",\n        \"system_cpu_usage_short\": \"系統\",\n        \"title\": \"程序\",\n        \"uptime\": \"運作時間\",\n        \"uptime_short\": \"運作\"\n      },\n      \"threads\": {\n        \"daemon\": \"背景執行緒\",\n        \"live\": \"執行中\",\n        \"peak_live\": \"峰值\",\n        \"title\": \"執行緒\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/index.spec.ts",
    "content": "import { screen, waitFor } from '@testing-library/vue';\nimport { HttpResponse, http } from 'msw';\nimport { describe, expect, it, vi } from 'vitest';\n\nimport DetailsView from './index.vue';\n\nimport { applications } from '@/mocks/applications/data';\nimport { server } from '@/mocks/server';\nimport Application from '@/services/application';\nimport { render } from '@/test-utils';\n\ndescribe('InstanceDetails', () => {\n  describe('Metrics', () => {\n    it('should hide loading spinner, when network call fails', async () => {\n      const application = new Application(applications[0]);\n      const instance = application.instances[0];\n\n      server.use(\n        http.get('/instances/:instanceId/actuator/metrics', () => {\n          return HttpResponse.json({});\n        }),\n      );\n\n      render(DetailsView, {\n        props: {\n          instance,\n          application,\n        },\n      });\n\n      await waitFor(async () => {\n        expect(\n          await screen.queryByTestId('instance-section-loading-spinner'),\n        ).not.toBeInTheDocument();\n      });\n    });\n\n    it('should hide loading spinner, when metrics endpoint is not exposed', async () => {\n      const application = new Application(applications[0]);\n      const instance = application.instances[0];\n\n      instance.hasEndpoint = vi.fn().mockImplementation(function (endpoint) {\n        return endpoint !== 'metrics';\n      });\n\n      render(DetailsView, {\n        props: {\n          instance,\n          application,\n        },\n      });\n\n      await waitFor(async () => {\n        expect(\n          await screen.queryByTestId('instance-section-loading-spinner'),\n        ).not.toBeInTheDocument();\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/index.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-instance-section :error=\"error\" :loading=\"!hasLoaded\">\n    <template #before>\n      <details-nav :application=\"application\" :instance=\"instance\" />\n      <details-hero :instance=\"instance\" />\n    </template>\n\n    <div class=\"flex gap-6 flex-col lg:flex-row\">\n      <div class=\"flex-1\">\n        <details-info v-if=\"hasInfo\" :instance=\"instance\" />\n        <details-metadata v-if=\"hasMetadata\" :instance=\"instance\" />\n      </div>\n      <div class=\"flex-1\">\n        <details-health :instance=\"instance\" />\n      </div>\n    </div>\n\n    <div class=\"flex gap-6 flex-col lg:flex-row\">\n      <div class=\"flex-1\">\n        <details-process\n          v-if=\"hasProcess\"\n          :instance=\"instance\"\n          class=\"break-inside-avoid\"\n        />\n        <details-gc v-if=\"hasGc\" :instance=\"instance\" />\n      </div>\n      <div class=\"flex-1\">\n        <details-threads v-if=\"hasThreads\" :instance=\"instance\" />\n      </div>\n    </div>\n\n    <div class=\"flex gap-6 flex-col lg:flex-row\">\n      <div class=\"flex-1\">\n        <details-memory v-if=\"hasMemory\" :instance=\"instance\" type=\"heap\" />\n      </div>\n      <div class=\"flex-1\">\n        <details-memory v-if=\"hasMemory\" :instance=\"instance\" type=\"nonheap\" />\n      </div>\n    </div>\n\n    <div class=\"flex gap-6 flex-col lg:flex-row\">\n      <div class=\"flex-1\">\n        <details-datasources v-if=\"hasDatasources\" :instance=\"instance\" />\n      </div>\n      <div class=\"flex-1\">\n        <details-caches v-if=\"hasCaches\" :instance=\"instance\" />\n      </div>\n    </div>\n  </sba-instance-section>\n</template>\n\n<script>\nimport Application from '@/services/application';\nimport Instance from '@/services/instance';\nimport { VIEW_GROUP } from '@/views/ViewGroup';\nimport detailsCaches from '@/views/instances/details/details-caches';\nimport detailsDatasources from '@/views/instances/details/details-datasources';\nimport detailsGc from '@/views/instances/details/details-gc';\nimport detailsHealth from '@/views/instances/details/details-health';\nimport DetailsHero from '@/views/instances/details/details-hero';\nimport detailsInfo from '@/views/instances/details/details-info';\nimport detailsMemory from '@/views/instances/details/details-memory';\nimport detailsMetadata from '@/views/instances/details/details-metadata';\nimport DetailsNav from '@/views/instances/details/details-nav';\nimport detailsProcess from '@/views/instances/details/details-process';\nimport detailsThreads from '@/views/instances/details/details-threads';\nimport SbaInstanceSection from '@/views/instances/shell/sba-instance-section';\n\nexport default {\n  components: {\n    SbaInstanceSection,\n    DetailsNav,\n    DetailsHero,\n    detailsHealth,\n    detailsInfo,\n    detailsProcess,\n    detailsThreads,\n    detailsDatasources,\n    detailsMemory,\n    detailsGc,\n    detailsCaches,\n    detailsMetadata,\n  },\n  props: {\n    application: {\n      type: Application,\n      default: () => ({}),\n    },\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  data: () => ({\n    hasLoaded: true,\n    error: null,\n    metrics: [],\n  }),\n  computed: {\n    hasCaches() {\n      return this.hasMetric('cache.gets');\n    },\n    hasDatasources() {\n      return this.hasMetric('jdbc.connections.active');\n    },\n    hasGc() {\n      return this.hasMetric('jvm.gc.pause');\n    },\n    hasInfo() {\n      return this.instance.hasEndpoint('info');\n    },\n    hasMemory() {\n      return this.hasMetric('jvm.memory.max');\n    },\n    hasProcess() {\n      return this.hasMetric('process.uptime');\n    },\n    hasThreads() {\n      return this.hasMetric('jvm.threads.live');\n    },\n    hasMetadata() {\n      return this.instance.registration && this.instance.registration.metadata;\n    },\n  },\n  watch: {\n    instance() {\n      this.fetchMetricIndex();\n    },\n  },\n  created() {\n    this.fetchMetricIndex();\n  },\n  methods: {\n    hasMetric(metric) {\n      return this.metrics && this.metrics.includes(metric);\n    },\n    async fetchMetricIndex() {\n      if (this.instance.hasEndpoint('metrics')) {\n        this.hasLoaded = false;\n        this.error = null;\n        try {\n          const res = await this.instance.fetchMetrics();\n          this.metrics = res.data.names;\n        } catch (error) {\n          console.warn('Fetching metric index failed:', error);\n          this.error = error;\n        } finally {\n          this.hasLoaded = true;\n        }\n      }\n    },\n  },\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      name: 'instances/details',\n      parent: 'instances',\n      path: 'details',\n      component: this,\n      label: 'instances.details.label',\n      group: VIEW_GROUP.INSIGHTS,\n      order: 0,\n    });\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/instance-switcher.vue",
    "content": "<template>\n  <div v-if=\"otherInstances.length > 0\" class=\"relative inline-block z-50\">\n    <button\n      class=\"inline-flex justify-center items-center bg-gray-100 w-full rounded-md text-black hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium\"\n      @click=\"showInstances = !showInstances\"\n    >\n      <sba-status\n        :status=\"currentInstance.statusInfo.status\"\n        class=\"mr-1 hidden md:block\"\n      />\n      <span\n        class=\"hidden sm:block truncate pr-1 w-full\"\n        v-text=\"currentInstance.registration.name\"\n      />\n      <span class=\"block sm:hidden\">{{ currentInstance.id }}</span>\n      <span class=\"hidden sm:block\">({{ currentInstance.id }})</span>\n\n      <svg\n        class=\"-mr-1 ml-2 h-5 w-5\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n        viewBox=\"0 0 20 20\"\n        fill=\"currentColor\"\n        aria-hidden=\"true\"\n      >\n        <path\n          d=\"M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z\"\n        />\n      </svg>\n    </button>\n\n    <transition\n      enter-active-class=\"transition ease-out duration-200\"\n      enter-from-class=\"opacity-0 translate-y-1\"\n      enter-to-class=\"opacity-100 translate-y-0\"\n      leave-active-class=\"transition ease-in duration-150\"\n      leave-from-class=\"opacity-100 translate-y-0\"\n      leave-to-class=\"opacity-0 translate-y-1\"\n    >\n      <div\n        v-if=\"showInstances\"\n        v-on-clickaway=\"() => (showInstances = false)\"\n        class=\"absolute -ml-2 mt-3 transform px-2 w-screen max-w-md\"\n      >\n        <div\n          class=\"rounded-lg shadow-lg ring-1 ring-black ring-opacity-5 overflow-hidden overflow-y-auto max-h-32 md:max-h-96\"\n        >\n          <div class=\"relative grid gap-4 bg-white p-4\">\n            <a\n              v-for=\"otherInstance in otherInstances\"\n              :key=\"otherInstance.id\"\n              class=\"-m-3 p-3 flex items-center rounded-lg hover:bg-gray-50\"\n              @click.stop=\"switchToInstance(otherInstance)\"\n            >\n              <sba-status\n                :status=\"otherInstance.statusInfo.status\"\n                class=\"mr-3\"\n              />\n              <div>\n                <div v-text=\"otherInstance.registration.name\" />\n                <div class=\"text-xs italic\" v-text=\"otherInstance.id\" />\n              </div>\n            </a>\n          </div>\n        </div>\n      </div>\n    </transition>\n  </div>\n</template>\n\n<script>\nimport { directive as onClickaway } from 'vue3-click-away';\n\nimport Instance from '@/services/instance';\n\nexport default {\n  directives: { onClickaway },\n  props: {\n    instances: {\n      type: Array,\n      required: true,\n    },\n    currentInstance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  data() {\n    return {\n      showInstances: false,\n    };\n  },\n  computed: {\n    otherInstances() {\n      return this.instances\n        .filter((i) => i.id !== this.currentInstance.id)\n        .sort((a, b) => a.id.localeCompare(b.id));\n    },\n  },\n  methods: {\n    switchToInstance(instance) {\n      this.showInstances = false;\n      this.$router.push({\n        name: 'instances/details',\n        params: { instanceId: instance.id },\n      });\n    },\n  },\n};\n</script>\n\n<style scoped></style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/mem-chart.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <LineChart\n    :config=\"config\"\n    :data=\"data\"\n    :datasets=\"datasets\"\n    label=\"timestamp\"\n  />\n</template>\n\n<script setup lang=\"ts\">\nimport moment from 'moment';\nimport prettyBytes from 'pretty-bytes';\nimport { computed } from 'vue';\nimport { useI18n } from 'vue-i18n';\n\nimport LineChart from '@/views/instances/details/LineChart';\n\nconst { t } = useI18n();\n\nconst { data } = defineProps<{\n  data: Array<any>;\n}>();\n\nconst datasets = computed(() => {\n  const hasMetaspace = Object.values(data).some((d) => d.metaspace !== null);\n\n  const _datasets: Record<string, { label: string }> = {\n    used: {\n      label: 'instances.details.memory.used',\n    },\n    committed: {\n      label: 'instances.details.memory.committed',\n    },\n  };\n\n  if (hasMetaspace) {\n    _datasets.metaspace = {\n      label: 'instances.details.memory.metaspace',\n    };\n  }\n\n  return _datasets;\n});\n\nconst config = {\n  options: {\n    plugins: {\n      tooltip: {\n        callbacks: {\n          title: (ctx) => {\n            return prettyBytes(ctx[0].parsed.y);\n          },\n          label: (ctx) => {\n            return t(ctx.dataset.label);\n          },\n        },\n      },\n    },\n    scales: {\n      y: {\n        ticks: {\n          callback: (label) => prettyBytes(label),\n        },\n      },\n      x: {\n        ticks: {\n          callback: (label) => moment(label).format('HH:mm:ss'),\n        },\n      },\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/process-uptime.ts",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport moment from 'moment';\nimport { h } from 'vue';\n\nimport subscribing from '../../../mixins/subscribing';\n\nimport { timer } from '@/utils/rxjs';\n\nexport default {\n  props: ['value'],\n  mixins: [subscribing],\n  data: () => ({\n    startTs: null,\n    offset: null,\n  }),\n  render() {\n    return h('span', this.clock);\n  },\n  computed: {\n    clock() {\n      if (!this.value) {\n        return null;\n      }\n      const duration = moment.duration(this.value + this.offset);\n      return `${Math.floor(\n        duration.asDays(),\n      )}d ${duration.hours()}h ${duration.minutes()}m ${duration.seconds()}s`;\n    },\n  },\n  watch: {\n    value: 'subscribe',\n  },\n  methods: {\n    createSubscription() {\n      if (this.value) {\n        this.startTs = moment();\n        this.offset = 0;\n        return timer(0, 1000).subscribe({\n          next: () => {\n            this.offset = moment().valueOf() - this.startTs.valueOf();\n          },\n        });\n      }\n    },\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/details/threads-chart.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <LineChart\n    label=\"timestamp\"\n    :datasets=\"datasets\"\n    :config=\"config\"\n    :data=\"data\"\n  />\n</template>\n\n<script>\nimport moment from 'moment';\nimport { useI18n } from 'vue-i18n';\n\nimport LineChart from '@/views/instances/details/LineChart';\n\nexport default {\n  components: { LineChart },\n  props: {\n    data: {\n      type: Array,\n      default: () => [],\n    },\n  },\n  setup(props) {\n    const { t } = useI18n();\n    return { ...props, t };\n  },\n  data() {\n    return {\n      datasets: {\n        live: {\n          label: this.t('instances.details.threads.live'),\n        },\n        daemon: {\n          label: this.t('instances.details.threads.daemon'),\n        },\n      },\n      config: {\n        options: {\n          plugins: {\n            tooltip: {\n              callbacks: {\n                title: function (ctx) {\n                  return ctx[0].parsed.y;\n                },\n                label: function (ctx) {\n                  return ctx.dataset.label;\n                },\n              },\n            },\n          },\n          scales: {\n            x: {\n              ticks: {\n                callback: (label) => {\n                  return moment(label).format('HH:mm:ss');\n                },\n              },\n            },\n          },\n        },\n      },\n    };\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/env/busrefresh.spec.ts",
    "content": "import userEvent from '@testing-library/user-event';\nimport { screen } from '@testing-library/vue';\nimport { HttpResponse, http } from 'msw';\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport { server } from '@/mocks/server';\nimport Instance from '@/services/instance';\nimport { render } from '@/test-utils';\nimport Busrefresh from '@/views/instances/env/busrefresh.vue';\n\ndescribe('Busrefresh', () => {\n  const busRefreshInstanceContextMock = vi.fn();\n  let emitted;\n\n  function createInstance() {\n    const instance = new Instance({ id: 1233 });\n    instance.busRefreshContext = busRefreshInstanceContextMock;\n    return instance;\n  }\n\n  beforeEach(async () => {\n    server.use(\n      http.post('/instances/:instanceId/actuator/busrefresh', () => {\n        return HttpResponse.json({});\n      }),\n    );\n    const vm = render(Busrefresh, {\n      props: {\n        instance: createInstance(),\n      },\n    });\n    emitted = vm.emitted;\n  });\n\n  it('should trigger busrefresh on confirm', async () => {\n    busRefreshInstanceContextMock.mockResolvedValue({});\n    const busRefreshButton = await screen.findByText(\n      'instances.env.bus_refresh',\n    );\n    await userEvent.click(busRefreshButton);\n\n    const confirmButton = await screen.findByText('Confirm');\n    await userEvent.click(confirmButton);\n\n    expect(emitted().refresh[0][0]).toBe(true);\n  });\n\n  it('should handle busrefresh failure gracefully', async () => {\n    busRefreshInstanceContextMock.mockRejectedValueOnce(new Error());\n\n    const busRefreshButton = await screen.findByText(\n      'instances.env.bus_refresh',\n    );\n    await userEvent.click(busRefreshButton);\n\n    const confirmButton = await screen.findByText('Confirm');\n    await userEvent.click(confirmButton);\n\n    expect(emitted().refresh).toBeUndefined();\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/env/busrefresh.vue",
    "content": "<template>\n  <sba-confirm-button\n    :title=\"$t('instances.env.bus_refresh_title')\"\n    class=\"inline-flex focus:z-10\"\n    @click=\"refreshInstance\"\n  >\n    <span v-text=\"t('instances.env.bus_refresh')\" />\n  </sba-confirm-button>\n</template>\n\n<script lang=\"ts\">\nimport { useNotificationCenter } from '@stekoe/vue-toast-notificationcenter';\nimport { useI18n } from 'vue-i18n';\n\nimport SbaConfirmButton from '@/components/sba-confirm-button.vue';\n\nimport Instance from '@/services/instance';\n\nconst notificationCenter = useNotificationCenter({});\n\nexport default {\n  components: { SbaConfirmButton },\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  emits: ['refresh'],\n  setup() {\n    const i18n = useI18n();\n    return {\n      t: i18n.t,\n    };\n  },\n  methods: {\n    async refreshInstance() {\n      this.instance\n        .busRefreshContext()\n        .then(() => {\n          notificationCenter.success(\n            this.t('instances.env.bus_refresh_success'),\n          );\n          this.$emit('refresh', true);\n        })\n        .catch(() => {\n          notificationCenter.error(this.t('instances.env.bus_refresh_failure'));\n        });\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/env/env-manager.vue",
    "content": "<!--\n  - Copyright 2014-2020 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-panel :title=\"$t('instances.env.manager')\">\n    <div\n      v-for=\"(prop, index) in managedProperties\"\n      :key=\"`managed-${index}`\"\n      class=\"flex gap-2 pb-2\"\n    >\n      <sba-input\n        v-model=\"prop.name\"\n        :error=\"prop.validation\"\n        :list=\"allPropertyNames\"\n        :name=\"prop.name || 'new-prop-name'\"\n        class=\"flex-1\"\n        placeholder=\"Property name\"\n        type=\"text\"\n        @input=\"handlePropertyNameChange(prop, index)\"\n      />\n      <sba-input\n        v-model=\"prop.input\"\n        :name=\"prop.name || 'new-prop-value'\"\n        class=\"flex-1\"\n        placeholder=\"Value\"\n        type=\"text\"\n        @input=\"prop.status = null\"\n      >\n        <template #append>\n          <span v-if=\"prop.status === 'executing'\">\n            <font-awesome-icon\n              :icon=\"['fas', 'sync-alt']\"\n              class=\"animate-spin\"\n            />\n          </span>\n          <span v-else-if=\"prop.status === 'failed'\">\n            <font-awesome-icon icon=\"exclamation-triangle\" />\n          </span>\n          <span\n            v-else-if=\"prop.status === 'completed' || prop.input === prop.value\"\n          >\n            <font-awesome-icon icon=\"check\" />\n          </span>\n          <span v-else-if=\"prop.input !== prop.value\">\n            <font-awesome-icon icon=\"pencil-alt\" />\n          </span>\n        </template>\n      </sba-input>\n    </div>\n\n    <div class=\"flex gap-2 justify-end items-start\">\n      <sba-toggle-scope-button\n        v-if=\"application.instances.length > 1\"\n        v-model=\"scope\"\n        :instance-count=\"application.instances.length\"\n      />\n\n      <sba-confirm-button\n        :disabled=\"!hasManagedProperty || resetStatus === 'executing'\"\n        class=\"button is-light\"\n        @click=\"resetEnvironment\"\n      >\n        <span\n          v-if=\"resetStatus === 'completed'\"\n          v-text=\"$t('instances.env.context_resetted')\"\n        />\n        <span\n          v-else-if=\"resetStatus === 'failed'\"\n          v-text=\"$t('instances.env.context_reset_failed')\"\n        />\n        <span v-else v-text=\"$t('instances.env.context_reset')\" />\n      </sba-confirm-button>\n      <sba-confirm-button\n        :disabled=\"\n          hasErrorProperty ||\n          !hasChangedProperty ||\n          updateStatus === 'executing'\n        \"\n        class=\"button is-primary\"\n        @click=\"updateEnvironment\"\n      >\n        <span\n          v-if=\"updateStatus === 'completed'\"\n          v-text=\"$t('instances.env.context_updated')\"\n        />\n        <span\n          v-else-if=\"updateStatus === 'failed'\"\n          v-text=\"$t('instances.env.context_update_failed')\"\n        />\n        <span v-else v-text=\"$t('instances.env.context_update')\" />\n      </sba-confirm-button>\n    </div>\n  </sba-panel>\n</template>\n\n<script>\nimport { debounce, uniq } from 'lodash-es';\n\nimport { ActionScope } from '@/components/ActionScope';\n\nimport Application from '@/services/application';\nimport Instance from '@/services/instance';\nimport { concatMap, filter, from, listen } from '@/utils/rxjs';\n\nexport default {\n  props: {\n    application: {\n      type: Application,\n      required: true,\n    },\n    instance: {\n      type: Instance,\n      required: true,\n    },\n    propertySources: {\n      type: Array,\n      default: () => [],\n    },\n  },\n  emits: ['refresh', 'update'],\n  data: () => ({\n    error: null,\n    resetStatus: null,\n    updateStatus: null,\n    scope: ActionScope.INSTANCE,\n    managedProperties: [\n      {\n        name: null,\n        input: null,\n        value: null,\n        status: null,\n        validation: null,\n      },\n    ],\n    updateTimeout: null,\n    resetTimeout: null,\n  }),\n  computed: {\n    allPropertyNames() {\n      return uniq(\n        this.propertySources\n          .map((ps) => (ps.properties ? Object.keys(ps.properties) : []))\n          .reduce((result, names) => result.concat(names))\n          .sort(),\n      );\n    },\n    managerPropertySource() {\n      return (\n        this.propertySources.find((ps) => ps.name === 'manager') || {\n          name: 'manager',\n          properties: {},\n        }\n      );\n    },\n    hasManagedProperty() {\n      return (\n        this.managedProperties.findIndex((property) => !!property.name) >= 0\n      );\n    },\n    hasChangedProperty() {\n      return (\n        this.managedProperties.findIndex(\n          (property) => property.input !== property.value,\n        ) >= 0\n      );\n    },\n    hasErrorProperty() {\n      return (\n        this.managedProperties.findIndex(\n          (property) => property.validation !== null,\n        ) >= 0\n      );\n    },\n  },\n  watch: {\n    managerPropertySource: {\n      handler: 'updateManagedProperties',\n      immediate: true,\n    },\n    managedProperties: {\n      deep: true,\n      handler() {\n        const counts = this.managedProperties.reduce((acc, v) => {\n          if (v.name) {\n            acc[v.name] = (acc[v.name] || 0) + 1;\n          }\n          return acc;\n        }, {});\n\n        this.managedProperties.forEach((property) => {\n          if (!property.name) {\n            if (property.input) {\n              property.validation = 'Property name is required';\n            }\n            return;\n          }\n          const count = counts[property.name] || 0;\n          if (count > 1) {\n            property.validation = 'Property name must be unique';\n            return;\n          }\n          property.validation = null;\n        });\n      },\n    },\n  },\n  beforeUnmount() {\n    if (this.updateTimeout) {\n      clearTimeout(this.updateTimeout);\n      this.updateTimeout = null;\n    }\n    if (this.resetTimeout) {\n      clearTimeout(this.resetTimeout);\n      this.resetTimeout = null;\n    }\n  },\n  methods: {\n    handlePropertyNameChange: debounce(function (prop, idx) {\n      if (prop.name && idx === this.managedProperties.length - 1) {\n        this.managedProperties.push({\n          name: null,\n          input: null,\n          value: null,\n          status: null,\n          validation: null,\n        });\n      }\n    }, 250),\n    updateEnvironment() {\n      from(this.managedProperties)\n        .pipe(\n          filter(\n            (property) => !!property.name && property.input !== property.value,\n          ),\n          listen((status) => (this.updateStatus = status)),\n          concatMap((property) => {\n            let target;\n\n            if (this.scope === 'instance') {\n              target = this.instance;\n            } else {\n              target = this.application;\n            }\n\n            return from(target.setEnv(property.name, property.input)).pipe(\n              listen((status) => (property.status = status)),\n              listen((status) => (this.updateStatus = status)),\n            );\n          }),\n        )\n        .subscribe({\n          complete: () => {\n            this.updateTimeout = setTimeout(\n              () => (this.updateStatus = null),\n              2500,\n            );\n            return this.$emit('update');\n          },\n          error: () => this.$emit('update'),\n        });\n    },\n    resetEnvironment() {\n      let target;\n\n      if (this.scope === 'instance') {\n        target = this.instance;\n      } else {\n        target = this.application;\n      }\n\n      from(target.resetEnv())\n        .pipe(listen((status) => (this.resetStatus = status)))\n        .subscribe({\n          complete: () => {\n            this.managedProperties = [\n              {\n                name: null,\n                input: null,\n                value: null,\n                status: null,\n                validation: null,\n              },\n            ];\n            this.resetTimeout = setTimeout(\n              () => (this.resetStatus = null),\n              2500,\n            );\n            return this.$emit('refresh');\n          },\n          error: () => this.$emit('refresh'),\n        });\n    },\n    updateManagedProperties(manager) {\n      Object.entries(manager.properties).forEach(([name, property]) => {\n        const managedProperty = this.managedProperties.find(\n          (property) => property.name === name,\n        );\n        if (managedProperty) {\n          managedProperty.value = property.value;\n        } else {\n          const idx = this.managedProperties.length - 1;\n          this.managedProperties.splice(idx, 0, {\n            name,\n            input: property.value,\n            value: property.value,\n            status: null,\n            validation: null,\n          });\n        }\n      });\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/env/i18n.de.json",
    "content": "{\n  \"instances\": {\n    \"env\": {\n      \"label\": \"Umgebungskonfiguration\",\n      \"manager\": \"Umgebungskonfiguration anpassen\",\n      \"active_profile\": \"Profil\",\n      \"context_refresh\": \"Kontext neu laden\",\n      \"bus_refresh\": \"Spring Cloud Bus-refresh\",\n      \"bus_refresh_success\": \"Spring Cloud Bus Refresh erfolgreich!\",\n      \"bus_refresh_failure\": \"Fehlgeschlagen: Konnte Spring Cloud Bus Refresh nicht ausführen!\",\n      \"bus_refresh_title\": \"Sendet eine Refresh Message an den konfigurierten Spring Cloud Bus, der eine Refresh Message an alle verbundenen Nodes sendet\",\n      \"context_refresh_failed\": \"Fehlgeschlagen\",\n      \"context_refreshed\": \"Erfolreich!\",\n      \"context_reset\": \"Zurücksetzen\",\n      \"context_reset_failed\": \"Fehlgeschlagen\",\n      \"context_resetted\": \"Zurückgesetzt\",\n      \"context_update\": \"Aktualisieren\",\n      \"context_update_failed\": \"Fehlgeschlagen\",\n      \"context_updated\": \"Aktualisiert\",\n      \"no_properties\": \"Keine Eigenschaften gesetzt\",\n      \"refresh\": \"Umgebungskonfiguration neu laden\",\n      \"application\": \"Anwendung\",\n      \"instance\": \"Instanz\",\n      \"refreshed_configurations\": \"Aktualisierte Konfigurationen:\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/env/i18n.en.json",
    "content": "{\n  \"instances\": {\n    \"env\": {\n      \"label\": \"Environment\",\n      \"manager\": \"Environment Manager\",\n      \"active_profile\": \"Profile\",\n      \"context_refresh\": \"Refresh context\",\n      \"bus_refresh\": \"Spring Cloud Bus-refresh\",\n      \"bus_refresh_success\": \"Spring Cloud Bus successfully refreshed.\",\n      \"bus_refresh_failure\": \"Failure: Unable to trigger Spring Cloud Bus refresh!\",\n      \"bus_refresh_title\": \"Sends a refresh message to the configured Spring Cloud Bus which broadcasts a refresh to all nodes listening\",\n      \"context_refresh_failed\": \"Failed\",\n      \"context_refreshed\": \"Context refreshed\",\n      \"context_reset\": \"Reset\",\n      \"context_reset_failed\": \"Failed\",\n      \"context_resetted\": \"Resetted\",\n      \"context_update\": \"Update\",\n      \"context_update_failed\": \"Failed\",\n      \"context_updated\": \"Updated\",\n      \"no_properties\": \"No properties set\",\n      \"title\": \"Environment Manager\",\n      \"refresh\": \"Refresh environment\",\n      \"application\": \"Application\",\n      \"instance\": \"Instance\",\n      \"refreshed_configurations\": \"Refreshed configurations:\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/env/i18n.es.json",
    "content": "{\n  \"instances\": {\n    \"env\": {\n      \"label\": \"Ambiente\",\n      \"manager\": \"Administrador de configuración\",\n      \"active_profile\": \"Perfil\",\n      \"context_refresh\": \"Refrescar contexto\",\n      \"context_refresh_failed\": \"Fallido\",\n      \"context_refreshed\": \"Contexto actualizado\",\n      \"context_reset\": \"Reiniciar\",\n      \"context_reset_failed\": \"Fallido\",\n      \"context_resetted\": \"Reiniciado\",\n      \"context_update\": \"Actualizar\",\n      \"context_update_failed\": \"Fallido\",\n      \"context_updated\": \"Actualizado\",\n\n      \"no_properties\": \"No hay propiedades configuradas\",\n      \"title\": \"Administrador de configuración de ambiente\",\n      \"refresh\": \"Refrescar configuración\",\n      \"application\": \"Aplicación\",\n      \"instance\": \"Instancia\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/env/i18n.fr.json",
    "content": "{\n  \"instances\": {\n    \"env\": {\n      \"label\": \"Environnement\",\n      \"manager\": \"Gestionnaire d'environnement\",\n      \"active_profile\": \"Profil\",\n      \"context_refresh\": \"Rafraîchir le contexte\",\n      \"context_refresh_failed\": \"Echec\",\n      \"context_refreshed\": \"Contexte rafraîchi\",\n      \"context_reset\": \"Réinitialiser\",\n      \"context_reset_failed\": \"Echec\",\n      \"context_resetted\": \"Réinitialisé\",\n      \"context_update\": \"Mettre à jour\",\n      \"context_update_failed\": \"Echec\",\n      \"context_updated\": \"Mis à jour\",\n      \"no_properties\": \"Pas de propriétés définies\",\n      \"title\": \"Gestion d'environnement\",\n      \"refresh\": \"Refresh environment\",\n      \"application\": \"Application\",\n      \"instance\": \"Instance\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/env/i18n.is.json",
    "content": "{\n  \"instances\": {\n    \"env\": {\n      \"label\": \"Umhverfi\",\n      \"manager\": \"Umhverfisstjórnandi\",\n      \"active_profile\": \"Profile\",\n      \"context_refresh\": \"Uppfæra samhengi\",\n      \"context_refresh_failed\": \"Mistekist\",\n      \"context_refreshed\": \"Samhengi uppfært\",\n      \"context_reset\": \"Núllstilla\",\n      \"context_reset_failed\": \"Mistekist\",\n      \"context_resetted\": \"Núllstillt\",\n      \"context_update\": \"Uppfærsla\",\n      \"context_update_failed\": \"Mistekist\",\n      \"context_updated\": \"Uppfært\",\n      \"no_properties\": \"Engir eiginleikar stillt\",\n      \"title\": \"Umhverfisstjórnandi\",\n      \"refresh\": \"Refresh environment\",\n      \"application\": \"Application\",\n      \"instance\": \"Instance\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/env/i18n.ko.json",
    "content": "{\n  \"instances\": {\n    \"env\": {\n      \"label\": \"환경\",\n      \"manager\": \"환경 관리자\",\n      \"active_profile\": \"프로파일\",\n      \"context_refresh\": \"컨텍스트 갱신\",\n      \"context_refresh_failed\": \"실패\",\n      \"context_refreshed\": \"컨텍스트가 갱신되었습니다.\",\n      \"context_reset\": \"초기화\",\n      \"context_reset_failed\": \"실패\",\n      \"context_resetted\": \"초기화됨\",\n      \"context_update\": \"갱신\",\n      \"context_update_failed\": \"갱신실패\",\n      \"context_updated\": \"갱신됨\",\n      \"no_properties\": \"프로퍼티 없음\",\n      \"title\": \"환경 관리자\",\n      \"refresh\": \"환경 새로고침\",\n      \"application\": \"애플리케이션\",\n      \"instance\": \"인스턴스\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/env/i18n.pt-BR.json",
    "content": "{\n  \"instances\": {\n    \"env\": {\n      \"label\": \"Ambiente\",\n      \"manager\": \"Gerenciador de Ambiente\",\n      \"active_profile\": \"Perfil\",\n      \"context_refresh\": \"Atualizar contexto\",\n      \"context_refresh_failed\": \"Falha\",\n      \"context_refreshed\": \"Contexto atualizado\",\n      \"context_reset\": \"Redefinir\",\n      \"context_reset_failed\": \"Falhou\",\n      \"context_resetted\": \"Redefinido\",\n      \"context_update\": \"Atualizado\",\n      \"context_update_failed\": \"Falhou\",\n      \"context_updated\": \"Atualizado\",\n      \"no_properties\": \"Nenhuma propriedade definida\",\n      \"title\": \"Gerenciador de Ambiente\",\n      \"refresh\": \"Refresh environment\",\n      \"application\": \"Application\",\n      \"instance\": \"Instance\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/env/i18n.ru.json",
    "content": "{\n  \"instances\": {\n    \"env\": {\n      \"label\": \"Окружение\",\n      \"manager\": \"Менеджер окружения\",\n      \"active_profile\": \"Профиль\",\n      \"context_refresh\": \"Обновление контекста\",\n      \"context_refresh_failed\": \"Ошибка\",\n      \"context_refreshed\": \"Контекст обновлен\",\n      \"context_reset\": \"Сброс\",\n      \"context_reset_failed\": \"Ошибка\",\n      \"context_resetted\": \"Перезапущен\",\n      \"context_update\": \"Обновить\",\n      \"context_update_failed\": \"Ошибка\",\n      \"context_updated\": \"Обновлено\",\n      \"no_properties\": \"Параметры не установлены\",\n      \"title\": \"Менеджер окружения\",\n      \"refresh\": \"Refresh environment\",\n      \"application\": \"Application\",\n      \"instance\": \"Instance\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/env/i18n.zh-CN.json",
    "content": "{\n  \"instances\": {\n    \"env\": {\n      \"label\": \"环境\",\n      \"manager\": \"环境管理\",\n      \"active_profile\": \"配置文件\",\n      \"context_refresh\": \"刷新内容\",\n      \"context_refresh_failed\": \"失败\",\n      \"context_refreshed\": \"内容已刷新\",\n      \"context_reset\": \"重置\",\n      \"context_reset_failed\": \"失败\",\n      \"context_resetted\": \"已重置\",\n      \"context_update\": \"更新\",\n      \"context_update_failed\": \"失败\",\n      \"context_updated\": \"已更新\",\n      \"no_properties\": \"属性未设置。\",\n      \"title\": \"环境管理\",\n      \"refresh\": \"Refresh environment\",\n      \"application\": \"Application\",\n      \"instance\": \"Instance\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/env/i18n.zh-TW.json",
    "content": "{\n  \"instances\": {\n    \"env\": {\n      \"label\": \"環境變數\",\n      \"manager\": \"環境變數管理\",\n      \"active_profile\": \"Profile\",\n      \"context_refresh\": \"重新整理 Context\",\n      \"bus_refresh\": \"Spring Cloud Bus 重新整理\",\n      \"bus_refresh_success\": \"Spring Cloud Bus 已成功重新整理。\",\n      \"bus_refresh_failure\": \"失敗：無法觸發 Spring Cloud Bus 重新整理！\",\n      \"bus_refresh_title\": \"向已設定的 Spring Cloud Bus 發送重新整理訊息，廣播至所有監聽的節點\",\n      \"context_refresh_failed\": \"失敗\",\n      \"context_refreshed\": \"Context 已重新整理\",\n      \"context_reset\": \"重設\",\n      \"context_reset_failed\": \"失敗\",\n      \"context_resetted\": \"已重設\",\n      \"context_update\": \"更新\",\n      \"context_update_failed\": \"失敗\",\n      \"context_updated\": \"已更新\",\n      \"no_properties\": \"未設定屬性\",\n      \"title\": \"環境變數管理\",\n      \"refresh\": \"重新整理環境變數\",\n      \"application\": \"應用程式\",\n      \"instance\": \"執行個體\",\n      \"refreshed_configurations\": \"已重新整理的設定：\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/env/index.vue",
    "content": "<!--\n  - Copyright 2014-2020 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-instance-section :error=\"error\" :loading=\"isLoading\">\n    <template #before>\n      <sba-sticky-subnav v-if=\"env\">\n        <div class=\"flex\">\n          <div v-if=\"instance.hasEndpoint('refresh')\" class=\"mr-1\">\n            <refresh\n              :application=\"application\"\n              :instance=\"instance\"\n              @refresh=\"fetchEnv\"\n            />\n          </div>\n          <div v-if=\"instance.hasEndpoint('busrefresh')\" class=\"mr-1\">\n            <busrefresh :instance=\"instance\" @refresh=\"fetchEnv\" />\n          </div>\n          <div class=\"flex-1\">\n            <sba-input\n              v-model=\"filter\"\n              :placeholder=\"$t('term.filter')\"\n              name=\"filter\"\n              type=\"search\"\n            >\n              <template #prepend>\n                <font-awesome-icon icon=\"filter\" />\n              </template>\n            </sba-input>\n          </div>\n        </div>\n      </sba-sticky-subnav>\n    </template>\n\n    <template #default>\n      <div v-if=\"env && env?.activeProfiles.length > 0\" class=\"mb-6 gap-1 flex\">\n        <span v-for=\"profile in env.activeProfiles\" :key=\"profile\">\n          <sba-tag\n            :key=\"profile\"\n            :label=\"$t('instances.env.active_profile')\"\n            :value=\"profile\"\n          />\n        </span>\n      </div>\n\n      <sba-env-manager\n        v-if=\"env && hasEnvManagerSupport\"\n        :application=\"application\"\n        :instance=\"instance\"\n        :property-sources=\"env.propertySources\"\n        @refresh=\"fetchEnv\"\n        @update=\"fetchEnv\"\n      />\n\n      <sba-modal data-testid=\"refreshModal\">\n        <template #header>\n          <span v-text=\"$t('instances.env.context_refreshed')\" />\n        </template>\n        <template #body>\n          <span v-html=\"$t('instances.env.refreshed_configurations')\" />\n        </template>\n      </sba-modal>\n\n      <sba-panel\n        v-for=\"propertySource in propertySources\"\n        :key=\"propertySource.name\"\n        :header-sticks-below=\"'#subnavigation'\"\n        :title=\"propertySource.name\"\n      >\n        <div\n          v-if=\"\n            propertySource.properties &&\n            Object.keys(propertySource.properties).length > 0\n          \"\n          class=\"-mx-4 -my-3\"\n        >\n          <div\n            v-for=\"(value, name) in propertySource.properties\"\n            :key=\"`${propertySource.name}-${name}`\"\n            class=\"bg-white px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6\"\n          >\n            <dt class=\"text-sm font-medium text-gray-500\">\n              <span v-text=\"name\" /><br />\n              <small v-if=\"value.origin\" v-text=\"value.origin\" />\n            </dt>\n            <dd\n              class=\"mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2\"\n              v-text=\"getValue(name, value.value)\"\n            />\n          </div>\n        </div>\n        <p v-else class=\"is-muted\" v-text=\"$t('instances.env.no_properties')\" />\n      </sba-panel>\n    </template>\n  </sba-instance-section>\n</template>\n\n<script>\nimport { pickBy } from 'lodash-es';\n\nimport Application from '@/services/application';\nimport Instance from '@/services/instance';\nimport { VIEW_GROUP } from '@/views/ViewGroup';\nimport Busrefresh from '@/views/instances/env/busrefresh.vue';\nimport sbaEnvManager from '@/views/instances/env/env-manager';\nimport refresh from '@/views/instances/env/refresh';\nimport SbaInstanceSection from '@/views/instances/shell/sba-instance-section';\n\nconst filterProperty = (needle) => (property, name) => {\n  return (\n    name.toString().toLowerCase().includes(needle) ||\n    (property.value && property.value.toString().toLowerCase().includes(needle))\n  );\n};\nconst filterProperties = (needle, properties) =>\n  pickBy(properties, filterProperty(needle));\nconst filterPropertySource = (needle) => (propertySource) => {\n  if (!propertySource || !propertySource.properties) {\n    return null;\n  }\n  return {\n    ...propertySource,\n    properties: filterProperties(needle, propertySource.properties),\n  };\n};\n\nexport default {\n  components: { Busrefresh, SbaInstanceSection, sbaEnvManager, refresh },\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n    application: {\n      type: Application,\n      required: true,\n    },\n  },\n  data: () => ({\n    isLoading: true,\n    error: null,\n    env: null,\n    filter: null,\n    hasEnvManagerSupport: false,\n    propertyNamesToEscape: ['line.separator'],\n  }),\n  computed: {\n    propertySources() {\n      if (!this.env) {\n        return [];\n      }\n      if (!this.filter) {\n        return this.env.propertySources;\n      }\n      return this.env.propertySources\n        .map(filterPropertySource(this.filter.toLowerCase()))\n        .filter((ps) => ps && Object.keys(ps.properties).length > 0);\n    },\n  },\n  created() {\n    this.fetchEnv();\n    this.determineEnvManagerSupport();\n  },\n  methods: {\n    async fetchEnv() {\n      this.error = null;\n      try {\n        const res = await this.instance.fetchEnv();\n        this.env = res.data;\n      } catch (error) {\n        console.warn('Fetching environment failed:', error);\n        this.error = error;\n      }\n      this.isLoading = false;\n    },\n    async determineEnvManagerSupport() {\n      try {\n        this.hasEnvManagerSupport = await this.instance.hasEnvManagerSupport();\n      } catch (error) {\n        console.warn('Determine env manager support failed:', error);\n        this.hasEnvManagerSupport = false;\n      }\n    },\n    getValue(name, value) {\n      if (this.propertyNamesToEscape.includes(name)) {\n        return value.replace(/\\n/g, '\\\\n').replace(/\\r/g, '\\\\r');\n      }\n      return value;\n    },\n  },\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      name: 'instances/env',\n      parent: 'instances',\n      path: 'env',\n      component: this,\n      label: 'instances.env.label',\n      group: VIEW_GROUP.INSIGHTS,\n      order: 100,\n      isEnabled: ({ instance }) => instance.hasEndpoint('env'),\n    });\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/env/refresh.spec.ts",
    "content": "import userEvent from '@testing-library/user-event';\nimport { screen } from '@testing-library/vue';\nimport { cloneDeep } from 'lodash-es';\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport { applications } from '../../../mocks/applications/data';\n\nimport Application from '@/services/application';\nimport Instance from '@/services/instance';\nimport { render } from '@/test-utils';\nimport Refresh from '@/views/instances/env/refresh.vue';\n\ndescribe('Refresh', () => {\n  const refreshInstanceContext = vi.fn().mockResolvedValue({\n    data: ['spring.redis.host', 'spring.datasource.url'],\n  });\n\n  const refreshApplicationContext = vi.fn().mockResolvedValue({\n    data: [\n      {\n        instanceId: '123',\n        status: 200,\n        body: '[\"spring.redis.host\",\"spring.datasource.url\"]',\n        contentType: 'application/json',\n      },\n      {\n        instanceId: '456',\n        status: 200,\n        body: '[\"spring.redis.host\",\"spring.datasource.url\"]',\n        contentType: 'application/json',\n      },\n    ],\n  });\n\n  function createInstance() {\n    const instance = new Instance({ id: 1233 });\n    instance.refreshContext = refreshInstanceContext;\n    return instance;\n  }\n\n  function createApplication() {\n    const application = new Application(cloneDeep(applications[1]));\n    application.instances.push(createInstance());\n    application.refreshContext = refreshApplicationContext;\n    return application;\n  }\n\n  beforeEach(async () => {\n    render(Refresh, {\n      props: {\n        instance: createInstance(),\n        application: createApplication(),\n      },\n    });\n  });\n\n  it('shows the changed configurations for the current instance', async () => {\n    const refreshButton = await screen.findByText(\n      'instances.env.context_refresh',\n    );\n    await userEvent.click(refreshButton);\n\n    const confirmButton = await screen.findByText('Confirm');\n    await userEvent.click(confirmButton);\n\n    expect(screen.findByText('Refreshed configurations:')).toBeDefined();\n    expect(screen.findByText('spring.redis.host')).toBeDefined();\n    expect(screen.findByText('spring.datasource.url')).toBeDefined();\n  });\n\n  it('shows the changed configurations for all instances', async () => {\n    const toggleScopeButton = await screen.getByRole('button', {\n      name: 'Instance',\n    });\n    await userEvent.click(toggleScopeButton);\n\n    const refreshButton = await screen.findByText(\n      'instances.env.context_refresh',\n    );\n    await userEvent.click(refreshButton);\n\n    const confirmButton = await screen.findByText('Confirm');\n    await userEvent.click(confirmButton);\n\n    expect(screen.findByText('Refreshed configurations:')).toBeDefined();\n    expect(screen.findByText('spring.redis.host')).toBeDefined();\n    expect(screen.findByText('spring.datasource.url')).toBeDefined();\n    expect(screen.findByText('instanceId: 123')).toBeDefined();\n    expect(screen.findByText('instanceId: 456')).toBeDefined();\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/env/refresh.vue",
    "content": "<template>\n  <sba-action-button-scoped\n    :instance-count=\"instanceCount\"\n    :action-fn=\"refreshContext\"\n    :show-info=\"false\"\n    :label=\"$t('instances.env.context_refresh')\"\n  >\n    <template #default>\n      {{ $t('instances.env.context_refresh') }}\n    </template>\n    <template #completed>\n      <span v-text=\"$t('instances.env.context_refreshed')\" />\n    </template>\n    <template #failed>\n      <span v-text=\"$t('instances.env.context_refresh_failed')\" />\n    </template>\n  </sba-action-button-scoped>\n\n  <sba-modal v-model=\"isModalOpen\" data-testid=\"refreshModal\">\n    <template #header>\n      <span v-text=\"$t('instances.env.context_refreshed')\" />\n    </template>\n    <template #body>\n      <span v-html=\"$t('instances.env.refreshed_configurations')\" />\n      <p v-html=\"refreshedPropertiesHtml\" />\n    </template>\n    <template #footer>\n      <button class=\"button is-success\" @click=\"closeModal\">\n        {{ $t('term.ok') }}\n      </button>\n    </template>\n  </sba-modal>\n</template>\n\n<script>\nimport { ActionScope } from '@/components/ActionScope';\n\nimport Application from '@/services/application';\nimport Instance from '@/services/instance';\n\nexport default {\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n    application: {\n      type: Application,\n      required: true,\n    },\n  },\n  emits: ['refresh'],\n  data() {\n    return {\n      refreshedProperties: [],\n      isModalOpen: false,\n      currentScope: ActionScope.INSTANCE,\n    };\n  },\n  computed: {\n    instanceCount() {\n      return this.application.instances.length;\n    },\n    refreshedPropertiesHtml() {\n      if (\n        this.currentScope === 'instance' &&\n        this.refreshedProperties.length > 0\n      ) {\n        return (\n          '<ul class=\"properties-list\">' +\n          this.refreshedProperties[0].changedProperties\n            .map((entry) => '<li>' + entry + '</li>')\n            .join('') +\n          '</ul>'\n        );\n      } else {\n        return (\n          '<ul class=\"properties-list\">' +\n          this.refreshedProperties\n            .filter((property) => property.changedProperties.length > 0)\n            .map(\n              (entry) =>\n                '<li>instanceId: ' +\n                entry.instanceId +\n                '<ul class=\"properties-list\">' +\n                entry.changedProperties\n                  .map((property) => '<li>' + property + '</li>')\n                  .join('') +\n                '</ul>',\n            )\n            .join('') +\n          '</ul>'\n        );\n      }\n    },\n  },\n  methods: {\n    async refreshInstance() {\n      await this.instance.refreshContext().then((response) => {\n        this.refreshedProperties = [\n          {\n            instanceId: this.instance.id,\n            changedProperties: response.data,\n          },\n        ];\n        this.isModalOpen = response.data.length > 0;\n      });\n    },\n    async refreshApplication() {\n      await this.application.refreshContext().then((response) => {\n        this.refreshedProperties = response.data.map((entry) => ({\n          instanceId: entry.instanceId,\n          changedProperties: JSON.parse(entry.body),\n        }));\n        this.isModalOpen = this.refreshedProperties.some(\n          (props) => props.changedProperties.length > 0,\n        );\n      });\n    },\n    async refreshContext(scope) {\n      this.currentScope = scope;\n      if (scope === 'instance') {\n        await this.refreshInstance();\n      } else {\n        await this.refreshApplication();\n      }\n\n      this.$emit('refresh', this.refreshedProperties.length > 0);\n    },\n    closeModal() {\n      this.refreshedProperties = [];\n      this.isModalOpen = false;\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/flyway/i18n.de.json",
    "content": "{\n  \"instances\": {\n    \"flyway\": {\n      \"label\": \"Flyway\",\n      \"type\": \"Typ\",\n      \"checksum\": \"Prüfsumme\",\n      \"version\": \"Version\",\n      \"description\": \"Beschreibung\",\n      \"script\": \"Skript\",\n      \"state\": \"Status\",\n      \"installed_by\": \"Installiert von\",\n      \"installed_on\": \"Installiert am\",\n      \"installed_rank\": \"Reihenfolge\",\n      \"execution_time\": \"Ausführungszeit\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/flyway/i18n.en.json",
    "content": "{\n  \"instances\": {\n    \"flyway\": {\n      \"label\": \"Flyway\",\n      \"type\": \"Type\",\n      \"checksum\": \"Checksum\",\n      \"version\": \"Version\",\n      \"description\": \"Description\",\n      \"script\": \"Script\",\n      \"state\": \"State\",\n      \"installed_by\": \"Installed by\",\n      \"installed_on\": \"Installed on\",\n      \"installed_rank\": \"Installed rank\",\n      \"execution_time\": \"Execution Time\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/flyway/i18n.es.json",
    "content": "{\n  \"instances\": {\n    \"flyway\": {\n      \"label\": \"Flyway\",\n\n      \"type\": \"Tipo\",\n      \"checksum\": \"Checksum\",\n      \"version\": \"Versión\",\n      \"description\": \"Descripción\",\n      \"script\": \"Script\",\n      \"state\": \"Estado\",\n      \"installed_by\": \"Instalado por\",\n      \"installed_on\": \"Instalado en\",\n      \"installed_rank\": \"Rango instalado\",\n      \"execution_time\": \"Tiempo ejecución\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/flyway/i18n.fr.json",
    "content": "{\n  \"instances\": {\n    \"flyway\": {\n      \"label\": \"Flyway\",\n      \"type\": \"Type\",\n      \"checksum\": \"Checksum\",\n      \"version\": \"Version\",\n      \"description\": \"Description\",\n      \"script\": \"Script\",\n      \"state\": \"État\",\n      \"installed_by\": \"Installé par\",\n      \"installed_on\": \"Installé sur\",\n      \"installed_rank\": \"Ordre d'installation\",\n      \"execution_time\": \"Temps d'éxecution\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/flyway/i18n.is.json",
    "content": "{\n  \"instances\": {\n    \"flyway\": {\n      \"label\": \"Flyway\",\n      \"type\": \"Gerð\",\n      \"checksum\": \"Checksum\",\n      \"version\": \"Útgafa\",\n      \"description\": \"Lýsing\",\n      \"script\": \"Forskrift\",\n      \"state\": \"Staða\",\n      \"installed_by\": \" Sett upp af\",\n      \"installed_on\": \"Sett upp á\",\n      \"installed_rank\": \"Uppsett staða\",\n      \"execution_time\": \"Tími framkvæmdarinnar\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/flyway/i18n.ko.json",
    "content": "{\n  \"instances\": {\n    \"flyway\": {\n      \"label\": \"Flyway\",\n      \"type\": \"타입\",\n      \"checksum\": \"체크섬\",\n      \"version\": \"버전\",\n      \"description\": \"설명\",\n      \"script\": \"스크립트\",\n      \"state\": \"상태\",\n      \"installed_by\": \"에 의해 설치\",\n      \"installed_on\": \"에 설치된\",\n      \"installed_rank\": \"설치 순위\",\n      \"execution_time\": \"실행시간\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/flyway/i18n.pt-BR.json",
    "content": "{\n  \"instances\": {\n    \"flyway\": {\n      \"label\": \"Flyway\",\n      \"type\": \"Tipo\",\n      \"checksum\": \"Checksum\",\n      \"version\": \"Versão\",\n      \"description\": \"Descrição\",\n      \"script\": \"Script\",\n      \"state\": \"Estado\",\n      \"installed_by\": \"Instalado por\",\n      \"installed_on\": \"Instalado em\",\n      \"installed_rank\": \"Classificação instalada\",\n      \"execution_time\": \"Tempo de Execução\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/flyway/i18n.ru.json",
    "content": "{\n  \"instances\": {\n    \"flyway\": {\n      \"label\": \"Flyway\",\n      \"type\": \"Тип\",\n      \"checksum\": \"Контрольная сумма\",\n      \"version\": \"Версия\",\n      \"description\": \"Описание\",\n      \"script\": \"Скрипт\",\n      \"state\": \"Статус\",\n      \"installed_by\": \"Кем установлено\",\n      \"installed_on\": \"Когда установлено\",\n      \"installed_rank\": \"Установленный ранг\",\n      \"execution_time\": \"Время выполнения\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/flyway/i18n.zh-CN.json",
    "content": "{\n  \"instances\": {\n    \"flyway\": {\n      \"label\": \"Flyway\",\n      \"type\": \"类型\",\n      \"checksum\": \"校验码\",\n      \"version\": \"版本\",\n      \"description\": \"描述\",\n      \"script\": \"脚本\",\n      \"state\": \"状态\",\n      \"installed_by\": \"安装人\",\n      \"installed_on\": \"安装位置\",\n      \"installed_rank\": \"安装等级\",\n      \"execution_time\": \"执行时间\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/flyway/i18n.zh-TW.json",
    "content": "{\n  \"instances\": {\n    \"flyway\": {\n      \"label\": \"Flyway\",\n      \"type\": \"類型\",\n      \"checksum\": \"檢查碼\",\n      \"version\": \"版本\",\n      \"description\": \"說明\",\n      \"script\": \"指令碼\",\n      \"state\": \"狀態\",\n      \"installed_by\": \"安裝者\",\n      \"installed_on\": \"安裝時間\",\n      \"installed_rank\": \"安裝順序\",\n      \"execution_time\": \"執行時間\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/flyway/index.vue",
    "content": "<!--\n  - Copyright 2014-2019 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-instance-section :error=\"error\" :loading=\"!hasLoaded\">\n    <template v-for=\"(context, ctxName) in contexts\" :key=\"ctxName\">\n      <sba-panel\n        v-for=\"(report, name) in context.flywayBeans\"\n        :key=\"`${ctxName}-${name}`\"\n        :header-sticks-below=\"'#subnavigation'\"\n        :title=\"`${ctxName}: ${name}`\"\n        class=\"migration\"\n      >\n        <table class=\"table w-full\">\n          <thead>\n            <tr>\n              <th v-text=\"$t('instances.flyway.type')\" />\n              <th v-text=\"$t('instances.flyway.checksum')\" />\n              <th v-text=\"$t('instances.flyway.version')\" />\n              <th v-text=\"$t('instances.flyway.description')\" />\n              <th v-text=\"$t('instances.flyway.script')\" />\n              <th v-text=\"$t('instances.flyway.state')\" />\n              <th v-text=\"$t('instances.flyway.installed_by')\" />\n              <th v-text=\"$t('instances.flyway.installed_on')\" />\n              <th v-text=\"$t('instances.flyway.installed_rank')\" />\n              <th v-text=\"$t('instances.flyway.execution_time')\" />\n            </tr>\n          </thead>\n          <tbody>\n            <tr\n              v-for=\"migration in report.migrations\"\n              :key=\"migration.checksum\"\n            >\n              <td v-text=\"migration.type\" />\n              <td v-text=\"migration.checksum\" />\n              <td v-text=\"migration.version\" />\n              <td v-text=\"migration.description\" />\n              <td v-text=\"migration.script\" />\n              <td>\n                <span\n                  :class=\"stateClass(migration.state)\"\n                  class=\"tag\"\n                  v-text=\"migration.state\"\n                />\n              </td>\n              <td v-text=\"migration.installedBy\" />\n              <td v-text=\"migration.installedOn\" />\n              <td v-text=\"migration.installedRank\" />\n              <td v-text=\"`${migration.executionTime}ms`\" />\n            </tr>\n          </tbody>\n        </table>\n      </sba-panel>\n    </template>\n  </sba-instance-section>\n</template>\n\n<script>\nimport Instance from '@/services/instance';\nimport { VIEW_GROUP } from '@/views/ViewGroup';\nimport SbaInstanceSection from '@/views/instances/shell/sba-instance-section';\n\nexport default {\n  components: { SbaInstanceSection },\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  data: () => ({\n    hasLoaded: false,\n    error: null,\n    contexts: null,\n  }),\n  computed: {},\n  created() {\n    this.fetchFlyway();\n  },\n  methods: {\n    async fetchFlyway() {\n      this.error = null;\n      try {\n        const res = await this.instance.fetchFlyway();\n        this.contexts = res.data.contexts;\n      } catch (error) {\n        console.warn('Fetching flyway reports failed:', error);\n        this.error = error;\n      }\n      this.hasLoaded = true;\n    },\n    stateClass(state) {\n      switch (state) {\n        case 'BASELINE':\n        case 'MISSING_SUCCESS':\n        case 'SUCCESS':\n        case 'OUT_OF_ORDER':\n        case 'FUTURE_SUCCESS':\n          return 'is-success';\n        case 'PENDING':\n        case 'ABOVE_TARGET':\n        case 'PREINIT':\n        case 'BELOW_BASELINE':\n        case 'IGNORED':\n          return 'is-warning';\n        case 'MISSING_FAILED':\n        case 'FAILED':\n        case 'FUTURE_FAILED':\n          return 'is-danger';\n        default:\n          return 'is-light';\n      }\n    },\n  },\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      name: 'instances/flyway',\n      parent: 'instances',\n      path: 'flyway',\n      component: this,\n      label: 'instances.flyway.label',\n      group: VIEW_GROUP.DATA,\n      order: 900,\n      isEnabled: ({ instance }) => instance.hasEndpoint('flyway'),\n    });\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/gateway/add-route.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div>\n    <div v-if=\"error\" class=\"message is-danger\">\n      <div class=\"message-body\">\n        <strong>\n          <font-awesome-icon\n            class=\"has-text-warning\"\n            icon=\"exclamation-triangle\"\n          />\n          <span v-text=\"$t('instances.gateway.route.adding_failed')\" />\n        </strong>\n        <p v-text=\"error.message\" />\n      </div>\n    </div>\n\n    <div class=\"field\">\n      <label\n        class=\"label\"\n        for=\"routeId\"\n        v-text=\"$t('instances.gateway.route.id')\"\n      />\n      <p class=\"control\">\n        <input id=\"routeId\" v-model=\"routeId\" class=\"input\" required />\n      </p>\n    </div>\n\n    <div class=\"field\">\n      <label\n        class=\"label\"\n        for=\"order\"\n        v-text=\"$t('instances.gateway.route.order')\"\n      />\n      <p class=\"control\">\n        <input\n          id=\"order\"\n          v-model=\"routeOrder\"\n          class=\"input\"\n          placeholder=\"0\"\n          type=\"number\"\n        />\n      </p>\n    </div>\n\n    <div class=\"field\">\n      <label\n        class=\"label\"\n        for=\"predicates\"\n        v-text=\"$t('instances.gateway.route.predicates')\"\n      />\n      <p class=\"control\">\n        <textarea\n          id=\"predicates\"\n          v-model=\"routePredicates\"\n          class=\"textarea\"\n          placeholder=\"[]\"\n          required\n          rows=\"4\"\n        />\n      </p>\n    </div>\n\n    <div class=\"field\">\n      <label\n        class=\"label\"\n        for=\"filters\"\n        v-text=\"$t('instances.gateway.route.filters')\"\n      />\n      <p class=\"control\">\n        <textarea\n          id=\"filters\"\n          v-model=\"routeFilters\"\n          class=\"textarea\"\n          placeholder=\"[]\"\n          rows=\"4\"\n        />\n      </p>\n    </div>\n\n    <div class=\"field\">\n      <label\n        class=\"label\"\n        for=\"routeUri\"\n        v-text=\"$t('instances.gateway.route.uri')\"\n      />\n      <p class=\"control\">\n        <input\n          id=\"routeUri\"\n          v-model=\"routeUri\"\n          class=\"input\"\n          placeholder=\"http://example.com\"\n          required\n        />\n      </p>\n    </div>\n\n    <div class=\"field is-grouped is-grouped-right\">\n      <div class=\"control\">\n        <button\n          :disabled=\"!isAddingRoutePossible\"\n          class=\"button is-primary\"\n          @click=\"addRoute\"\n          v-text=\"$t('instances.gateway.route.add_route')\"\n        />\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\nimport Instance from '@/services/instance';\nimport { from } from '@/utils/rxjs';\n\nexport default {\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  emits: ['route-added'],\n  data: () => ({\n    error: null,\n    routeId: null,\n    routePredicates: null,\n    routeFilters: null,\n    routeUri: null,\n    routeOrder: null,\n    addRouteTimeout: null,\n  }),\n  computed: {\n    isAddingRoutePossible() {\n      return this.routeId && this.routePredicates && this.routeUri;\n    },\n  },\n  beforeUnmount() {\n    if (this.addRouteTimeout) {\n      clearTimeout(this.addRouteTimeout);\n      this.addRouteTimeout = null;\n    }\n  },\n  methods: {\n    addRoute() {\n      const newRoute = {\n        id: this.routeId,\n        predicates: this.routePredicates\n          ? JSON.parse(this.routePredicates)\n          : undefined,\n        filters: this.routeFilters ? JSON.parse(this.routeFilters) : [],\n        uri: this.routeUri,\n        order: this.routeOrder || 0,\n      };\n      from(this.instance.addGatewayRoute(newRoute)).subscribe({\n        complete: () => {\n          this.routeId = null;\n          this.routePredicates = null;\n          this.routeFilters = null;\n          this.routeUri = null;\n          this.routeOrder = null;\n          this.error = null;\n          this.addRouteTimeout = setTimeout(\n            () => this.$emit('route-added'),\n            2500,\n          );\n        },\n        error: (error) => {\n          this.error = error;\n        },\n      });\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/gateway/global-filters.vue",
    "content": "<!--\n  - Copyright 2014-2019 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div :class=\"{ 'is-loading': isLoading }\">\n    <sba-alert v-if=\"error\" :error=\"error\" :title=\"$t('term.fetch_failed')\" />\n\n    <sba-panel :header-sticks-below=\"'#subnavigation'\" title=\"Global Filters\">\n      <div v-if=\"globalFilters.length > 0\" class=\"field\">\n        <p class=\"control is-expanded has-icons-left\">\n          <input v-model=\"filterCriteria\" class=\"input\" type=\"search\" />\n          <span class=\"icon is-small is-left\">\n            <font-awesome-icon icon=\"filter\" />\n          </span>\n        </p>\n      </div>\n\n      <table class=\"table is-fullwidth\">\n        <thead>\n          <tr>\n            <th v-text=\"$t('instances.gateway.filters.filter_name')\" />\n            <th v-text=\"$t('instances.gateway.filters.order')\" />\n          </tr>\n        </thead>\n        <tbody>\n          <tr v-for=\"filter in globalFilters\" :key=\"filter.name\">\n            <td>\n              <span class=\"is-breakable\" v-text=\"filter.name\" />\n              <span class=\"is-muted\" v-text=\"`@${filter.objectId}`\" />\n            </td>\n            <td v-text=\"filter.order\" />\n          </tr>\n          <tr v-if=\"globalFilters.length === 0\">\n            <td class=\"is-muted\" colspan=\"7 \">\n              <p\n                v-if=\"isLoading\"\n                class=\"is-loading\"\n                v-text=\"$t('instances.gateway.filters.loading')\"\n              />\n              <p\n                v-else\n                v-text=\"$t('instances.gateway.filters.no_filters_found')\"\n              />\n            </td>\n          </tr>\n        </tbody>\n      </table>\n    </sba-panel>\n  </div>\n</template>\n\n<script>\nimport Instance from '@/services/instance';\nimport { compareBy } from '@/utils/collections';\n\nconst globalFilterHasKeyword = (globalFilter, keyword) => {\n  return globalFilter.name.toString().toLowerCase().includes(keyword);\n};\n\nconst sortGlobalFilter = (globalFilters) => {\n  return [...globalFilters].sort(compareBy((f) => f.order));\n};\n\nexport default {\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  data: () => ({\n    isLoading: false,\n    error: null,\n    $globalFilters: [],\n    filterCriteria: null,\n  }),\n  computed: {\n    globalFilters() {\n      if (!this.filterCriteria) {\n        return sortGlobalFilter(this.$data.$globalFilters);\n      }\n      const filtered = this.$data.$globalFilters.filter((globalFilter) =>\n        globalFilterHasKeyword(globalFilter, this.filterCriteria.toLowerCase()),\n      );\n      return sortGlobalFilter(filtered);\n    },\n  },\n  created() {\n    this.fetchGlobalFilters();\n  },\n  methods: {\n    async fetchGlobalFilters() {\n      this.error = null;\n      this.isLoading = true;\n      try {\n        const response = await this.instance.fetchGatewayGlobalFilters();\n        this.$data.$globalFilters = Object.entries(response.data).map(\n          ([name, order]) => {\n            const [className, objectId] = name.split('@');\n            return {\n              name: className,\n              objectId,\n              order,\n            };\n          },\n        );\n      } catch (error) {\n        console.warn('Fetching global filters failed:', error);\n        this.error = error;\n      }\n      this.isLoading = false;\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/gateway/i18n.de.json",
    "content": "{\n  \"instances\": {\n    \"gateway\": {\n      \"label\": \"Gateway\",\n      \"route\": {\n        \"add_route\": \"Route hinzufügen\",\n        \"adding_failed\": \"Hinzufügen der Route fehlgeschlagen.\",\n        \"cache_refreshed\": \"Route-Cache aktualisiert\",\n        \"cache_refresh\": \"Aktualisiere Route-Cache\",\n        \"cache_refresh_failed\": \"Fehlgeschlagen\",\n        \"deleted\": \"Gelöscht\",\n        \"delete\": \"Löschen\",\n        \"delete_failed\": \"Fehlgeschlagen\",\n        \"filters\": \"Filter\",\n        \"id\": \"Id\",\n        \"loading\": \"Routen werden geladen...\",\n        \"no_definition_provided\": \"Keine Routing-Informationen vorhanden.\",\n        \"no_routes_found\": \"Keine Routen gefunden.\",\n        \"order\": \"Reihenfolge\",\n        \"predicates\": \"Predicates\",\n        \"uri\": \"URI\"\n      },\n      \"filters\": {\n        \"filter_name\": \"Filtername\",\n        \"loading\": \"Global-Filter werden geladen...\",\n        \"no_filters_found\": \"Keine Globalen-Filter gefunden.\",\n        \"order\": \"Reihenfolge\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/gateway/i18n.en.json",
    "content": "{\n  \"instances\": {\n    \"gateway\": {\n      \"label\": \"Gateway\",\n      \"route\": {\n        \"add_route\": \"Add route\",\n        \"adding_failed\": \"Adding route failed.\",\n        \"cache_refreshed\": \"Routes Cache refreshed\",\n        \"cache_refresh\": \"Refresh Routes Cache\",\n        \"cache_refresh_failed\": \"Failed\",\n        \"deleted\": \"Deleted\",\n        \"delete\": \"Delete\",\n        \"delete_failed\": \"Failed\",\n        \"filters\": \"Filters\",\n        \"id\": \"Id\",\n        \"loading\": \"Loading Routes...\",\n        \"no_definition_provided\": \"No Route Definition provided.\",\n        \"no_routes_found\": \"No Routes found.\",\n        \"order\": \"Order\",\n        \"predicates\": \"Predicates\",\n        \"uri\": \"URI\"\n      },\n      \"filters\": {\n        \"filter_name\": \"Filter name\",\n        \"loading\": \"Loading Global Filters...\",\n        \"no_filters_found\": \"No Global Filters found.\",\n        \"order\": \"Order\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/gateway/i18n.es.json",
    "content": "{\n  \"instances\": {\n    \"gateway\": {\n      \"label\": \"Gateway\",\n      \"route\": {\n        \"add_route\": \"Agregar ruta\",\n        \"adding_failed\": \"Falla agregando la ruta.\",\n        \"cache_refreshed\": \"Cache de rutas refrescada\",\n        \"cache_refresh\": \"Refrescar Cache de Rutas\",\n        \"cache_refresh_failed\": \"Fallida\",\n        \"deleted\": \"Eliminada\",\n        \"delete\": \"Eliminar\",\n        \"delete_failed\": \"Fallida\",\n\n        \"filters\": \"Filtros\",\n        \"id\": \"Id\",\n        \"loading\": \"Cargando rutas...\",\n        \"no_definition_provided\": \"No se encuentra ninguna definición de rutas.\",\n        \"no_routes_found\": \"Rutas no encontradas.\",\n        \"order\": \"Órden\",\n        \"predicates\": \"Predicados\",\n        \"uri\": \"URI\"\n      },\n      \"filters\": {\n        \"filter_name\": \"Nombre del filtro\",\n        \"loading\": \"Cargando filtros globales...\",\n        \"no_filters_found\": \"No se encontraron filtros globales.\",\n        \"order\": \"Órden\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/gateway/i18n.fr.json",
    "content": "{\n  \"instances\": {\n    \"gateway\": {\n      \"label\": \"Gateway\",\n      \"route\": {\n        \"add_route\": \"Ajouter une route\",\n        \"adding_failed\": \"Echec de l'ajout d'une route.\",\n        \"cache_refreshed\": \"Cache des routes rafraîchi\",\n        \"cache_refresh\": \"Rafraîchir le cache des routes\",\n        \"cache_refresh_failed\": \"Echec\",\n        \"deleted\": \"Supprimé\",\n        \"delete\": \"Supprimer\",\n        \"delete_failed\": \"Echec\",\n        \"filters\": \"Filtres\",\n        \"id\": \"Id\",\n        \"loading\": \"Chargement des routes...\",\n        \"no_definition_provided\": \"Aucune définition de routes fournies.\",\n        \"no_routes_found\": \"Aucune route trouvée.\",\n        \"order\": \"Ordre\",\n        \"predicates\": \"Prédicats\",\n        \"uri\": \"URI\"\n      },\n      \"filters\": {\n        \"filter_name\": \"Nom du filtre\",\n        \"loading\": \"Chargement des filtres globaux...\",\n        \"no_filters_found\": \"Aucun filtre global trouvé.\",\n        \"order\": \"Ordre\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/gateway/i18n.is.json",
    "content": "{\n  \"instances\": {\n    \"gateway\": {\n      \"label\": \"Gátt\",\n      \"route\": {\n        \"add_route\": \"Bæta við vegi\",\n        \"adding_failed\": \"Mistókst að bæta við vegi.\",\n        \"cache_refreshed\": \"Skyndiminni vega uppfært\",\n        \"cache_refresh\": \"Uppfæra skyndiminni vega\",\n        \"cache_refresh_failed\": \"Mistekist\",\n        \"deleted\": \"Eytt\",\n        \"delete\": \"Eyða\",\n        \"delete_failed\": \"Mistekist\",\n        \"filters\": \"Síur\",\n        \"id\": \"Id\",\n        \"loading\": \"Hlaða vegir…\",\n        \"no_definition_provided\": \"Engin skilgreining vegs í boði.\",\n        \"no_routes_found\": \"Fundið engir vegir.\",\n        \"order\": \"Röð\",\n        \"predicates\": \"Umsagnir\",\n        \"uri\": \"URI\"\n      },\n      \"filters\": {\n        \"filter_name\": \"Nafn síu\",\n        \"loading\": \"Hlaða alheims-síur…\",\n        \"no_filters_found\": \"Fundið engar alheims-síur.\",\n        \"order\": \"Röð\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/gateway/i18n.ko.json",
    "content": "{\n  \"instances\": {\n    \"gateway\": {\n      \"label\": \"게이트웨이\",\n      \"route\": {\n        \"add_route\": \"라우트 추가\",\n        \"adding_failed\": \"라우트 추가 실패했습니다.\",\n        \"cache_refreshed\": \"라우트 캐시가 갱신되었습니다.\",\n        \"cache_refresh\": \"라우트 캐시 갱신\",\n        \"cache_refresh_failed\": \"갱신 실패\",\n        \"deleted\": \"삭제됨\",\n        \"delete\": \"삭제\",\n        \"delete_failed\": \"삭제 실패\",\n        \"filters\": \"필터\",\n        \"id\": \"Id\",\n        \"loading\": \"라우트 불러오는 중...\",\n        \"no_definition_provided\": \"라우트 정의가 제공되지 않습니다.\",\n        \"no_routes_found\": \"라우트가 없습니다.\",\n        \"order\": \"순서\",\n        \"predicates\": \"Predicates\",\n        \"uri\": \"URI\"\n      },\n      \"filters\": {\n        \"filter_name\": \"필터명\",\n        \"loading\": \"글로벌 필터 불러오는 중...\",\n        \"no_filters_found\": \"글로벌 필터를 찾을 수 없습니다.\",\n        \"order\": \"순서\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/gateway/i18n.pt-BR.json",
    "content": "{\n  \"instances\": {\n    \"gateway\": {\n      \"label\": \"Gateway\",\n      \"route\": {\n        \"add_route\": \"Adicionar rota\",\n        \"adding_failed\": \"Falha ao adicionar rota.\",\n        \"cache_refreshed\": \"Cache de rotas atualizado\",\n        \"cache_refresh\": \"Atualizar cache de rotas\",\n        \"cache_refresh_failed\": \"Falha\",\n        \"deleted\": \"Removido\",\n        \"delete\": \"Remover\",\n        \"delete_failed\": \"Falha\",\n        \"filters\": \"Filtros\",\n        \"id\": \"Id\",\n        \"loading\": \"Carregando Rotas...\",\n        \"no_definition_provided\": \"Nenhuma definição de Rota fornecida.\",\n        \"no_routes_found\": \"Nenhuma Rota foi encontrada.\",\n        \"order\": \"Ordem\",\n        \"predicates\": \"Predicates\",\n        \"uri\": \"URI\"\n      },\n      \"filters\": {\n        \"filter_name\": \"Nome do filtro\",\n        \"loading\": \"Carregando Filtros Globais...\",\n        \"no_filters_found\": \"Nunhum Filtro Global foi encontrado.\",\n        \"order\": \"Ordem\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/gateway/i18n.ru.json",
    "content": "{\n  \"instances\": {\n    \"gateway\": {\n      \"label\": \"Шлюз\",\n      \"route\": {\n        \"add_route\": \"Добавить маршрут\",\n        \"adding_failed\": \"Ошибка при добавлении маршрута.\",\n        \"cache_refreshed\": \"Кэш маршрутов обновлен\",\n        \"cache_refresh\": \"Обновление кэша маршрутов\",\n        \"cache_refresh_failed\": \"Ошибка\",\n        \"deleted\": \"Удален\",\n        \"delete\": \"Удалить\",\n        \"delete_failed\": \"Ошибка\",\n        \"filters\": \"Фильтры\",\n        \"id\": \"Id\",\n        \"loading\": \"Загрузка маршрутов...\",\n        \"no_definition_provided\": \"Определение маршрута не предоставлено.\",\n        \"no_routes_found\": \"Маршруты не найдены.\",\n        \"order\": \"Порядок\",\n        \"predicates\": \"Предикаты\",\n        \"uri\": \"URI\"\n      },\n      \"filters\": {\n        \"filter_name\": \"Название фильтра\",\n        \"loading\": \"Загрузка глобальный фильтров...\",\n        \"no_filters_found\": \"Глобальные фильтра не найдены.\",\n        \"order\": \"Порядок\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/gateway/i18n.zh-CN.json",
    "content": "{\n  \"instances\": {\n    \"gateway\": {\n      \"label\": \"网关\",\n      \"route\": {\n        \"add_route\": \"添加路由\",\n        \"adding_failed\": \"添加路由失败。\",\n        \"cache_refreshed\": \"路由缓存已刷新\",\n        \"cache_refresh\": \"刷新路由缓存\",\n        \"cache_refresh_failed\": \"刷新失败\",\n        \"deleted\": \"已删除\",\n        \"delete\": \"删除\",\n        \"delete_failed\": \"删除失败\",\n        \"filters\": \"过滤器\",\n        \"id\": \"Id\",\n        \"loading\": \"加载路由中...\",\n        \"no_definition_provided\": \"没有提供路由定义。\",\n        \"no_routes_found\": \"未找到路由。\",\n        \"order\": \"列表\",\n        \"predicates\": \"谓语\",\n        \"uri\": \"URI\"\n      },\n      \"filters\": {\n        \"filter_name\": \"过滤器名称\",\n        \"loading\": \"加载全局过滤器中...\",\n        \"no_filters_found\": \"未找到全局过滤器。\",\n        \"order\": \"列表\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/gateway/i18n.zh-TW.json",
    "content": "{\n  \"instances\": {\n    \"gateway\": {\n      \"label\": \"閘道\",\n      \"route\": {\n        \"add_route\": \"新增路由\",\n        \"adding_failed\": \"新增路由失敗。\",\n        \"cache_refreshed\": \"路由快取已重新整理\",\n        \"cache_refresh\": \"重新整理路由快取\",\n        \"cache_refresh_failed\": \"失敗\",\n        \"deleted\": \"已刪除\",\n        \"delete\": \"刪除\",\n        \"delete_failed\": \"失敗\",\n        \"filters\": \"篩選器\",\n        \"id\": \"ID\",\n        \"loading\": \"正在載入路由...\",\n        \"no_definition_provided\": \"未提供路由定義。\",\n        \"no_routes_found\": \"找不到路由。\",\n        \"order\": \"順序\",\n        \"predicates\": \"述詞\",\n        \"uri\": \"URI\"\n      },\n      \"filters\": {\n        \"filter_name\": \"篩選器名稱\",\n        \"loading\": \"正在載入全域篩選器...\",\n        \"no_filters_found\": \"找不到全域篩選器。\",\n        \"order\": \"順序\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/gateway/index.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <section class=\"section\">\n    <global-filters :instance=\"instance\" />\n    <routes :instance=\"instance\" />\n  </section>\n</template>\n\n<script>\nimport Instance from '@/services/instance';\nimport { VIEW_GROUP } from '@/views/ViewGroup';\nimport globalFilters from '@/views/instances/gateway/global-filters';\nimport routes from '@/views/instances/gateway/routes';\n\nexport default {\n  components: {\n    globalFilters,\n    routes,\n  },\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      name: 'instances/gateway',\n      parent: 'instances',\n      path: 'gateway',\n      component: this,\n      label: 'instances.gateway.label',\n      group: VIEW_GROUP.WEB,\n      order: 960,\n      isEnabled: ({ instance }) => instance.hasEndpoint('gateway'),\n    });\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/gateway/refresh-route-cache.vue",
    "content": "<!--\n  - Copyright 2014-2020 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div class=\"field\">\n    <div class=\"control\">\n      <button\n        :class=\"{\n          'is-loading': refreshingRouteCache === 'executing',\n          'is-danger': refreshingRouteCache === 'failed',\n          'is-info': refreshingRouteCache === 'completed',\n        }\"\n        :disabled=\"refreshingRouteCache === 'executing'\"\n        class=\"button is-light\"\n        @click=\"refreshRoutesCache\"\n      >\n        <span\n          v-if=\"refreshingRouteCache === 'completed'\"\n          v-text=\"$t('instances.gateway.route.cache_refreshed')\"\n        />\n        <span\n          v-else-if=\"refreshingRouteCache === 'failed'\"\n          v-text=\"$t('instances.gateway.route.cache_refresh_failed')\"\n        />\n        <span v-else v-text=\"$t('instances.gateway.route.cache_refresh')\" />\n      </button>\n    </div>\n  </div>\n</template>\n<script>\nimport Instance from '@/services/instance';\nimport { from, listen } from '@/utils/rxjs';\n\nexport default {\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  emits: ['routes-refreshed'],\n  data: () => ({\n    refreshingRouteCache: null,\n    refreshTimeout: null,\n  }),\n  beforeUnmount() {\n    if (this.refreshTimeout) {\n      clearTimeout(this.refreshTimeout);\n      this.refreshTimeout = null;\n    }\n  },\n  methods: {\n    refreshRoutesCache() {\n      from(this.instance.refreshGatewayRoutesCache())\n        .pipe(listen((status) => (this.refreshingRouteCache = status)))\n        .subscribe({\n          complete: () => {\n            this.$emit('routes-refreshed');\n            this.refreshTimeout = setTimeout(\n              () => (this.refreshingRouteCache = null),\n              2500,\n            );\n          },\n        });\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/gateway/route-definition.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div class=\"route-definition-container\">\n    <div class=\"route-definition\">\n      <div class=\"route-definition-header\">\n        <font-awesome-icon icon=\"search\" />&nbsp;\n        <span v-text=\"$t('instances.gateway.route.predicates')\" />\n      </div>\n      <div\n        v-for=\"predicate in routeDefinition.predicates\"\n        :key=\"predicate.name\"\n        class=\"route-definition-content\"\n      >\n        <div class=\"route-definition-category\" v-text=\"predicate.name\" />\n        <ul>\n          <li\n            v-for=\"item in transformArgs(predicate.args)\"\n            :key=\"item\"\n            v-text=\"item\"\n          />\n        </ul>\n      </div>\n    </div>\n\n    <font-awesome-icon\n      icon=\"angle-double-right\"\n      class=\"route-definition-spacer\"\n    />\n\n    <div v-if=\"routeDefinition.filters.length > 0\" class=\"route-definition\">\n      <div class=\"route-definition-header\">\n        <font-awesome-icon icon=\"filter\" />&nbsp;\n        <span v-text=\"$t('instances.gateway.route.filters')\" />\n      </div>\n      <div\n        v-for=\"filter in routeDefinition.filters\"\n        :key=\"filter.name\"\n        class=\"route-definition-content\"\n      >\n        <div class=\"route-definition-category\" v-text=\"filter.name\" />\n        <ul>\n          <li\n            v-for=\"item in transformArgs(filter.args)\"\n            :key=\"item\"\n            v-text=\"item\"\n          />\n        </ul>\n      </div>\n    </div>\n\n    <font-awesome-icon\n      v-if=\"routeDefinition.filters.length > 0\"\n      icon=\"angle-double-right\"\n      class=\"route-definition-spacer\"\n    />\n\n    <div class=\"route-definition\">\n      <div class=\"route-definition-header\">\n        <font-awesome-icon icon=\"map-marker-alt\" />&nbsp;\n        <span v-text=\"$t('instances.gateway.route.uri')\" />\n      </div>\n      <div class=\"route-definition-content\" v-text=\"routeDefinition.uri\" />\n    </div>\n  </div>\n</template>\n\n<script>\nexport default {\n  props: {\n    routeDefinition: {\n      type: Object,\n      required: true,\n    },\n  },\n  methods: {\n    transformArgs(args) {\n      return Object.entries(args).map(([key, value]) => `${key} : ${value}`);\n    },\n  },\n};\n</script>\n\n<style lang=\"css\">\n.route-definition {\n  display: block;\n  min-width: 12em;\n  max-width: 28em;\n  padding: 0.5em;\n  margin: 1.25em;\n  background-color: #fff;\n  border-radius: 6px;\n  box-shadow:\n    0 2px 3px rgba(0, 0, 0, 0.1),\n    0 0 0 1px rgba(0, 0, 0, 0.1);\n}\n.route-definition-container {\n  display: flex;\n  align-items: center;\n}\n.route-definition-spacer {\n  font-size: 1.25rem;\n  min-width: 1em;\n  max-width: 3.5em;\n}\n.route-definition-header {\n  font-size: 1.25rem;\n}\n.route-definition-content {\n  background-color: #00d1b2;\n  color: #fff;\n  border-radius: 4px;\n  padding: 0.25em;\n  margin: 0.25em;\n}\n.route-definition-category {\n  font-weight: 700;\n  border-bottom: 1px solid #fafafa;\n  display: block;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/gateway/route.vue",
    "content": "<!--\n  - Copyright 2014-2020 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div class=\"route-container\">\n    <div class=\"route\">\n      <div class=\"route-header\">\n        <font-awesome-icon icon=\"search\" />&nbsp;\n        <span v-text=\"$t('instances.gateway.route.predicates')\" />\n      </div>\n      <div class=\"route-content\" v-text=\"route.predicate\" />\n    </div>\n\n    <font-awesome-icon icon=\"angle-double-right\" class=\"route-spacer\" />\n\n    <div v-if=\"route.filters.length > 0\" class=\"route\">\n      <div class=\"route-header\">\n        <font-awesome-icon icon=\"filter\" />&nbsp;\n        <span v-text=\"$t('instances.gateway.route.filters')\" />\n      </div>\n      <div\n        v-for=\"filter in route.filters\"\n        :key=\"filter\"\n        class=\"route-content\"\n        v-text=\"filter\"\n      />\n    </div>\n\n    <font-awesome-icon\n      v-if=\"route.filters.length > 0\"\n      icon=\"angle-double-right\"\n      class=\"route-spacer\"\n    />\n\n    <div class=\"route\">\n      <div class=\"route-header\">\n        <font-awesome-icon icon=\"map-marker-alt\" />&nbsp;\n        <span v-text=\"$t('instances.gateway.route.uri')\" />\n      </div>\n      <div class=\"route-content\" v-text=\"route.uri\" />\n    </div>\n  </div>\n</template>\n\n<script>\nexport default {\n  props: {\n    route: {\n      type: Object,\n      required: true,\n    },\n  },\n  methods: {\n    transformArgs(args) {\n      return Object.entries(args).map(([key, value]) => `${key} : ${value}`);\n    },\n  },\n};\n</script>\n\n<style lang=\"css\">\n.route {\n  display: block;\n  min-width: 12em;\n  max-width: 28em;\n  padding: 0.5em;\n  margin: 1.25em;\n  background-color: #fff;\n  border-radius: 6px;\n  box-shadow:\n    0 2px 3px rgba(0, 0, 0, 0.1),\n    0 0 0 1px rgba(0, 0, 0, 0.1);\n}\n.route-container {\n  display: flex;\n  align-items: center;\n}\n.route-spacer {\n  font-size: 1.25rem;\n  min-width: 1em;\n  max-width: 3.5em;\n}\n.route-header {\n  font-size: 1.25rem;\n}\n.route-content {\n  background-color: #00d1b2;\n  color: #fff;\n  border-radius: 4px;\n  padding: 0.25em;\n  margin: 0.25em;\n}\n.route-category {\n  font-weight: 700;\n  border-bottom: 1px solid #fafafa;\n  display: block;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/gateway/routes-list.vue",
    "content": "<!--\n  - Copyright 2014-2020 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <table class=\"table routes is-fullwidth is-hoverable\">\n    <thead>\n      <tr>\n        <th v-text=\"$t('instances.gateway.route.id')\" />\n        <th v-text=\"$t('instances.gateway.route.order')\" />\n        <th />\n      </tr>\n    </thead>\n    <tbody>\n      <template v-for=\"route in routes\" :key=\"route.route_id\">\n        <tr\n          class=\"is-selectable\"\n          @click=\"\n            showDetails[route.route_id]\n              ? delete showDetails[route.route_id]\n              : (showDetails[route.route_id] = true)\n          \"\n        >\n          <td class=\"is-breakable\" v-text=\"route.route_id\" />\n          <td v-text=\"route.order\" />\n          <td class=\"routes__delete-action\">\n            <sba-confirm-button\n              :class=\"{\n                'is-loading': deleting[route.route_id] === 'executing',\n                'is-danger': deleting[route.route_id] === 'failed',\n                'is-info': deleting[route.route_id] === 'completed',\n              }\"\n              :disabled=\"deleting[route.route_id] === 'executing'\"\n              class=\"button refresh-button is-light\"\n              @click.stop=\"deleteRoute(route.route_id)\"\n            >\n              <span\n                v-if=\"deleting[route.route_id] === 'completed'\"\n                v-text=\"$t('instances.gateway.route.deleted')\"\n              />\n              <span\n                v-else-if=\"deleting[route.route_id] === 'failed'\"\n                v-text=\"$t('instances.gateway.route.delete_failed')\"\n              />\n              <span v-else>\n                <font-awesome-icon icon=\"trash\" />\n                <span v-text=\"$t('instances.gateway.route.delete')\" />\n              </span>\n            </sba-confirm-button>\n          </td>\n        </tr>\n        <tr\n          v-if=\"showDetails[route.route_id]\"\n          :key=\"`${route.route_id}-detail`\"\n        >\n          <td class=\"has-background-white-bis\" colspan=\"3\">\n            <route-definition\n              v-if=\"route.route_definition\"\n              :route-definition=\"route.route_definition\"\n            />\n            <route v-if=\"route.uri\" :route=\"route\" />\n            <pre\n              v-else-if=\"route.route_object\"\n              class=\"is-breakable\"\n              v-text=\"toJson(route.route_object)\"\n            />\n            <span\n              v-else\n              class=\"is-muted\"\n              v-text=\"$t('instances.gateway.route.no_definition_provided')\"\n            />\n          </td>\n        </tr>\n      </template>\n      <tr v-if=\"routes.length === 0\">\n        <td class=\"is-muted\" colspan=\"3\">\n          <p\n            v-if=\"isLoading\"\n            class=\"is-loading\"\n            v-text=\"$t('instances.gateway.route.loading')\"\n          />\n          <p v-else v-text=\"$t('instances.gateway.route.no_routes_found')\" />\n        </td>\n      </tr>\n    </tbody>\n  </table>\n</template>\n<script>\nimport Instance from '@/services/instance';\nimport { from, listen } from '@/utils/rxjs';\nimport Route from '@/views/instances/gateway/route';\nimport RouteDefinition from '@/views/instances/gateway/route-definition';\n\nexport default {\n  components: { RouteDefinition, Route },\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n    routes: {\n      type: Array,\n      required: true,\n    },\n    isLoading: {\n      type: Boolean,\n      default: false,\n    },\n  },\n  emits: ['route-deleted'],\n  data: () => ({\n    showDetails: {},\n    deleting: {},\n  }),\n  methods: {\n    toJson(obj) {\n      return JSON.stringify(obj, null, 4);\n    },\n    deleteRoute(routeId) {\n      from(this.instance.deleteGatewayRoute(routeId))\n        .pipe(listen((status) => (this.deleting[routeId] = status)))\n        .subscribe({\n          complete: () => this.$emit('route-deleted'),\n        });\n    },\n  },\n};\n</script>\n<style lang=\"css\">\n.routes td,\n.routes th {\n  vertical-align: middle;\n}\n\n.routes__delete-action {\n  text-align: right;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/gateway/routes.vue",
    "content": "<!--\n  - Copyright 2014-2019 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div :class=\"{ 'is-loading': isLoading }\">\n    <sba-panel\n      v-if=\"routes\"\n      :header-sticks-below=\"'#subnavigation'\"\n      title=\"Routes\"\n    >\n      <refresh-route-cache\n        :instance=\"instance\"\n        @routes-refreshed=\"fetchRoutes\"\n      />\n\n      <sba-alert v-if=\"error\" :error=\"error\" :title=\"$t('term.fetch_failed')\" />\n\n      <div class=\"field\">\n        <p class=\"control is-expanded has-icons-left\">\n          <input v-model=\"routesFilterCriteria\" class=\"input\" type=\"search\" />\n          <span class=\"icon is-small is-left\">\n            <font-awesome-icon icon=\"filter\" />\n          </span>\n        </p>\n      </div>\n\n      <routes-list\n        :instance=\"instance\"\n        :is-loading=\"isLoading\"\n        :routes=\"routes\"\n        @route-deleted=\"fetchRoutes\"\n      />\n    </sba-panel>\n    <sba-panel title=\"Add Route\">\n      <add-route :instance=\"instance\" @route-added=\"fetchRoutes\" />\n    </sba-panel>\n  </div>\n</template>\n\n<script>\nimport Instance from '@/services/instance';\nimport { anyValueMatches, compareBy } from '@/utils/collections';\nimport addRoute from '@/views/instances/gateway/add-route';\nimport refreshRouteCache from '@/views/instances/gateway/refresh-route-cache';\nimport routesList from '@/views/instances/gateway/routes-list';\n\nconst routeDefinitionMatches = (routeDef, keyword) => {\n  if (!routeDef) {\n    return false;\n  }\n  const predicate = (value) => String(value).toLowerCase().includes(keyword);\n  return (\n    (routeDef.uri && anyValueMatches(routeDef.uri.toString(), predicate)) ||\n    anyValueMatches(routeDef.predicates, predicate) ||\n    anyValueMatches(routeDef.filters, predicate)\n  );\n};\n\nconst routeMatches = (route, keyword) => {\n  return (\n    route.route_id.toString().toLowerCase().includes(keyword) ||\n    routeDefinitionMatches(route.route_definition, keyword)\n  );\n};\n\nconst sortRoutes = (routes) => {\n  return [...routes].sort(compareBy((r) => r.order));\n};\n\nexport default {\n  components: {\n    refreshRouteCache,\n    routesList,\n    addRoute,\n  },\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  data: () => ({\n    isLoading: false,\n    error: null,\n    $routes: [],\n    routesFilterCriteria: null,\n  }),\n  computed: {\n    routes() {\n      if (!this.routesFilterCriteria) {\n        return sortRoutes(this.$data.$routes);\n      }\n      const filtered = this.$data.$routes.filter((route) =>\n        routeMatches(route, this.routesFilterCriteria.toLowerCase()),\n      );\n      return sortRoutes(filtered);\n    },\n  },\n  created() {\n    this.fetchRoutes();\n  },\n  methods: {\n    async fetchRoutes() {\n      this.error = null;\n      this.isLoading = true;\n      try {\n        const response = await this.instance.fetchGatewayRoutes();\n        this.$data.$routes = response.data;\n      } catch (error) {\n        console.warn('Fetching routes failed:', error);\n        this.error = error;\n      }\n      this.isLoading = false;\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/heapdump/i18n.de.json",
    "content": "{\n  \"instances\": {\n    \"heapdump\": {\n      \"label\": \"Heap-Dump\",\n      \"download\": \"Heap-Dump herunterladen\",\n      \"warn_dump_expensive\": \"Das Erzeugen eines Heap-Dumps kann die CPU und Festplatte stark belasten.\",\n      \"warn_sensitive_data\": \"Ein Heap-Dump kann sensible Daten enthalten. Bitte sorgsam behandeln.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/heapdump/i18n.en.json",
    "content": "{\n  \"instances\": {\n    \"heapdump\": {\n      \"label\": \"Heap Dump\",\n      \"download\": \"Download Heap Dump\",\n      \"warn_dump_expensive\": \"Dumping the heap may be expensive in terms of cpu and disk space.\",\n      \"warn_sensitive_data\": \"A heap dump may contain sensitive data. Please handle with care.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/heapdump/i18n.es.json",
    "content": "{\n  \"instances\": {\n    \"heapdump\": {\n      \"label\": \"Heap Dump\",\n      \"download\": \"Descargar Heap Dump\",\n      \"warn_dump_expensive\": \"Volcar el heap puede ser costoso en términos de cpu y espacio de disco.\",\n      \"warn_sensitive_data\": \"Un heap dump puede contener información sensible. Por favor sea cuidadoso.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/heapdump/i18n.fr.json",
    "content": "{\n  \"instances\": {\n    \"heapdump\": {\n      \"label\": \"Heap Dump\",\n      \"download\": \"Télécharger le Heap Dump\",\n      \"warn_dump_expensive\": \"Cette opération peut être coûteuse en terme de CPU et d'espace disque.\",\n      \"warn_sensitive_data\": \"Un heap dump peut contenir des informations sensibles. Veuillez manipuler ces données avec précautions.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/heapdump/i18n.is.json",
    "content": "{\n  \"instances\": {\n    \"heapdump\": {\n      \"label\": \"Heap Dump\",\n      \"download\": \"Hala Heap Dump niður\",\n      \"warn_dump_expensive\": \"þetta ferli getur verið dýrt með tilliti til CPU og geymslurýma.\",\n      \"warn_sensitive_data\": \"Heap dump má innihalda gögn sem þurfa að vörn. Fjallið varlega.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/heapdump/i18n.ko.json",
    "content": "{\n  \"instances\": {\n    \"heapdump\": {\n      \"label\": \"힙 덤프\",\n      \"download\": \"힙 덤프 다운로드\",\n      \"warn_dump_expensive\": \"힙 덤프는 CPU와 디스크 공간 측면에서 비용이 많이 들 수 있습니다.\",\n      \"warn_sensitive_data\": \"힙 덤프에는 민감한 데이터가 포함될 가능성이 있습니다. 주의해서 취급하시기 바랍니다.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/heapdump/i18n.pt-BR.json",
    "content": "{\n  \"instances\": {\n    \"heapdump\": {\n      \"label\": \"Despejo de Pilha\",\n      \"download\": \"Download de Despejo de Pilha\",\n      \"warn_dump_expensive\": \"Descarregar o heap pode ser caro em termos de CPU e espaço em disco.\",\n      \"warn_sensitive_data\": \"Um descarregamento de heap pode conter  dados sensíveis . Por favor, manuseie com cuidado.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/heapdump/i18n.ru.json",
    "content": "{\n  \"instances\": {\n    \"heapdump\": {\n      \"label\": \"Дамп кучи\",\n      \"download\": \"Загрузить дамп кучи\",\n      \"warn_dump_expensive\": \"Снятие дампа кучи может быть затратен с точки зрения ЦПУ и дискового пространства.\",\n      \"warn_sensitive_data\": \"Дамп кучи может содержать конфиденциальную информацию. Пожалуйста, будьте осторожны.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/heapdump/i18n.zh-CN.json",
    "content": "{\n  \"instances\": {\n    \"heapdump\": {\n      \"label\": \"内存转储\",\n      \"download\": \"下载内存转储文件\",\n      \"warn_dump_expensive\": \"从CPU和磁盘空间来看，内存转储文件可能很大。\",\n      \"warn_sensitive_data\": \"内存转储文件可能包含敏感数据。请小心处理。\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/heapdump/i18n.zh-TW.json",
    "content": "{\n  \"instances\": {\n    \"heapdump\": {\n      \"label\": \"堆積傾印\",\n      \"download\": \"下載堆積傾印\",\n      \"warn_dump_expensive\": \"進行堆積傾印可能會消耗大量 CPU 和磁碟空間。\",\n      \"warn_sensitive_data\": \"堆積傾印可能包含敏感資料，請謹慎處理。\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/heapdump/index.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-instance-section>\n    <template #before>\n      <sba-sticky-subnav>\n        <div class=\"text-right\">\n          <sba-button @click=\"downloadHeap()\">\n            <font-awesome-icon icon=\"download\" />&nbsp;\n            <span v-text=\"$t('instances.heapdump.download')\" />\n          </sba-button>\n        </div>\n      </sba-sticky-subnav>\n    </template>\n    <div\n      class=\"m-auto flex relative items-center gap-2 py-3 pl-4 pr-10 leading-normal text-red-700 bg-red-100 rounded-lg shadow mb-3 backdrop-filter backdrop-blur-sm bg-opacity-80\"\n      role=\"alert\"\n    >\n      <div>\n        <svg\n          class=\"h-12\"\n          fill=\"none\"\n          stroke=\"currentColor\"\n          stroke-width=\"2\"\n          viewBox=\"0 0 24 24\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n        >\n          <path\n            d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n            stroke-linecap=\"round\"\n            stroke-linejoin=\"round\"\n          />\n        </svg>\n      </div>\n      <div class=\"flex-1\">\n        <p v-text=\"$t('instances.heapdump.warn_sensitive_data')\" />\n        <p v-text=\"$t('instances.heapdump.warn_dump_expensive')\" />\n      </div>\n    </div>\n  </sba-instance-section>\n</template>\n\n<script>\nimport Instance from '@/services/instance';\nimport { VIEW_GROUP } from '@/views/ViewGroup';\nimport SbaInstanceSection from '@/views/instances/shell/sba-instance-section';\n\nexport default {\n  components: { SbaInstanceSection },\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  methods: {\n    downloadHeap() {\n      window.open(`instances/${this.instance.id}/actuator/heapdump`, '_blank');\n    },\n  },\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      name: 'instances/heapdump',\n      parent: 'instances',\n      path: 'heapdump',\n      component: this,\n      label: 'instances.heapdump.label',\n      group: VIEW_GROUP.JVM,\n      order: 800,\n      isEnabled: ({ instance }) => instance.hasEndpoint('heapdump'),\n    });\n  },\n};\n</script>\n\n<style lang=\"css\">\n.heapdump {\n  display: flex;\n  justify-content: space-around;\n}\n.heapdump > div {\n  display: flex;\n  flex-direction: column;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httpexchanges/Exchange.ts",
    "content": "import { mapKeys } from 'lodash-es';\nimport moment from 'moment/moment';\n\nconst normalize = (obj) => mapKeys(obj, (value, key) => key.toLowerCase());\n\nexport class Exchange {\n  readonly timestamp: moment.Moment;\n  readonly request: {\n    uri: string;\n    method: string;\n    headers: { [key: string]: string[] };\n  };\n  readonly response: {\n    status?: number;\n    headers?: { [key: string]: string[] };\n  } | null;\n\n  readonly timeTaken?: string;\n\n  constructor({ timestamp, request, response, ...exchange }) {\n    Object.assign(this, exchange);\n    this.timestamp = moment(timestamp);\n    this.request = { ...request, headers: normalize(request.headers) };\n    this.response = response\n      ? { ...response, headers: normalize(response.headers) }\n      : null;\n  }\n\n  get key() {\n    return `${this.timestamp.valueOf()}-${this.request.method}-${\n      this.request.uri\n    }`;\n  }\n\n  get contentLengthResponse() {\n    return this.extractContentLength(this.response);\n  }\n\n  get contentLengthRequest() {\n    return this.extractContentLength(this.request);\n  }\n\n  get contentTypeResponse() {\n    return this.extractContentType(this.response);\n  }\n\n  get contentTypeRequest() {\n    return this.extractContentType(this.request);\n  }\n\n  extractContentLength(origin) {\n    const contentLength =\n      origin &&\n      origin.headers['content-length'] &&\n      origin.headers['content-length'][0];\n    if (contentLength && /^\\d+$/.test(contentLength)) {\n      return parseInt(contentLength);\n    }\n    return null;\n  }\n\n  extractContentType(origin) {\n    const contentType =\n      origin &&\n      origin.headers['content-type'] &&\n      origin.headers['content-type'][0];\n    if (contentType) {\n      const idx = contentType.indexOf(';');\n      return idx >= 0 ? contentType.substring(0, idx) : contentType;\n    }\n    return null;\n  }\n\n  compareTo(other: Exchange) {\n    return this.timestamp.isAfter(other.timestamp);\n  }\n\n  isPending() {\n    return !this.response;\n  }\n\n  isSuccess() {\n    return this.response && this.response.status <= 399;\n  }\n\n  isClientError() {\n    return (\n      this.response &&\n      this.response.status >= 400 &&\n      this.response.status <= 499\n    );\n  }\n\n  isServerError() {\n    return (\n      this.response &&\n      this.response.status >= 500 &&\n      this.response.status <= 599\n    );\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httpexchanges/content-column.vue",
    "content": "<script setup lang=\"ts\">\nimport { faArrowDown, faArrowUp } from '@fortawesome/free-solid-svg-icons';\nimport { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';\nimport prettyBytes from 'pretty-bytes';\nimport { computed } from 'vue';\n\nimport { Exchange } from '@/views/instances/httpexchanges/Exchange';\n\nconst { data } = defineProps<{\n  data: Exchange;\n}>();\n\nconst hasRequestData = computed(() => {\n  return data.contentTypeRequest || data.contentLengthRequest;\n});\n\nconst hasResponseData = computed(() => {\n  return data.contentTypeResponse || data.contentLengthResponse;\n});\n</script>\n\n<template>\n  <div v-if=\"hasRequestData\" class=\"request flex items-center gap-1\">\n    <font-awesome-icon :icon=\"faArrowUp\" class=\"text-xs text-gray-500\" />\n    <span>{{ data.contentTypeRequest }}</span>\n    <span v-if=\"data.contentLengthRequest\" class=\"text-xs text-gray-500\">\n      ({{ prettyBytes(data.contentLengthRequest) }})\n    </span>\n  </div>\n  <div v-if=\"hasResponseData\" class=\"response flex items-center gap-1\">\n    <font-awesome-icon :icon=\"faArrowDown\" class=\"text-xs text-gray-500\" />\n    <span>{{ data.contentTypeResponse }}</span>\n    <span v-if=\"data.contentLengthResponse\" class=\"text-xs text-gray-500\">\n      ({{ prettyBytes(data.contentLengthResponse) }})\n    </span>\n  </div>\n</template>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httpexchanges/exchanges-chart.vue",
    "content": "<!--\n  - Copyright 2014-2020 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div class=\"exchange-chart\">\n    <canvas ref=\"chartCanvas\" />\n  </div>\n</template>\n\n<script>\nimport {\n  BarController,\n  BarElement,\n  CategoryScale,\n  Chart,\n  LinearScale,\n  TimeScale,\n  Tooltip,\n} from 'chart.js';\nimport 'chartjs-adapter-moment';\nimport { parse } from 'iso8601-duration';\nimport { markRaw } from 'vue';\n\nimport { toMilliseconds } from '@/utils/iso8601-duration';\n\nChart.register(\n  BarController,\n  BarElement,\n  CategoryScale,\n  LinearScale,\n  TimeScale,\n  Tooltip,\n);\n\nconst interval = 1000;\n\nexport default {\n  props: {\n    exchanges: {\n      type: Array,\n      default: () => [],\n    },\n  },\n  data: () => ({\n    chart: null,\n    cachedChartData: [],\n  }),\n  computed: {\n    chartData() {\n      if (this.exchanges.length <= 0) {\n        return [];\n      }\n      const chartData = [];\n      let idx = this.exchanges.length - 1;\n      const oldest =\n        this.exchanges[this.exchanges.length - 1].timestamp.valueOf();\n      const newest = this.exchanges[0].timestamp.valueOf();\n\n      for (\n        let time = Math.floor(oldest.valueOf() / interval) * interval;\n        time <= newest;\n        time += interval\n      ) {\n        const bucket = {\n          timeStart: time,\n          timeEnd: time + interval,\n          totalCount: 0,\n          totalSuccess: 0,\n          totalClientErrors: 0,\n          totalServerErrors: 0,\n          totalTime: 0,\n          maxTime: 0,\n        };\n\n        while (\n          idx >= 0 &&\n          this.exchanges[idx].timestamp.valueOf() < time + interval\n        ) {\n          const exchange = this.exchanges[idx];\n          bucket.totalCount++;\n          if (exchange.isSuccess()) {\n            bucket.totalSuccess++;\n          } else if (exchange.isClientError()) {\n            bucket.totalClientErrors++;\n          } else if (exchange.isServerError()) {\n            bucket.totalServerErrors++;\n          }\n          if (exchange.timeTaken) {\n            let timeTakenInMillis = toMilliseconds(parse(exchange.timeTaken));\n            bucket.totalTime += timeTakenInMillis;\n            bucket.maxTime = Math.max(bucket.maxTime, timeTakenInMillis);\n          }\n          idx--;\n        }\n        chartData.push(bucket);\n      }\n\n      return chartData;\n    },\n  },\n  watch: {\n    chartData: {\n      handler(newData) {\n        this.cachedChartData = JSON.parse(JSON.stringify(newData));\n        this.updateChart();\n      },\n      deep: true,\n    },\n  },\n  mounted() {\n    this.cachedChartData = JSON.parse(JSON.stringify(this.chartData));\n    this.initChart();\n  },\n  beforeUnmount() {\n    if (this.chart) {\n      this.chart.destroy();\n      this.chart = null;\n    }\n  },\n  methods: {\n    initChart() {\n      const ctx = this.$refs.chartCanvas.getContext('2d');\n      const chartDataRef = this.cachedChartData;\n\n      this.chart = markRaw(\n        new Chart(ctx, {\n          type: 'bar',\n          data: {\n            labels: [],\n            datasets: [\n              {\n                label: this.$t(\n                  'instances.httpexchanges.chart.successful_requests',\n                ),\n                data: [],\n                backgroundColor: 'rgba(72, 199, 142, 0.8)',\n                stack: 'stack0',\n              },\n              {\n                label: this.$t('instances.httpexchanges.chart.status_4xx'),\n                data: [],\n                backgroundColor: 'rgba(255, 224, 138, 0.8)',\n                stack: 'stack0',\n              },\n              {\n                label: this.$t('instances.httpexchanges.chart.status_5xx'),\n                data: [],\n                backgroundColor: 'rgba(241, 70, 104, 0.8)',\n                stack: 'stack0',\n              },\n            ],\n          },\n          options: {\n            responsive: true,\n            maintainAspectRatio: false,\n            scales: {\n              x: {\n                type: 'time',\n                time: {\n                  unit: 'second',\n                  displayFormats: {\n                    second: 'HH:mm:ss',\n                  },\n                },\n                grid: {\n                  display: false,\n                },\n              },\n              y: {\n                stacked: true,\n                beginAtZero: true,\n                grid: {\n                  color: 'rgba(181, 181, 181, 0.2)',\n                  drawBorder: false,\n                },\n                ticks: {\n                  precision: 0,\n                },\n              },\n            },\n            plugins: {\n              tooltip: {\n                mode: 'index',\n                intersect: false,\n                callbacks: {\n                  footer: (tooltipItems) => {\n                    const index = tooltipItems[0].dataIndex;\n                    const bucket = chartDataRef[index];\n                    if (!bucket) return '';\n\n                    const avgTime =\n                      bucket.totalCount > 0\n                        ? Math.floor(bucket.totalTime / bucket.totalCount)\n                        : 0;\n\n                    return [\n                      `${this.$t('instances.httpexchanges.chart.total_requests')}: ${bucket.totalCount}`,\n                      `${this.$t('instances.httpexchanges.chart.max_time')}: ${bucket.maxTime.toFixed(0)}ms`,\n                      `${this.$t('instances.httpexchanges.chart.avg_time')}: ${avgTime.toFixed(0)}ms`,\n                    ];\n                  },\n                },\n              },\n            },\n          },\n        }),\n      );\n\n      this.updateChart();\n    },\n    updateChart() {\n      if (!this.chart || !this.cachedChartData) return;\n\n      const data = this.cachedChartData;\n      const labels = data.map((d) => d.timeStart);\n      const successData = data.map((d) => d.totalSuccess);\n      const clientErrorData = data.map((d) => d.totalClientErrors);\n      const serverErrorData = data.map((d) => d.totalServerErrors);\n\n      this.chart.data.labels = labels;\n      this.chart.data.datasets[0].data = successData;\n      this.chart.data.datasets[1].data = clientErrorData;\n      this.chart.data.datasets[2].data = serverErrorData;\n\n      this.chart.update('none');\n    },\n  },\n};\n</script>\n\n<style lang=\"css\">\n.exchange-chart {\n  height: 200px;\n  width: 100%;\n}\n\n.exchange-chart canvas {\n  max-height: 200px;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httpexchanges/exchanges-list.vue",
    "content": "<!--\n  - Copyright 2014-2020 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <DataTable\n    v-model:expanded-rows=\"expandedRows\"\n    striped-rows\n    data-key=\"key\"\n    size=\"small\"\n    :value=\"exchanges\"\n    paginator\n    :rows=\"50\"\n    :rows-per-page-options=\"[5, 10, 20, 50, 100, 250, 500]\"\n  >\n    <Column expander style=\"width: 2rem\" />\n    <template #expansion=\"slotProps\">\n      <code\n        class=\"whitespace-pre-wrap text-sm\"\n        v-text=\"JSON.stringify(slotProps.data, null, 2)\"\n      />\n    </template>\n\n    <Column field=\"timestamp\" :header=\"$t('instances.httpexchanges.timestamp')\">\n      <template #body=\"{ data }\">\n        <span class=\"whitespace-nowrap\">\n          {{ formatDateTime(data.timestamp) }}\n        </span>\n      </template>\n    </Column>\n\n    <Column\n      field=\"request.uri\"\n      :header=\"$t('instances.httpexchanges.uri_parts')\"\n    >\n      <template #body=\"{ data }\">\n        <div>\n          <div class=\"whitespace-nowrap\">\n            {{ getLastPathSegment(data.request.uri) }}\n          </div>\n          <div class=\"text-xs text-gray-500 whitespace-nowrap\">\n            {{ getPrecedingPath(data.request.uri) }}\n          </div>\n        </div>\n      </template>\n    </Column>\n    <Column\n      field=\"request.uri\"\n      style=\"max-width: 200px\"\n      :header=\"$t('instances.httpexchanges.uri')\"\n    >\n      <template #body=\"{ data }\">\n        <div\n          v-tooltip.top=\"data.request.uri\"\n          class=\"overflow-hidden text-ellipsis whitespace-nowrap text-left\"\n          style=\"direction: rtl\"\n        >\n          {{ data.request.uri }}\n        </div>\n      </template>\n    </Column>\n    <Column\n      field=\"request.method\"\n      :header=\"$t('instances.httpexchanges.method')\"\n    />\n    <Column field=\"status\" :header=\"$t('instances.httpexchanges.status')\">\n      <template #body=\"{ data }\">\n        <code>{{ data.response.status }}</code>\n        <div class=\"text-xs text-gray-500 whitespace-nowrap\">\n          {{ getHttpStatusText(data.response.status) }}\n        </div>\n      </template>\n    </Column>\n    <Column\n      class=\"whitespace-nowrap\"\n      :header=\"$t('instances.httpexchanges.content')\"\n    >\n      <template #body=\"{ data }\">\n        <ContentColumn :data=\"data\" />\n      </template>\n    </Column>\n\n    <Column field=\"time\" :header=\"$t('instances.httpexchanges.time')\">\n      <template #body=\"{ data }\">\n        {{\n          data.timeTaken\n            ? `${toMilliseconds(parse(data.timeTaken)).toFixed(0)} ms`\n            : ''\n        }}\n      </template>\n    </Column>\n\n    <template #empty>\n      <div class=\"text-center\" v-text=\"$t('instances.httpexchanges.no_data')\" />\n    </template>\n  </DataTable>\n</template>\n\n<script setup lang=\"ts\">\nimport { parse } from 'iso8601-duration';\nimport { Column, DataTable } from 'primevue';\nimport { ref } from 'vue';\n\nimport ContentColumn from './content-column.vue';\n\nimport { useDateTimeFormatter } from '@/composables/useDateTimeFormatter';\nimport { getHttpStatusText } from '@/utils/http-status';\nimport { toMilliseconds } from '@/utils/iso8601-duration';\nimport { Exchange } from '@/views/instances/httpexchanges/Exchange';\n\nconst { formatDateTime } = useDateTimeFormatter();\n\ntype Props = {\n  exchanges?: Array<Exchange>;\n};\n\nwithDefaults(defineProps<Props>(), {\n  exchanges: () => [],\n});\n\nconst expandedRows = ref([]);\n\nconst getLastPathSegment = (uri: string): string => {\n  try {\n    const url = new URL(uri);\n    const pathSegments = url.pathname.split('/').filter((segment) => segment);\n    return pathSegments.length > 0\n      ? pathSegments[pathSegments.length - 1]\n      : '/';\n  } catch {\n    // If URL parsing fails, try to extract from path directly\n    const pathSegments = uri.split('/').filter((segment) => segment);\n    return pathSegments.length > 0\n      ? pathSegments[pathSegments.length - 1]\n      : '/';\n  }\n};\n\nconst getPrecedingPath = (uri: string): string => {\n  try {\n    const url = new URL(uri);\n    const pathSegments = url.pathname.split('/').filter((segment) => segment);\n    if (pathSegments.length <= 1) return '';\n    return '/' + pathSegments.slice(0, -1).join('/');\n  } catch {\n    // If URL parsing fails, try to extract from path directly\n    const pathSegments = uri.split('/').filter((segment) => segment);\n    if (pathSegments.length <= 1) return '';\n    return '/' + pathSegments.slice(0, -1).join('/');\n  }\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httpexchanges/i18n.de.json",
    "content": "{\n  \"instances\": {\n    \"httpexchanges\": {\n      \"label\": \"HTTP-Exchanges\",\n      \"chart\": {\n        \"avg_time\": \"ø Dauer\",\n        \"max_time\": \"Max. Dauer\",\n        \"status_4xx\": \"Status 4xx\",\n        \"status_5xx\": \"Status 5xx\",\n        \"successful_requests\": \"Erfolgreiche Anfragen\",\n        \"total_requests\": \"Anfragen insgesamt\"\n      },\n      \"limit\": \"Limit\",\n      \"filter\": {\n        \"client_errors\": \"Client-Fehler\",\n        \"exclude_actuator\": \"Ignoriere {actuator}/**\",\n        \"server_errors\": \"Server-Fehler\",\n        \"success\": \"Erfolgreich\"\n      },\n      \"method\": \"Methode\",\n      \"no_data\": \"Keine Anzeigedaten vorhanden.\",\n      \"time\": \"Dauer\",\n      \"timestamp\": \"Zeitstempel\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httpexchanges/i18n.en.json",
    "content": "{\n  \"instances\": {\n    \"httpexchanges\": {\n      \"auto_follow\": \"Auto update\",\n      \"label\": \"HTTP Exchanges\",\n      \"chart\": {\n        \"avg_time\": \"ø duration\",\n        \"max_time\": \"max duration\",\n        \"status_4xx\": \"status 4xx\",\n        \"status_5xx\": \"status 5xx\",\n        \"successful_requests\": \"successful requests\",\n        \"total_requests\": \"total requests\"\n      },\n      \"content\": \"Content-Type / Size\",\n      \"limit\": \"limit\",\n      \"filter\": {\n        \"client_errors\": \"client errors\",\n        \"exclude_actuator\": \"exclude {actuator}/**\",\n        \"server_errors\": \"server errors\",\n        \"success\": \"success\"\n      },\n      \"method\": \"Method\",\n      \"no_data\": \"There is no data to display.\",\n      \"status\": \"Status\",\n      \"time\": \"Time\",\n      \"timestamp\": \"Timestamp\",\n      \"uri\": \"Url\",\n      \"uri_parts\": \"Name\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httpexchanges/i18n.es.json",
    "content": "{\n  \"instances\": {\n    \"httpexchanges\": {\n      \"chart\": {\n        \"avg_time\": \"ø duración\",\n        \"max_time\": \"duración máxima\",\n        \"status_4xx\": \"estado 4xx\",\n        \"status_5xx\": \"estado 5xx\",\n        \"successful_requests\": \"solicitudes exitosas\",\n        \"total_requests\": \"solicitudes totales\"\n      },\n      \"limit\": \"límite\",\n      \"filter\": {\n        \"client_errors\": \"Errores de clientes\",\n        \"exclude_actuator\": \"excluir {actuator}/**\",\n        \"server_errors\": \"errores de servidor\",\n        \"success\": \"éxito\"\n      },\n      \"method\": \"Método\",\n      \"status\": \"Estado\",\n      \"time\": \"Duración\",\n      \"timestamp\": \"Horario\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httpexchanges/i18n.fr.json",
    "content": "{\n  \"instances\": {\n    \"httpexchanges\": {\n      \"label\": \"Exchanges HTTP\",\n      \"chart\": {\n        \"avg_time\": \"ø duration\",\n        \"max_time\": \"durée max\",\n        \"status_4xx\": \"statut 4xx\",\n        \"status_5xx\": \"statut 5xx\",\n        \"successful_requests\": \"requêtes en succès\",\n        \"total_requests\": \"requêtes au total\"\n      },\n      \"limit\": \"limite\",\n      \"filter\": {\n        \"client_errors\": \"Erreurs clientes\",\n        \"exclude_actuator\": \"exclure {actuator}/**\",\n        \"server_errors\": \"Erreurs serveurs\",\n        \"success\": \"succès\"\n      },\n      \"method\": \"Methodes\",\n      \"status\": \"Statut\",\n      \"time\": \"Temps\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httpexchanges/i18n.is.json",
    "content": "{\n  \"instances\": {\n    \"httpexchanges\": {\n      \"label\": \"HTTP-Exchanges\",\n      \"chart\": {\n        \"avg_time\": \"ø tímalengd\",\n        \"max_time\": \"Hámarkstímalengd\",\n        \"status_4xx\": \"Staða 4xx\",\n        \"status_5xx\": \"Staða 5xx\",\n        \"successful_requests\": \"Farsælar fyrirspurnir\",\n        \"total_requests\": \"Samtals fyrirspurnir\"\n      },\n      \"limit\": \"Takmörkun\",\n      \"filter\": {\n        \"client_errors\": \"Vandamál biðlara\",\n        \"exclude_actuator\": \"hunsa {actuator}/**\",\n        \"server_errors\": \"Vandamál þjóns\",\n        \"success\": \"farsælt\"\n      },\n      \"method\": \"Aðferð\",\n      \"status\": \"Staða\",\n      \"time\": \"Tímalengd\",\n      \"timestamp\": \"Tímapunktur\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httpexchanges/i18n.ko.json",
    "content": "{\n  \"instances\": {\n    \"httpexchanges\": {\n      \"chart\": {\n        \"avg_time\": \"ø 기간\",\n        \"max_time\": \"최대 기간\",\n        \"status_4xx\": \"4xx 상태\",\n        \"status_5xx\": \"5xx 상태\",\n        \"successful_requests\": \"성공적인 요청\",\n        \"total_requests\": \"전체 요청\"\n      },\n      \"limit\": \"제한\",\n      \"filter\": {\n        \"client_errors\": \"클라이언트 에러\",\n        \"exclude_actuator\": \"제외 {actuator}/**\",\n        \"server_errors\": \"서버 에러\",\n        \"success\": \"성공\"\n      },\n      \"status\": \"상태\",\n      \"time\": \"시간\",\n      \"timestamp\": \"타임스탬프\",\n      \"uri\": \"경로\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httpexchanges/i18n.pt-BR.json",
    "content": "{\n  \"instances\": {\n    \"httpexchanges\": {\n      \"chart\": {\n        \"avg_time\": \"ø duração\",\n        \"max_time\": \"duração máxima\",\n        \"status_4xx\": \"status 4xx\",\n        \"status_5xx\": \"status 5xx\",\n        \"successful_requests\": \"Requisições sucedidas\",\n        \"total_requests\": \"total de requisições\"\n      },\n      \"limit\": \"limite\",\n      \"filter\": {\n        \"client_errors\": \"erros de cliente\",\n        \"exclude_actuator\": \"excluir {actuator}/**\",\n        \"server_errors\": \"erros do servidor\",\n        \"success\": \"sucesso\"\n      },\n      \"method\": \"Metodo\",\n      \"time\": \"Tempo\",\n      \"uri\": \"Caminho\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httpexchanges/i18n.ru.json",
    "content": "{\n  \"instances\": {\n    \"httpexchanges\": {\n      \"chart\": {\n        \"avg_time\": \"ø задержка\",\n        \"max_time\": \"максимальная задержка\",\n        \"status_4xx\": \"статус 4xx\",\n        \"status_5xx\": \"статус 5xx\",\n        \"successful_requests\": \"успешные запросы\",\n        \"total_requests\": \"всего запросов\"\n      },\n      \"limit\": \"ограничение\",\n      \"filter\": {\n        \"client_errors\": \"ошибки на клиенте\",\n        \"exclude_actuator\": \"исключить {actuator}/**\",\n        \"server_errors\": \"ошибки сервера\",\n        \"success\": \"успех\"\n      },\n      \"method\": \"Метод\",\n      \"status\": \"Статус\",\n      \"time\": \"Время\",\n      \"timestamp\": \"Время\",\n      \"uri\": \"Путь\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httpexchanges/i18n.zh-CN.json",
    "content": "{\n  \"instances\": {\n    \"httpexchanges\": {\n      \"chart\": {\n        \"avg_time\": \"平均时长\",\n        \"max_time\": \"最大时长\",\n        \"status_4xx\": \"4xx失败总数\",\n        \"status_5xx\": \"5xx失败总数\",\n        \"successful_requests\": \"请求成功\",\n        \"total_requests\": \"请求总数\"\n      },\n      \"filter\": {\n        \"client_errors\": \"客户端错误\",\n        \"exclude_actuator\": \"排除 {actuator}/**\",\n        \"server_errors\": \"服务器错误\",\n        \"success\": \"请求成功\"\n      },\n      \"method\": \"方法\",\n      \"status\": \"状态\",\n      \"time\": \"时长\",\n      \"timestamp\": \"时间戳\",\n      \"uri\": \"路径\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httpexchanges/i18n.zh-TW.json",
    "content": "{\n  \"instances\": {\n    \"httpexchanges\": {\n      \"label\": \"HTTP 交換\",\n      \"chart\": {\n        \"avg_time\": \"平均時長\",\n        \"max_time\": \"最大時長\",\n        \"status_4xx\": \"4xx 狀態碼\",\n        \"status_5xx\": \"5xx 狀態碼\",\n        \"successful_requests\": \"成功請求數\",\n        \"total_requests\": \"總請求數\"\n      },\n      \"limit\": \"限制\",\n      \"filter\": {\n        \"client_errors\": \"用戶端錯誤\",\n        \"exclude_actuator\": \"排除 {actuator}/**\",\n        \"server_errors\": \"伺服器錯誤\",\n        \"success\": \"成功\"\n      },\n      \"method\": \"方法\",\n      \"status\": \"狀態\",\n      \"time\": \"時長\",\n      \"timestamp\": \"時間戳記\",\n      \"uri\": \"路徑\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httpexchanges/index.spec.ts",
    "content": "import userEvent from '@testing-library/user-event';\nimport { screen, waitFor } from '@testing-library/vue';\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport Instance from '@/services/instance';\nimport { render } from '@/test-utils';\nimport { Exchange } from '@/views/instances/httpexchanges/Exchange';\nimport HttpExchanges from '@/views/instances/httpexchanges/index.vue';\n\ndescribe('HttpExchanges - excludeActuator', () => {\n  const createExchange = (uri: string, status: number = 200) => {\n    return new Exchange({\n      timestamp: new Date().toISOString(),\n      request: {\n        uri,\n        method: 'GET',\n        headers: {},\n      },\n      response: {\n        status,\n        headers: {},\n      },\n    });\n  };\n\n  const createInstance = (\n    fetchHttpExchanges,\n    serviceUrl = 'http://localhost:8080',\n    managementUrl = 'http://localhost:8080/actuator',\n  ) => {\n    const instance = new Instance({\n      id: '4711',\n      registration: {\n        serviceUrl,\n        managementUrl,\n      },\n    });\n    instance.fetchHttpExchanges = fetchHttpExchanges;\n    instance.hasEndpoint = vi.fn().mockReturnValue(true);\n    return instance;\n  };\n\n  describe('when actuator path is present', () => {\n    const exchanges = [\n      createExchange('http://127.0.0.1:8080/api/users'),\n      createExchange('http://127.0.0.1:8080/actuator/health'),\n      createExchange('http://127.0.0.1:8080/MyTestSubPath/actuator/health'),\n      createExchange('http://127.0.0.1:8080/actuator/metrics'),\n      createExchange('http://127.0.0.1:8080/api/products'),\n      createExchange('http://127.0.0.1:8080/actuator/info'),\n    ];\n\n    const fetchHttpExchanges = vi.fn().mockResolvedValue({\n      data: {\n        exchanges: exchanges.map((e) => ({\n          timestamp: e.timestamp.toISOString(),\n          request: e.request,\n          response: e.response,\n        })),\n      },\n    });\n\n    beforeEach(async () => {\n      fetchHttpExchanges.mockClear();\n    });\n\n    it('excludes actuator exchanges by default', async () => {\n      render(HttpExchanges, {\n        props: {\n          instance: createInstance(fetchHttpExchanges),\n        },\n      });\n\n      await waitFor(() => expect(fetchHttpExchanges).toHaveBeenCalled());\n\n      // Wait for exchanges to be loaded and verify only non-actuator URIs are visible\n      await waitFor(() => {\n        // Should show non-actuator endpoints\n        expect(\n          screen.getByText('http://127.0.0.1:8080/api/users'),\n        ).toBeInTheDocument();\n        expect(\n          screen.getByText('http://127.0.0.1:8080/api/products'),\n        ).toBeInTheDocument();\n\n        // Should NOT show actuator endpoints\n        expect(\n          screen.queryByText('http://127.0.0.1:8080/actuator/health'),\n        ).not.toBeInTheDocument();\n        expect(\n          screen.queryByText(\n            'http://127.0.0.1:8080/MyTestSubPath/actuator/health',\n          ),\n        ).not.toBeInTheDocument();\n        expect(\n          screen.queryByText('http://127.0.0.1:8080/actuator/metrics'),\n        ).not.toBeInTheDocument();\n        expect(\n          screen.queryByText('http://127.0.0.1:8080/actuator/info'),\n        ).not.toBeInTheDocument();\n      });\n    });\n\n    it('includes actuator exchanges when filter is disabled', async () => {\n      render(HttpExchanges, {\n        props: {\n          instance: createInstance(fetchHttpExchanges),\n        },\n      });\n\n      await waitFor(() => expect(fetchHttpExchanges).toHaveBeenCalled());\n\n      // Verify initially actuator endpoints are hidden\n      await waitFor(() => {\n        expect(\n          screen.queryByText('http://127.0.0.1:8080/actuator/health'),\n        ).not.toBeInTheDocument();\n      });\n\n      // Find and uncheck the exclude actuator checkbox\n      const excludeActuatorCheckbox = await screen.findByLabelText(\n        'instances.httpexchanges.filter.exclude_actuator',\n      );\n      await userEvent.click(excludeActuatorCheckbox);\n\n      // Wait for all exchanges to be shown including actuator endpoints\n      await waitFor(() => {\n        expect(\n          screen.getByText('http://127.0.0.1:8080/api/users'),\n        ).toBeInTheDocument();\n        expect(\n          screen.getByText('http://127.0.0.1:8080/api/products'),\n        ).toBeInTheDocument();\n        expect(\n          screen.getByText('http://127.0.0.1:8080/actuator/health'),\n        ).toBeInTheDocument();\n        expect(\n          screen.getByText('http://127.0.0.1:8080/actuator/metrics'),\n        ).toBeInTheDocument();\n        expect(\n          screen.getByText('http://127.0.0.1:8080/actuator/info'),\n        ).toBeInTheDocument();\n      });\n    });\n\n    it('calculates actuator path from managementUrl and serviceUrl', async () => {\n      render(HttpExchanges, {\n        props: {\n          instance: createInstance(\n            fetchHttpExchanges,\n            'http://localhost:8080',\n            'http://localhost:8080/actuator',\n          ),\n        },\n      });\n\n      await waitFor(() => expect(fetchHttpExchanges).toHaveBeenCalled());\n\n      // Check that the checkbox exists\n      const checkbox = await screen.findByLabelText(\n        'instances.httpexchanges.filter.exclude_actuator',\n      );\n      expect(checkbox).toBeDefined();\n\n      // Verify the component calculated actuatorPath as '/actuator'\n      // by checking that it filters URIs containing '/actuator'\n      await waitFor(() => {\n        expect(\n          screen.queryByText('http://127.0.0.1:8080/actuator/health'),\n        ).not.toBeInTheDocument();\n      });\n    });\n\n    it('toggles actuator filter on and off', async () => {\n      render(HttpExchanges, {\n        props: {\n          instance: createInstance(fetchHttpExchanges),\n        },\n      });\n\n      await waitFor(() => expect(fetchHttpExchanges).toHaveBeenCalled());\n\n      const excludeActuatorCheckbox = await screen.findByLabelText(\n        'instances.httpexchanges.filter.exclude_actuator',\n      );\n\n      // Initially checked (excluding actuator)\n      expect(excludeActuatorCheckbox).toBeChecked();\n\n      // Uncheck to include actuator\n      await userEvent.click(excludeActuatorCheckbox);\n      await waitFor(() => {\n        expect(excludeActuatorCheckbox).not.toBeChecked();\n      });\n\n      // Check again to exclude actuator\n      await userEvent.click(excludeActuatorCheckbox);\n      await waitFor(() => {\n        expect(excludeActuatorCheckbox).toBeChecked();\n      });\n    });\n  });\n\n  describe('edge cases with actuator in domain name', () => {\n    it('does not filter out URLs with \"actuator\" in domain but not in path', async () => {\n      // Test case where \"actuator\" appears in the domain name but the endpoint\n      // itself is not an actuator endpoint\n      const exchanges = [\n        createExchange('http://actuator.localhost/weather', 200),\n        createExchange('http://localhost/actuator-demo/health', 200),\n        createExchange('http://localhost/actuatordemo', 200),\n        createExchange('http://127.0.0.1:8080/weather', 200),\n        createExchange('http://127.0.0.1:8080/api/data', 200),\n        createExchange('http://127.0.0.1:8080/actuator/health', 200),\n        createExchange('http://127.0.0.1:8080/actuator', 200),\n      ];\n\n      const fetchHttpExchanges = vi.fn().mockResolvedValue({\n        data: {\n          exchanges: exchanges.map((e) => ({\n            timestamp: e.timestamp.toISOString(),\n            request: e.request,\n            response: e.response,\n          })),\n        },\n      });\n\n      render(HttpExchanges, {\n        props: {\n          instance: createInstance(\n            fetchHttpExchanges,\n            'https://actuator-web-frontend-aspire-actuators.dev.localhost:7096',\n            'https://actuator-web-frontend-aspire-actuators.dev.localhost:7096/actuator',\n          ),\n        },\n      });\n\n      await waitFor(() => expect(fetchHttpExchanges).toHaveBeenCalled());\n\n      // With excludeActuator enabled by default, verify correct URIs are shown/hidden\n      await waitFor(() => {\n        // These should be visible - they don't have /actuator in the path\n        expect(\n          screen.getByText('http://actuator.localhost/weather'),\n        ).toBeInTheDocument();\n        expect(\n          screen.getByText('http://127.0.0.1:8080/weather'),\n        ).toBeInTheDocument();\n        expect(\n          screen.getByText('http://127.0.0.1:8080/api/data'),\n        ).toBeInTheDocument();\n        expect(\n          screen.getByText('http://localhost/actuator-demo/health'),\n        ).toBeInTheDocument();\n        expect(\n          screen.getByText('http://localhost/actuatordemo'),\n        ).toBeInTheDocument();\n\n        // This should be hidden - it has /actuator in the path\n        expect(\n          screen.queryByText('http://127.0.0.1:8080/actuator/health'),\n        ).not.toBeInTheDocument();\n        expect(\n          screen.queryByText('http://127.0.0.1:8080/actuator'),\n        ).not.toBeInTheDocument();\n      });\n\n      // Verify the checkbox is present and checked\n      const excludeActuatorCheckbox = await screen.findByLabelText(\n        'instances.httpexchanges.filter.exclude_actuator',\n      );\n      expect(excludeActuatorCheckbox).toBeChecked();\n\n      // Uncheck to include all exchanges\n      await userEvent.click(excludeActuatorCheckbox);\n\n      // Now all exchanges including the actuator one should be shown\n      await waitFor(() => {\n        expect(\n          screen.getByText('http://actuator.localhost/weather'),\n        ).toBeInTheDocument();\n        expect(\n          screen.getByText('http://127.0.0.1:8080/weather'),\n        ).toBeInTheDocument();\n        expect(\n          screen.getByText('http://127.0.0.1:8080/api/data'),\n        ).toBeInTheDocument();\n        expect(\n          screen.getByText('http://127.0.0.1:8080/actuator/health'),\n        ).toBeInTheDocument();\n      });\n    });\n  });\n\n  describe('excludeActuator filter with other filters', () => {\n    const exchanges = [\n      createExchange('http://127.0.0.1:8080/api/users', 200),\n      createExchange('http://127.0.0.1:8080/actuator/health', 200),\n      createExchange('http://127.0.0.1:8080/actuator/metrics', 500),\n      createExchange('http://127.0.0.1:8080/api/products', 404),\n      createExchange('http://127.0.0.1:8080/actuator/info', 200),\n    ];\n\n    const fetchHttpExchanges = vi.fn().mockResolvedValue({\n      data: {\n        exchanges: exchanges.map((e) => ({\n          timestamp: e.timestamp.toISOString(),\n          request: e.request,\n          response: e.response,\n        })),\n      },\n    });\n\n    beforeEach(() => {\n      fetchHttpExchanges.mockClear();\n    });\n\n    it('works in combination with success filter', async () => {\n      render(HttpExchanges, {\n        props: {\n          instance: createInstance(fetchHttpExchanges),\n        },\n      });\n\n      await waitFor(() => expect(fetchHttpExchanges).toHaveBeenCalled());\n\n      // Wait for initial exchanges to load\n      await waitFor(() => {\n        expect(\n          screen.getByText('http://127.0.0.1:8080/api/users'),\n        ).toBeInTheDocument();\n      });\n\n      // Uncheck success filter to hide successful requests\n      const successCheckbox = await screen.findByLabelText(\n        'instances.httpexchanges.filter.success',\n      );\n      await userEvent.click(successCheckbox);\n\n      // Should show only non-successful, non-actuator exchanges\n      // That's just the 404 /api/products\n      await waitFor(() => {\n        expect(\n          screen.getByText('http://127.0.0.1:8080/api/products'),\n        ).toBeInTheDocument();\n\n        // Successful non-actuator endpoints should be hidden\n        expect(\n          screen.queryByText('http://127.0.0.1:8080/api/users'),\n        ).not.toBeInTheDocument();\n\n        // Actuator endpoints should still be hidden (both successful and not)\n        expect(\n          screen.queryByText('http://127.0.0.1:8080/actuator/health'),\n        ).not.toBeInTheDocument();\n        expect(\n          screen.queryByText('http://127.0.0.1:8080/actuator/metrics'),\n        ).not.toBeInTheDocument();\n        expect(\n          screen.queryByText('http://127.0.0.1:8080/actuator/info'),\n        ).not.toBeInTheDocument();\n      });\n    });\n\n    it('works in combination with URI filter', async () => {\n      render(HttpExchanges, {\n        props: {\n          instance: createInstance(fetchHttpExchanges),\n        },\n      });\n\n      await waitFor(() => expect(fetchHttpExchanges).toHaveBeenCalled());\n\n      // Wait for initial exchanges to load\n      await waitFor(() => {\n        expect(\n          screen.getByText('http://127.0.0.1:8080/api/users'),\n        ).toBeInTheDocument();\n      });\n\n      // Filter by 'users' - should match only /api/users\n      const inputs = screen.getAllByDisplayValue('');\n      const filterInput = inputs.find(\n        (input) => input.getAttribute('name') === 'filter',\n      );\n      if (!filterInput) throw new Error('Filter input not found');\n      await userEvent.type(filterInput, 'users');\n\n      // Wait for filter to be applied - should show only the users endpoint\n      await waitFor(() => {\n        expect(\n          screen.getByText('http://127.0.0.1:8080/api/users'),\n        ).toBeInTheDocument();\n\n        // Other non-actuator endpoints should be hidden by URI filter\n        expect(\n          screen.queryByText('http://127.0.0.1:8080/api/products'),\n        ).not.toBeInTheDocument();\n\n        // Actuator endpoints should remain hidden\n        expect(\n          screen.queryByText('http://127.0.0.1:8080/actuator/health'),\n        ).not.toBeInTheDocument();\n        expect(\n          screen.queryByText('http://127.0.0.1:8080/actuator/metrics'),\n        ).not.toBeInTheDocument();\n        expect(\n          screen.queryByText('http://127.0.0.1:8080/actuator/info'),\n        ).not.toBeInTheDocument();\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httpexchanges/index.vue",
    "content": "<!--\n  - Copyright 2014-2020 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-instance-section :error=\"error\" :loading=\"!hasLoaded\">\n    <template #before>\n      <sba-sticky-subnav>\n        <div class=\"flex gap-2\">\n          <sba-input\n            v-model=\"filter.uri\"\n            :placeholder=\"$t('term.filter')\"\n            class=\"flex-1\"\n            name=\"filter\"\n            type=\"search\"\n          >\n            <template #prepend>\n              <font-awesome-icon icon=\"filter\" />\n            </template>\n            <template #append>\n              <span class=\"button is-static\">\n                <span v-text=\"filteredExchanges.length\" />\n                /\n                <span v-text=\"exchanges.length\" />\n              </span>\n            </template>\n          </sba-input>\n\n          <sba-input\n            v-model=\"limit\"\n            name=\"limit\"\n            :min=\"0\"\n            class=\"w-32\"\n            type=\"number\"\n          >\n            <template #prepend>\n              {{ $t('instances.httpexchanges.limit') }}\n            </template>\n          </sba-input>\n\n          <div class=\"grid grid-rows-2 grid-flow-col gap-x-2 text-sm\">\n            <sba-checkbox\n              v-model=\"filter.showSuccess\"\n              :label=\"$t('instances.httpexchanges.filter.success')\"\n            />\n            <sba-checkbox\n              v-model=\"filter.showClientErrors\"\n              :label=\"$t('instances.httpexchanges.filter.client_errors')\"\n            />\n            <sba-checkbox\n              v-model=\"filter.showServerErrors\"\n              :label=\"$t('instances.httpexchanges.filter.server_errors')\"\n            />\n            <sba-checkbox\n              v-model=\"filter.excludeActuator\"\n              :label=\"\n                $t('instances.httpexchanges.filter.exclude_actuator', {\n                  actuator: '/actuator',\n                })\n              \"\n            />\n          </div>\n          <div>\n            <sba-button\n              :title=\"$t('instances.httpexchanges.auto_follow')\"\n              :primary=\"autoFollow\"\n              @click=\"autoFollow = !autoFollow\"\n            >\n              <font-awesome-icon :icon=\"faArrowsDownToLine\" />\n              <span\n                class=\"sr-only\"\n                v-text=\"$t('instances.httpexchanges.auto_follow')\"\n              />\n            </sba-button>\n          </div>\n        </div>\n      </sba-sticky-subnav>\n    </template>\n\n    <sba-panel>\n      <sba-exchanges-chart :exchanges=\"listedExchanges\" class=\"mb-6\" />\n    </sba-panel>\n\n    <sba-panel seamless>\n      <sba-exchanges-list :exchanges=\"listedExchanges\" />\n    </sba-panel>\n  </sba-instance-section>\n</template>\n\n<script>\nimport { faArrowsDownToLine } from '@fortawesome/free-solid-svg-icons';\nimport { debounce } from 'lodash-es';\nimport moment from 'moment';\nimport { take } from 'rxjs/operators';\n\nimport SbaCheckbox from '@/components/sba-checkbox';\n\nimport subscribing from '@/mixins/subscribing';\nimport Instance from '@/services/instance';\nimport { concatMap, delay, retryWhen, timer } from '@/utils/rxjs';\nimport { VIEW_GROUP } from '@/views/ViewGroup';\nimport { Exchange } from '@/views/instances/httpexchanges/Exchange';\nimport SbaExchangesChart from '@/views/instances/httpexchanges/exchanges-chart';\nimport SbaExchangesList from '@/views/instances/httpexchanges/exchanges-list';\nimport SbaInstanceSection from '@/views/instances/shell/sba-instance-section';\n\nconst addToFilter = (oldFilter, addedFilter) =>\n  !oldFilter\n    ? addedFilter\n    : (val, key) => oldFilter(val, key) && addedFilter(val, key);\n\nexport default {\n  components: {\n    SbaCheckbox,\n    SbaInstanceSection,\n    SbaExchangesList,\n    SbaExchangesChart,\n  },\n  mixins: [subscribing],\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  data: () => ({\n    hasLoaded: false,\n    error: null,\n    exchanges: [],\n    listOffset: 0,\n    faArrowsDownToLine,\n    filter: {\n      excludeActuator: true,\n      showSuccess: true,\n      showClientErrors: true,\n      showServerErrors: true,\n      uri: null,\n    },\n    limit: 1000,\n    selection: null,\n    autoFollow: true,\n  }),\n  computed: {\n    filteredExchanges() {\n      return this.filterExchanges(this.exchanges);\n    },\n    listedExchanges() {\n      const exchanges = this.filterExchanges(\n        this.exchanges.slice(this.listOffset),\n      );\n      if (!this.selection) {\n        return exchanges;\n      }\n      const [start, end] = this.selection;\n      return exchanges.filter(\n        (exchange) =>\n          !exchange.timestamp.isBefore(start) &&\n          !exchange.timestamp.isAfter(end),\n      );\n    },\n    lastTimestamp() {\n      return this.exchanges.length > 0\n        ? this.exchanges[0].timestamp\n        : moment(0);\n    },\n  },\n  watch: {\n    limit: debounce(function (value) {\n      if (this.exchanges.length > value) {\n        this.exchanges = Object.freeze(this.exchanges.slice(0, value));\n      }\n    }, 250),\n    autoFollow: function (value) {\n      if (value) {\n        this.showNewExchanges();\n      }\n    },\n  },\n  methods: {\n    showNewExchanges() {\n      this.listOffset = 0;\n    },\n    async fetchHttpExchanges() {\n      const response = await this.instance.fetchHttpExchanges();\n      const exchanges = response.data.exchanges\n        .map((exchange) => new Exchange(exchange))\n        .filter((exchange) => exchange.timestamp.isAfter(this.lastTimestamp));\n      exchanges.sort((a, b) => -1 * a.compareTo(b));\n      return exchanges;\n    },\n    createSubscription() {\n      return timer(0, 5000)\n        .pipe(\n          concatMap(this.fetchHttpExchanges),\n          retryWhen((err) => {\n            return err.pipe(delay(1000), take(2));\n          }),\n        )\n        .subscribe({\n          next: (exchanges) => {\n            this.hasLoaded = true;\n            if (this.exchanges.length > 0) {\n              this.listOffset += exchanges.length;\n            }\n            this.exchanges = [...exchanges, ...this.exchanges].slice(\n              0,\n              this.limit ?? 1000,\n            );\n            if (this.autoFollow && exchanges.length > 0) {\n              this.showNewExchanges();\n            }\n          },\n          error: (error) => {\n            this.hasLoaded = true;\n            console.warn('Fetching exchanges failed:', error);\n            this.error = error;\n          },\n        });\n    },\n    filterExchanges(exchanges) {\n      let filterFn = null;\n      if (this.filter.excludeActuator) {\n        filterFn = addToFilter(filterFn, (exchange) => {\n          try {\n            const uri = exchange.request.uri;\n            const pathname = new URL(uri).pathname;\n            const regex = /\\/actuator(\\/|$|\\?)/;\n\n            return !regex.test(pathname);\n          } catch {\n            return true;\n          }\n        });\n      }\n      if (this.filter.uri) {\n        const normalizedFilter = this.filter.uri.toLowerCase();\n        filterFn = addToFilter(filterFn, (exchange) =>\n          exchange.request.uri.toLowerCase().includes(normalizedFilter),\n        );\n      }\n      if (!this.filter.showSuccess) {\n        filterFn = addToFilter(filterFn, (exchange) => !exchange.isSuccess());\n      }\n      if (!this.filter.showClientErrors) {\n        filterFn = addToFilter(\n          filterFn,\n          (exchange) => !exchange.isClientError(),\n        );\n      }\n      if (!this.filter.showServerErrors) {\n        filterFn = addToFilter(\n          filterFn,\n          (exchange) => !exchange.isServerError(),\n        );\n      }\n      return filterFn ? exchanges.filter(filterFn) : exchanges;\n    },\n  },\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      name: 'instances/httpexchanges',\n      parent: 'instances',\n      path: 'httpexchanges',\n      component: this,\n      label: 'instances.httpexchanges.label',\n      group: VIEW_GROUP.WEB,\n      order: 500,\n      isEnabled: ({ instance }) => instance.hasEndpoint('httpexchanges'),\n    });\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httptrace/i18n.de.json",
    "content": "{\n  \"instances\": {\n    \"httptrace\": {\n      \"label\": \"HTTP-Traces\",\n      \"chart\": {\n        \"avg_time\": \"ø Dauer\",\n        \"max_time\": \"Max. Dauer\",\n        \"status_4xx\": \"Status 4xx\",\n        \"status_5xx\": \"Status 5xx\",\n        \"successful_requests\": \"Erfolgreiche Anfragen\",\n        \"total_requests\": \"Anfragen insgesamt\"\n      },\n      \"content_type_response\": \"Response Content-Type\",\n      \"content_type_request\": \"Request Content-Type\",\n      \"fetching_failed\": \"Das Laden der Tracing-Informationen ist fehlgeschlagen.\",\n      \"limit\": \"Limit\",\n      \"filter\": {\n        \"client_errors\": \"Client-Fehler\",\n        \"exclude_actuator\": \"ignoriere  {actuator}/**\",\n        \"server_errors\": \"Server-Fehler\",\n        \"success\": \"erfolgreich\"\n      },\n      \"length_response\": \"Response Größe\",\n      \"length_request\": \"Request Größe\",\n      \"method\": \"Methode\",\n      \"no_traces_found\": \"Keine Traces gefunden.\",\n      \"status\": \"Status\",\n      \"time\": \"Dauer\",\n      \"timestamp\": \"Zeitstempel\",\n      \"uri\": \"Pfad\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httptrace/i18n.en.json",
    "content": "{\n  \"instances\": {\n    \"httptrace\": {\n      \"label\": \"HTTP Traces\",\n      \"chart\": {\n        \"avg_time\": \"ø duration\",\n        \"max_time\": \"max duration\",\n        \"status_4xx\": \"status 4xx\",\n        \"status_5xx\": \"status 5xx\",\n        \"successful_requests\": \"successful requests\",\n        \"total_requests\": \"total requests\"\n      },\n      \"content_type_response\": \"Response Content-Type\",\n      \"content_type_request\": \"Request Content-Type\",\n      \"length_request\": \"Request Length\",\n      \"fetching_failed\": \"Fetching traces failed.\",\n      \"limit\": \"limit\",\n      \"filter\": {\n        \"client_errors\": \"client errors\",\n        \"exclude_actuator\": \"exclude {actuator}/**\",\n        \"server_errors\": \"server errors\",\n        \"success\": \"success\"\n      },\n      \"length_response\": \"Response Length\",\n      \"method\": \"Method\",\n      \"no_traces_found\": \"No traces found.\",\n      \"status\": \"Status\",\n      \"time\": \"Time\",\n      \"timestamp\": \"Timestamp\",\n      \"uri\": \"Path\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httptrace/i18n.es.json",
    "content": "{\n  \"instances\": {\n    \"httptrace\": {\n      \"label\": \"HTTP Traces\",\n      \"chart\": {\n        \"avg_time\": \"ø duración\",\n        \"max_time\": \"duración máxima\",\n        \"status_4xx\": \"estado 4xx\",\n        \"status_5xx\": \"estado 5xx\",\n        \"successful_requests\": \"solicitudes exitosas\",\n        \"total_requests\": \"solicitudes totales\"\n      },\n      \"content_type_response\": \"Response Content-Type\",\n      \"content_type_request\": \"Request Content-Type\",\n      \"length_request\": \"Request Length\",\n      \"fetching_failed\": \"Falla recuperando la traza.\",\n      \"limit\": \"límite\",\n      \"filter\": {\n        \"client_errors\": \"Errores de clientes\",\n        \"exclude_actuator\": \"excluir {actuator}/**\",\n        \"server_errors\": \"errores de servidor\",\n        \"success\": \"éxito\"\n      },\n      \"length_response\": \"Response Length\",\n      \"method\": \"Método\",\n      \"no_traces_found\": \"No se encontraron trazas.\",\n      \"status\": \"Estado\",\n      \"time\": \"Duración\",\n      \"timestamp\": \"Horario\",\n      \"uri\": \"Path\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httptrace/i18n.fr.json",
    "content": "{\n  \"instances\": {\n    \"httptrace\": {\n      \"label\": \"Traces HTTP\",\n      \"chart\": {\n        \"avg_time\": \"ø duration\",\n        \"max_time\": \"durée max\",\n        \"status_4xx\": \"statut 4xx\",\n        \"status_5xx\": \"statut 5xx\",\n        \"successful_requests\": \"requêtes en succès\",\n        \"total_requests\": \"requêtes au total\"\n      },\n      \"content_type_response\": \"Response Content-Type\",\n      \"content_type_request\": \"Request Content-Type\",\n      \"length_request\": \"Request Longueur\",\n      \"fetching_failed\": \"Echec de la récupération des traces HTTP.\",\n      \"limit\": \"limite\",\n      \"filter\": {\n        \"client_errors\": \"Erreurs clientes\",\n        \"exclude_actuator\": \"exclure {actuator}/**\",\n        \"server_errors\": \"Erreurs serveurs\",\n        \"success\": \"succès\"\n      },\n      \"length_response\": \"Response Longueur\",\n      \"method\": \"Methodes\",\n      \"no_traces_found\": \"Aucune traces trouvées.\",\n      \"status\": \"Statut\",\n      \"time\": \"Temps\",\n      \"timestamp\": \"Timestamp\",\n      \"uri\": \"Path\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httptrace/i18n.is.json",
    "content": "{\n  \"instances\": {\n    \"httptrace\": {\n      \"label\": \"HTTP-Traces\",\n      \"chart\": {\n        \"avg_time\": \"ø tímalengd\",\n        \"max_time\": \"Hámarkstímalengd\",\n        \"status_4xx\": \"Staða 4xx\",\n        \"status_5xx\": \"Staða 5xx\",\n        \"successful_requests\": \"Farsælar fyrirspurnir\",\n        \"total_requests\": \"Samtals fyrirspurnir\"\n      },\n      \"content_type_response\": \"Innihaldstegund svarsins\",\n      \"content_type_request\": \"Innihaldstegund beiðninnar\",\n      \"length_request\": \"Lengd beiðninnar\",\n      \"fetching_failed\": \"Mistókst að sækja upplýsingar um HTTP spor.\",\n      \"limit\": \"Takmörkun\",\n      \"filter\": {\n        \"client_errors\": \"Vandamál biðlara\",\n        \"exclude_actuator\": \"hunsa {actuator}/**\",\n        \"server_errors\": \"Vandamál þjóns\",\n        \"success\": \"farsælt\"\n      },\n      \"length_response\": \"Stærð svarsins\",\n      \"method\": \"Aðferð\",\n      \"no_traces_found\": \"Fundið engin spor.\",\n      \"status\": \"Staða\",\n      \"time\": \"Tímalengd\",\n      \"timestamp\": \"Tímapunktur\",\n      \"uri\": \"URI\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httptrace/i18n.ko.json",
    "content": "{\n  \"instances\": {\n    \"httptrace\": {\n      \"label\": \"HTTP 트레이스\",\n      \"chart\": {\n        \"avg_time\": \"ø 기간\",\n        \"max_time\": \"최대 기간\",\n        \"status_4xx\": \"4xx 상태\",\n        \"status_5xx\": \"5xx 상태\",\n        \"successful_requests\": \"성공적인 요청\",\n        \"total_requests\": \"전체 요청\"\n      },\n      \"content_type_response\": \"응답 Content-Type\",\n      \"content_type_request\": \"요청 Content-Type\",\n      \"length_request\": \"요청 길이\",\n      \"fetching_failed\": \"트레이스 정보를 가져오는데 실패했습니다.\",\n      \"limit\": \"제한\",\n      \"filter\": {\n        \"client_errors\": \"클라이언트 에러\",\n        \"exclude_actuator\": \"제외 {actuator}/**\",\n        \"server_errors\": \"서버 에러\",\n        \"success\": \"성공\"\n      },\n      \"length_response\": \"응답 길이\",\n      \"method\": \"Method\",\n      \"no_traces_found\": \"트레이스 정보가 없습니다.\",\n      \"status\": \"상태\",\n      \"time\": \"시간\",\n      \"timestamp\": \"타임스탬프\",\n      \"uri\": \"경로\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httptrace/i18n.pt-BR.json",
    "content": "{\n  \"instances\": {\n    \"httptrace\": {\n      \"label\": \"HTTP Traces\",\n      \"chart\": {\n        \"avg_time\": \"ø duração\",\n        \"max_time\": \"duração máxima\",\n        \"status_4xx\": \"status 4xx\",\n        \"status_5xx\": \"status 5xx\",\n        \"successful_requests\": \"Requisições sucedidas\",\n        \"total_requests\": \"total de requisições\"\n      },\n      \"content_type_response\": \"Response Content-Type\",\n      \"content_type_request\": \"Request Content-Type\",\n      \"length_request\": \"Request Length\",\n      \"fetching_failed\": \"Falha na busca de traces.\",\n      \"limit\": \"limite\",\n      \"filter\": {\n        \"client_errors\": \"erros de cliente\",\n        \"exclude_actuator\": \"excluir {actuator}/**\",\n        \"server_errors\": \"erros do servidor\",\n        \"success\": \"sucesso\"\n      },\n      \"length_response\": \"Response Tamanho\",\n      \"method\": \"Metodo\",\n      \"no_traces_found\": \"Nenhum trace encontrado.\",\n      \"status\": \"Status\",\n      \"time\": \"Tempo\",\n      \"timestamp\": \"Timestamp\",\n      \"uri\": \"Caminho\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httptrace/i18n.ru.json",
    "content": "{\n  \"instances\": {\n    \"httptrace\": {\n      \"label\": \"Трассировка HTTP-запросов\",\n      \"chart\": {\n        \"avg_time\": \"ø задержка\",\n        \"max_time\": \"максимальная задержка\",\n        \"status_4xx\": \"статус 4xx\",\n        \"status_5xx\": \"статус 5xx\",\n        \"successful_requests\": \"успешные запросы\",\n        \"total_requests\": \"всего запросов\"\n      },\n      \"content_type_response\": \"Тип содержимого ответа\",\n      \"content_type_request\": \"Тип содержимого запроса\",\n      \"length_request\": \"Длина запроса\",\n      \"fetching_failed\": \"Ошибка трассировки.\",\n      \"limit\": \"ограничение\",\n      \"filter\": {\n        \"client_errors\": \"ошибки на клиенте\",\n        \"exclude_actuator\": \"исключить {actuator}/**\",\n        \"server_errors\": \"ошибки сервера\",\n        \"success\": \"успех\"\n      },\n      \"length_response\": \"Длина ответа\",\n      \"method\": \"Метод\",\n      \"no_traces_found\": \"Трассировка не найдена.\",\n      \"status\": \"Статус\",\n      \"time\": \"Время\",\n      \"timestamp\": \"Время\",\n      \"uri\": \"Путь\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httptrace/i18n.zh-CN.json",
    "content": "{\n  \"instances\": {\n    \"httptrace\": {\n      \"label\": \"HTTP跟踪\",\n      \"chart\": {\n        \"avg_time\": \"平均时长\",\n        \"max_time\": \"最大时长\",\n        \"status_4xx\": \"4xx失败总数\",\n        \"status_5xx\": \"5xx失败总数\",\n        \"successful_requests\": \"请求成功\",\n        \"total_requests\": \"请求总数\"\n      },\n      \"content_type_response\": \"Response 内容类型\",\n      \"content_type_request\": \"Request 内容类型\",\n      \"length_request\": \"Request 长度\",\n      \"fetching_failed\": \"获取跟踪信息失败。\",\n      \"limit\": \"limit\",\n      \"filter\": {\n        \"client_errors\": \"客户端错误\",\n        \"exclude_actuator\": \"排除 {actuator}/**\",\n        \"server_errors\": \"服务器错误\",\n        \"success\": \"请求成功\"\n      },\n      \"length_response\": \"Response 长度\",\n      \"method\": \"方法\",\n      \"no_traces_found\": \"没有跟踪信息。\",\n      \"status\": \"状态\",\n      \"time\": \"时长\",\n      \"timestamp\": \"时间戳\",\n      \"uri\": \"路径\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httptrace/i18n.zh-TW.json",
    "content": "{\n  \"instances\": {\n    \"httptrace\": {\n      \"label\": \"HTTP 追蹤\",\n      \"chart\": {\n        \"avg_time\": \"平均時長\",\n        \"max_time\": \"最大時長\",\n        \"status_4xx\": \"4xx 狀態碼\",\n        \"status_5xx\": \"5xx 狀態碼\",\n        \"successful_requests\": \"成功請求數\",\n        \"total_requests\": \"總請求數\"\n      },\n      \"content_type_response\": \"回應 Content-Type\",\n      \"content_type_request\": \"請求 Content-Type\",\n      \"length_request\": \"請求長度\",\n      \"fetching_failed\": \"取得追蹤資訊失敗。\",\n      \"limit\": \"限制\",\n      \"filter\": {\n        \"client_errors\": \"用戶端錯誤\",\n        \"exclude_actuator\": \"排除 {actuator}/**\",\n        \"server_errors\": \"伺服器錯誤\",\n        \"success\": \"成功\"\n      },\n      \"length_response\": \"回應長度\",\n      \"method\": \"方法\",\n      \"no_traces_found\": \"找不到追蹤資訊。\",\n      \"status\": \"狀態\",\n      \"time\": \"時長\",\n      \"timestamp\": \"時間戳記\",\n      \"uri\": \"路徑\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httptrace/index.vue",
    "content": "<!--\n  - Copyright 2014-2020 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-instance-section :error=\"error\" :loading=\"!hasLoaded\">\n    <template #before>\n      <sba-sticky-subnav>\n        <div class=\"flex gap-2\">\n          <sba-input\n            v-model=\"filter.uri\"\n            :placeholder=\"$t('term.filter')\"\n            class=\"flex-1\"\n            name=\"filter\"\n            type=\"search\"\n          >\n            <template #prepend>\n              <font-awesome-icon icon=\"filter\" />\n            </template>\n            <template #append>\n              <span class=\"button is-static\">\n                <span v-text=\"filteredTraces.length\" />\n                /\n                <span v-text=\"traces.length\" />\n              </span>\n            </template>\n          </sba-input>\n\n          <sba-input v-model=\"limit\" :min=\"0\" class=\"w-32\" type=\"number\">\n            <template #prepend>\n              {{ $t('instances.httptrace.limit') }}\n            </template>\n          </sba-input>\n\n          <div class=\"grid grid-rows-2 grid-flow-col gap-x-2 text-sm\">\n            <sba-checkbox\n              v-model=\"filter.showSuccess\"\n              :label=\"$t('instances.httptrace.filter.success')\"\n            />\n            <sba-checkbox\n              v-model=\"filter.showClientErrors\"\n              :label=\"$t('instances.httptrace.filter.client_errors')\"\n            />\n            <sba-checkbox\n              v-model=\"filter.showServerErrors\"\n              :label=\"$t('instances.httptrace.filter.server_errors')\"\n            />\n            <sba-checkbox\n              v-if=\"actuatorPath\"\n              v-model=\"filter.excludeActuator\"\n              :label=\"\n                $t('instances.httptrace.filter.exclude_actuator', {\n                  actuator: actuatorPath,\n                })\n              \"\n            />\n          </div>\n        </div>\n      </sba-sticky-subnav>\n    </template>\n\n    <sba-panel>\n      <sba-traces-chart\n        :traces=\"filteredTraces\"\n        class=\"mb-6\"\n        @selected=\"updateSelection\"\n      />\n\n      <sba-traces-list\n        :new-traces-count=\"newTracesCount\"\n        :traces=\"listedTraces\"\n        @show-new-traces=\"showNewTraces\"\n      />\n    </sba-panel>\n  </sba-instance-section>\n</template>\n\n<script>\nimport { debounce, mapKeys } from 'lodash-es';\nimport moment from 'moment';\nimport { take } from 'rxjs/operators';\n\nimport SbaCheckbox from '@/components/sba-checkbox';\n\nimport subscribing from '@/mixins/subscribing';\nimport Instance from '@/services/instance';\nimport { concatMap, delay, retryWhen, timer } from '@/utils/rxjs';\nimport { VIEW_GROUP } from '@/views/ViewGroup';\nimport sbaTracesChart from '@/views/instances/httptrace/traces-chart';\nimport sbaTracesList from '@/views/instances/httptrace/traces-list';\nimport SbaInstanceSection from '@/views/instances/shell/sba-instance-section';\n\nconst addToFilter = (oldFilter, addedFilter) =>\n  !oldFilter\n    ? addedFilter\n    : (val, key) => oldFilter(val, key) && addedFilter(val, key);\n\nconst normalize = (obj) => mapKeys(obj, (value, key) => key.toLowerCase());\n\nclass Trace {\n  constructor({ timestamp, request, response, ...trace }) {\n    Object.assign(this, trace);\n    this.timestamp = moment(timestamp);\n    this.request = { ...request, headers: normalize(request.headers) };\n    this.response = response\n      ? { ...response, headers: normalize(response.headers) }\n      : null;\n  }\n\n  get key() {\n    return `${this.timestamp.valueOf()}-${this.request.method}-${\n      this.request.uri\n    }`;\n  }\n\n  get contentLengthResponse() {\n    return this.extractContentLength(this.response);\n  }\n\n  get contentLengthRequest() {\n    return this.extractContentLength(this.request);\n  }\n\n  get contentTypeResponse() {\n    return this.extractContentType(this.response);\n  }\n\n  get contentTypeRequest() {\n    return this.extractContentType(this.request);\n  }\n\n  extractContentLength(origin) {\n    const contentLength =\n      origin &&\n      origin.headers['content-length'] &&\n      origin.headers['content-length'][0];\n    if (contentLength && /^\\d+$/.test(contentLength)) {\n      return parseInt(contentLength);\n    }\n    return null;\n  }\n\n  extractContentType(origin) {\n    const contentType =\n      origin &&\n      origin.headers['content-type'] &&\n      origin.headers['content-type'][0];\n    if (contentType) {\n      const idx = contentType.indexOf(';');\n      return idx >= 0 ? contentType.substring(0, idx) : contentType;\n    }\n    return null;\n  }\n\n  compareTo(other) {\n    return this.timestamp - other.timestamp;\n  }\n\n  isPending() {\n    return !this.response;\n  }\n\n  isSuccess() {\n    return this.response && this.response.status <= 399;\n  }\n\n  isClientError() {\n    return (\n      this.response &&\n      this.response.status >= 400 &&\n      this.response.status <= 499\n    );\n  }\n\n  isServerError() {\n    return (\n      this.response &&\n      this.response.status >= 500 &&\n      this.response.status <= 599\n    );\n  }\n}\n\nexport default {\n  components: {\n    SbaCheckbox,\n    SbaInstanceSection,\n    sbaTracesList,\n    sbaTracesChart,\n  },\n  mixins: [subscribing],\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  data: () => ({\n    hasLoaded: false,\n    error: null,\n    traces: [],\n    listOffset: 0,\n    filter: {\n      excludeActuator: true,\n      showSuccess: true,\n      showClientErrors: true,\n      showServerErrors: true,\n      uri: null,\n    },\n    limit: 1000,\n    selection: null,\n  }),\n  computed: {\n    actuatorPath() {\n      if (\n        this.instance.registration.managementUrl.includes(\n          this.instance.registration.serviceUrl,\n        )\n      ) {\n        const appendix = this.instance.registration.managementUrl.substring(\n          this.instance.registration.serviceUrl.length,\n        );\n        if (appendix.length > 0) {\n          return appendix.startsWith('/') ? appendix : `/${appendix}`;\n        }\n      }\n      return null;\n    },\n    filteredTraces() {\n      return this.filterTraces(this.traces);\n    },\n    newTracesCount() {\n      return this.selection\n        ? 0\n        : this.filterTraces(this.traces.slice(0, this.listOffset)).length;\n    },\n    listedTraces() {\n      const traces = this.filterTraces(this.traces.slice(this.listOffset));\n      if (!this.selection) {\n        return traces;\n      }\n      const [start, end] = this.selection;\n      return traces.filter(\n        (trace) =>\n          !trace.timestamp.isBefore(start) && !trace.timestamp.isAfter(end),\n      );\n    },\n    lastTimestamp() {\n      return this.traces.length > 0 ? this.traces[0].timestamp : moment(0);\n    },\n  },\n  watch: {\n    limit: debounce(function (value) {\n      if (this.traces.length > value) {\n        this.traces = Object.freeze(this.traces.slice(0, value));\n      }\n    }, 250),\n  },\n  methods: {\n    updateSelection(selection) {\n      this.selection = selection;\n      this.showNewTraces();\n    },\n    showNewTraces() {\n      this.listOffset = 0;\n    },\n    async fetchHttptrace() {\n      const response = await this.instance.fetchHttptrace();\n      const traces = response.data.traces\n        .map((trace) => new Trace(trace))\n        .filter((trace) => trace.timestamp.isAfter(this.lastTimestamp));\n      traces.sort((a, b) => -1 * a.compareTo(b));\n      return traces;\n    },\n    createSubscription() {\n      return timer(0, 5000)\n        .pipe(\n          concatMap(this.fetchHttptrace),\n          retryWhen((err) => {\n            return err.pipe(delay(1000), take(2));\n          }),\n        )\n        .subscribe({\n          next: (traces) => {\n            this.hasLoaded = true;\n            if (this.traces.length > 0) {\n              this.listOffset += traces.length;\n            }\n            this.traces = [...traces, ...this.traces].slice(0, this.limit);\n          },\n          error: (error) => {\n            this.hasLoaded = true;\n            console.warn('Fetching traces failed:', error);\n            this.error = error;\n          },\n        });\n    },\n    filterTraces(traces) {\n      let filterFn = null;\n      if (this.actuatorPath !== null && this.filter.excludeActuator) {\n        filterFn = addToFilter(\n          filterFn,\n          (trace) => !trace.request.uri.includes(this.actuatorPath),\n        );\n      }\n      if (this.filter.uri) {\n        const normalizedFilter = this.filter.uri.toLowerCase();\n        filterFn = addToFilter(filterFn, (trace) =>\n          trace.request.uri.toLowerCase().includes(normalizedFilter),\n        );\n      }\n      if (!this.filter.showSuccess) {\n        filterFn = addToFilter(filterFn, (trace) => !trace.isSuccess());\n      }\n      if (!this.filter.showClientErrors) {\n        filterFn = addToFilter(filterFn, (trace) => !trace.isClientError());\n      }\n      if (!this.filter.showServerErrors) {\n        filterFn = addToFilter(filterFn, (trace) => !trace.isServerError());\n      }\n      return filterFn ? traces.filter(filterFn) : traces;\n    },\n  },\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      name: 'instances/httptrace',\n      parent: 'instances',\n      path: 'httptrace',\n      component: this,\n      label: 'instances.httptrace.label',\n      group: VIEW_GROUP.WEB,\n      order: 500,\n      isEnabled: ({ instance }) => instance.hasEndpoint('httptrace'),\n    });\n  },\n};\n</script>\n<style lang=\"css\">\n.httptraces__limit {\n  width: 5em;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httptrace/traces-chart.vue",
    "content": "<!--\n  - Copyright 2014-2020 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div class=\"trace-chart\">\n    <div\n      v-if=\"tooltipSelection\"\n      :class=\"`trace-chart__tooltip--${\n        x(tooltipSelection[0]) > width / 2 ? 'left' : 'right'\n      }`\"\n      class=\"trace-chart__tooltip\"\n    >\n      <table class=\"is-narrow is-size-7\">\n        <tbody>\n          <tr>\n            <th v-text=\"$t('instances.httptrace.chart.total_requests')\" />\n            <td v-text=\"tooltipContent.totalCount\" />\n          </tr>\n          <tr>\n            <th v-text=\"$t('instances.httptrace.chart.successful_requests')\" />\n            <td v-text=\"tooltipContent.totalSuccess\" />\n          </tr>\n          <tr>\n            <th v-text=\"$t('instances.httptrace.chart.status_4xx')\" />\n            <td v-text=\"tooltipContent.totalClientErrors\" />\n          </tr>\n          <tr>\n            <th v-text=\"$t('instances.httptrace.chart.status_5xx')\" />\n            <td v-text=\"tooltipContent.totalServerErrors\" />\n          </tr>\n          <tr>\n            <th v-text=\"$t('instances.httptrace.chart.max_time')\" />\n            <td v-text=\"`${tooltipContent.maxTime}ms`\" />\n          </tr>\n          <tr>\n            <th v-text=\"$t('instances.httptrace.chart.avg_time')\" />\n            <td v-text=\"`${tooltipContent.avgTime}ms`\" />\n          </tr>\n        </tbody>\n      </table>\n    </div>\n    <svg class=\"trace-chart__svg\" />\n  </div>\n</template>\n\n<script>\n//see https://github.com/d3/d3/issues/2733#issuecomment-190743489\nimport moment from 'moment';\n\nimport d3 from '@/utils/d3';\n\nconst interval = 1000;\nexport default {\n  props: {\n    traces: {\n      type: Array,\n      default: () => [],\n    },\n  },\n  emits: ['selected'],\n  data: () => ({\n    brushSelection: null,\n    hovered: null,\n  }),\n  computed: {\n    chartData() {\n      if (this.traces.length <= 0) {\n        return [];\n      }\n      const chartData = [];\n      const now = moment().valueOf();\n      let idx = this.traces.length - 1;\n      const oldest = this.traces[this.traces.length - 1].timestamp.valueOf();\n\n      for (\n        let time = Math.floor(oldest.valueOf() / interval) * interval;\n        time < now;\n        time += interval\n      ) {\n        const bucket = {\n          timeStart: time,\n          timeEnd: time + interval,\n          totalCount: 0,\n          totalSuccess: 0,\n          totalClientErrors: 0,\n          totalServerErrors: 0,\n          totalTime: 0,\n          maxTime: 0,\n        };\n\n        while (\n          idx >= 0 &&\n          this.traces[idx].timestamp.valueOf() < time + interval\n        ) {\n          const trace = this.traces[idx];\n          bucket.totalCount++;\n          if (trace.isSuccess()) {\n            bucket.totalSuccess++;\n          } else if (trace.isClientError()) {\n            bucket.totalClientErrors++;\n          } else if (trace.isServerError()) {\n            bucket.totalServerErrors++;\n          }\n          if (trace.timeTaken) {\n            bucket.totalTime += trace.timeTaken;\n            bucket.maxTime = Math.max(bucket.maxTime, trace.timeTaken);\n          }\n          idx--;\n        }\n        chartData.push(bucket);\n      }\n\n      return chartData;\n    },\n    tooltipSelection() {\n      return this.brushSelection\n        ? this.brushSelection\n        : this.hovered\n          ? [this.hovered, this.hovered + interval]\n          : null;\n    },\n    tooltipContent() {\n      const selection = this.tooltipSelection;\n      const totals = this.chartData\n        .filter(\n          (bucket) =>\n            bucket.timeStart.valueOf() >= selection[0] &&\n            bucket.timeStart.valueOf() < selection[1],\n        )\n        .reduce(\n          (current, next) => ({\n            totalCount: current.totalCount + next.totalCount,\n            totalSuccess: current.totalSuccess + next.totalSuccess,\n            totalClientErrors:\n              current.totalClientErrors + next.totalClientErrors,\n            totalServerErrors:\n              current.totalServerErrors + next.totalServerErrors,\n            totalTime: current.totalTime + next.totalTime,\n            maxTime: Math.max(current.maxTime, next.maxTime),\n          }),\n          {\n            totalCount: 0,\n            totalSuccess: 0,\n            totalClientErrors: 0,\n            totalServerErrors: 0,\n            totalTime: 0,\n            maxTime: 0,\n          },\n        );\n      return {\n        ...totals,\n        avgTime:\n          totals.totalCount > 0\n            ? Math.floor(totals.totalTime / totals.totalCount)\n            : 0,\n      };\n    },\n  },\n  watch: {\n    chartData: 'drawChart',\n    hovered(newVal) {\n      if (newVal) {\n        this.hover\n          .attr('opacity', 1)\n          .attr('d', `M${this.x(newVal)},${this.height} ${this.x(newVal)},0`);\n      } else {\n        this.hover.attr('opacity', 0);\n      }\n    },\n    brushSelection(newVal) {\n      this.$emit('selected', newVal);\n    },\n  },\n  mounted() {\n    const margin = {\n      top: 20,\n      right: 20,\n      bottom: 30,\n      left: 20,\n    };\n\n    this.width =\n      this.$el.getBoundingClientRect().width - margin.left - margin.right;\n    this.height =\n      this.$el.getBoundingClientRect().height - margin.top - margin.bottom;\n\n    this.chartLayer = d3\n      .select(this.$el.querySelector('.trace-chart__svg'))\n      .append('g')\n      .attr('transform', `translate(${margin.left},${margin.top})`);\n\n    this.xAxis = this.chartLayer\n      .append('g')\n      .attr('class', 'trace-chart__axis-x')\n      .attr('transform', `translate(0,${this.height})`);\n\n    this.yAxis = this.chartLayer\n      .append('g')\n      .attr('class', 'trace-chart__axis-y')\n      .attr('stroke', null);\n\n    this.areas = this.chartLayer.append('g');\n\n    this.hover = this.chartLayer\n      .append('path')\n      .attr('class', 'trace-chart__hover')\n      .attr('opacity', 0);\n\n    this.brushGroup = this.chartLayer\n      .append('g')\n      .attr('class', 'trace-chart__brush');\n\n    this.drawChart(this.chartData);\n  },\n  beforeUnmount() {\n    if (this.brushGroup) {\n      this.brushGroup.on('.brush', null);\n      this.brushGroup.on('mousemove', null);\n      this.brushGroup.on('mouseout', null);\n    }\n\n    if (this.chartLayer) {\n      this.chartLayer.selectAll('*').remove();\n    }\n\n    this.brushGroup = null;\n    this.chartLayer = null;\n  },\n  methods: {\n    drawChart(data) {\n      ///setup x and y scale\n      const x = d3\n        .scaleTime()\n        .range([0, this.width])\n        .domain(d3.extent(data, (d) => d.timeStart));\n      this.x = x;\n\n      const y = d3\n        .scaleLinear()\n        .range([this.height, 0])\n        .domain([0, d3.max(data, (d) => d.totalCount)]);\n\n      //draw areas\n      const area = d3\n        .area()\n        .x((d) => x(d.data.timeStart))\n        .y0((d) => y(d[0]))\n        .y1((d) => y(d[1]));\n\n      const stack = d3\n        .stack()\n        .keys(['totalSuccess', 'totalClientErrors', 'totalServerErrors']);\n\n      const d = this.areas.selectAll('.trace-chart__area').data(stack(data));\n\n      d.enter()\n        .append('path')\n        .merge(d)\n        .attr('class', (d) => `trace-chart__area trace-chart__area--${d.key}`)\n        .attr('d', area);\n\n      d.exit().remove();\n\n      //draw axis\n      this.xAxis.call(\n        d3\n          .axisBottom(x)\n          .ticks(10)\n          .tickFormat((d) => moment(d).format('HH:mm:ss')),\n      );\n\n      this.yAxis\n        .call(\n          d3\n            .axisRight(y)\n            .ticks(\n              Math.min(\n                5,\n                d3.max(data, (d) => d.totalCount),\n              ),\n            )\n            .tickSize(this.width),\n        )\n        .call((axis) =>\n          axis\n            .selectAll('.tick text')\n            .attr('x', -2)\n            .attr('dy', 2)\n            .attr('text-anchor', 'end'),\n        );\n\n      //draw brush selection\n      const brush = d3\n        .brushX()\n        .extent([\n          [0, 0],\n          [this.width, this.height],\n        ])\n        .on('start', (event) => {\n          if (event.selection) {\n            this.isBrushing = true;\n            this.hovered = null;\n          }\n        })\n        .on('brush', (event) => {\n          if (!event.sourceEvent) {\n            return;\n          }\n\n          if (event.selection) {\n            const floor =\n              Math.floor(x.invert(event.selection[0]) / interval) * interval;\n            const ceil =\n              Math.ceil(x.invert(event.selection[1]) / interval) * interval;\n            d3.select(this).call(event.target.move, [floor, ceil].map(x));\n            this.brushSelection = [floor, ceil];\n          }\n        })\n        .on('end', (event) => {\n          this.isBrushing = false;\n          if (!event.selection) {\n            this.brushSelection = null;\n          }\n        });\n\n      this.brushGroup\n        .call(brush)\n        .on('mousemove', (event) => {\n          if (this.isBrushing) {\n            return;\n          }\n          const mouseX = d3.pointer(\n            event,\n            this.brushGroup.select('.overlay').node(),\n          )[0];\n          this.hovered = Math.floor(x.invert(mouseX) / interval) * interval;\n        })\n        .on('mouseout', () => {\n          this.hovered = null;\n        });\n\n      brush.move(\n        this.brushGroup,\n        this.brushSelection ? this.brushSelection.map(x) : null,\n      );\n    },\n  },\n};\n</script>\n\n<style lang=\"css\">\n.trace-chart__svg {\n  height: 200px;\n  width: 100%;\n}\n.trace-chart__hover {\n  stroke: #b5b5b5;\n  stroke-width: 1px;\n}\n.trace-chart__tooltip {\n  position: absolute;\n  background: #000;\n  opacity: 0.8;\n  pointer-events: none;\n  border-radius: 6px;\n  padding: 0.825em;\n  width: 200px;\n}\n.trace-chart__tooltip table th,\n.trace-chart__tooltip table td {\n  border: none;\n  color: #b5b5b5;\n  padding: 0.25em 0.75em;\n}\n.trace-chart__tooltip table td {\n  text-align: right;\n}\n.trace-chart__tooltip--left {\n  left: 5px;\n}\n.trace-chart__tooltip--right {\n  right: 5px;\n}\n.trace-chart .selection {\n  stroke: none;\n  fill: rgba(0, 0, 0, 0.2);\n  fill-opacity: 1;\n}\n.trace-chart__axis-y .domain {\n  stroke: none;\n}\n.trace-chart__axis-y .tick:not(:first-of-type) line {\n  stroke-dasharray: 2, 2;\n  stroke: #b5b5b5;\n}\n.trace-chart__area--totalSuccess {\n  fill: #48c78e;\n  opacity: 0.8;\n}\n.trace-chart__area--totalClientErrors {\n  fill: #ffe08a;\n  opacity: 0.8;\n}\n.trace-chart__area--totalServerErrors {\n  fill: #f14668;\n  opacity: 0.8;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/httptrace/traces-list.vue",
    "content": "<!--\n  - Copyright 2014-2020 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <table class=\"httptraces table table-full table-sm\">\n    <thead>\n      <tr>\n        <th\n          class=\"httptraces__trace-timestamp\"\n          v-text=\"$t('instances.httptrace.timestamp')\"\n        />\n        <th\n          class=\"httptraces__trace-method\"\n          v-text=\"$t('instances.httptrace.method')\"\n        />\n        <th\n          class=\"httptraces__trace-uri\"\n          v-text=\"$t('instances.httptrace.uri')\"\n        />\n        <th\n          class=\"httptraces__trace-status\"\n          v-text=\"$t('instances.httptrace.status')\"\n        />\n        <th\n          class=\"httptraces__trace-contentType\"\n          v-text=\"$t('instances.httptrace.content_type_request')\"\n        />\n        <th\n          class=\"httptraces__trace-contentLength\"\n          v-text=\"$t('instances.httptrace.length_request')\"\n        />\n        <th\n          class=\"httptraces__trace-contentType\"\n          v-text=\"$t('instances.httptrace.content_type_response')\"\n        />\n        <th\n          class=\"httptraces__trace-contentLength\"\n          v-text=\"$t('instances.httptrace.length_response')\"\n        />\n        <th\n          class=\"httptraces__trace-timeTaken\"\n          v-text=\"$t('instances.httptrace.time')\"\n        />\n      </tr>\n    </thead>\n    <tbody>\n      <tr v-if=\"newTracesCount > 0\" key=\"new-traces\">\n        <td\n          colspan=\"9\"\n          class=\"text-center\"\n          @click=\"$emit('show-new-traces')\"\n          v-text=\"`${newTracesCount} new traces`\"\n        />\n      </tr>\n    </tbody>\n    <transition-group tag=\"tbody\" name=\"fade-in\">\n      <template v-for=\"trace in traces\" :key=\"trace.key\">\n        <tr\n          class=\"is-selectable\"\n          :class=\"{ 'httptraces__trace---is-detailed': showDetails[trace.key] }\"\n          @click=\"\n            showDetails[trace.key]\n              ? delete showDetails[trace.key]\n              : (showDetails[trace.key] = true)\n          \"\n        >\n          <td\n            class=\"httptraces__trace-timestamp\"\n            v-text=\"formatDate(trace.timestamp)\"\n          />\n          <td class=\"httptraces__trace-method\" v-text=\"trace.request.method\" />\n          <td class=\"httptraces__trace-uri\" v-text=\"trace.request.uri\" />\n          <td class=\"httptraces__trace-status\">\n            <span\n              class=\"tag\"\n              :class=\"{\n                'is-muted': trace.isPending(),\n                'is-success': trace.isSuccess(),\n                'is-warning': trace.isClientError(),\n                'is-danger': trace.isServerError(),\n              }\"\n              v-text=\"trace.response ? trace.response.status : 'pending'\"\n            />\n          </td>\n          <td\n            class=\"httptraces__trace-contentType\"\n            v-text=\"trace.contentTypeRequest\"\n          />\n          <td\n            class=\"httptraces__trace-contentLength\"\n            v-text=\"\n              trace.contentLengthRequest\n                ? prettyBytes(trace.contentLengthRequest)\n                : ''\n            \"\n          />\n          <td\n            class=\"httptraces__trace-contentType\"\n            v-text=\"trace.contentTypeResponse\"\n          />\n          <td\n            class=\"httptraces__trace-contentLength\"\n            v-text=\"\n              trace.contentLengthResponse\n                ? prettyBytes(trace.contentLengthResponse)\n                : ''\n            \"\n          />\n          <td\n            class=\"httptraces__trace-timeTaken\"\n            v-text=\"\n              trace.timeTaken !== null && typeof trace.timeTaken !== 'undefined'\n                ? `${trace.timeTaken} ms`\n                : ''\n            \"\n          />\n        </tr>\n        <tr v-if=\"showDetails[trace.key]\" :key=\"`${trace.key}-detail`\">\n          <td colspan=\"7\">\n            <pre class=\"httptraces__trace-detail\" v-text=\"toJson(trace)\" />\n          </td>\n        </tr>\n      </template>\n      <tr v-if=\"traces.length === 0\" key=\"no-traces\">\n        <td\n          class=\"is-muted\"\n          colspan=\"7\"\n          v-text=\"$t('instances.httptrace.no_traces_found')\"\n        />\n      </tr>\n    </transition-group>\n  </table>\n</template>\n\n<script>\nimport prettyBytes from 'pretty-bytes';\n\nimport { useDateTimeFormatter } from '@/composables/useDateTimeFormatter';\n\nexport default {\n  props: {\n    newTracesCount: {\n      type: Number,\n      default: 0,\n    },\n    traces: {\n      type: Array,\n      default: () => [],\n    },\n  },\n  emits: ['show-new-traces'],\n  setup() {\n    const { formatDateTime } = useDateTimeFormatter();\n\n    return {\n      formatDate: formatDateTime,\n    };\n  },\n  data: () => ({\n    showDetails: {},\n  }),\n  methods: {\n    prettyBytes,\n    toJson(obj) {\n      return JSON.stringify(obj, null, 4);\n    },\n  },\n};\n</script>\n\n<style lang=\"css\">\n.httptraces {\n  table-layout: fixed;\n}\n.httptraces td {\n  vertical-align: middle;\n  overflow: hidden;\n  word-wrap: break-word;\n}\n.httptraces__trace--is-detailed td {\n  border: none !important;\n}\n.httptraces__trace-timestamp {\n  width: 130px;\n}\n.httptraces__trace-method {\n  @apply font-mono;\n  width: 100px;\n}\n.httptraces__trace-uri {\n  width: auto;\n}\n.httptraces__trace-status {\n  @apply font-mono;\n  width: 80px;\n}\n.httptraces__trace-contentType {\n  width: 200px;\n}\n.httptraces__trace-contentLength {\n  width: 100px;\n}\n.httptraces__trace-timeTaken {\n  width: 120px;\n}\n.httptraces__trace-detail {\n  overflow: auto;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/iframe/IframeView.vue",
    "content": "<template>\n  <iframe\n    class=\"flex w-full h-full\"\n    :src=\"params.url\"\n    sandbox=\"allow-scripts allow-same-origin\"\n    referrerpolicy=\"no-referrer\"\n    loading=\"lazy\"\n    title=\"Google\"\n  ></iframe>\n</template>\n\n<script setup lang=\"ts\">\nimport { useRoute } from 'vue-router';\n\nconst { params } = useRoute();\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/iframe/index.ts",
    "content": "import IframeView from '@/views/instances/iframe/IframeView.vue';\n\nexport default {\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      name: 'instances/custom-link-view',\n      parent: 'instances',\n      path: 'custom-link/:url',\n      component: IframeView,\n      label: 'instances.custom-link.label',\n      order: Number.MAX_SAFE_INTEGER,\n      isEnabled: () => false,\n    });\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/jolokia/MBean.ts",
    "content": "import { flatMap, fromPairs } from 'lodash-es';\n\nimport { MBeanDescriptor } from './MBeanDescriptor';\n\nexport class MBean {\n  constructor({ descriptor, op, ...mBean }) {\n    Object.assign(this, mBean);\n    this.descriptor = new MBeanDescriptor(descriptor);\n    const flattenedOps = flatMap(Object.entries(op || {}), ([name, value]) => {\n      if (Array.isArray(value)) {\n        return value.map((v) => [name, v]);\n      } else {\n        return [[name, value]];\n      }\n    }).map(([name, operation]) => [\n      getOperationName(name, operation),\n      operation,\n    ]);\n    this.op = flattenedOps.length > 0 ? fromPairs(flattenedOps) : null;\n  }\n}\n\nconst getOperationName = (name, descriptor) => {\n  const params = descriptor.args.map((arg) => arg.type).join(',');\n  return `${name}(${params})`;\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/jolokia/MBeanDescriptor.ts",
    "content": "export class MBeanDescriptor {\n  constructor(raw) {\n    Object.assign(this, MBeanDescriptor.parse(raw));\n    this.raw = raw;\n  }\n\n  static parse(raw) {\n    const attributes = raw\n      .split(',')\n      .map((attribute) => attribute.split('='))\n      .map(([name, value]) => ({ name, value }));\n    const displayName = attributes\n      .map(({ value }) => value)\n      .join(' ')\n      .trim();\n    return { attributes, displayName };\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/jolokia/i18n.de.json",
    "content": "{\n  \"instances\": {\n    \"jolokia\": {\n      \"label\": \"JMX\",\n      \"close\": \"Schließen\",\n      \"domains\": \"Domains\",\n      \"execute\": \"Ausführen\",\n      \"executing\": \"Führe aus...\",\n      \"execute_modal_header\": \"<code>{name}</code> ausführen?\",\n      \"execution_failed\": \"Ausführung fehlgeschlagen.\",\n      \"execution_successful\": \"Ausführung erfolgreich.\",\n      \"mbeans\": \"MBeans\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/jolokia/i18n.en.json",
    "content": "{\n  \"instances\": {\n    \"jolokia\": {\n      \"label\": \"JMX\",\n      \"close\": \"Close\",\n      \"domains\": \"Domains\",\n      \"execute\": \"Execute\",\n      \"executing\": \"Executing...\",\n      \"execute_modal_header\": \"Execute <code>{name}</code>?\",\n      \"execution_failed\": \"Execution failed.\",\n      \"execution_successful\": \"Execution successful.\",\n      \"mbeans\": \"MBeans\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/jolokia/i18n.es.json",
    "content": "{\n  \"instances\": {\n    \"jolokia\": {\n      \"label\": \"JMX\",\n      \"close\": \"Cerrar\",\n      \"domains\": \"Dominios\",\n      \"execute\": \"Ejecutar\",\n      \"executing\": \"Ejecutando...\",\n      \"execution_failed\": \"Ejecución fallida.\",\n      \"execution_successful\": \"Ejecución exitosa.\",\n      \"mbeans\": \"MBeans\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/jolokia/i18n.fr.json",
    "content": "{\n  \"instances\": {\n    \"jolokia\": {\n      \"label\": \"JMX\",\n      \"close\": \"Fermer\",\n      \"domains\": \"Domaines\",\n      \"execute\": \"Exécuter\",\n      \"executing\": \"Exécution...\",\n      \"execution_failed\": \"Execution en échec.\",\n      \"execution_successful\": \"Execution en succès.\",\n      \"mbeans\": \"MBeans\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/jolokia/i18n.is.json",
    "content": "{\n  \"instances\": {\n    \"jolokia\": {\n      \"label\": \"JMX\",\n      \"close\": \"Ljúka\",\n      \"domains\": \"Lén\",\n      \"execute\": \"Framkvæma\",\n      \"executing\": \"Framkvæma…\",\n      \"execution_failed\": \"Framkvæmd mistekist.\",\n      \"execution_successful\": \"Framkvæmd farsæl.\",\n      \"mbeans\": \"MBeans\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/jolokia/i18n.ko.json",
    "content": "{\n  \"instances\": {\n    \"jolokia\": {\n      \"label\": \"JMX\",\n      \"close\": \"닫기\",\n      \"domains\": \"도메인\",\n      \"execute\": \"실행\",\n      \"executing\": \"실행 중...\",\n      \"execution_failed\": \"실행 실패.\",\n      \"execution_successful\": \"실행 성공.\",\n      \"mbeans\": \"MBeans\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/jolokia/i18n.pt-BR.json",
    "content": "{\n  \"instances\": {\n    \"jolokia\": {\n      \"label\": \"JMX\",\n      \"close\": \"Fechar\",\n      \"domains\": \"Domínios\",\n      \"execute\": \"Executar\",\n      \"executing\": \"Executando...\",\n      \"execution_failed\": \"Falha na execução.\",\n      \"execution_successful\": \"Execução bem sucedida.\",\n      \"mbeans\": \"MBeans\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/jolokia/i18n.ru.json",
    "content": "{\n  \"instances\": {\n    \"jolokia\": {\n      \"label\": \"JMX\",\n      \"close\": \"Закрыть\",\n      \"domains\": \"Домены\",\n      \"execute\": \"Выполнить\",\n      \"executing\": \"Выполнение...\",\n      \"execution_failed\": \"Ошибка при выполнении.\",\n      \"execution_successful\": \"Выполнено успешно.\",\n      \"mbeans\": \"MBeans\",\n      \"execute_modal_header\": \"Выполнить <code>{name}</code>?\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/jolokia/i18n.zh-CN.json",
    "content": "{\n  \"instances\": {\n    \"jolokia\": {\n      \"label\": \"Java管理扩展\",\n      \"close\": \"关闭\",\n      \"domains\": \"域\",\n      \"execute\": \"运行\",\n      \"executing\": \"运行中...\",\n      \"execution_failed\": \"运行失败。\",\n      \"execution_successful\": \"运行成功。\",\n      \"mbeans\": \"可管理资源\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/jolokia/i18n.zh-TW.json",
    "content": "{\n  \"instances\": {\n    \"jolokia\": {\n      \"label\": \"JMX\",\n      \"close\": \"關閉\",\n      \"domains\": \"網域\",\n      \"execute\": \"執行\",\n      \"executing\": \"執行中...\",\n      \"execute_modal_header\": \"確定要執行 <code>{name}</code>？\",\n      \"execution_failed\": \"執行失敗。\",\n      \"execution_successful\": \"執行成功。\",\n      \"mbeans\": \"MBeans\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/jolokia/index.spec.ts",
    "content": "import userEvent from '@testing-library/user-event';\nimport { screen, waitFor } from '@testing-library/vue';\nimport { beforeEach, describe, expect, it } from 'vitest';\n\nimport { applications } from '@/mocks/applications/data';\nimport Application from '@/services/application';\nimport { render } from '@/test-utils';\nimport Jolokia from '@/views/instances/jolokia/index.vue';\n\ndescribe('Jolokia', () => {\n  beforeEach(async () => {\n    const application = new Application(applications[0]);\n    const instance = application.instances[0];\n\n    render(Jolokia, {\n      props: {\n        application,\n        instance,\n      },\n    });\n  });\n\n  it('DefaultDomain is selected as default', async () => {\n    const button = await screen.findByRole('button', {\n      name: 'DefaultDomain',\n    });\n    await waitFor(() => {\n      expect(button).toHaveClass('is-active');\n    });\n  });\n\n  it('click on specific domain opens JMX Beans list', async () => {\n    const button = await screen.findByRole('button', {\n      name: 'com.codecentric.boot.sample',\n    });\n    await userEvent.click(button);\n\n    await waitFor(async () => {\n      expect(await screen.findByText('StringMapManagedBean')).toBeVisible();\n      expect(\n        await screen.findByText('StringMapManagedBean.StringSetter'),\n      ).toBeVisible();\n    });\n  });\n\n  describe('Attributes', () => {\n    beforeEach(async () => {\n      const button = await screen.findByRole('button', {\n        name: 'com.codecentric.boot.sample',\n      });\n      await userEvent.click(button);\n\n      await userEvent.click(await screen.findByText('stringMapManagedBean'));\n    });\n\n    it('opens attributes on default after click on bean header', async () => {\n      const inputForTest = await screen.findByLabelText('Test');\n      expect(inputForTest).toBeDisabled();\n    });\n\n    it('allows to edit writeable attributes', async () => {\n      const inputForTest = await screen.findByText('Test');\n      await userEvent.dblClick(inputForTest);\n      expect(inputForTest).toBeEnabled();\n    });\n  });\n\n  describe('Operations', () => {\n    it('allows to open tab to display operations', async () => {\n      const button = await screen.findByRole('button', {\n        name: 'com.codecentric.boot.sample',\n      });\n      await userEvent.click(button);\n\n      await userEvent.click(await screen.findByText('stringMapManagedBean'));\n      await userEvent.click(await screen.findByText('Operations'));\n\n      const inputForTest = await screen.findByText('getSize()');\n      expect(inputForTest).toBeVisible();\n    });\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/jolokia/index.vue",
    "content": "<!--\n  - Copyright 2014-2019 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-instance-section\n    :error=\"error\"\n    :layout-options=\"{ noMargin: true, isFlex: true }\"\n    :loading=\"!hasLoaded\"\n    class=\"bg-white h-full\"\n  >\n    <nav class=\"w-1/4 p-2\">\n      <div class=\"nav\">\n        <button\n          v-for=\"domain in domains\"\n          :key=\"domain.domain\"\n          :class=\"{ 'is-active': domain === selectedDomain }\"\n          class=\"nav-item\"\n          @click=\"select(domain)\"\n          v-text=\"domain.domain\"\n        />\n      </div>\n    </nav>\n    <div class=\"flex-1 overflow-y-scroll bg-white p-2\">\n      <div class=\"flex h-full items-start\">\n        <div\n          v-if=\"selectedDomain\"\n          :title=\"selectedDomain.domain\"\n          class=\"flex-1 gap-1 grid grid-cols-1\"\n        >\n          <div\n            v-for=\"mBean in selectedDomain.mBeans\"\n            :id=\"mBean.descriptor.raw\"\n            :key=\"mBean.descriptor.raw\"\n            class=\"m-bean\"\n          >\n            <header\n              :class=\"{\n                'is-primary': mBean === selectedMBean,\n                'is-selectable': mBean !== selectedMBean,\n              }\"\n              class=\"m-bean--header hero\"\n              @click=\"select(selectedDomain, mBean)\"\n            >\n              <sba-icon-button\n                v-if=\"mBean === selectedMBean\"\n                :icon=\"['far', 'times-circle']\"\n                class=\"m-bean--header--close\"\n                @click.stop=\"select(selectedDomain)\"\n              />\n              <dl class=\"m-bean-attributes\">\n                <div\n                  v-for=\"attribute in mBean.descriptor.attributes\"\n                  :key=\"`mBean-desc-${attribute.name}`\"\n                >\n                  <dt v-text=\"attribute.name\" />\n                  <dd v-text=\"attribute.value\" />\n                </div>\n              </dl>\n            </header>\n            <div class=\"relative\">\n              <div v-if=\"mBean === selectedMBean\" class=\"tabs\">\n                <ul class=\"nav-tabs\">\n                  <li v-if=\"mBean.attr\">\n                    <a\n                      :class=\"{ 'is-active': selected.view === 'attributes' }\"\n                      class=\"nav-item\"\n                      @click.stop=\"\n                        select(selectedDomain, selectedMBean, 'attributes')\n                      \"\n                      v-text=\"$t('term.attributes')\"\n                    />\n                  </li>\n                  <li v-if=\"mBean.op\">\n                    <a\n                      :class=\"{ 'is-active': selected.view === 'operations' }\"\n                      class=\"nav-item\"\n                      @click.stop=\"\n                        select(selectedDomain, selectedMBean, 'operations')\n                      \"\n                      v-text=\"$t('term.operations')\"\n                    />\n                  </li>\n                </ul>\n              </div>\n\n              <div v-if=\"mBean === selectedMBean\" class=\"mt-3 mx-1\">\n                <m-bean-attributes\n                  v-if=\"selected.view === 'attributes'\"\n                  :application=\"application\"\n                  :domain=\"selectedDomain.domain\"\n                  :instance=\"instance\"\n                  :m-bean=\"mBean\"\n                />\n                <m-bean-operations\n                  v-if=\"selected.view === 'operations'\"\n                  :application=\"application\"\n                  :domain=\"selectedDomain.domain\"\n                  :instance=\"instance\"\n                  :m-bean=\"mBean\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </sba-instance-section>\n</template>\n\n<script>\nimport { isEmpty, sortBy } from 'lodash-es';\nimport { directive as onClickaway } from 'vue3-click-away';\n\nimport Application from '@/services/application';\nimport Instance from '@/services/instance';\nimport { VIEW_GROUP } from '@/views/ViewGroup';\nimport { MBean } from '@/views/instances/jolokia/MBean';\nimport mBeanAttributes from '@/views/instances/jolokia/m-bean-attributes';\nimport mBeanOperations from '@/views/instances/jolokia/m-bean-operations';\nimport SbaInstanceSection from '@/views/instances/shell/sba-instance-section';\n\nexport default {\n  components: {\n    SbaInstanceSection,\n    mBeanOperations,\n    mBeanAttributes,\n  },\n  directives: { onClickaway },\n  props: {\n    application: {\n      type: Application,\n      required: true,\n    },\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  data: () => ({\n    hasLoaded: false,\n    error: null,\n    domains: [],\n    selected: {\n      domain: null,\n      mBean: null,\n      view: null,\n    },\n  }),\n  computed: {\n    selectedDomain() {\n      return this.domains.find((d) => d.domain === this.selected.domain);\n    },\n    selectedMBean() {\n      return (\n        this.selectedDomain &&\n        this.selectedDomain.mBeans.find(\n          (b) => b.descriptor.raw === this.selected.mBean,\n        )\n      );\n    },\n  },\n  watch: {\n    $route: {\n      immediate: true,\n      handler() {\n        if (this.$route.name === 'instances/jolokia') {\n          if (!isEmpty(this.$route.query)) {\n            this.selected = this.$route.query;\n          } else if (this.domains.length > 0) {\n            this.select(this.domains[0]);\n          }\n        }\n      },\n    },\n    async selectedMBean(newVal) {\n      if (newVal) {\n        if (document.getElementById(newVal.descriptor.raw)?.scrollIntoView) {\n          document.getElementById(newVal.descriptor.raw)?.scrollIntoView();\n        }\n      }\n    },\n  },\n  created() {\n    this.fetchMBeans();\n  },\n  methods: {\n    async fetchMBeans() {\n      this.error = null;\n      try {\n        const res = await this.instance.listMBeans();\n        const domains = sortBy(res.data, [(d) => d.domain]);\n        this.domains = domains.map((domain) => ({\n          ...domain,\n          mBeans: sortBy(\n            domain.mBeans.map((mBean) => new MBean(mBean)),\n            [(b) => b.descriptor.displayName],\n          ),\n        }));\n        if (!this.selectedDomain && this.domains.length > 0) {\n          this.select(this.domains[0]);\n        }\n      } catch (error) {\n        console.warn('Fetching MBeans failed:', error);\n        this.error = error;\n      }\n      this.hasLoaded = true;\n    },\n    select(domain, mBean, view) {\n      const selected = {\n        domain: domain && domain.domain,\n        mBean: mBean && mBean.descriptor.raw,\n        view:\n          view ||\n          (mBean\n            ? mBean.attr\n              ? 'attributes'\n              : mBean.op\n                ? 'operations'\n                : null\n            : null),\n      };\n      this.$router.replace({\n        name: 'instances/jolokia',\n        query: selected,\n      });\n    },\n  },\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      name: 'instances/jolokia',\n      parent: 'instances',\n      path: 'jolokia',\n      label: 'instances.jolokia.label',\n      component: this,\n      group: VIEW_GROUP.JVM,\n      order: 350,\n      isEnabled: ({ instance }) => instance.hasEndpoint('jolokia'),\n    });\n  },\n};\n</script>\n\n<style lang=\"scss\" scoped>\n.tabs {\n  @apply text-sm font-medium text-center text-gray-500 border-b border-gray-200 dark:text-gray-400 dark:border-gray-700;\n\n  .nav-tabs {\n    @apply flex flex-wrap -mb-px;\n\n    .nav-item {\n      @apply cursor-pointer inline-block p-4 border-b-2 border-transparent rounded-t-lg hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300;\n\n      &.is-active {\n        @apply inline-block p-4 text-blue-600 border-b-2 border-blue-600 rounded-t-lg dark:text-blue-500 dark:border-blue-500;\n      }\n    }\n  }\n}\n\n.m-bean {\n  transition: all ease-out 86ms;\n  @apply relative p-2 rounded border;\n  @apply bg-white;\n}\n\n.m-bean:hover {\n}\n\n.m-bean.is-active {\n  margin: 0.75rem -0.75rem;\n  max-width: unset;\n}\n\n.m-bean.is-active .m-bean--header {\n  padding-bottom: 0;\n}\n\n.m-bean--header {\n  cursor: pointer;\n}\n\n.m-bean--header .level .level-left {\n  width: 100%;\n}\n\n.m-bean--header .level .level-left .level-item {\n  min-width: 0;\n  flex-shrink: 1;\n}\n\n.m-bean--header .level .level-left .level-item p {\n  overflow: hidden;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n}\n\n.m-bean--header--close {\n  position: absolute;\n  right: 0;\n  top: 0;\n}\n\n.m-bean-attributes {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 20px;\n\n  dt {\n    @apply text-xs text-gray-500;\n  }\n}\n\n.nav {\n  @apply flex flex-col;\n\n  .nav-item {\n    @apply px-2 py-1 rounded w-full text-left;\n\n    &:nth-child(odd) {\n      @apply bg-gray-50;\n    }\n\n    &.is-active {\n      @apply font-bold;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/jolokia/m-bean-attribute.spec.ts",
    "content": "/*\n * Copyright 2014-2025 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport userEvent from '@testing-library/user-event';\nimport { screen, waitFor } from '@testing-library/vue';\nimport { describe, expect, it, vi } from 'vitest';\n\nimport { render } from '@/test-utils';\nimport MBeanAttribute from '@/views/instances/jolokia/m-bean-attribute.vue';\n\ndescribe('m-bean-attribute.vue', () => {\n  const baseProps = {\n    name: 'testAttribute',\n    descriptor: { desc: 'Test attribute description', rw: true },\n    value: 'initialValue',\n    onSaveValue: vi.fn(),\n  };\n\n  it('renders input when value is primitive and not complex', async () => {\n    render(MBeanAttribute, { props: baseProps });\n\n    expect(screen.getByLabelText('testAttribute')).toBeInTheDocument();\n    expect(screen.queryByRole('textbox', { name: /textarea/i })).toBeNull();\n    expect(screen.getByRole('button')).toBeEnabled();\n  });\n\n  it('renders readonly textarea when value is complex object', async () => {\n    const complexProps = {\n      ...baseProps,\n      value: { foo: 'bar' },\n    };\n    render(MBeanAttribute, { props: complexProps });\n\n    expect(screen.getByLabelText('testAttribute')).toBeInTheDocument();\n    expect(screen.getByRole('textbox')).toHaveValue(\n      JSON.stringify(complexProps.value, null, 4),\n    );\n    expect(screen.getByRole('textbox')).toHaveAttribute('readonly');\n    expect(screen.getByRole('textbox')).toBeDisabled();\n    expect(screen.queryByRole('button')).toBeNull();\n  });\n\n  it('enables editing mode on edit button click if descriptor.rw is true and value is not complex', async () => {\n    render(MBeanAttribute, { props: baseProps });\n\n    const editButton = screen.getByRole('button');\n    await userEvent.click(editButton);\n\n    const input = screen.getByLabelText('testAttribute');\n    expect(input).not.toHaveAttribute('disabled');\n    expect(input).not.toHaveAttribute('readonly');\n\n    expect(screen.getByText('Cancel')).toBeInTheDocument();\n    expect(screen.getByText('Save')).toBeInTheDocument();\n  });\n\n  it('does not enable editing mode if descriptor.rw is false', async () => {\n    const props = {\n      ...baseProps,\n      descriptor: { ...baseProps.descriptor, rw: false },\n    };\n    render(MBeanAttribute, { props });\n\n    const editButton = screen.getByRole('button');\n    await userEvent.click(editButton);\n\n    const input = screen.getByLabelText('testAttribute');\n    expect(input).toHaveAttribute('disabled');\n    expect(input).toHaveAttribute('readonly');\n\n    expect(screen.queryByText('Cancel')).toBeNull();\n    expect(screen.queryByText('Save')).toBeNull();\n  });\n\n  it('cancels editing mode on cancel button click', async () => {\n    render(MBeanAttribute, { props: baseProps });\n\n    const editButton = screen.getByRole('button');\n    await userEvent.click(editButton);\n\n    const cancelButton = screen.getByText('Cancel');\n    await userEvent.click(cancelButton);\n\n    const input = screen.getByLabelText('testAttribute');\n    expect(input).toHaveAttribute('disabled');\n    expect(input).toHaveAttribute('readonly');\n\n    expect(screen.queryByText('Cancel')).toBeNull();\n    expect(screen.queryByText('Save')).toBeNull();\n  });\n\n  it('calls onSaveValue and disables editing mode on save', async () => {\n    const onSaveValue = vi\n      .fn()\n      .mockResolvedValue({ data: { value: 'ok', status: 200 }, error: null });\n    render(MBeanAttribute, { props: { ...baseProps, onSaveValue } });\n\n    const editButton = screen.getByRole('button');\n    await userEvent.click(editButton);\n\n    const input = screen.getByLabelText('testAttribute');\n    await userEvent.clear(input);\n    await userEvent.type(input, 'newValue');\n\n    const saveButton = screen.getByText('Save');\n    await userEvent.click(saveButton);\n\n    await waitFor(() => {\n      expect(onSaveValue).toHaveBeenCalled();\n    });\n\n    expect(screen.getByLabelText('testAttribute')).toHaveAttribute('disabled');\n    expect(screen.getByLabelText('testAttribute')).toHaveAttribute('readonly');\n  });\n\n  it('displays error if onSaveValue throws', async () => {\n    const error = new Error('save failed');\n    const onSaveValue = vi.fn().mockRejectedValue(error);\n    render(MBeanAttribute, { props: { ...baseProps, onSaveValue } });\n\n    const editButton = screen.getByRole('button');\n    await userEvent.click(editButton);\n\n    const input = screen.getByLabelText('testAttribute');\n    await userEvent.type(input, 'newValue');\n\n    const saveButton = screen.getByText('Save');\n    await userEvent.click(saveButton);\n\n    await waitFor(() => {\n      expect(onSaveValue).toHaveBeenCalled();\n    });\n  });\n\n  it('disables save button if input equals value', async () => {\n    render(MBeanAttribute, { props: baseProps });\n\n    const editButton = screen.getByRole('button');\n    await userEvent.click(editButton);\n\n    const saveButton = screen.getByText('Save');\n    expect(saveButton).toBeDisabled();\n  });\n\n  it('updates input when prop value changes', async () => {\n    const { rerender } = render(MBeanAttribute, { props: baseProps });\n\n    expect(screen.getByLabelText('testAttribute')).toHaveValue('initialValue');\n\n    await rerender({ value: 'newValue' });\n\n    expect(screen.getByLabelText('testAttribute')).toHaveValue('newValue');\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/jolokia/m-bean-attribute.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div class=\"flex items-center gap-1\">\n    <sba-input\n      v-if=\"!hasComplexValue\"\n      ref=\"inputRef\"\n      v-model=\"input\"\n      :disabled=\"!editing\"\n      :error=\"error\"\n      :hint=\"descriptor.desc\"\n      :label=\"name\"\n      :name=\"name\"\n      :readonly=\"!editing\"\n      :title=\"input\"\n      class=\"flex-1\"\n      @dblclick=\"edit\"\n      @keyup.esc=\"cancel\"\n      @keyup.enter=\"save\"\n    >\n      <template #prepend>\n        <button :disabled=\"!descriptor.rw\" class=\"button\" @click=\"edit\">\n          <font-awesome-icon v-if=\"descriptor.rw\" icon=\"pencil-alt\" />\n          <font-awesome-icon v-else icon=\"eye\" />\n        </button>\n      </template>\n    </sba-input>\n    <div v-else class=\"flex-1\">\n      <label\n        :for=\"`${name}-textarea`\"\n        class=\"block text-sm font-medium text-gray-700\"\n        v-text=\"name\"\n      ></label>\n      <div class=\"mt-1 flex\">\n        <textarea\n          :id=\"`${name}-textarea`\"\n          v-model=\"jsonValue\"\n          :name=\"name\"\n          readonly\n          disabled\n          class=\"flex-1 m-bean-attribute--text\"\n        />\n      </div>\n      <div class=\"py-2\">\n        <div class=\"text-xs text-gray-500\" v-text=\"descriptor.desc\"></div>\n      </div>\n    </div>\n\n    <sba-button-group v-if=\"editing\">\n      <sba-button @click=\"cancel\">\n        {{ $t('term.cancel') }}\n      </sba-button>\n      <sba-button\n        :class=\"{ 'is-loading': saving }\"\n        :disabled=\"value === input\"\n        primary\n        @click=\"save\"\n      >\n        {{ $t('term.save') }}\n      </sba-button>\n    </sba-button-group>\n  </div>\n</template>\n\n<script>\nimport { responseHandler } from '@/views/instances/jolokia/responseHandler';\n\nexport default {\n  props: {\n    name: {\n      type: String,\n      required: true,\n    },\n    descriptor: {\n      type: Object,\n      required: true,\n    },\n    value: {\n      type: [Object, String, Number, null],\n      required: true,\n    },\n    onSaveValue: {\n      type: Function,\n      required: true,\n    },\n  },\n  data() {\n    return {\n      input: this.value,\n      editing: false,\n      saving: false,\n      error: null,\n    };\n  },\n  computed: {\n    hasComplexValue() {\n      return this.value !== null && typeof this.value === 'object';\n    },\n    jsonValue() {\n      return JSON.stringify(this.value, null, 4);\n    },\n  },\n  watch: {\n    value(val) {\n      this.input = val;\n    },\n  },\n  methods: {\n    async edit() {\n      if (this.descriptor.rw && !this.hasComplexValue) {\n        this.editing = true;\n        await this.$nextTick();\n        this.$refs.inputRef.focus?.();\n      }\n    },\n    cancel() {\n      this.editing = false;\n    },\n    async save() {\n      this.saving = true;\n      try {\n        let response = await this.onSaveValue(this.input);\n        const { result, error } = responseHandler(response);\n        this.result = result;\n        this.error = error;\n        this.editing = false;\n      } catch (error) {\n        console.warn(`Error saving attribute ${this.name}`, error);\n        this.error = error;\n      } finally {\n        this.saving = false;\n      }\n    },\n  },\n};\n</script>\n\n<style lang=\"css\" scoped>\n.m-bean-attribute--text {\n  resize: vertical;\n  min-height: 120px;\n  cursor: not-allowed;\n  @apply rounded shadow-sm border-gray-300;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/jolokia/m-bean-attributes.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div>\n    <div v-if=\"loading\" class=\"mt-4 mb-2\">\n      <sba-loading-spinner />\n    </div>\n    <template v-else>\n      <div\n        v-if=\"application.instances.length > 1\"\n        class=\"absolute right-0 top-0\"\n      >\n        <sba-toggle-scope-button\n          v-model=\"scope\"\n          :instance-count=\"application.instances.length\"\n          class=\"bg-white px-4 py-2 pt-3\"\n        />\n      </div>\n\n      <sba-alert v-if=\"error\" :error=\"error\" :title=\"$t('term.fetch_failed')\" />\n\n      <m-bean-attribute\n        v-for=\"(attribute, name) in mBean.attr\"\n        :key=\"`attr-${name}`\"\n        :descriptor=\"attribute\"\n        :name=\"name\"\n        :on-save-value=\"(value) => writeAttribute(name, value)\"\n        :value=\"attributeValues && attributeValues[name]\"\n      />\n    </template>\n  </div>\n</template>\n\n<script>\nimport Application from '@/services/application';\nimport Instance from '@/services/instance';\nimport { MBean } from '@/views/instances/jolokia/MBean';\nimport mBeanAttribute from '@/views/instances/jolokia/m-bean-attribute';\n\nexport default {\n  components: { mBeanAttribute },\n  props: {\n    domain: {\n      type: String,\n      required: true,\n    },\n    mBean: {\n      type: MBean,\n      required: true,\n    },\n    instance: {\n      type: Instance,\n      required: true,\n    },\n    application: {\n      type: Application,\n      required: true,\n    },\n  },\n  data: () => ({\n    attributeValues: null,\n    error: null,\n    scope: 'instance',\n    loading: true,\n  }),\n  created() {\n    this.readAttributes();\n  },\n  methods: {\n    async readAttributes() {\n      try {\n        this.loading = true;\n        const response = await this.instance.readMBeanAttributes(\n          this.domain,\n          this.mBean.descriptor.raw,\n        );\n        this.attributeValues = response.data.value;\n      } catch (error) {\n        console.warn('Fetching MBean attributes failed:', error);\n        this.error = error;\n      } finally {\n        this.loading = false;\n      }\n    },\n    writeAttribute: async function (attribute, value) {\n      try {\n        const target =\n          this.scope === 'instance' ? this.instance : this.application;\n        return await target.writeMBeanAttribute(\n          this.domain,\n          this.mBean.descriptor.raw,\n          attribute,\n          value,\n        );\n      } catch (error) {\n        console.warn(`Error saving attribute ${attribute}`, error);\n      }\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/jolokia/m-bean-operation-invocation.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-modal :model-value=\"showModal\" @close=\"abort\">\n    <template #header>\n      {{ state }}\n      <template v-if=\"state === STATE_COMPLETED\">\n        {{ name }} -\n        {{ $t('instances.jolokia.execution_successful') }}\n      </template>\n      <template v-else>\n        {{ name }}\n      </template>\n    </template>\n\n    <template #body>\n      <template v-if=\"state === STATE_INPUT_ARGS\">\n        <section @keyup.ctrl.enter=\"invoke(args)\">\n          <template v-for=\"(arg, idx) in descriptor.args\" :key=\"arg.name\">\n            <sba-input\n              v-model=\"args[idx]\"\n              :hint=\"arg.desc !== arg.name ? arg.desc : undefined\"\n              class=\"mb-1\"\n            >\n              <template #prepend>\n                <span v-text=\"arg.name\" />:&nbsp;\n                <small v-text=\"arg.type\" />\n              </template>\n            </sba-input>\n          </template>\n        </section>\n      </template>\n\n      <template v-else-if=\"state === STATE_EXECUTING\">\n        <sba-loading-spinner />\n      </template>\n\n      <template v-else-if=\"state === STATE_COMPLETED\">\n        <pre\n          v-if=\"descriptor.ret !== 'void'\"\n          class=\"overflow-auto text-xs\"\n          v-text=\"prettyPrintedResult\"\n        />\n      </template>\n\n      <template v-else-if=\"state === STATE_FAILED\">\n        <div class=\"p-2 mb-2 rounded bg-sba-100\">\n          <strong>\n            <font-awesome-icon class=\"pr-1\" icon=\"exclamation-triangle\" />\n            <span v-text=\"$t('instances.jolokia.execution_failed')\" />\n          </strong>\n          <p v-text=\"error.message\" />\n        </div>\n        <code class=\"text-xs\">\n          <pre v-if=\"error.stacktrace\" v-text=\"error.stacktrace\" />\n          <pre\n            v-if=\"error.response && error.response.data\"\n            v-text=\"error.response.data\"\n          />\n        </code>\n      </template>\n    </template>\n\n    <template #footer>\n      <template v-if=\"state === STATE_INPUT_ARGS\">\n        <div class=\"flex flex-row gap-1\">\n          <sba-button primary @click=\"invoke(args)\">\n            {{ $t('instances.jolokia.execute') }}\n          </sba-button>\n          <sba-button @click=\"abort\">\n            {{ $t('term.cancel') }}\n          </sba-button>\n        </div>\n      </template>\n\n      <template v-else-if=\"state === STATE_COMPLETED\">\n        <sba-button primary @click=\"abort\">\n          {{ $t('term.close') }}\n        </sba-button>\n      </template>\n\n      <template v-else-if=\"state === STATE_FAILED\">\n        <sba-button primary @click=\"abort\">\n          {{ $t('instances.jolokia.close') }}\n        </sba-button>\n      </template>\n    </template>\n  </sba-modal>\n</template>\n\n<script>\nimport {\n  STATE_COMPLETED,\n  STATE_EXECUTING,\n  STATE_FAILED,\n  STATE_INPUT_ARGS,\n  STATE_PREPARED,\n  responseHandler,\n} from '@/views/instances/jolokia/responseHandler';\n\nexport default {\n  props: {\n    name: {\n      type: String,\n      required: true,\n    },\n    descriptor: {\n      type: Object,\n      required: true,\n    },\n    value: {\n      type: null,\n      default: null,\n    },\n    onClose: {\n      type: Function,\n      required: true,\n    },\n    onExecute: {\n      type: Function,\n      required: true,\n    },\n  },\n  data: () => ({\n    state: null,\n    error: null,\n    args: null,\n    result: null,\n    showModal: true,\n    STATE_EXECUTING,\n    STATE_FAILED,\n    STATE_INPUT_ARGS,\n    STATE_PREPARED,\n    STATE_COMPLETED,\n  }),\n  computed: {\n    prettyPrintedResult() {\n      if (this.result && typeof this.result === 'string') {\n        try {\n          const o = JSON.parse(this.result);\n          return JSON.stringify(o, undefined, 4);\n        } catch {\n          return this.result;\n        }\n      } else if (typeof result === 'object') {\n        return JSON.stringify(this.result, undefined, 4);\n      }\n      return this.result;\n    },\n  },\n  created() {\n    this.invoke();\n  },\n  mounted() {\n    document.addEventListener('keyup', this.keyHandler);\n  },\n  beforeUnmount() {\n    document.removeEventListener('keyup', this.keyHandler);\n  },\n  methods: {\n    abort() {\n      this.showModal = false;\n      this.onClose();\n    },\n    invoke(args) {\n      this.state =\n        args || this.descriptor.args.length === 0\n          ? STATE_PREPARED\n          : STATE_INPUT_ARGS;\n      this.args = args || new Array(this.descriptor.args.length);\n      this.error = null;\n      this.result = null;\n\n      if (this.state === STATE_PREPARED) {\n        this.execute();\n      }\n    },\n    async execute() {\n      this.state = STATE_EXECUTING;\n      try {\n        const response = await this.onExecute(this.args);\n        const { result, state, error } = responseHandler(response);\n        this.result = result;\n        this.state = state;\n        this.error = error;\n      } catch (error) {\n        this.state = STATE_FAILED;\n        this.error = error;\n        console.warn('Invocation failed', error);\n      }\n    },\n    keyHandler(event) {\n      if (event.keyCode === 27) {\n        this.abort();\n      }\n    },\n  },\n};\n</script>\n\n<style scoped>\n.modal-card-title {\n  word-break: break-all;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/jolokia/m-bean-operation.spec.ts",
    "content": "/*\n * Copyright 2014-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport userEvent from '@testing-library/user-event';\nimport { screen, waitFor } from '@testing-library/vue';\nimport { describe, expect, it } from 'vitest';\n\nimport { render } from '@/test-utils';\nimport MBeanOperation from '@/views/instances/jolokia/m-bean-operation.vue';\n\ndescribe('m-bean-operation.vue', () => {\n  it('should open confirmation modal if operation contains no arguments', async () => {\n    const wrapper = render(MBeanOperation, {\n      props: {\n        name: 'doIt()',\n        descriptor: { args: [], ret: 'java.sth.doIt()' },\n      },\n    });\n\n    await userEvent.click(screen.getByText('doIt()'));\n\n    await waitFor(() => {\n      screen.findByRole('dialog');\n    });\n\n    await screen.findAllByTestId('mBeanOperationModal');\n\n    const buttonOK = screen.queryByRole('button', { name: 'OK' });\n    await userEvent.click(buttonOK);\n\n    expect(wrapper.emitted().click).toBeDefined();\n  });\n\n  it('should not open confirmation modal if operation contains arguments', async () => {\n    render(MBeanOperation, {\n      props: {\n        name: 'doIt()',\n        descriptor: { args: [{ property1: '' }], ret: 'java.sth.doIt()' },\n      },\n    });\n\n    await userEvent.click(screen.getByText('doIt()'));\n    await waitFor(() => {\n      screen.findByRole('dialog');\n    });\n\n    expect(screen.queryByTestId('mBeanOperationModal')).toBeNull();\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/jolokia/m-bean-operation.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <button\n    :title=\"$t('instances.jolokia.execute')\"\n    class=\"text-left mb-3 block flex items-center\"\n    @click=\"execute($event)\"\n  >\n    <font-awesome-icon class=\"mr-2 hidden md:block\" icon=\"cogs\" />\n    <div>\n      <span :title=\"name\" v-text=\"shortenedName\" />:\n      <small\n        :title=\"descriptor.ret\"\n        class=\"text-gray-400\"\n        v-text=\"shortenedRet\"\n      />\n      <p\n        v-if=\"showDescription\"\n        class=\"text-gray-400 text-xs mt-1\"\n        v-text=\"descriptor.desc\"\n      />\n    </div>\n  </button>\n\n  <sba-modal v-model=\"isModalOpen\" data-testid=\"mBeanOperationModal\">\n    <template #header>\n      <span\n        v-html=\"\n          $t('instances.jolokia.execute_modal_header', { name: shortenedName })\n        \"\n      />\n    </template>\n    <template #footer>\n      <sba-button @click=\"closeModal\">{{ $t('term.close') }}</sba-button>\n      <sba-button primary @click=\"executeOperation($event)\"\n        >{{ $t('term.ok') }}\n      </sba-button>\n    </template>\n  </sba-modal>\n</template>\n\n<script>\nimport { truncateJavaType } from '@/views/instances/jolokia/utils';\n\nexport default {\n  props: {\n    name: {\n      type: String,\n      required: true,\n    },\n    descriptor: {\n      type: Object,\n      required: true,\n    },\n  },\n  emits: ['click'],\n  data() {\n    return {\n      isModalOpen: false,\n    };\n  },\n  computed: {\n    shortenedName() {\n      return truncateJavaType(this.name);\n    },\n    shortenedRet() {\n      return truncateJavaType(this.descriptor.ret);\n    },\n    showDescription() {\n      let name = this.name.split('(').shift();\n      return name !== this.descriptor.desc;\n    },\n  },\n  methods: {\n    closeModal() {\n      this.isModalOpen = false;\n    },\n    executeOperation(event) {\n      this.$emit('click', event);\n      this.isModalOpen = false;\n    },\n    execute(event) {\n      if (this.descriptor.args.length === 0) {\n        this.isModalOpen = true;\n      } else {\n        this.executeOperation(event);\n      }\n    },\n  },\n};\n</script>\n\n<style>\n.is-truncated {\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/jolokia/m-bean-operations.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div v-if=\"application.instances.length > 1\" class=\"absolute right-0 top-0\">\n    <sba-toggle-scope-button\n      v-model=\"scope\"\n      :instance-count=\"application.instances.length\"\n      class=\"bg-white px-4 py-2 pt-3\"\n    />\n  </div>\n\n  <m-bean-operation\n    v-for=\"(operation, name) in mBean.op\"\n    :key=\"`op-${name}`\"\n    :descriptor=\"operation\"\n    :name=\"name\"\n    @click=\"invoke(name, operation)\"\n  />\n  <m-bean-operation-invocation\n    v-if=\"invocation\"\n    :descriptor=\"invocation.descriptor\"\n    :name=\"invocation.name\"\n    :on-close=\"closeInvocation\"\n    :on-execute=\"execute\"\n  />\n</template>\n\n<script>\nimport Application from '@/services/application';\nimport Instance from '@/services/instance';\nimport { MBean } from '@/views/instances/jolokia/MBean';\nimport mBeanOperation from '@/views/instances/jolokia/m-bean-operation';\nimport mBeanOperationInvocation from '@/views/instances/jolokia/m-bean-operation-invocation';\n\nexport default {\n  components: { mBeanOperation, mBeanOperationInvocation },\n  props: {\n    domain: {\n      type: String,\n      required: true,\n    },\n    mBean: {\n      type: MBean,\n      required: true,\n    },\n    application: {\n      type: Application,\n      required: true,\n    },\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  data: () => ({\n    invocation: null,\n    scope: 'instance',\n  }),\n  methods: {\n    closeInvocation() {\n      this.invocation = null;\n    },\n    invoke(name, descriptor) {\n      this.invocation = { name, descriptor };\n    },\n    execute(args) {\n      const target =\n        this.scope === 'instance' ? this.instance : this.application;\n      return target.invokeMBeanOperation(\n        this.domain,\n        this.mBean.descriptor.raw,\n        this.invocation.name,\n        args,\n      );\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/jolokia/responseHandler.spec.ts",
    "content": "import { describe, expect, it } from 'vitest';\n\nimport {\n  STATE_COMPLETED,\n  STATE_FAILED,\n  responseHandler,\n} from '@/views/instances/jolokia/responseHandler';\n\nconst instanceResultFailure = {\n  data: {\n    request: {\n      mbean: 'org.springframework.boot:name=Auditevents,type=Endpoint',\n      arguments: ['asd', '^12', 'asd'],\n      type: 'exec',\n      operation:\n        'events(java.lang.String,java.time.OffsetDateTime,java.lang.String)',\n    },\n    stacktrace:\n      'java.lang.IllegalArgumentException/java.lang.Thread.run(Thread.java:829)',\n    error_type: 'java.lang.IllegalArgumentException',\n    error:\n      'java.lang.IllegalArgumentException : Cannot convert string ^12 to type java.time.OffsetDateTime because no converter could be found',\n    status: 400,\n  },\n};\n\nconst instanceResultSuccess = {\n  data: {\n    request: {\n      mbean: 'org.springframework.boot:name=Auditevents,type=Endpoint',\n      arguments: ['asd', null, 'asd'],\n      type: 'exec',\n      operation:\n        'events(java.lang.String,java.time.OffsetDateTime,java.lang.String)',\n    },\n    value: { events: [] },\n    timestamp: 1650660516,\n    status: 200,\n  },\n};\n\nconst applicationResultFailed = {\n  data: [\n    {\n      instanceId: 'be5a5af1e545',\n      status: 200,\n      body: '{\"request\":{\"mbean\":\"org.springframework.boot:name=Auditevents,type=Endpoint\",\"arguments\":[null,\"12\",null],\"type\":\"exec\",\"operation\":\"events(java.lang.String,java.time.OffsetDateTime,java.lang.String)\"},\"stacktrace\":\"java.lang.IllegalArgumentException: Cannot convert string ^12 to type java.time.OffsetDateTime because no converter could be found\",\"error_type\":\"java.lang.IllegalArgumentException\",\"error\":\"java.lang.IllegalArgumentException : Cannot convert string ^12 to type java.time.OffsetDateTime because no converter could be found\",\"status\":400}',\n      contentType: 'text/plain;charset=utf-8',\n    },\n    {\n      instanceId: '3153b559eebc',\n      status: 200,\n      body: '{\"request\":{\"mbean\":\"org.springframework.boot:name=Auditevents,type=Endpoint\",\"arguments\":[null,\"12\",null],\"type\":\"exec\",\"operation\":\"events(java.lang.String,java.time.OffsetDateTime,java.lang.String)\"},\"stacktrace\":\"java.lang.IllegalArgumentException: Cannot convert string ^12 to type java.time.OffsetDateTime because no converter could be found\",\"error_type\":\"java.lang.IllegalArgumentException\",\"error\":\"java.lang.IllegalArgumentException : Cannot convert string ^12 to type java.time.OffsetDateTime because no converter could be found\",\"status\":400}',\n      contentType: 'text/plain;charset=utf-8',\n    },\n  ],\n};\n\nconst applicationResultSuccess = {\n  data: [\n    {\n      instanceId: '3153b559eebc',\n      status: 200,\n      body: '{\"request\":{\"mbean\":\"org.springframework.boot:name=Auditevents,type=Endpoint\",\"arguments\":[null,null,null],\"type\":\"exec\",\"operation\":\"events(java.lang.String,java.time.OffsetDateTime,java.lang.String)\"},\"value\":{\"events\":[]},\"timestamp\":1650701249,\"status\":200}',\n      contentType: 'text/plain;charset=utf-8',\n    },\n    {\n      instanceId: 'be5a5af1e545',\n      status: 200,\n      body: '{\"request\":{\"mbean\":\"org.springframework.boot:name=Auditevents,type=Endpoint\",\"arguments\":[null,null,null],\"type\":\"exec\",\"operation\":\"events(java.lang.String,java.time.OffsetDateTime,java.lang.String)\"},\"value\":{\"events\":[]},\"timestamp\":1650701249,\"status\":200}',\n      contentType: 'text/plain;charset=utf-8',\n    },\n  ],\n};\n\ndescribe('responseHandler', () => {\n  describe('On Instance level', () => {\n    it('should succeed when a correct return value is given', () => {\n      const { state, result } = responseHandler(instanceResultSuccess);\n\n      expect(state).toBe(STATE_COMPLETED);\n      expect(result).toEqual({ events: [] });\n    });\n\n    it('should handle jolokia returned errors', () => {\n      const { state, error } = responseHandler(instanceResultFailure);\n\n      expect(state).toBe(STATE_FAILED);\n      expect(error.message).toBe(\n        'Execution failed: java.lang.IllegalArgumentException : Cannot convert string ^12 to type java.time.OffsetDateTime because no converter could be found',\n      );\n    });\n  });\n\n  describe('On Application level', () => {\n    it('should succeed when a correct return value is given', () => {\n      const { state, result } = responseHandler(applicationResultSuccess);\n\n      expect(state).toBe(STATE_COMPLETED);\n      expect(result[0].value).toEqual(expect.objectContaining({ events: [] }));\n      expect(result[1].value).toEqual(expect.objectContaining({ events: [] }));\n    });\n\n    it('should handle jolokia returned errors', () => {\n      const { state, error } = responseHandler(applicationResultFailed);\n\n      expect(state).toBe(STATE_FAILED);\n      expect(error.message).toBe(\n        'Execution failed: java.lang.IllegalArgumentException : Cannot convert string ^12 to type java.time.OffsetDateTime because no converter could be found',\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/jolokia/responseHandler.ts",
    "content": "export const STATE_PREPARED = 'prepared';\nexport const STATE_INPUT_ARGS = 'input-args';\nexport const STATE_EXECUTING = 'executing';\nexport const STATE_FAILED = 'failed';\nexport const STATE_COMPLETED = 'completed';\n\nexport function resultContainsErrorStatus(result) {\n  if (result.status >= 400) {\n    return true;\n  }\n\n  const parsedResponse = parseValue(result.data);\n  if (Array.isArray(parsedResponse)) {\n    return parsedResponse.some((r) => r.status >= 400);\n  } else {\n    return result.data.status >= 400;\n  }\n}\n\nexport function parseValue(data) {\n  if (Array.isArray(data)) {\n    return data.map((elem) => {\n      const parsedBody = JSON.parse(elem['body']);\n      return {\n        instanceId: elem['instanceId'],\n        ...parsedBody,\n      };\n    });\n  } else {\n    return data.value;\n  }\n}\n\nexport function responseHandler(result) {\n  if (resultContainsErrorStatus(result)) {\n    const parsedResult = parseValue(result.data);\n    let failedRequest = result.data;\n\n    // Show first failed request\n    if (Array.isArray(result.data)) {\n      failedRequest = parsedResult[0];\n    }\n\n    const error = new Error(`Execution failed: ${failedRequest.error}`);\n    error.stacktrace = failedRequest.stacktrace;\n    console.warn('Invocation failed', error);\n\n    return {\n      state: STATE_FAILED,\n      error,\n    };\n  } else {\n    return {\n      result: parseValue(result.data),\n      state: STATE_COMPLETED,\n    };\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/jolokia/utils.spec.ts",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { describe, expect, it } from 'vitest';\n\nimport {\n  truncateJavaType,\n  truncatePackageName,\n} from '@/views/instances/jolokia/utils';\n\ndescribe('utils', () => {\n  it('truncateJavaType', () => {\n    expect(truncateJavaType('java.lang.String')).toEqual('String');\n  });\n\n  it.each`\n    length | expected\n    ${0}   | ${'Bar'}\n    ${5}   | ${'m.s.s.Bar'}\n    ${10}  | ${'m.s.s.Bar'}\n    ${15}  | ${'m.s.sample.Bar'}\n    ${16}  | ${'m.sub.sample.Bar'}\n    ${26}  | ${'mainPackage.sub.sample.Bar'}\n  `('truncatePackageName having length $length', ({ expected, length }) => {\n    const result = truncatePackageName('mainPackage.sub.sample.Bar', length);\n    expect(result).toEqual(expected);\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/jolokia/utils.ts",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport function truncateJavaType(javaType) {\n  return javaType.replace(/java\\.[^A-Z]*/g, '');\n}\n\n/**\n * Truncates the given package name to the given length.\n *\n * It works similar to {@link https://logback.qos.ch/manual/layouts.html#conversionWord}\n *\n * @param javaType\n * @param length\n * @returns {string|*}\n */\nexport function truncatePackageName(javaType, length) {\n  const split = javaType.split('.');\n  if (length > 0) {\n    const clazzName = split.pop();\n\n    let shortened;\n    for (let i = 0; i <= split.length; i++) {\n      shortened = [\n        ...[...split].splice(0, i).map((p) => p.charAt(0)),\n        ...[...split].splice(i),\n        clazzName,\n      ].join('.');\n\n      if (shortened.length <= length) {\n        return shortened;\n      }\n    }\n\n    return shortened;\n  } else {\n    return split?.pop();\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/liquibase/i18n.de.json",
    "content": "{\n  \"instances\": {\n    \"liquibase\": {\n      \"label\": \"Liquibase\",\n      \"author\": \"Autor\",\n      \"changelog\": \"Changelog\",\n      \"checksum\": \"Prüfsumme\",\n      \"comments\": \"Kommentare\",\n      \"contexts\": \"Kontexte\",\n      \"deployment_id\": \"Deployment Id\",\n      \"description\": \"Beschreibung\",\n      \"execution\": \"Ausführung\",\n      \"execution_date\": \"Ausführungs-Datum\",\n      \"execution_order\": \"Reihenfolge\",\n      \"id\": \"Id\",\n      \"labels\": \"Label\",\n      \"tag\": \"Tag\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/liquibase/i18n.en.json",
    "content": "{\n  \"instances\": {\n    \"liquibase\": {\n      \"label\": \"Liquibase\",\n      \"author\": \"Author\",\n      \"changelog\": \"Changelog\",\n      \"checksum\": \"Checksum\",\n      \"comments\": \"Comments\",\n      \"contexts\": \"Contexts\",\n      \"deployment_id\": \"Deployment Id\",\n      \"description\": \"Description\",\n      \"execution\": \"Execution\",\n      \"execution_date\": \"Execution Date\",\n      \"execution_order\": \"Execution Order\",\n\n      \"id\": \"Id\",\n      \"labels\": \"Labels\",\n      \"tag\": \"Tag\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/liquibase/i18n.es.json",
    "content": "{\n  \"instances\": {\n    \"liquibase\": {\n      \"label\": \"Liquibase\",\n      \"author\": \"Autor\",\n      \"changelog\": \"Registro de cambios\",\n      \"checksum\": \"Checksum\",\n      \"comments\": \"Comentarios\",\n      \"contexts\": \"Contextos\",\n      \"deployment_id\": \"Despliegue Id\",\n      \"description\": \"Descripción\",\n      \"execution\": \"Ejecución\",\n      \"execution_date\": \"Fecha de Ejecución\",\n      \"execution_order\": \"Órden de Ejecución\",\n\n      \"id\": \"Id\",\n      \"labels\": \"Etiquetas\",\n      \"tag\": \"Tag\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/liquibase/i18n.fr.json",
    "content": "{\n  \"instances\": {\n    \"liquibase\": {\n      \"label\": \"Liquibase\",\n      \"author\": \"Author\",\n      \"changelog\": \"Changelog\",\n      \"checksum\": \"Checksum\",\n      \"comments\": \"Comments\",\n      \"contexts\": \"Contexts\",\n      \"deployment_id\": \"Deployment Id\",\n      \"description\": \"Description\",\n      \"execution\": \"Execution\",\n      \"execution_date\": \"Execution Date\",\n      \"execution_order\": \"Execution Order\",\n\n      \"id\": \"Id\",\n      \"labels\": \"Labels\",\n      \"tag\": \"Tag\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/liquibase/i18n.is.json",
    "content": "{\n  \"instances\": {\n    \"liquibase\": {\n      \"label\": \"Liquibase\",\n      \"author\": \"Höfundur\",\n      \"changelog\": \"Breytingaferill\",\n      \"checksum\": \"Prófsumma\",\n      \"comments\": \"Athugasemdir\",\n      \"contexts\": \"Samhengi\",\n      \"deployment_id\": \"Deployment Id\",\n      \"description\": \"Lýsing\",\n      \"execution\": \"Framkvæmd\",\n      \"execution_date\": \"Dagsetning framkvæmdarinnar\",\n      \"execution_order\": \"Röð framkvæmdarinnar\",\n\n      \"id\": \"Id\",\n      \"labels\": \"Merkimiðar\",\n      \"tag\": \"Merki\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/liquibase/i18n.ko.json",
    "content": "{\n  \"instances\": {\n    \"liquibase\": {\n      \"label\": \"Liquibase\",\n      \"author\": \"작성자\",\n      \"changelog\": \"변경이력\",\n      \"checksum\": \"체크섬\",\n      \"comments\": \"코멘트\",\n      \"contexts\": \"컨텍스트\",\n      \"deployment_id\": \"배포 Id\",\n      \"description\": \"비고\",\n      \"execution\": \"실행\",\n      \"execution_date\": \"실행 일시\",\n      \"execution_order\": \"실행 순서\",\n\n      \"id\": \"아이디\",\n      \"labels\": \"라벨\",\n      \"tag\": \"태그\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/liquibase/i18n.pt-BR.json",
    "content": "{\n  \"instances\": {\n    \"liquibase\": {\n      \"label\": \"Liquibase\",\n      \"author\": \"Autor\",\n      \"changelog\": \"Changelog\",\n      \"checksum\": \"Checksum\",\n      \"comments\": \"Comentários\",\n      \"contexts\": \"Contextos\",\n      \"deployment_id\": \"ID de implantação\",\n      \"description\": \"Descrição\",\n      \"execution\": \"Execução\",\n      \"execution_date\": \"Data da Execução\",\n      \"execution_order\": \"Ordem de Execução\",\n\n      \"id\": \"Id\",\n      \"labels\": \"Labels\",\n      \"tag\": \"Tag\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/liquibase/i18n.ru.json",
    "content": "{\n  \"instances\": {\n    \"liquibase\": {\n      \"label\": \"Liquibase\",\n      \"author\": \"Автор\",\n      \"changelog\": \"Журнал изменений\",\n      \"checksum\": \"Контрольная сумма\",\n      \"comments\": \"Комментарии\",\n      \"contexts\": \"Контексты\",\n      \"deployment_id\": \"Id развертывания\",\n      \"description\": \"Описание\",\n      \"execution\": \"Выполнение\",\n      \"execution_date\": \"Дата выполнения\",\n      \"execution_order\": \"Порядок выполнения\",\n\n      \"id\": \"Id\",\n      \"labels\": \"Метки\",\n      \"tag\": \"Тег\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/liquibase/i18n.zh-CN.json",
    "content": "{\n  \"instances\": {\n    \"liquibase\": {\n      \"label\": \"Liquibase\",\n      \"author\": \"作者\",\n      \"changelog\": \"更新日志\",\n      \"checksum\": \"校验码\",\n      \"comments\": \"备注\",\n      \"contexts\": \"上下文\",\n      \"deployment_id\": \"部署Id\",\n      \"description\": \"描述\",\n      \"execution\": \"执行\",\n      \"execution_date\": \"执行日期\",\n      \"execution_order\": \"执行顺序\",\n\n      \"id\": \"Id\",\n      \"labels\": \"标签\",\n      \"tag\": \"标记\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/liquibase/i18n.zh-TW.json",
    "content": "{\n  \"instances\": {\n    \"liquibase\": {\n      \"label\": \"Liquibase\",\n      \"author\": \"作者\",\n      \"changelog\": \"變更日誌\",\n      \"checksum\": \"檢查碼\",\n      \"comments\": \"備註\",\n      \"contexts\": \"執行環境\",\n      \"deployment_id\": \"部署 ID\",\n      \"description\": \"說明\",\n      \"execution\": \"執行\",\n      \"execution_date\": \"執行日期\",\n      \"execution_order\": \"執行順序\",\n\n      \"id\": \"ID\",\n      \"labels\": \"標籤\",\n      \"tag\": \"標記\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/liquibase/index.vue",
    "content": "<!--\n  - Copyright 2014-2019 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-instance-section :error=\"error\" :loading=\"!hasLoaded\">\n    <template v-for=\"(context, ctxName) in contexts\" :key=\"ctxName\">\n      <template\n        v-for=\"(report, name) in context.liquibaseBeans\"\n        :key=\"`${ctxName}-${name}`\"\n      >\n        <sba-panel\n          :header-sticks-below=\"'#subnavigation'\"\n          :title=\"`${ctxName}: ${name}`\"\n          class=\"change-set\"\n        >\n          <table class=\"table is-hoverable w-full\">\n            <thead>\n              <tr>\n                <th v-text=\"$t('instances.liquibase.id')\" />\n                <th v-text=\"$t('instances.liquibase.execution')\" />\n                <th v-text=\"$t('instances.liquibase.description')\" />\n                <th v-text=\"$t('instances.liquibase.tag')\" />\n                <th v-text=\"$t('instances.liquibase.contexts')\" />\n                <th v-text=\"$t('instances.liquibase.labels')\" />\n              </tr>\n            </thead>\n            <tbody>\n              <template\n                v-for=\"changeSet in report.changeSets\"\n                :key=\"`${ctxName}-${name}-${changeSet.id}`\"\n              >\n                <tr\n                  class=\"is-selectable\"\n                  @click=\"\n                    showDetails[changeSet.checksum]\n                      ? delete showDetails[changeSet.checksum]\n                      : (showDetails[changeSet.checksum] = true)\n                  \"\n                >\n                  <td v-text=\"changeSet.id\" />\n                  <td>\n                    <span\n                      :class=\"execClass(execType)\"\n                      class=\"tag\"\n                      v-text=\"changeSet.execType\"\n                    />\n                  </td>\n                  <td v-text=\"changeSet.description\" />\n                  <td v-text=\"changeSet.tag\" />\n                  <td v-text=\"changeSet.contexts.join(', ')\" />\n                  <td>\n                    <span\n                      v-for=\"label in changeSet.labels\"\n                      :key=\"`${ctxName}-${name}-${changeSet.id}-${label}`\"\n                      class=\"tag is-info\"\n                      v-text=\"label\"\n                    />\n                  </td>\n                </tr>\n                <tr\n                  v-if=\"showDetails[changeSet.checksum]\"\n                  :key=\"`${ctxName}-${name}-${changeSet.id}-details`\"\n                >\n                  <td colspan=\"6\">\n                    <table class=\"table is-fullwidth\">\n                      <tbody>\n                        <tr>\n                          <th v-text=\"$t('instances.liquibase.changelog')\" />\n                          <td colspan=\"3\" v-text=\"changeSet.changeLog\" />\n                          <th v-text=\"$t('instances.liquibase.author')\" />\n                          <td v-text=\"changeSet.author\" />\n                        </tr>\n                        <tr>\n                          <th v-text=\"$t('instances.liquibase.checksum')\" />\n                          <td v-text=\"changeSet.checksum\" />\n                          <th v-text=\"$t('instances.liquibase.comments')\" />\n                          <td colspan=\"3\" v-text=\"changeSet.comments\" />\n                        </tr>\n                        <tr>\n                          <th\n                            v-text=\"$t('instances.liquibase.execution_order')\"\n                          />\n                          <td v-text=\"changeSet.orderExecuted\" />\n                          <th\n                            v-text=\"$t('instances.liquibase.execution_date')\"\n                          />\n                          <td v-text=\"changeSet.dateExecuted\" />\n                          <th\n                            v-text=\"$t('instances.liquibase.deployment_id')\"\n                          />\n                          <td v-text=\"changeSet.deploymentId\" />\n                        </tr>\n                      </tbody>\n                    </table>\n                  </td>\n                </tr>\n              </template>\n            </tbody>\n          </table>\n        </sba-panel>\n      </template>\n    </template>\n  </sba-instance-section>\n</template>\n\n<script>\nimport Instance from '@/services/instance';\nimport { VIEW_GROUP } from '@/views/ViewGroup';\nimport SbaInstanceSection from '@/views/instances/shell/sba-instance-section';\n\nexport default {\n  components: { SbaInstanceSection },\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  data: () => ({\n    hasLoaded: false,\n    error: null,\n    contexts: null,\n    showDetails: {},\n  }),\n  computed: {},\n  created() {\n    this.fetchLiquibase();\n  },\n  methods: {\n    async fetchLiquibase() {\n      this.error = null;\n      try {\n        const res = await this.instance.fetchLiquibase();\n        this.contexts = res.data.contexts;\n      } catch (error) {\n        console.warn('Fetching Liquibase changeSets failed:', error);\n        this.error = error;\n      }\n      this.hasLoaded = true;\n    },\n    execClass(execType) {\n      switch (execType) {\n        case 'EXECUTED':\n          return 'is-success';\n        case 'FAILED':\n          return 'is-danger';\n        case 'SKIPPED':\n          return 'is-light';\n        case 'RERAN':\n        case 'MARK_RAN':\n          return 'is-warning';\n        default:\n          return 'is-info';\n      }\n    },\n  },\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      name: 'instances/liquibase',\n      parent: 'instances',\n      path: 'liquibase',\n      component: this,\n      label: 'instances.liquibase.label',\n      group: VIEW_GROUP.DATA,\n      order: 900,\n      isEnabled: ({ instance }) => instance.hasEndpoint('liquibase'),\n    });\n  },\n};\n</script>\nå\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/logfile/i18n.de.json",
    "content": "{\n  \"instances\": {\n    \"logfile\": {\n      \"label\": \"Log\",\n      \"download\": \"Herunterladen\",\n      \"wrap_lines\": \"Zeilen umbrechen\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/logfile/i18n.en.json",
    "content": "{\n  \"instances\": {\n    \"logfile\": {\n      \"label\": \"Logfile\",\n      \"download\": \"Download\",\n      \"wrap_lines\": \"Wrap lines\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/logfile/i18n.es.json",
    "content": "{\n  \"instances\": {\n    \"logfile\": {\n      \"label\": \"Archivo de log\",\n      \"download\": \"Descargar\",\n\n      \"wrap_lines\": \"Ajustar líneas\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/logfile/i18n.fr.json",
    "content": "{\n  \"instances\": {\n    \"logfile\": {\n      \"label\": \"Fichier de log\",\n      \"download\": \"Téléchargement\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/logfile/i18n.is.json",
    "content": "{\n  \"instances\": {\n    \"logfile\": {\n      \"label\": \"Annálaskrá\",\n      \"download\": \"Niðurhal\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/logfile/i18n.ko.json",
    "content": "{\n  \"instances\": {\n    \"logfile\": {\n      \"label\": \"로그파일\",\n      \"download\": \"다운로드\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/logfile/i18n.pt-BR.json",
    "content": "{\n  \"instances\": {\n    \"logfile\": {\n      \"label\": \"Arquivo de Log\",\n      \"download\": \"Download\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/logfile/i18n.ru.json",
    "content": "{\n  \"instances\": {\n    \"logfile\": {\n      \"label\": \"Лог-файл\",\n      \"download\": \"Загрузить\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/logfile/i18n.zh-CN.json",
    "content": "{\n  \"instances\": {\n    \"logfile\": {\n      \"label\": \"日志文件\",\n      \"download\": \"下载\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/logfile/i18n.zh-TW.json",
    "content": "{\n  \"instances\": {\n    \"logfile\": {\n      \"label\": \"日誌檔案\",\n      \"download\": \"下載\",\n      \"wrap_lines\": \"自動換行\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/logfile/index.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-instance-section :error=\"error\" :loading=\"!hasLoaded\">\n    <template #before>\n      <sba-sticky-subnav>\n        <div class=\"flex items-center justify-end gap-1\">\n          <div class=\"flex-1\">\n            <span v-text=\"$t('instances.logfile.label')\" />&nbsp;\n            <small class=\"hidden md:block\" v-text=\"skippedBytesString\" />\n          </div>\n          <div class=\"flex items-start\">\n            <div class=\"flex items-center h-5\">\n              <input\n                id=\"wraplines\"\n                v-model=\"wrapLines\"\n                class=\"focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded\"\n                name=\"wraplines\"\n                type=\"checkbox\"\n              />\n            </div>\n            <div class=\"ml-3 text-sm\">\n              <label\n                class=\"font-medium text-gray-700\"\n                for=\"wraplines\"\n                v-text=\"$t('instances.logfile.wrap_lines')\"\n              />\n            </div>\n          </div>\n\n          <div class=\"mx-3 btn-group\">\n            <sba-button :disabled=\"atTop\" @click=\"scrollToTop\">\n              <svg\n                class=\"h-4 w-4\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                stroke-width=\"2\"\n                viewBox=\"0 0 24 24\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <path\n                  d=\"M7 11l5-5m0 0l5 5m-5-5v12\"\n                  stroke-linecap=\"round\"\n                  stroke-linejoin=\"round\"\n                />\n              </svg>\n            </sba-button>\n            <sba-button :disabled=\"atBottom\" @click=\"scrollToBottom\">\n              <svg\n                class=\"h-4 w-4\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                stroke-width=\"2\"\n                viewBox=\"0 0 24 24\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <path\n                  d=\"M17 13l-5 5m0 0l-5-5m5 5V6\"\n                  stroke-linecap=\"round\"\n                  stroke-linejoin=\"round\"\n                />\n              </svg>\n            </sba-button>\n          </div>\n\n          <sba-button class=\"hidden md:block\" @click=\"downloadLogfile()\">\n            <font-awesome-icon icon=\"download\" />&nbsp;\n            <span v-text=\"$t('instances.logfile.download')\" />\n          </sba-button>\n        </div>\n      </sba-sticky-subnav>\n    </template>\n\n    <div\n      :class=\"{ 'wrap-lines': wrapLines }\"\n      class=\"log-viewer overflow-x-auto text-sm -mx-6 -my-20 pt-14\"\n    >\n      <table class=\"table-striped\" />\n    </div>\n  </sba-instance-section>\n</template>\n\n<script>\nimport { AnsiUp } from 'ansi_up/ansi_up';\nimport { chunk } from 'lodash-es';\nimport prettyBytes from 'pretty-bytes';\nimport { debounceTime, fromEvent } from 'rxjs';\n\nimport subscribing from '@/mixins/subscribing';\nimport sbaConfig from '@/sba-config';\nimport Instance from '@/services/instance';\nimport autolink from '@/utils/autolink';\nimport {\n  animationFrameScheduler,\n  concatAll,\n  concatMap,\n  map,\n  of,\n  tap,\n} from '@/utils/rxjs';\nimport { VIEW_GROUP } from '@/views/ViewGroup';\nimport SbaInstanceSection from '@/views/instances/shell/sba-instance-section';\n\nexport default {\n  components: { SbaInstanceSection },\n  mixins: [subscribing],\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  data: () => ({\n    hasLoaded: false,\n    error: null,\n    atBottom: false,\n    atTop: true,\n    skippedBytes: null,\n    wrapLines: true,\n    scrollSubcription: null,\n  }),\n  computed: {\n    skippedBytesString() {\n      if (this.skippedBytes != null) {\n        return `skipped ${prettyBytes(this.skippedBytes)}`;\n      }\n      return '';\n    },\n  },\n  created() {\n    this.ansiUp = new AnsiUp();\n    this.scrollSubcription = fromEvent(window, 'scroll')\n      .pipe(\n        debounceTime(25),\n        map((event) => event.target.scrollingElement.scrollTop),\n      )\n      .subscribe((scrollTop) => {\n        this.atTop = scrollTop === 0;\n        this.atBottom =\n          document.scrollingElement.clientHeight ===\n          document.scrollingElement.scrollHeight -\n            document.scrollingElement.scrollTop;\n      });\n  },\n  beforeUnmount() {\n    if (this.scrollSubcription && !this.scrollSubcription.closed) {\n      try {\n        this.scrollSubcription.unsubscribe();\n      } finally {\n        this.scrollSubcription = null;\n      }\n    }\n  },\n  methods: {\n    prettyBytes,\n    createSubscription() {\n      this.error = null;\n      return this.instance\n        .streamLogfile(sbaConfig.uiSettings.pollTimer.logfile)\n        .pipe(\n          tap(\n            (part) => (this.skippedBytes = this.skippedBytes || part.skipped),\n          ),\n          concatMap((part) => chunk(part.addendum.split(/\\r?\\n/), 250)),\n          map((lines) => of(lines, animationFrameScheduler)),\n          concatAll(),\n        )\n        .subscribe({\n          next: (lines) => {\n            this.hasLoaded = true;\n            lines.forEach((line) => {\n              const row = document.createElement('tr');\n              const col = document.createElement('td');\n              const pre = document.createElement('pre');\n              pre.innerHTML = autolink(this.ansiUp.ansi_to_html(line));\n              col.appendChild(pre);\n              row.appendChild(col);\n              document.querySelector('.log-viewer > table')?.appendChild(row);\n            });\n\n            if (this.atBottom) {\n              this.scrollToBottom();\n            }\n          },\n          error: (error) => {\n            this.hasLoaded = true;\n            console.warn('Fetching logfile failed:', error);\n            this.error = error;\n          },\n        });\n    },\n    scrollToTop() {\n      document.scrollingElement.scrollTop = 0;\n    },\n    scrollToBottom() {\n      document.scrollingElement.scrollTop =\n        document.scrollingElement.scrollHeight;\n    },\n    downloadLogfile() {\n      window.open(`instances/${this.instance.id}/actuator/logfile`, '_blank');\n    },\n  },\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      name: 'instances/logfile',\n      parent: 'instances',\n      path: 'logfile',\n      component: this,\n      label: 'instances.logfile.label',\n      group: VIEW_GROUP.LOGGING,\n      order: 200,\n      isEnabled: ({ instance }) => instance.hasEndpoint('logfile'),\n    });\n  },\n};\n</script>\n\n<style lang=\"css\">\n.log-viewer pre {\n  padding: 0 0.75em;\n  margin-bottom: 1px;\n}\n\n.log-viewer pre:hover {\n  background: #dbdbdb;\n}\n\n.log-viewer.wrap-lines pre {\n  @apply whitespace-pre-wrap;\n}\n\n.log-viewer {\n  background-color: #fff;\n  overflow: auto;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/loggers/i18n.de.json",
    "content": "{\n  \"instances\": {\n    \"loggers\": {\n      \"label\": \"Logger\",\n      \"application\": \"Anwendung\",\n      \"fetch_failed_some_instances\": \"Das Laden der Log-Informationen {failed} von {count} Instanzen ist fehlgeschlagen. Für diese wird das Konfigurieren der Logger sehr wahrscheinlich fehlschlagen.\",\n      \"filter\": {\n        \"class_only\": \"nur Klassen\",\n        \"configured\": \"konfiguriert\"\n      },\n      \"group\": {\n        \"label\": \"Logger-Gruppen\"\n      },\n      \"instance\": \"Instanz\",\n      \"new\": \"neu\",\n      \"no_loggers_found\": \"Keine Logger gefunden.\",\n      \"unset\": \"Zurücksetzen\",\n      \"setting_loglevel_failed\": \"Fehler beim Setzen des Loglevels '{loglevel}' für den Logger '{logger}'.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/loggers/i18n.en.json",
    "content": "{\n  \"instances\": {\n    \"loggers\": {\n      \"label\": \"Loggers\",\n      \"application\": \"Application\",\n      \"fetch_failed_some_instances\": \"Fetching loggers for {failed} of {count} instances failed. Configuring loggers for those are likely to fail.\",\n      \"filter\": {\n        \"class_only\": \"class only\",\n        \"configured\": \"configured\"\n      },\n      \"group\": {\n        \"label\": \"Logging Groups\"\n      },\n      \"instance\": \"Instance\",\n      \"new\": \"new\",\n      \"no_loggers_found\": \"No loggers found.\",\n      \"unset\": \"Unset\",\n      \"setting_loglevel_failed\": \"Setting {logger} to {loglevel} failed.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/loggers/i18n.es.json",
    "content": "{\n  \"instances\": {\n    \"loggers\": {\n      \"label\": \"Loggers\",\n      \"application\": \"Aplicación\",\n\n      \"fetch_failed_some_instances\": \"Falla obteniendo loggers para {failed} de {count} instancias. Configurar loggers para estas probablemente falle.\",\n      \"filter\": {\n        \"class_only\": \"sólo clases\",\n        \"configured\": \"configurado\"\n      },\n      \"instance\": \"Instancia\",\n      \"new\": \"nuevo\",\n      \"no_loggers_found\": \"No se encontraron loggers.\",\n      \"unset\": \"Reiniciar\",\n      \"setting_loglevel_failed\": \"Falla al cambiar {logger} a {loglevel}.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/loggers/i18n.fr.json",
    "content": "{\n  \"instances\": {\n    \"loggers\": {\n      \"label\": \"Loggers\",\n      \"application\": \"Application\",\n      \"filter\": {\n        \"class_only\": \"class uniquement\",\n        \"configured\": \"configuré\"\n      },\n      \"instance\": \"Instance\",\n      \"new\": \"Nouveau\",\n      \"no_loggers_found\": \"Aucun loggers trouvé.\",\n      \"unset\": \"Réinitialiser\",\n      \"setting_loglevel_failed\": \"Echec de la modification d'état du {logger} vers {loglevel}.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/loggers/i18n.is.json",
    "content": "{\n  \"instances\": {\n    \"loggers\": {\n      \"label\": \"Annálsritarar\",\n      \"application\": \"Forrit\",\n      \"fetch_failed_some_instances\": \"Mistókst að sækja annálsritara fyrir {failed} af {count} eintökum. Stillingin þeirra annálsritara mun hugsanlega mistakast.\",\n      \"filter\": {\n        \"class_only\": \"bara klasar\",\n        \"configured\": \"stillt\"\n      },\n      \"instance\": \"Eintak\",\n      \"new\": \"nýtt\",\n      \"no_loggers_found\": \"Fundið engar annálsritarar.\",\n      \"unset\": \"Núllstilla\",\n      \"setting_loglevel_failed\": \"Stilla {logger} á {loglevel} mistekist.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/loggers/i18n.ko.json",
    "content": "{\n  \"instances\": {\n    \"logfile\": {\n      \"label\": \"로그파일\",\n      \"download\": \"다운로드\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/loggers/i18n.pt-BR.json",
    "content": "{\n  \"instances\": {\n    \"loggers\": {\n      \"label\": \"Loggers\",\n      \"application\": \"Aplicação\",\n      \"filter\": {\n        \"class_only\": \"somente classe\",\n        \"configured\": \"configurado\"\n      },\n      \"instance\": \"Instancia\",\n      \"new\": \"novo\",\n      \"no_loggers_found\": \"Nunhum logger foi encontrado.\",\n      \"unset\": \"Redefinir\",\n      \"setting_loglevel_failed\": \"A configuração de {logger} como {loglevel} falhou.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/loggers/i18n.ru.json",
    "content": "{\n  \"instances\": {\n    \"loggers\": {\n      \"label\": \"Журналы\",\n      \"application\": \"Приложение\",\n      \"filter\": {\n        \"class_only\": \"только класс\",\n        \"configured\": \"настроенные\"\n      },\n      \"instance\": \"Экземпляр\",\n      \"new\": \"новый\",\n      \"no_loggers_found\": \"Журналы не найдены.\",\n      \"unset\": \"Сбросить\",\n      \"setting_loglevel_failed\": \"Ошибка при установке {logger} на {loglevel}.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/loggers/i18n.zh-CN.json",
    "content": "{\n  \"instances\": {\n    \"loggers\": {\n      \"label\": \"日志配置\",\n      \"application\": \"应用\",\n      \"filter\": {\n        \"class_only\": \"只显示类\",\n        \"configured\": \"已配置\"\n      },\n      \"instance\": \"实例\",\n      \"new\": \"新\",\n      \"no_loggers_found\": \"未找到日志配置。\",\n      \"unset\": \"重置\",\n      \"setting_loglevel_failed\": \"设置 {logger} {loglevel} 失败。\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/loggers/i18n.zh-TW.json",
    "content": "{\n  \"instances\": {\n    \"loggers\": {\n      \"label\": \"日誌設定\",\n      \"application\": \"應用程式\",\n      \"fetch_failed_some_instances\": \"取得 {failed}/{count} 個執行個體的日誌設定失敗。對這些執行個體設定日誌等級可能會失敗。\",\n      \"filter\": {\n        \"class_only\": \"只限類別\",\n        \"configured\": \"已設定\"\n      },\n      \"group\": {\n        \"label\": \"日誌群組\"\n      },\n      \"instance\": \"執行個體\",\n      \"new\": \"新增\",\n      \"no_loggers_found\": \"找不到日誌設定。\",\n      \"unset\": \"取消設定\",\n      \"setting_loglevel_failed\": \"將 {logger} 設定為 {loglevel} 失敗。\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/loggers/index.vue",
    "content": "<!--\n  - Copyright 2014-2019 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <loggers\n    :instance-count=\"application.instances.length\"\n    :loggers-service=\"service\"\n    @change-scope=\"changeScope\"\n  />\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, ref } from 'vue';\n\nimport { ActionScope } from '@/components/ActionScope';\n\nimport Application from '@/services/application';\nimport Instance from '@/services/instance';\nimport { VIEW_GROUP } from '@/views/ViewGroup';\nimport Loggers from '@/views/instances/loggers/loggers';\nimport {\n  ApplicationLoggers,\n  InstanceLoggers,\n} from '@/views/instances/loggers/service';\n\nconst { instance, application } = defineProps<{\n  instance: Instance;\n  application: Application;\n}>();\n\nconst scope = ref(\n  application.instances.length > 1\n    ? ActionScope.APPLICATION\n    : ActionScope.INSTANCE,\n);\n\nconst service = computed(() =>\n  scope.value === ActionScope.INSTANCE\n    ? new InstanceLoggers(instance)\n    : new ApplicationLoggers(application),\n);\n\nfunction changeScope(newScope) {\n  scope.value = newScope;\n}\n</script>\n\n<script lang=\"ts\">\nimport { VIEW_GROUP } from '@/views/ViewGroup';\n\nexport default {\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      name: 'instances/loggers',\n      parent: 'instances',\n      path: 'loggers',\n      label: 'instances.loggers.label',\n      component: this,\n      group: VIEW_GROUP.LOGGING,\n      order: 300,\n      isEnabled: ({ instance }) => instance.hasEndpoint('loggers'),\n    });\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/loggers/logger-control.vue",
    "content": "<!--\n  - Copyright 2014-2019 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div class=\"inline-flex gap-1\">\n    <sba-button-group>\n      <sba-button\n        v-for=\"levelOption in levelOptions\"\n        :key=\"levelOption\"\n        :class=\"cssClass(levelOption)\"\n        @click.stop=\"selectLevel(levelOption)\"\n        v-text=\"levelOption\"\n      />\n    </sba-button-group>\n    <sba-button\n      v-if=\"allowReset\"\n      :class=\"{ 'is-loading': getStatusForLevel(null) === 'executing' }\"\n      :disabled=\"!isConfigured\"\n      @click.stop=\"selectLevel(null)\"\n      v-text=\"$t('instances.loggers.unset')\"\n    />\n  </div>\n</template>\n\n<script>\nexport default {\n  props: {\n    value: {\n      type: Array,\n      required: true,\n    },\n    levelOptions: {\n      type: Array,\n      required: true,\n    },\n    allowReset: {\n      type: Boolean,\n      default: true,\n    },\n    status: {\n      type: Object,\n      default: null,\n    },\n  },\n  emits: ['input'],\n  computed: {\n    isConfigured() {\n      return this.value.some((l) => Boolean(l.configuredLevel));\n    },\n  },\n  methods: {\n    hasEffectiveLevel(level) {\n      return this.value.some((l) => {\n        if (this.isLoggingGroup(l)) {\n          return this.hasConfiguredLevel(level);\n        } else {\n          return l.effectiveLevel === level;\n        }\n      });\n    },\n    hasConfiguredLevel(level) {\n      return this.value.some((l) => l.configuredLevel === level);\n    },\n    isLoggingGroup(logger) {\n      return !!logger.members;\n    },\n    selectLevel(level) {\n      this.$emit('input', level);\n    },\n    getStatusForLevel(level) {\n      if (this.status && this.status.level === level) {\n        return this.status.status;\n      }\n    },\n    cssClass(level) {\n      return {\n        'logger-control__level--inherited': !this.hasConfiguredLevel(level),\n        'is-active is-danger':\n          level === 'TRACE' && this.hasEffectiveLevel('TRACE'),\n        'is-active is-warning':\n          level === 'DEBUG' && this.hasEffectiveLevel('DEBUG'),\n        'is-active is-info': level === 'INFO' && this.hasEffectiveLevel('INFO'),\n        'is-active is-success':\n          level === 'WARN' && this.hasEffectiveLevel('WARN'),\n        'is-active is-light':\n          level === 'ERROR' && this.hasEffectiveLevel('ERROR'),\n        'is-active is-extra-light':\n          level === 'FATAL' && this.hasEffectiveLevel('FATAL'),\n        'is-active is-black': level === 'OFF' && this.hasEffectiveLevel('OFF'),\n        'is-loading': this.getStatusForLevel(level) === 'executing',\n      };\n    },\n  },\n};\n</script>\n\n<style>\n.logger-control__level--inherited {\n  @apply bg-opacity-70 !important;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/loggers/loggers-list.spec.ts",
    "content": "import { shallowMount } from '@vue/test-utils';\nimport { describe, expect, it } from 'vitest';\nimport { nextTick } from 'vue';\n\nimport LoggersList from './loggers-list.vue';\n\ndescribe('Loggers List', () => {\n  it('should extend list on scroll', async () => {\n    const loggers = createLoggers(500);\n    const loggersList = shallowMount(LoggersList, {\n      props: {\n        levels: ['OFF', 'ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE'],\n        loggers: loggers,\n        loggersStatus: {},\n      },\n    });\n\n    expect(loggersList.vm.visibleLimit).toBe(25);\n    expect(loggersList.text()).toContain('my-logger-24');\n    expect(loggersList.text()).not.toContain('my-logger-25');\n\n    loggersList.vm.$refs.infiniteLoading.$emit('infinite', {\n      loaded: () => {},\n      complete: () => {},\n    });\n\n    expect(loggersList.vm.visibleLimit).toBe(50);\n    await nextTick();\n\n    expect(loggersList.text()).toContain('my-logger-49');\n    expect(loggersList.text()).not.toContain('my-logger-50');\n  });\n\n  function createLoggers(number: number): Array<any> {\n    return Array(number)\n      .fill(0)\n      .map((_, i) => createLogger(i));\n  }\n\n  function createLogger(i: number): any {\n    return {\n      name: `my-logger-${i}`,\n      level: [{ effectiveLevel: 'INFO', instanceId: '123' }],\n    };\n  }\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/loggers/loggers-list.vue",
    "content": "<!--\n  - Copyright 2014-2019 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <table class=\"w-full\">\n    <tbody>\n      <tr v-for=\"logger in loggers.slice(0, visibleLimit)\" :key=\"logger.name\">\n        <td class=\"w-9/12\">\n          <span class=\"break-all\" v-text=\"logger.name\" />&nbsp;\n          <sba-tag\n            v-if=\"logger.isNew\"\n            class=\"tag is-primary is-uppercase\"\n            :value=\"$t('instances.loggers.new')\"\n          />\n        </td>\n        <td class=\"w-1/4\">\n          <sba-logger-control\n            class=\"is-pulled-right\"\n            :level-options=\"levels\"\n            :value=\"logger.level\"\n            :status=\"loggersStatus[logger.name]\"\n            :allow-reset=\"logger.name !== 'ROOT'\"\n            @input=\"(level) => $emit('configureLogger', { logger, level })\"\n          />\n          <sba-alert\n            v-if=\"\n              loggersStatus[logger.name] &&\n              loggersStatus[logger.name].status === 'failed'\n            \"\n            :error=\"\n              new Error(\n                $t('instances.loggers.setting_loglevel_failed', {\n                  logger: logger.name,\n                  loglevel: loggersStatus[logger.name].level,\n                }),\n              )\n            \"\n          />\n        </td>\n      </tr>\n      <tr v-if=\"loggers.length === 0\">\n        <td\n          class=\"is-muted\"\n          colspan=\"5\"\n          v-text=\"$t('instances.loggers.no_loggers_found')\"\n        />\n      </tr>\n    </tbody>\n\n    <InfiniteLoading\n      v-if=\"infiniteScroll && loggers.length !== 0\"\n      ref=\"infiniteLoading\"\n      :identifier=\"loggers\"\n      @infinite=\"increaseScroll\"\n    >\n      <template #complete>\n        <span />\n      </template>\n    </InfiniteLoading>\n  </table>\n</template>\n\n<script setup lang=\"ts\">\nimport InfiniteLoading from 'v3-infinite-loading';\nimport { ref } from 'vue';\n\nimport 'v3-infinite-loading/lib/style.css';\n\nimport SbaLoggerControl from '@/views/instances/loggers/logger-control';\n\nconst { loggers, infiniteScroll = true } = defineProps<{\n  levels: string[];\n  loggers: any[];\n  loggersStatus: Record<string, any>;\n  infiniteScroll?: boolean;\n}>();\n\ndefineEmits<{\n  (event: 'configureLogger', payload: { logger: any; level: string }): void;\n}>();\n\nconst visibleLimit = ref(25);\n\nfunction increaseScroll($state) {\n  if (visibleLimit.value <= loggers.length) {\n    visibleLimit.value += 25;\n    $state.loaded();\n  } else {\n    $state.complete();\n  }\n}\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/loggers/loggers.vue",
    "content": "<!--\n  - Copyright 2014-2020 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-instance-section :error=\"error\" :loading=\"!hasLoaded\">\n    <template #before>\n      <sba-sticky-subnav>\n        <div class=\"flex gap-2\">\n          <sba-toggle-scope-button\n            v-if=\"instanceCount > 1\"\n            v-model=\"scope\"\n            :instance-count=\"instanceCount\"\n            :show-info=\"false\"\n          />\n\n          <div class=\"flex-1\">\n            <sba-input\n              v-model=\"filter.name\"\n              :placeholder=\"$t('term.filter')\"\n              name=\"filter\"\n              type=\"search\"\n            >\n              <template #prepend>\n                <font-awesome-icon icon=\"filter\" />\n              </template>\n              <template #append>\n                <span v-text=\"filteredLoggers.length\" /> /\n                <span v-text=\"loggerConfig.loggers.length\" />\n              </template>\n            </sba-input>\n          </div>\n\n          <!-- FILTER -->\n          <div>\n            <div class=\"flex items-start\">\n              <div class=\"flex items-center h-5\">\n                <input\n                  id=\"classOnly\"\n                  v-model=\"filter.classOnly\"\n                  class=\"focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded\"\n                  name=\"wraplines\"\n                  type=\"checkbox\"\n                />\n              </div>\n              <div class=\"ml-3 text-sm\">\n                <label\n                  class=\"font-medium text-gray-700\"\n                  for=\"classOnly\"\n                  v-text=\"t('instances.loggers.filter.class_only')\"\n                />\n              </div>\n            </div>\n\n            <div class=\"flex items-start\">\n              <div class=\"flex items-center h-5\">\n                <input\n                  id=\"configuredOnly\"\n                  v-model=\"filter.configuredOnly\"\n                  class=\"focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded\"\n                  name=\"wraplines\"\n                  type=\"checkbox\"\n                />\n              </div>\n              <div class=\"ml-3 text-sm\">\n                <label\n                  class=\"font-medium text-gray-700\"\n                  for=\"configuredOnly\"\n                  v-text=\"t('instances.loggers.filter.configured')\"\n                />\n              </div>\n            </div>\n          </div>\n          <!-- // FILTER -->\n        </div>\n      </sba-sticky-subnav>\n    </template>\n\n    <sba-panel\n      v-if=\"loggerConfig.groups\"\n      :title=\"t('instances.loggers.group.label')\"\n    >\n      <loggers-list\n        :infinite-scroll=\"false\"\n        :levels=\"loggerConfig.levels\"\n        :loggers=\"loggerConfig.groups\"\n        :loggers-status=\"loggersStatus\"\n        @configure-logger=\"\n          ({ logger, level }) => configureLogger(logger, level)\n        \"\n      />\n    </sba-panel>\n\n    <sba-panel :title=\"t('instances.loggers.label')\">\n      <div v-if=\"failedInstances > 0\" class=\"message is-warning\">\n        <div class=\"message-body\">\n          <sba-alert\n            :title=\"\n              t('instances.loggers.fetch_failed_some_instances', {\n                failed: failedInstances,\n                count: instanceCount,\n              })\n            \"\n            severity=\"WARN\"\n          />\n        </div>\n      </div>\n\n      <loggers-list\n        :levels=\"loggerConfig.levels\"\n        :loggers=\"filteredLoggers\"\n        :loggers-status=\"loggersStatus\"\n        @configure-logger=\"\n          ({ logger, level }) => configureLogger(logger, level)\n        \"\n      />\n    </sba-panel>\n  </sba-instance-section>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, reactive, ref, watch } from 'vue';\nimport { useI18n } from 'vue-i18n';\n\nimport { ActionScope } from '@/components/ActionScope';\nimport SbaAlert from '@/components/sba-alert';\nimport SbaToggleScopeButton from '@/components/sba-toggle-scope-button.vue';\n\nimport { finalize, from, listen } from '@/utils/rxjs';\nimport LoggersList from '@/views/instances/loggers/loggers-list';\nimport {\n  ApplicationLoggers,\n  InstanceLoggers,\n} from '@/views/instances/loggers/service';\nimport SbaInstanceSection from '@/views/instances/shell/sba-instance-section';\n\nconst { t } = useI18n();\n\nconst isClassName = (name) => /\\.[A-Z]/.test(name);\n\nconst addToFilter = (oldFilter, addedFilter) =>\n  !oldFilter\n    ? addedFilter\n    : (val, key) => oldFilter(val, key) && addedFilter(val, key);\n\nconst addLoggerCreationEntryIfLoggerNotPresent = (nameFilter, loggers) => {\n  if (nameFilter && !loggers.some((logger) => logger.name === nameFilter)) {\n    loggers.unshift({\n      level: [\n        {\n          /**\n           * If not null, effectiveLevel is the same as configuredLevel\n           * If null, effectiveLevel is the same as the level of the parent logger\n           */\n          configuredLevel: null, // Explicitly configured logging level for the logger\n          effectiveLevel: null,\n          instanceId: null,\n        },\n      ],\n      name: nameFilter,\n      isNew: true,\n    });\n  }\n};\n\nconst { loggersService, instanceCount = 0 } = defineProps<{\n  instanceCount?: number;\n  loggersService: InstanceLoggers | ApplicationLoggers;\n}>();\n\nconst emit = defineEmits<{\n  (\n    e: 'changeScope',\n    scope: ActionScope.APPLICATION | ActionScope.INSTANCE,\n  ): void;\n}>();\n\nconst hasLoaded = ref(false);\nconst error = ref(null);\nconst failedInstances = ref(0);\nconst loggerConfig = ref({ loggers: [], levels: [], groups: [] });\nconst loggersStatus = ref({});\nconst filter = reactive({\n  name: '',\n  classOnly: false,\n  configuredOnly: false,\n});\nconst scope = ref(ActionScope.APPLICATION);\n\nconst filteredLoggers = computed(() => {\n  const filterFn = getFilterFn();\n  const filteredLoggers = filterFn\n    ? loggerConfig.value.loggers.filter(filterFn)\n    : loggerConfig.value.loggers;\n  addLoggerCreationEntryIfLoggerNotPresent(filter.name, filteredLoggers);\n  return filteredLoggers;\n});\n\nwatch(scope, (scope) => {\n  emit('changeScope', scope);\n});\n\nwatch(\n  () => loggersService,\n  () => {\n    if (loggersService) {\n      fetchLoggers();\n    }\n  },\n  { immediate: true },\n);\n\nfunction configureLogger(logger, level) {\n  from(loggersService.configureLogger(logger.name, level))\n    .pipe(\n      listen(\n        (status) => (loggersStatus.value[logger.name] = { level, status }),\n      ),\n      finalize(() => fetchLoggers()),\n    )\n    .subscribe({\n      error: (error) =>\n        console.warn(`Configuring logger '${logger.name}' failed:`, error),\n    });\n}\n\nasync function fetchLoggers() {\n  error.value = null;\n  failedInstances.value = 0;\n  try {\n    let { errors, ...rest } = await loggersService.fetchLoggers();\n    loggerConfig.value = Object.freeze(rest);\n    failedInstances.value = errors.length;\n  } catch (e) {\n    console.warn('Fetching loggers failed:', e);\n    error.value = e;\n  }\n  hasLoaded.value = true;\n}\n\nfunction getFilterFn() {\n  let filterFn = null;\n\n  if (filter.classOnly) {\n    filterFn = addToFilter(filterFn, (logger) => isClassName(logger.name));\n  }\n\n  if (filter.configuredOnly) {\n    filterFn = addToFilter(filterFn, (logger) =>\n      logger.level.some((l) => Boolean(l.configuredLevel)),\n    );\n  }\n\n  if (filter.name) {\n    const normalizedFilter = filter.name.toLowerCase();\n    filterFn = addToFilter(filterFn, (logger) =>\n      logger.name.toLowerCase().includes(normalizedFilter),\n    );\n  }\n\n  return filterFn;\n}\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/loggers/service.spec.ts",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { HttpResponse, http } from 'msw';\nimport { describe, expect, it, vi } from 'vitest';\n\nimport { ApplicationLoggers, InstanceLoggers } from './service';\n\nimport { server } from '@/mocks/server';\nimport Instance from '@/services/instance';\n\ndescribe('InstanceLoggers', () => {\n  const instance = new Instance({ id: 'test-1' });\n  instance.configureLogger = vi.fn();\n\n  const service = new InstanceLoggers(instance);\n\n  it('should configure loggers', () => {\n    service.configureLogger('ROOT', 'WARN');\n    expect(instance.configureLogger).toHaveBeenCalledWith('ROOT', 'WARN');\n  });\n\n  it('should fetch loggers', async () => {\n    server.use(\n      http.get('*/loggers', async () => {\n        return HttpResponse.json({\n          groups: {\n            sba: {\n              members: [\n                'de.codecentric.boot.admin.client',\n                'de.codecentric.boot.admin.server',\n              ],\n            },\n          },\n          levels: ['TRACE', 'INFO', 'FATAL'],\n          loggers: {\n            ROOT: {\n              configuredLevel: 'INFO',\n              effectiveLevel: 'INFO',\n            },\n            'de.codecentric': {\n              configuredLevel: null,\n              effectiveLevel: 'INFO',\n            },\n          },\n        });\n      }),\n      http.get('*/loggers/*', async () => {\n        return HttpResponse.json({\n          configuredLevel: 'INFO',\n          effectiveLevel: 'INFO',\n        });\n      }),\n    );\n\n    const cfg = await service.fetchLoggers();\n\n    expect(cfg).toEqual({\n      errors: [],\n      levels: ['TRACE', 'INFO', 'FATAL'],\n      loggers: [\n        {\n          name: 'ROOT',\n          level: [\n            {\n              instanceId: 'test-1',\n              configuredLevel: 'INFO',\n              effectiveLevel: 'INFO',\n            },\n          ],\n        },\n        {\n          name: 'de.codecentric',\n          level: [\n            {\n              instanceId: 'test-1',\n              configuredLevel: null,\n              effectiveLevel: 'INFO',\n            },\n          ],\n        },\n      ],\n      groups: [\n        {\n          name: 'sba',\n          level: [\n            {\n              instanceId: 'test-1',\n              members: [\n                'de.codecentric.boot.admin.client',\n                'de.codecentric.boot.admin.server',\n              ],\n            },\n          ],\n        },\n      ],\n    });\n  });\n});\n\ndescribe('ApplicationLoggers', () => {\n  const application = {\n    fetchLoggers: vi.fn(),\n    configureLogger: vi.fn(),\n  };\n  const service = new ApplicationLoggers(application);\n\n  it('should configure loggers', () => {\n    service.configureLogger('ROOT', 'WARN');\n    expect(application.configureLogger).toHaveBeenCalledWith('ROOT', 'WARN');\n  });\n\n  it('should fetch loggers', async () => {\n    application.fetchLoggers.mockReturnValue(\n      Promise.resolve({\n        responses: [\n          {\n            instanceId: 'test-1',\n            status: 200,\n            body: {\n              levels: ['TRACE', 'FATAL'],\n              loggers: {\n                ROOT: {\n                  configuredLevel: 'INFO',\n                  effectiveLevel: 'INFO',\n                },\n                'de.codecentric': {\n                  configuredLevel: null,\n                  effectiveLevel: 'INFO',\n                },\n              },\n            },\n          },\n          {\n            instanceId: 'test-2',\n            status: 200,\n            body: {\n              levels: ['INFO'],\n              loggers: {\n                ROOT: {\n                  configuredLevel: 'INFO',\n                  effectiveLevel: 'INFO',\n                },\n                'de.codecentric': {\n                  configuredLevel: 'WARN',\n                  effectiveLevel: 'WARN',\n                },\n              },\n            },\n          },\n        ],\n      }),\n    );\n\n    const cfg = await service.fetchLoggers();\n\n    expect(cfg).toEqual({\n      errors: [],\n      levels: ['TRACE', 'FATAL', 'INFO'],\n      loggers: [\n        {\n          name: 'ROOT',\n          level: [\n            {\n              instanceId: 'test-1',\n              configuredLevel: 'INFO',\n              effectiveLevel: 'INFO',\n            },\n            {\n              instanceId: 'test-2',\n              configuredLevel: 'INFO',\n              effectiveLevel: 'INFO',\n            },\n          ],\n        },\n        {\n          name: 'de.codecentric',\n          level: [\n            {\n              instanceId: 'test-1',\n              configuredLevel: null,\n              effectiveLevel: 'INFO',\n            },\n            {\n              instanceId: 'test-2',\n              configuredLevel: 'WARN',\n              effectiveLevel: 'WARN',\n            },\n          ],\n        },\n      ],\n    });\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/loggers/service.ts",
    "content": "/*\n * Copyright 2014-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { flatMap, groupBy, union } from 'lodash-es';\n\nimport Instance from '@/services/instance';\n\nconst convertLoggers = function (loggers, instanceId) {\n  return Object.entries(loggers).map(([name, config]) => ({\n    name,\n    level: [{ ...config, instanceId }],\n  }));\n};\n\nexport class InstanceLoggers {\n  private readonly instance: Instance;\n\n  constructor(instance: Instance) {\n    this.instance = instance;\n  }\n\n  async fetchLoggers() {\n    const { data: loggerConfig } = await this.instance.fetchLoggers();\n    return {\n      errors: [],\n      levels: loggerConfig.levels,\n      loggers: convertLoggers(loggerConfig.loggers, this.instance.id),\n      groups: convertLoggers(loggerConfig.groups, this.instance.id),\n    };\n  }\n\n  async configureLogger(name, level) {\n    await this.instance.configureLogger(name, level);\n  }\n}\n\nexport class ApplicationLoggers {\n  constructor(application) {\n    this.application = application;\n  }\n\n  async fetchLoggers() {\n    let errors;\n    let levels;\n    let loggers;\n\n    try {\n      const { responses } = await this.application.fetchLoggers();\n      const successful = responses.filter(\n        (r) => r.body && r.status >= 200 && r.status < 299,\n      );\n\n      errors = responses\n        .filter((r) => r.status >= 400)\n        .map((r) => ({\n          instanceId: r.instanceId,\n          error: 'HTTP Status ' + r.status,\n        }));\n      loggers = Object.entries(\n        groupBy(\n          flatMap(successful, (r) =>\n            convertLoggers(r.body.loggers, r.instanceId),\n          ),\n          (l) => l.name,\n        ),\n      ).map(([name, configs]) => ({\n        name,\n        level: flatMap(configs, (c) => c.level),\n      }));\n      levels = union(...successful.map((r) => r.body.levels));\n    } catch {\n      console.warn('Failed to fetch loggers for some instances');\n\n      errors = [];\n      levels = [];\n      loggers = [];\n    }\n\n    return {\n      errors,\n      levels,\n      loggers,\n    };\n  }\n\n  async configureLogger(name, level) {\n    let responses;\n\n    try {\n      responses = (await this.application.configureLogger(name, level))\n        .responses;\n    } catch {\n      responses = [];\n    }\n\n    const errors = responses\n      .filter((r) => r.status >= 400)\n      .map((r) => ({\n        instanceId: r.instanceId,\n        error: 'HTTP Status ' + r.status,\n      }));\n    if (errors.length > 0) {\n      console.warn('Failed to set loglevel for some instances', errors);\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/mappings/DispatcherMappings.spec.ts",
    "content": "import { screen } from '@testing-library/vue';\nimport { beforeEach, describe, expect, it } from 'vitest';\n\nimport { render } from '@/test-utils';\nimport DispatcherMappings from '@/views/instances/mappings/DispatcherMappings.vue';\n\ndescribe('DispatcherMappings.vue', () => {\n  describe('dispatcherServlet available', () => {\n    beforeEach(async () => {\n      render(DispatcherMappings, {\n        props: {\n          dispatchers: {\n            dispatcherServlet: [\n              {\n                handler: 'Actuator root web endpoint',\n                predicate:\n                  '{GET [/actuator], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}',\n                details: {\n                  handlerMethod: {\n                    className:\n                      'org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping.WebMvcLinksHandler',\n                    name: 'links',\n                    descriptor:\n                      '(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)Ljava/util/Map;',\n                  },\n                  requestMappingConditions: {\n                    consumes: [],\n                    headers: [],\n                    methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],\n                    params: [],\n                    patterns: ['/actuator'],\n                    produces: [\n                      {\n                        mediaType:\n                          'application/vnd.spring-boot.actuator.v3+json',\n                        negated: false,\n                      },\n                      {\n                        mediaType:\n                          'application/vnd.spring-boot.actuator.v2+json',\n                        negated: false,\n                      },\n                      {\n                        mediaType: 'application/json',\n                        negated: false,\n                      },\n                    ],\n                  },\n                },\n              },\n            ],\n          },\n        },\n      });\n    });\n\n    it('should render endpoint path', async () => {\n      const path = await screen.getByRole('cell', { name: '/actuator' });\n      expect(path).toBeVisible();\n    });\n\n    it('should render produces information', async () => {\n      const produces = screen.getByRole('cell', {\n        name: 'application/vnd.spring-boot.actuator.v3+json, application/vnd.spring-boot.actuator.v2+json, application/json',\n      });\n      expect(produces).toBeVisible();\n    });\n\n    it('should render http-verbs', async () => {\n      const httpVerbs = screen.getByRole('cell', {\n        name: 'GET, POST, PUT, DELETE, OPTIONS',\n      });\n      expect(httpVerbs).toBeVisible();\n    });\n  });\n\n  describe('dispatcherServlet not available', () => {\n    it('should not crash when no data is available', () => {\n      render(DispatcherMappings);\n    });\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/mappings/DispatcherMappings.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div class=\"table-container\">\n    <table class=\"table w-full\">\n      <template\n        v-for=\"(handlerMappings, dispatcherName) in dispatchers\"\n        :key=\"dispatcherName\"\n      >\n        <thead>\n          <tr>\n            <th colspan=\"99\" v-text=\"dispatcherName\" />\n          </tr>\n        </thead>\n        <tbody>\n          <template\n            v-for=\"(mapping, idx) in handlerMappings\"\n            :key=\"`${dispatcherName}_${idx}_pattern`\"\n          >\n            <template\n              v-if=\"mapping.details && mapping.details.requestMappingConditions\"\n            >\n              <tr>\n                <td\n                  :rowspan=\"\n                    2 +\n                    countNonEmptyArrays(\n                      mapping.details.requestMappingConditions,\n                      'methods',\n                      'consumes',\n                      'produces',\n                      'params',\n                      'headers',\n                    )\n                  \"\n                >\n                  <div\n                    v-for=\"pattern in mapping.details.requestMappingConditions\n                      .patterns\"\n                    :key=\"`${dispatcherName}_${idx}_${pattern}`\"\n                  >\n                    <code v-text=\"pattern\" />\n                  </div>\n                </td>\n              </tr>\n\n              <tr\n                v-if=\"mapping.details.requestMappingConditions.methods.length\"\n                :key=\"`${dispatcherName}_${idx}_methods`\"\n              >\n                <th class=\"is-narrow\">\n                  <small v-text=\"$t('instances.mappings.http-verb')\" />\n                </th>\n                <td\n                  class=\"font-mono is-breakable\"\n                  v-text=\"\n                    mapping.details.requestMappingConditions.methods.join(', ')\n                  \"\n                />\n              </tr>\n\n              <tr\n                v-if=\"mapping.details.requestMappingConditions.consumes.length\"\n                :key=\"`${dispatcherName}_${idx}_consumes`\"\n              >\n                <th class=\"is-narrow\">\n                  <small v-text=\"$t('instances.mappings.consumes')\" />\n                </th>\n                <td\n                  class=\"font-mono is-breakable\"\n                  v-text=\"\n                    mediaTypePredicates(\n                      mapping.details.requestMappingConditions.consumes,\n                    )\n                  \"\n                />\n              </tr>\n\n              <tr\n                v-if=\"mapping.details.requestMappingConditions.produces.length\"\n                :key=\"`${dispatcherName}_${idx}_produces`\"\n              >\n                <th class=\"is-narrow\">\n                  <small v-text=\"$t('instances.mappings.produces')\" />\n                </th>\n                <td\n                  class=\"font-mono is-breakable\"\n                  v-text=\"\n                    mediaTypePredicates(\n                      mapping.details.requestMappingConditions.produces,\n                    )\n                  \"\n                />\n              </tr>\n\n              <tr\n                v-if=\"mapping.details.requestMappingConditions.params.length\"\n                :key=\"`${dispatcherName}_${idx}_params`\"\n              >\n                <th class=\"is-narrow\">\n                  <small v-text=\"$t('instances.mappings.parameters')\" />\n                </th>\n                <td\n                  class=\"font-mono is-breakable\"\n                  v-text=\"\n                    paramPredicates(\n                      mapping.details.requestMappingConditions.params,\n                    )\n                  \"\n                />\n              </tr>\n\n              <tr\n                v-if=\"mapping.details.requestMappingConditions.headers.length\"\n                :key=\"`${dispatcherName}_${idx}_headers`\"\n              >\n                <th class=\"is-narrow\">\n                  <small v-text=\"$t('instances.mappings.headers')\" />\n                </th>\n                <td\n                  class=\"font-mono is-breakable\"\n                  v-text=\"\n                    paramPredicates(\n                      mapping.details.requestMappingConditions.headers,\n                    )\n                  \"\n                />\n              </tr>\n\n              <tr :key=\"`${dispatcherName}_${idx}_handler`\">\n                <th class=\"is-narrow\">\n                  <small v-text=\"$t('instances.mappings.handler')\" />\n                </th>\n                <td class=\"is-breakable\" v-text=\"mapping.handler\" />\n              </tr>\n            </template>\n            <tr v-else :key=\"`${dispatcherName}_${idx}`\">\n              <td><code v-text=\"mapping.predicate\" /></td>\n              <th class=\"is-narrow is-breakable\">\n                <small v-text=\"$t('instances.mappings.handler')\" />\n              </th>\n              <td colspan=\"4\" v-text=\"mapping.handler\" />\n            </tr>\n          </template>\n        </tbody>\n      </template>\n    </table>\n  </div>\n</template>\n<script>\nexport default {\n  props: {\n    dispatchers: {\n      type: Object,\n      default: () => ({}),\n    },\n  },\n  methods: {\n    countNonEmptyArrays(obj, ...keys) {\n      return keys.map((key) => obj[key]).filter((a) => a && a.length).length;\n    },\n    mediaTypePredicates(types) {\n      return types\n        .map((p) => `${p.negate ? '!' : ''}${p.mediaType}`)\n        .join(', ');\n    },\n    paramPredicates(params) {\n      return params\n        .map((p) => `${p.name}: ${p.negate ? '!' : ''}${p.value}`)\n        .join(', ');\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/mappings/ServletFilterMappings.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div class=\"table-container\">\n    <table v-if=\"servletFilters.length\" class=\"table is-fullwidth\">\n      <thead>\n        <tr>\n          <th v-text=\"$t('instances.mappings.url_pattern')\" />\n          <th v-text=\"$t('instances.mappings.servlet_name')\" />\n          <th v-text=\"$t('instances.mappings.filter_name')\" />\n          <th v-text=\"$t('instances.mappings.class')\" />\n        </tr>\n      </thead>\n      <tbody>\n        <template\n          v-for=\"servletFilterMapping in servletFilters\"\n          :key=\"`${servletFilterMapping.name}`\"\n        >\n          <tr>\n            <td>\n              <div\n                v-for=\"mapping in servletFilterMapping.urlPatternMappings\"\n                :key=\"`${servletFilterMapping.name}_${mapping}`\"\n              >\n                <code v-text=\"mapping\" />\n              </div>\n            </td>\n            <td>\n              <div\n                v-for=\"mapping in servletFilterMapping.servletNameMappings\"\n                :key=\"`${servletFilterMapping.name}_${mapping}`\"\n                class=\"is-breakable\"\n                v-text=\"mapping\"\n              />\n            </td>\n            <td class=\"is-breakable\" v-text=\"servletFilterMapping.name\" />\n            <td class=\"is-breakable\" v-text=\"servletFilterMapping.className\" />\n          </tr>\n        </template>\n      </tbody>\n    </table>\n  </div>\n</template>\n<script>\nexport default {\n  props: {\n    servletFilters: {\n      type: Array,\n      default: () => [],\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/mappings/ServletMappings.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div class=\"table-container\">\n    <table v-if=\"servlets.length\" class=\"table is-fullwidth\">\n      <thead>\n        <tr>\n          <th v-text=\"$t('instances.mappings.url_pattern')\" />\n          <th v-text=\"$t('instances.mappings.servlet_name')\" />\n          <th v-text=\"$t('instances.mappings.class')\" />\n        </tr>\n      </thead>\n      <tbody>\n        <template\n          v-for=\"servletMapping in servlets\"\n          :key=\"`${servletMapping.name}`\"\n        >\n          <tr>\n            <td>\n              <div\n                v-for=\"mapping in servletMapping.mappings\"\n                :key=\"`${servletMapping.name}_${mapping}`\"\n              >\n                <code v-text=\"mapping\" />\n              </div>\n            </td>\n            <td class=\"is-breakable\" v-text=\"servletMapping.name\" />\n            <td class=\"is-breakable\" v-text=\"servletMapping.className\" />\n          </tr>\n        </template>\n      </tbody>\n    </table>\n  </div>\n</template>\n<script>\nexport default {\n  props: {\n    servlets: {\n      type: Array,\n      default: () => [],\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/mappings/i18n.de.json",
    "content": "{\n  \"instances\": {\n    \"mappings\": {\n      \"label\": \"Mappings\",\n      \"class\": \"Klasse\",\n      \"consumes\": \"Erwartet\",\n\n      \"filter_name\": \"Filtername\",\n      \"handler\": \"Handler\",\n      \"headers\": \"Headers\",\n      \"http-verb\": \"HTTP-Verb\",\n      \"mappings_not_supported_spring_boot_1\": \"Spring Boot 1.x Anwendungen unterstützen keine Mappings.\",\n      \"parameters\": \"Parameter\",\n      \"produces\": \"Produziert\",\n      \"servlet_name\": \"Servletname\",\n      \"url_pattern\": \"Url-Muster\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/mappings/i18n.en.json",
    "content": "{\n  \"instances\": {\n    \"mappings\": {\n      \"label\": \"Mappings\",\n      \"class\": \"Class\",\n      \"consumes\": \"Consumes\",\n\n      \"filter_name\": \"Filter Name\",\n      \"handler\": \"Handler\",\n      \"headers\": \"Headers\",\n      \"http-verb\": \"Method\",\n      \"mappings_not_supported_spring_boot_1\": \"Mappings are not supported for Spring Boot 1.x applications.\",\n      \"parameters\": \"Parameters\",\n      \"produces\": \"Produces\",\n      \"servlet_name\": \"Servlet Name\",\n      \"url_pattern\": \"Url Pattern\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/mappings/i18n.es.json",
    "content": "{\n  \"instances\": {\n    \"mappings\": {\n      \"label\": \"Mapeos\",\n      \"class\": \"Clase\",\n      \"consumes\": \"Consumo\",\n\n      \"filter_name\": \"Filtro\",\n      \"handler\": \"Manejador\",\n      \"headers\": \"Encabezados\",\n      \"http-verb\": \"Método\",\n      \"mappings_not_supported_spring_boot_1\": \"Los Mapeos no están soportados para aplicaciones Spring 1.X.\",\n      \"parameters\": \"Parámetros\",\n      \"produces\": \"Produce\",\n      \"servlet_name\": \"Servlet\",\n      \"url_pattern\": \"Patrón Url\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/mappings/i18n.fr.json",
    "content": "{\n  \"instances\": {\n    \"mappings\": {\n      \"label\": \"Mappings\",\n      \"class\": \"Class\",\n      \"consumes\": \"Consumes\",\n\n      \"filter_name\": \"Filtre sur le nom\",\n      \"handler\": \"Handler\",\n      \"headers\": \"Headers\",\n      \"http-verb\": \"Methodes\",\n      \"mappings_not_supported_spring_boot_1\": \"Les Mappings ne sont pas supportés par les applications Spring Boot 1.x.\",\n      \"parameters\": \"Paramètres\",\n      \"produces\": \"Produces\",\n      \"servlet_name\": \"Nom de la Servlet\",\n      \"url_pattern\": \"Url Pattern\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/mappings/i18n.is.json",
    "content": "{\n  \"instances\": {\n    \"mappings\": {\n      \"label\": \"Mappings\",\n      \"class\": \"Klasi\",\n      \"consumes\": \"Neyta\",\n\n      \"filter_name\": \"Nafn síu\",\n      \"handler\": \"Handler\",\n      \"headers\": \"Hausar\",\n      \"http-verb\": \"Gerð\",\n      \"mappings_not_supported_spring_boot_1\": \"Mappings eru ekki í boði fyrir Spring Boot 1.x forrit.\",\n      \"parameters\": \"Færibreyta\",\n      \"produces\": \"Framleiða\",\n      \"servlet_name\": \"Nafn servlet\",\n      \"url_pattern\": \"URL Pattern\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/mappings/i18n.ko.json",
    "content": "{\n  \"instances\": {\n    \"mappings\": {\n      \"label\": \"매핑\",\n      \"class\": \"클래스\",\n      \"consumes\": \"입력 유형\",\n\n      \"filter_name\": \"필터 이름\",\n      \"handler\": \"핸들러\",\n      \"headers\": \"헤더\",\n      \"http-verb\": \"메서드\",\n      \"mappings_not_supported_spring_boot_1\": \"매핑은 스프링부트 1.x 버전에서는 지원하지 않습니다.\",\n      \"parameters\": \"파라미터\",\n      \"produces\": \"반환 유형\",\n      \"servlet_name\": \"서블릿 이름\",\n      \"url_pattern\": \"Url 패턴\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/mappings/i18n.pt-BR.json",
    "content": "{\n  \"instances\": {\n    \"mappings\": {\n      \"label\": \"Mappings\",\n      \"class\": \"Calsses\",\n      \"consumes\": \"Consumidores\",\n\n      \"filter_name\": \"Nome do Filtro\",\n      \"handler\": \"Handler\",\n      \"headers\": \"Cabeçalhos\",\n      \"http-verb\": \"Método\",\n      \"mappings_not_supported_spring_boot_1\": \"Mappings não são suportados para aplicações Spring Boot 1.x.\",\n      \"parameters\": \"Parâmetros\",\n      \"produces\": \"Produtores\",\n      \"servlet_name\": \"Nome do Servlet\",\n      \"url_pattern\": \"Padrão de URL\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/mappings/i18n.ru.json",
    "content": "{\n  \"instances\": {\n    \"mappings\": {\n      \"label\": \"Маппинги\",\n      \"class\": \"Класс\",\n      \"consumes\": \"Потребляет\",\n      \"filter_name\": \"Название фильтра\",\n      \"handler\": \"Обработчик\",\n      \"headers\": \"Заголовки\",\n      \"http-verb\": \"Метод\",\n      \"mappings_not_supported_spring_boot_1\": \"Маппинги не поддерживаются для приложений на Spring Boot 1.x.\",\n      \"parameters\": \"Параметры\",\n      \"produces\": \"Производит\",\n      \"servlet_name\": \"Имя сервлета\",\n      \"url_pattern\": \"URL-шаблон\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/mappings/i18n.zh-CN.json",
    "content": "{\n  \"instances\": {\n    \"mappings\": {\n      \"label\": \"映射\",\n      \"class\": \"类\",\n      \"consumes\": \"提交内容类型\",\n\n      \"filter_name\": \"过滤器名称\",\n      \"handler\": \"处理程序\",\n      \"headers\": \"请求头\",\n      \"http-verb\": \"方法\",\n      \"mappings_not_supported_spring_boot_1\": \"Spring Boot 1.x的应用不支持映射。\",\n      \"parameters\": \"参数\",\n      \"produces\": \"返回内容类型\",\n      \"servlet_name\": \"程序名称\",\n      \"url_pattern\": \"URL模式\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/mappings/i18n.zh-TW.json",
    "content": "{\n  \"instances\": {\n    \"mappings\": {\n      \"label\": \"端點對應\",\n      \"class\": \"類別\",\n      \"consumes\": \"接收內容類型\",\n\n      \"filter_name\": \"篩選器名稱\",\n      \"handler\": \"處理器\",\n      \"headers\": \"標頭\",\n      \"http-verb\": \"方法\",\n      \"mappings_not_supported_spring_boot_1\": \"Spring Boot 1.x 的應用程式不支援端點對應。\",\n      \"parameters\": \"參數\",\n      \"produces\": \"回傳內容類型\",\n      \"servlet_name\": \"Servlet 名稱\",\n      \"url_pattern\": \"URL 模式\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/mappings/index.spec.ts",
    "content": "import { screen, waitFor } from '@testing-library/vue';\nimport { shallowMount } from '@vue/test-utils';\nimport { describe, expect, it, vi } from 'vitest';\n\nimport Mappings from './index.vue';\n\nimport { mappings } from '@/mocks/instance/mappings/data';\nimport Instance from '@/services/instance';\nimport { render } from '@/test-utils';\n\nconst mockedMapping =\n  mappings.contexts['spring-boot-admin-sample-servlet'].mappings;\nconst dispatcherServlets = mockedMapping.dispatcherServlets;\nconst servlets = mockedMapping.servlets;\nconst servletFilters = mockedMapping.servletFilters;\n\ndescribe('Mappings', () => {\n  it('should render the header for a context when just context name is provided', async () => {\n    renderWithInstance({\n      contexts: {\n        'spring-boot-admin-sample-servlet': {},\n      },\n    });\n\n    const header = await waitFor(() =>\n      screen.getByRole('heading', { name: 'spring-boot-admin-sample-servlet' }),\n    );\n    expect(header).toBeVisible();\n  });\n\n  it('should render handler name when dispatcherServlets are available', async () => {\n    renderWithInstance({\n      contexts: {\n        'spring-boot-admin-sample-servlet': {\n          mappings: {\n            dispatcherServlets,\n          },\n        },\n      },\n    });\n    const element = await waitFor(() =>\n      screen.getByRole('cell', { name: 'Actuator root web endpoint' }),\n    );\n    expect(element).toBeVisible();\n  });\n\n  it('should render classname when servlets are available', async () => {\n    renderWithInstance({\n      contexts: {\n        'spring-boot-admin-sample-servlet': {\n          mappings: {\n            servlets,\n          },\n        },\n      },\n    });\n    const element = await waitFor(() =>\n      screen.getByRole('cell', {\n        name: 'org.springframework.web.servlet.DispatcherServlet',\n      }),\n    );\n    expect(element).toBeVisible();\n  });\n\n  it('should render classname when servletFilters are available', async () => {\n    renderWithInstance({\n      contexts: {\n        'spring-boot-admin-sample-servlet': {\n          mappings: {\n            servletFilters,\n          },\n        },\n      },\n    });\n    const element = await waitFor(() =>\n      screen.getByRole('cell', { name: 'webMvcMetricsFilter' }),\n    );\n    expect(element).toBeVisible();\n  });\n\n  it.each`\n    contentType\n    ${'application/vnd.spring-boot.actuator.v2+json'}\n    ${'application/vnd.spring-boot.actuator.v3+json'}\n    ${'application/vnd.spring-boot.actuator.v2+json;charset=UTF-8'}\n    ${'application/vnd.spring-boot.actuator.v3+json;charset=UTF-8'}\n  `('should accept allowed content type headers', ({ contentType }) => {\n    const wrapper = shallowMount(Mappings, {\n      props: {\n        instance: createInstanceWithMappingsData(mappings),\n      },\n    });\n\n    expect(wrapper.vm.isSupportedContextType(contentType)).toBe(true);\n  });\n\n  it.each`\n    contentType\n    ${'application/vnd.spring-boot.actuator.v+json'}\n    ${'application/vnd.spring-boot.actuator.v34+json'}\n    ${'invalid'}\n    ${null}\n    ${undefined}\n  `('should reject content type headers', ({ contentType }) => {\n    const wrapper = shallowMount(Mappings, {\n      props: {\n        instance: createInstanceWithMappingsData(mappings),\n      },\n    });\n\n    expect(wrapper.vm.isSupportedContextType(contentType)).toBe(false);\n  });\n\n  // Helpers\n  function renderWithInstance(data) {\n    render(Mappings, {\n      props: {\n        instance: createInstanceWithMappingsData(data),\n      },\n    });\n  }\n\n  function createInstanceWithMappingsData(data) {\n    const instance = new Instance({ id: 4711 });\n    instance.fetchMappings = vi.fn().mockReturnValue({\n      headers: {\n        'content-type': 'application/vnd.spring-boot.actuator.v2+json',\n      },\n      data,\n    });\n    return instance;\n  }\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/mappings/index.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-instance-section :error=\"error\" :loading=\"!hasLoaded\">\n    <div v-if=\"isOldMetrics\" class=\"message is-warning\">\n      <div\n        class=\"message-body\"\n        v-text=\"$t('instances.mappings.mappings_not_supported_spring_boot_1')\"\n      />\n    </div>\n    <template v-for=\"(context, ctxName) in contexts\" :key=\"ctxName\">\n      <sba-panel :seamless=\"true\" :title=\"ctxName\">\n        <dispatcher-mappings\n          v-if=\"hasDispatcherServlets(context)\"\n          :key=\"`${ctxName}_dispatcherServlets`\"\n          :dispatchers=\"context.mappings.dispatcherServlets\"\n        />\n\n        <dispatcher-mappings\n          v-if=\"hasDispatcherHandlers(context)\"\n          :key=\"`${ctxName}_dispatcherHandlers`\"\n          :dispatchers=\"context.mappings.dispatcherHandlers\"\n        />\n\n        <servlet-mappings\n          v-if=\"hasServlet(context)\"\n          :key=\"`${ctxName}_servlets`\"\n          :servlets=\"context.mappings.servlets\"\n        />\n\n        <servlet-filter-mappings\n          v-if=\"hasServletFilters(context)\"\n          :key=\"`${ctxName}_servletFilters`\"\n          :servlet-filters=\"context.mappings.servletFilters\"\n        />\n      </sba-panel>\n    </template>\n  </sba-instance-section>\n</template>\n\n<script>\nimport SbaPanel from '@/components/sba-panel';\n\nimport Instance from '@/services/instance';\nimport { VIEW_GROUP } from '@/views/ViewGroup';\nimport DispatcherMappings from '@/views/instances/mappings/DispatcherMappings';\nimport ServletFilterMappings from '@/views/instances/mappings/ServletFilterMappings';\nimport ServletMappings from '@/views/instances/mappings/ServletMappings';\nimport SbaInstanceSection from '@/views/instances/shell/sba-instance-section';\n\nexport default {\n  components: {\n    SbaPanel,\n    SbaInstanceSection,\n    DispatcherMappings,\n    ServletMappings,\n    ServletFilterMappings,\n  },\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  data: () => ({\n    hasLoaded: false,\n    error: null,\n    contexts: null,\n    isOldMetrics: false,\n  }),\n  created() {\n    this.fetchMappings();\n  },\n  methods: {\n    hasDispatcherServlets(context) {\n      return context?.mappings?.dispatcherServlets !== undefined;\n    },\n    hasDispatcherHandlers(context) {\n      return context?.mappings?.dispatcherHandlers !== undefined;\n    },\n    hasServlet(context) {\n      return context?.mappings?.servlets !== undefined;\n    },\n    hasServletFilters(context) {\n      return context?.mappings?.servletFilters !== undefined;\n    },\n    isSupportedContextType(contentType) {\n      const supportedContentTypes = [\n        'application/vnd.spring-boot.actuator.v3+json',\n        'application/vnd.spring-boot.actuator.v2+json',\n      ];\n\n      return !!supportedContentTypes.find((supportedContentType) =>\n        (contentType || '').includes(supportedContentType),\n      );\n    },\n    async fetchMappings() {\n      this.error = null;\n      try {\n        const res = await this.instance.fetchMappings();\n        if (this.isSupportedContextType(res.headers['content-type'])) {\n          this.contexts = res.data.contexts;\n        } else {\n          this.isOldMetrics = true;\n        }\n      } catch (error) {\n        console.warn('Fetching mappings failed:', error);\n        this.error = error;\n      }\n      this.hasLoaded = true;\n    },\n  },\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      name: 'instances/mappings',\n      parent: 'instances',\n      path: 'mappings',\n      label: 'instances.mappings.label',\n      group: VIEW_GROUP.WEB,\n      component: this,\n      order: 450,\n      isEnabled: ({ instance }) => instance.hasEndpoint('mappings'),\n    });\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/metrics/i18n.de.json",
    "content": "{\n  \"instances\": {\n    \"metrics\": {\n      \"label\": \"Metriken\",\n      \"add_metric\": \"Metrik hinzufügen\",\n      \"fetching_tags\": \"Verfügbare Tags werden geladen\",\n      \"no_tags\": \"(keine Tags)\",\n      \"no_tags_available\": \"Keine Tags verfügbar.\",\n      \"metrics_not_supported_spring_boot_1\": \"Spring Boot 1.x Anwendungen unterstützen keine Metriken.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/metrics/i18n.en.json",
    "content": "{\n  \"instances\": {\n    \"metrics\": {\n      \"label\": \"Metrics\",\n      \"add_metric\": \"Add Metric\",\n      \"fetching_tags\": \"Fetching available tags\",\n      \"no_tags\": \"(no tags)\",\n      \"no_tags_available\": \"No tags available.\",\n      \"metrics_not_supported_spring_boot_1\": \"Metrics are not supported for Spring Boot 1.x applications.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/metrics/i18n.es.json",
    "content": "{\n  \"instances\": {\n    \"metrics\": {\n      \"label\": \"Métricas\",\n      \"add_metric\": \"Agregar Métrica\",\n\n      \"fetching_tags\": \"Recuperando tags disponibles\",\n      \"no_tags\": \"(no tags)\",\n      \"no_tags_available\": \"No hay tags disponibles.\",\n      \"metrics_not_supported_spring_boot_1\": \"Métricas no está soportada para aplicaciones Spring Boot 1.x .\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/metrics/i18n.fr.json",
    "content": "{\n  \"instances\": {\n    \"metrics\": {\n      \"label\": \"Metriques\",\n      \"add_metric\": \"Ajouter une metrique\",\n      \"fetching_tags\": \"Récupération des tags disponibles\",\n      \"no_tags\": \"(aucun tags)\",\n      \"no_tags_available\": \"Aucun tags disponibles.\",\n      \"metrics_not_supported_spring_boot_1\": \"Les métriques ne sont pas supportés par les applications Spring Boot 1.x.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/metrics/i18n.is.json",
    "content": "{\n  \"instances\": {\n    \"metrics\": {\n      \"label\": \"Firð\",\n      \"add_metric\": \"Bæta við firð\",\n      \"fetching_tags\": \"Sækja tilbuin merki…\",\n      \"no_tags\": \"(engin merki)\",\n      \"no_tags_available\": \"Engin merki í boði.\",\n      \"metrics_not_supported_spring_boot_1\": \"Firð eru ekki í boði fyrir Spring Boot 1.x forrit.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/metrics/i18n.ko.json",
    "content": "{\n  \"instances\": {\n    \"metrics\": {\n      \"label\": \"지표\",\n      \"add_metric\": \"지표 추가\",\n\n      \"fetching_tags\": \"가능한 태그를 가져오는 중 입니다.\",\n      \"no_tags\": \"(태그 없음)\",\n      \"no_tags_available\": \"가능한 태그가 없습니다.\",\n      \"metrics_not_supported_spring_boot_1\": \"지표는 Spring Boot 1.x 애플리케이션에서는 지원하지 않습니다.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/metrics/i18n.pt-BR.json",
    "content": "{\n  \"instances\": {\n    \"metrics\": {\n      \"label\": \"Métricas\",\n      \"add_metric\": \"Adicionar Métrica\",\n\n      \"fetching_tags\": \"Buscando tags disponíveis\",\n      \"no_tags\": \"(sem tags)\",\n      \"no_tags_available\": \"Nenhuma tag disponível.\",\n      \"metrics_not_supported_spring_boot_1\": \"As métricas não são suportadas para aplicações Spring Boot 1.x.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/metrics/i18n.ru.json",
    "content": "{\n  \"instances\": {\n    \"metrics\": {\n      \"label\": \"Метрики\",\n      \"add_metric\": \"Добавить метрику\",\n      \"fetching_tags\": \"Получение доступных тегов\",\n      \"no_tags\": \"(без тегов)\",\n      \"no_tags_available\": \"Теги недоступны.\",\n      \"metrics_not_supported_spring_boot_1\": \"Метрики не поддерживаются для приложений на Spring Boot 1.x.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/metrics/i18n.zh-CN.json",
    "content": "{\n  \"instances\": {\n    \"metrics\": {\n      \"label\": \"性能\",\n      \"add_metric\": \"添加指标\",\n\n      \"fetching_tags\": \"正在获取可用标签\",\n      \"no_tags\": \"(没有标签)\",\n      \"no_tags_available\": \"没有可用的标签。\",\n      \"metrics_not_supported_spring_boot_1\": \"Spring Boot 1.x 的应用无法获取性能指标。\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/metrics/i18n.zh-TW.json",
    "content": "{\n  \"instances\": {\n    \"metrics\": {\n      \"label\": \"指標\",\n      \"add_metric\": \"新增指標\",\n      \"fetching_tags\": \"正在取得可用標籤\",\n      \"no_tags\": \"(無標籤)\",\n      \"no_tags_available\": \"沒有可用的標籤。\",\n      \"metrics_not_supported_spring_boot_1\": \"Spring Boot 1.x 的應用程式不支援指標功能。\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/metrics/index.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-instance-section :error=\"error\" :loading=\"!hasLoaded\">\n    <sba-panel v-if=\"!isOldMetrics && availableMetrics.length > 0\">\n      <form class=\"grid grid-cols-6 gap-6\">\n        <div class=\"col-span-3\">\n          <div>\n            <label\n              class=\"block text-sm font-medium text-gray-700\"\n              for=\"metric\"\n              v-text=\"$t('instances.metrics.label')\"\n            />\n            <div class=\"mt-1 relative rounded-md shadow-sm\">\n              <select\n                id=\"metric\"\n                v-model=\"selectedMetric\"\n                class=\"focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md\"\n              >\n                <option\n                  v-for=\"metric in availableMetrics\"\n                  :key=\"metric\"\n                  v-text=\"metric\"\n                />\n              </select>\n            </div>\n          </div>\n        </div>\n        <div class=\"col-span-3 space-y-3\">\n          <template v-if=\"availableTags\">\n            <div v-for=\"tag in availableTags\" :key=\"tag.tag\">\n              <label\n                class=\"block text-sm font-medium text-gray-700\"\n                for=\"metric2\"\n                >{{ tag.tag }}</label\n              >\n              <div class=\"mt-1 relative rounded-md shadow-sm\">\n                <select\n                  id=\"metric2\"\n                  v-model=\"selectedTags[tag.tag]\"\n                  class=\"focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md\"\n                >\n                  <option :value=\"undefined\">-</option>\n                  <option\n                    v-for=\"value in tag.values\"\n                    :key=\"value\"\n                    :value=\"value\"\n                    v-text=\"value\"\n                  />\n                </select>\n              </div>\n            </div>\n          </template>\n        </div>\n      </form>\n\n      <template #footer>\n        <div class=\"text-right\">\n          <sba-button type=\"primary\" @click=\"handleSubmit\">\n            {{ $t('instances.metrics.add_metric') }}\n          </sba-button>\n        </div>\n      </template>\n    </sba-panel>\n\n    <p\n      v-if=\"stateFetchingTags === 'executing'\"\n      class=\"is-loading\"\n      v-text=\"$t('instances.metrics.fetching_tags')\"\n    />\n\n    <metric\n      v-for=\"metric in metrics\"\n      :key=\"metric.name\"\n      :instance=\"instance\"\n      :metric-name=\"metric.name\"\n      :statistic-types=\"metric.types\"\n      :tag-selections=\"metric.tagSelections\"\n      @remove=\"removeMetric\"\n      @type-select=\"handleTypeSelect\"\n    />\n  </sba-instance-section>\n</template>\n\n<script>\nimport { sortBy } from 'lodash-es';\n\nimport SbaButton from '@/components/sba-button.vue';\nimport SbaPanel from '@/components/sba-panel.vue';\n\nimport Instance from '@/services/instance';\nimport { VIEW_GROUP } from '@/views/ViewGroup';\nimport Metric from '@/views/instances/metrics/metric';\nimport SbaInstanceSection from '@/views/instances/shell/sba-instance-section';\n\nconst ApiVersion = Object.freeze({\n  V2: 'application/vnd.spring-boot.actuator.v2',\n  V3: 'application/vnd.spring-boot.actuator.v3',\n});\n\nfunction isActuatorApiVersionSupported(headerContentType) {\n  return (\n    headerContentType.includes(ApiVersion.V2) ||\n    headerContentType.includes(ApiVersion.V3)\n  );\n}\n\nexport default {\n  components: { SbaButton, SbaPanel, SbaInstanceSection, Metric },\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  data: () => ({\n    metrics: [],\n    error: null,\n    availableMetrics: [],\n    selectedMetric: null,\n    stateFetchingTags: null,\n    availableTags: null,\n    selectedTags: null,\n    isOldMetrics: false,\n    hasLoaded: false,\n  }),\n  watch: {\n    selectedMetric: 'fetchAvailableTags',\n    metrics: {\n      deep: true,\n      handler(value) {\n        this.persistMetrics(value);\n      },\n    },\n  },\n  created() {\n    this.fetchMetricIndex();\n    this.metrics = this.loadMetrics();\n  },\n  methods: {\n    handleSubmit() {\n      this.addMetric(this.selectedMetric, this.selectedTags);\n    },\n    handleTypeSelect(metricName, statistic, type) {\n      const metric = this.metrics.find((m) => m.name === metricName);\n      if (metric) {\n        metric.types = { ...metric.types, [statistic]: type };\n      }\n    },\n    removeMetric(metricName, idxTagSelection) {\n      const idxMetric = this.metrics.findIndex((m) => m.name === metricName);\n      if (idxMetric >= 0) {\n        const metric = this.metrics[idxMetric];\n        if (idxTagSelection < metric.tagSelections.length) {\n          metric.tagSelections.splice(idxTagSelection, 1);\n        }\n        if (metric.tagSelections.length === 0) {\n          this.metrics.splice(idxMetric, 1);\n        }\n      }\n    },\n    addMetric(metricName, tagSelection = {}) {\n      if (metricName) {\n        const metric = this.metrics.find((m) => m.name === metricName);\n        if (metric) {\n          metric.tagSelections = [...metric.tagSelections, { ...tagSelection }];\n        } else {\n          this.metrics = sortBy(\n            [\n              ...this.metrics,\n              {\n                name: metricName,\n                tagSelections: [{ ...tagSelection }],\n                types: {},\n              },\n            ],\n            [(m) => m.name],\n          );\n        }\n      }\n    },\n    loadMetrics() {\n      if (window.localStorage) {\n        let persistedMetrics = localStorage.getItem(\n          `applications/${this.instance.registration.name}/metrics`,\n        );\n        if (persistedMetrics) {\n          return JSON.parse(persistedMetrics);\n        }\n      }\n      return [];\n    },\n    persistMetrics(value) {\n      if (window.localStorage) {\n        localStorage.setItem(\n          `applications/${this.instance.registration.name}/metrics`,\n          JSON.stringify(value),\n        );\n      }\n    },\n    async fetchMetricIndex() {\n      this.error = null;\n      try {\n        const res = await this.instance.fetchMetrics();\n        if (isActuatorApiVersionSupported(res.headers['content-type'])) {\n          this.availableMetrics = res.data.names;\n          this.availableMetrics.sort();\n          this.selectedMetric = this.availableMetrics[0];\n        } else {\n          this.error = new Error(\n            this.$t('instances.metrics.metrics_not_supported_spring_boot_1'),\n          );\n          this.isOldMetrics = true;\n        }\n      } catch (error) {\n        console.warn('Fetching metric index failed:', error);\n        this.error = error;\n      } finally {\n        this.hasLoaded = true;\n      }\n    },\n    async fetchAvailableTags(metricName) {\n      this.availableTags = null;\n      this.stateFetchingTags = 'executing';\n      try {\n        const response = await this.instance.fetchMetric(metricName);\n        this.availableTags = response.data.availableTags;\n        this.stateFetchingTags = 'completed';\n        this.selectedTags = {};\n        if (this.availableTags) {\n          this.availableTags.forEach(\n            (t) => (this.selectedTags[t.tag] = undefined),\n          );\n        }\n      } catch (error) {\n        console.warn('Fetching metric tags failed:', error);\n        this.stateFetchingTags = 'failed';\n      }\n    },\n  },\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      name: 'instances/metrics',\n      parent: 'instances',\n      path: 'metrics',\n      component: this,\n      label: 'instances.metrics.label',\n      group: VIEW_GROUP.INSIGHTS,\n      order: 50,\n      isEnabled: ({ instance }) => instance.hasEndpoint('metrics'),\n    });\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/metrics/metric.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div>\n    <sba-panel :header-sticks-below=\"'#navigation'\" :title=\"metricName\">\n      <template #actions>\n        <div\n          v-for=\"statistic in statistics\"\n          :key=\"`head-${statistic}`\"\n          class=\"inline-flex items-center\"\n        >\n          <span\n            class=\"block font-medium text-gray-700 px-3\"\n            v-text=\"statistic\"\n          />\n\n          <div class=\"relative rounded-md shadow-sm\">\n            <select\n              :value=\"statisticTypes[statistic]\"\n              class=\"focus:ring-indigo-500 focus:border-indigo-500 block w-full text-sm border-gray-300 rounded-md\"\n              @change=\"\n                $emit('type-select', metricName, statistic, $event.target.value)\n              \"\n            >\n              <option :value=\"undefined\">-</option>\n              <option\n                :value=\"MetricValueType.INTEGER\"\n                v-text=\"$t('term.integer')\"\n              />\n              <option\n                :value=\"MetricValueType.FLOAT\"\n                v-text=\"$t('term.float')\"\n              />\n              <option\n                :value=\"MetricValueType.DURATION\"\n                v-text=\"$t('term.duration')\"\n              />\n              <option\n                :value=\"MetricValueType.MILLIS\"\n                v-text=\"$t('term.milliseconds')\"\n              />\n              <option\n                :value=\"MetricValueType.BYTES\"\n                v-text=\"$t('term.bytes')\"\n              />\n              <option\n                :value=\"MetricValueType.EPOCH_TIME\"\n                v-text=\"$t('term.epoch_time')\"\n              />\n            </select>\n          </div>\n        </div>\n      </template>\n\n      <div class=\"-mx-4 -my-3\">\n        <div\n          v-for=\"(tags, idx) in tagSelections\"\n          :key=\"idx\"\n          :class=\"{ 'bg-gray-50': idx % 2 !== 0 }\"\n          class=\"bg-white px-4 py-3 grid grid-cols-3 gap-4\"\n        >\n          <div\n            class=\"text-sm flex items-center font-medium text-gray-500 col-span-2\"\n          >\n            <span\n              :title=\"getLabel(tags)\"\n              class=\"whitespace-pre\"\n              v-text=\"getLabel(tags)\"\n            />\n            <span\n              v-if=\"errors[idx]\"\n              :title=\"errors[idx]\"\n              class=\"text-yellow-300 pl-1\"\n            >\n              <font-awesome-icon icon=\"exclamation-triangle\" />\n            </span>\n          </div>\n\n          <div class=\"mt-1 text-sm text-gray-900\">\n            <div class=\"flex items-center justify-end\">\n              <span\n                v-for=\"statistic in statistics\"\n                :key=\"`value-${idx}-${statistic}`\"\n                class=\"flex-1\"\n                v-text=\"getValue(measurements[idx], statistic)\"\n              />\n              <sba-icon-button\n                :icon=\"'trash'\"\n                class=\"self-end\"\n                @click.stop=\"handleRemove(idx)\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </sba-panel>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';\nimport moment from 'moment';\nimport prettyBytes from 'pretty-bytes';\nimport { take } from 'rxjs/operators';\nimport { useI18n } from 'vue-i18n';\n\nimport SbaIconButton from '@/components/sba-icon-button.vue';\nimport SbaPanel from '@/components/sba-panel.vue';\n\nimport { useDateTimeFormatter } from '@/composables/useDateTimeFormatter';\nimport subscribing from '@/mixins/subscribing';\nimport Instance from '@/services/instance';\nimport { concatMap, delay, from, retryWhen, timer } from '@/utils/rxjs';\n\nconst { formatDateTime } = useDateTimeFormatter();\n\nenum MetricValueType {\n  INTEGER = 'integer',\n  FLOAT = 'float',\n  DURATION = 'duration',\n  MILLIS = 'millis',\n  BYTES = 'bytes',\n  EPOCH_TIME = 'epoch_time',\n}\n\nenum BaseUnit {\n  NANOSECONDS = 'nanoseconds',\n  MICROSECONDS = 'microseconds',\n  MILLISECONDS = 'milliseconds',\n  SECONDS = 'seconds',\n}\n\n// c.f. for a full list in Java Source Code: io.micrometer.core.instrument.Statistic\ntype StatisticType =\n  | 'VALUE'\n  | 'COUNT'\n  | 'TOTAL'\n  | 'TOTAL_TIME'\n  | 'MAX'\n  | 'UNKNOWN'\n  | 'ACTIVE_TASKS'\n  | 'DURATION'\n  | string;\n\nconst formatDuration = (value: number, baseUnit: BaseUnit) => {\n  const duration = moment.duration(toMillis(value, baseUnit));\n  return `${Math.floor(\n    duration.asDays(),\n  )}d ${duration.hours()}h ${duration.minutes()}m ${duration.seconds()}s ${duration.milliseconds()}ms`;\n};\n\nconst formatMillis = (value: number, baseUnit: BaseUnit) => {\n  const duration = moment.duration(toMillis(value, baseUnit));\n  return `${moment.duration(duration).asMilliseconds().toFixed(0)} ms`;\n};\n\nexport const toMillis = (value: number, baseUnit: BaseUnit) => {\n  switch (baseUnit) {\n    case BaseUnit.NANOSECONDS:\n      return value / 1_000_000;\n    case BaseUnit.MICROSECONDS:\n      return value / 1_000;\n    case BaseUnit.MILLISECONDS:\n      return value;\n    case BaseUnit.SECONDS:\n    default:\n      return value * 1_000;\n  }\n};\n\nexport default {\n  name: 'Metric',\n  components: { SbaIconButton, FontAwesomeIcon, SbaPanel },\n  mixins: [subscribing],\n  props: {\n    metricName: {\n      type: String,\n      required: true,\n    },\n    instance: {\n      type: Instance,\n      required: true,\n    },\n    tagSelections: {\n      type: Array,\n      default: () => [{}],\n    },\n    statisticTypes: {\n      type: Object,\n      default: () => ({}),\n    },\n  },\n  emits: ['type-select', 'remove'],\n  setup() {\n    const i18n = useI18n();\n    return {\n      MetricValueType,\n      i18n,\n    };\n  },\n  data: () => ({\n    description: '',\n    baseUnit: undefined,\n    measurements: [],\n    statistics: [],\n    errors: [],\n  }),\n  watch: {\n    tagSelections(newVal, oldVal) {\n      newVal\n        .map((v, i) => [v, i])\n        .filter(([v]) => !oldVal.includes(v))\n        .forEach(([v, i]) => this.fetchMetric(v, i));\n    },\n  },\n  methods: {\n    handleRemove(idx) {\n      this.$emit('remove', this.metricName, idx);\n    },\n    getValue(\n      measurements: Array<{ statistic: StatisticType; value: any }>,\n      statistic: StatisticType,\n    ) {\n      const measurement =\n        measurements && measurements.find((m) => m.statistic === statistic);\n\n      if (!measurement) {\n        return undefined;\n      }\n\n      const type = this.statisticTypes?.[statistic];\n      switch (type) {\n        case MetricValueType.INTEGER:\n          return measurement.value.toFixed(0);\n        case MetricValueType.FLOAT:\n          return measurement.value.toFixed(4);\n        case MetricValueType.DURATION:\n          return formatDuration(measurement.value, this.baseUnit);\n        case MetricValueType.MILLIS:\n          return formatMillis(measurement.value, this.baseUnit);\n        case MetricValueType.BYTES:\n          return prettyBytes(measurement.value);\n        case MetricValueType.EPOCH_TIME:\n          return formatDateTime(\n            new Date(toMillis(measurement.value, this.baseUnit)),\n          );\n        default:\n          return measurement.value;\n      }\n    },\n    getLabel(tags) {\n      return (\n        Object.entries(tags)\n          .filter(([, value]) => typeof value !== 'undefined')\n          .map((pair) => pair.join(':'))\n          .join('\\n') || this.i18n.t('instances.metrics.no_tags')\n      );\n    },\n    async fetchMetric(tags, idx) {\n      try {\n        const response = await this.instance.fetchMetric(this.metricName, tags);\n        this.errors[idx] = null;\n        this.measurements[idx] = response.data.measurements;\n        if (idx === 0) {\n          this.description = response.data.description;\n          this.baseUnit = response.data.baseUnit;\n          this.statistics = response.data.measurements.map((m) => m.statistic);\n        }\n      } catch (error) {\n        console.warn(`Fetching metric ${this.metricName} failed:`, error);\n        this.errors[idx] = error;\n      }\n    },\n    fetchAllTags() {\n      return from(this.tagSelections || []).pipe(concatMap(this.fetchMetric));\n    },\n    createSubscription() {\n      return timer(0, 2500)\n        .pipe(\n          concatMap(this.fetchAllTags),\n          retryWhen((err) => err.pipe(delay(1000), take(2))),\n        )\n        .subscribe();\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/quartz/i18n.de.json",
    "content": "{\n  \"instances\": {\n    \"quartz\": {\n      \"label\": \"Quartz\",\n      \"no_data\": \"Keine Quartz Informationen vorhanden\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/quartz/i18n.en.json",
    "content": "{\n  \"instances\": {\n    \"quartz\": {\n      \"label\": \"Quartz\",\n      \"no_data\": \"No Quartz information available\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/quartz/i18n.es.json",
    "content": "{\n  \"instances\": {\n    \"quartz\": {\n      \"label\": \"Quartz\",\n      \"no_data\": \"No hay información de Quartz disponible\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/quartz/i18n.fr.json",
    "content": "{\n  \"instances\": {\n    \"quartz\": {\n      \"label\": \"Quartz\",\n      \"no_data\": \"Aucune information disponible sur Quartz\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/quartz/i18n.is.json",
    "content": "{\n  \"instances\": {\n    \"quartz\": {\n      \"label\": \"Quartz\",\n      \"no_data\": \"Engar upplýsingar fást um Quartz\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/quartz/i18n.ko.json",
    "content": "{\n  \"instances\": {\n    \"quartz\": {\n      \"label\": \"Quartz\",\n      \"no_data\": \"사용 가능한 Quartz 정보가 없습니다.\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/quartz/i18n.pt-BR.json",
    "content": "{\n  \"instances\": {\n    \"quartz\": {\n      \"label\": \"Quartz\",\n      \"no_data\": \"Nenhuma informação de Quartz disponível\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/quartz/i18n.ru.json",
    "content": "{\n  \"instances\": {\n    \"quartz\": {\n      \"label\": \"Quartz\",\n      \"no_data\": \"Нет информации о Quartz\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/quartz/i18n.zh-CN.json",
    "content": "{\n  \"instances\": {\n    \"quartz\": {\n      \"label\": \"Quartz\",\n      \"no_data\": \"没有可用的石英信息\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/quartz/i18n.zh-TW.json",
    "content": "{\n  \"instances\": {\n    \"quartz\": {\n      \"label\": \"Quartz\",\n      \"no_data\": \"沒有可用的 Quartz 資訊\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/quartz/index.vue",
    "content": "<template>\n  <section :class=\"{ 'is-loading': !hasLoaded }\" class=\"section\">\n    <template v-if=\"hasLoaded\">\n      <div v-if=\"error\" class=\"message is-danger\">\n        <div class=\"message-body\">\n          <strong>\n            <font-awesome-icon\n              class=\"has-text-danger\"\n              icon=\"exclamation-triangle\"\n            />\n            <span v-text=\"$t('term.fetch_failed')\" />\n          </strong>\n          <p v-text=\"error.message\" />\n        </div>\n      </div>\n      <div v-else-if=\"!hasJobs\" class=\"message is-warning\">\n        <div class=\"message-body\" v-text=\"$t('instances.quartz.no_data')\" />\n      </div>\n\n      <!-- CONTENT -->\n      <h1>Jobs</h1>\n      <table class=\"table is-fullwidth\">\n        <thead>\n          <tr>\n            <th>Job Name</th>\n            <th>Description</th>\n            <th>Group</th>\n            <th>Durable</th>\n            <th>Request Recovery</th>\n          </tr>\n        </thead>\n        <tbody>\n          <tr\n            v-for=\"jobDetail in jobDetails\"\n            :key=\"jobDetail.group + '-' + jobDetail.name\"\n          >\n            <td>\n              <span v-text=\"jobDetail.name\" />\n              <small v-text=\"'(' + jobDetail.className + ')'\" />\n            </td>\n            <td v-text=\"jobDetail.description\" />\n            <td v-text=\"jobDetail.group\" />\n            <td v-text=\"jobDetail.durable\" />\n            <td v-text=\"jobDetail.requestRecovery\" />\n          </tr>\n        </tbody>\n      </table>\n\n      <h1>Triggers</h1>\n      <table class=\"table is-fullwidth\">\n        <thead>\n          <tr>\n            <th>Name</th>\n            <th>Description</th>\n            <th>Group</th>\n            <th>State</th>\n            <th>Type</th>\n            <th>Priority</th>\n            <th>Start Time</th>\n            <th>End Time</th>\n            <th>Previous Fire Time</th>\n            <th>Next Fire Time</th>\n          </tr>\n        </thead>\n\n        <trigger-row\n          v-for=\"triggerDetail in triggerDetails\"\n          :key=\"triggerDetail.group + '-' + triggerDetail.name\"\n          :trigger-detail=\"triggerDetail\"\n        />\n      </table>\n    </template>\n  </section>\n</template>\n\n<script>\nimport Instance from '@/services/instance';\nimport '@/views/ViewGroup';\nimport { VIEW_GROUP } from '@/views/ViewGroup';\nimport TriggerRow from '@/views/instances/quartz/trigger-row';\n\nexport default {\n  components: { TriggerRow },\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  data() {\n    return {\n      jobDetails: {},\n      triggerDetails: {},\n      error: null,\n      hasLoaded: false,\n    };\n  },\n  computed: {\n    hasJobs() {\n      return Object.keys(this.jobDetails).length >= 0;\n    },\n  },\n  async created() {\n    this.fetchQuartzJobs();\n    this.fetchQuartzTriggers();\n  },\n  methods: {\n    async fetchQuartzJobs() {\n      this.hasLoaded = false;\n      this.error = null;\n      try {\n        const response = await this.instance.fetchQuartzJobs();\n        const jobList = response.data;\n        const promises = [];\n        for (const group in jobList.groups) {\n          promises.push(\n            ...jobList.groups[group].jobs.map((name) =>\n              this.instance\n                .fetchQuartzJob(group, name)\n                .then((response) => response.data),\n            ),\n          );\n        }\n        this.jobDetails = (await Promise.allSettled(promises)).map(\n          (result) => result.value,\n        );\n      } catch (error) {\n        console.warn('Fetching Quartz Jobs failed:', error);\n        this.error = error;\n      } finally {\n        this.hasLoaded = true;\n      }\n    },\n    async fetchQuartzTriggers() {\n      this.hasLoaded = false;\n      this.error = null;\n      try {\n        const response = await this.instance.fetchQuartzTriggers();\n        const groupList = response.data;\n        const promises = [];\n        for (const group in groupList.groups) {\n          promises.push(\n            ...groupList.groups[group].triggers.map((name) =>\n              this.instance\n                .fetchQuartzTrigger(group, name)\n                .then((response) => response.data),\n            ),\n          );\n        }\n        this.triggerDetails = (await Promise.allSettled(promises)).map(\n          (result) => result.value,\n        );\n      } catch (error) {\n        console.warn('Fetching Quartz Triggers failed:', error);\n        this.error = error;\n      } finally {\n        this.hasLoaded = true;\n      }\n    },\n  },\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      name: 'instances/quartz',\n      parent: 'instances',\n      path: 'quartz',\n      component: this,\n      label: 'instances.quartz.label',\n      group: VIEW_GROUP.INSIGHTS,\n      order: 50,\n      isEnabled: ({ instance }) => instance.hasEndpoint('quartz'),\n    });\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/quartz/trigger-row.spec.ts",
    "content": "import userEvent from '@testing-library/user-event';\nimport { screen } from '@testing-library/vue';\nimport { beforeEach, describe, expect, it } from 'vitest';\n\nimport TriggerRow from './trigger-row.vue';\n\nimport { render } from '@/test-utils';\n\ndescribe('trigger-row.vue', () => {\n  beforeEach(async () => {\n    render(TriggerRow, {\n      props: {\n        triggerDetail: {\n          group: 'group2',\n          name: 'triggerSampleJob2',\n          state: 'NORMAL',\n          type: 'simple',\n          startTime: 1629451789546,\n          previousFireTime: 1629452183546,\n          nextFireTime: 1629452185546,\n          priority: 0,\n          simple: { interval: 2000, repeatCount: -1, timesTriggered: 198 },\n        },\n      },\n    });\n  });\n\n  it('should render with closed details initially', async () => {\n    const intervalElement = await screen.queryByText('interval');\n    expect(intervalElement).toBeNull();\n  });\n\n  it('should render details when clicking on arrow', async () => {\n    const toggleArrow = await screen.findByRole('button');\n    await userEvent.click(toggleArrow);\n    const intervalElement = await screen.findByText('interval');\n    expect(intervalElement).toBeVisible();\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/quartz/trigger-row.vue",
    "content": "<template>\n  <tbody>\n    <tr>\n      <td>\n        <a\n          class=\"icon\"\n          :class=\"{ 'icon--open': isOpen }\"\n          role=\"button\"\n          @click=\"toggle\"\n        />\n        <span v-text=\"triggerDetail.name\" />\n      </td>\n      <td v-text=\"triggerDetail.description\" />\n      <td v-text=\"triggerDetail.group\" />\n      <td v-text=\"triggerDetail.state\" />\n      <td v-text=\"triggerDetail.type\" />\n      <td v-text=\"triggerDetail.priority\" />\n      <td v-text=\"triggerDetail.startTime\" />\n      <td v-text=\"triggerDetail.endTime\" />\n      <td v-text=\"triggerDetail.previousFireTime\" />\n      <td v-text=\"triggerDetail.nextFireTime\" />\n    </tr>\n    <tr v-if=\"isOpen\">\n      <td colspan=\"10\">\n        <table class=\"table\">\n          <tr v-for=\"(value, name) in triggerTypeDetails\" :key=\"name\">\n            <td v-text=\"name\" />\n            <td v-text=\"value\" />\n          </tr>\n        </table>\n      </td>\n    </tr>\n  </tbody>\n</template>\n\n<script>\nexport default {\n  name: 'TriggerRow',\n  props: {\n    triggerDetail: {\n      type: Object,\n      required: true,\n    },\n  },\n  data() {\n    return {\n      isOpen: false,\n    };\n  },\n  computed: {\n    triggerTypeDetails() {\n      return (\n        ['calendarInterval', 'cron', 'custom', 'dailyTimeInterval', 'simple']\n          .map((type) => this.triggerDetail[type])\n          .find((ttd) => ttd !== undefined) || {}\n      );\n    },\n  },\n  methods: {\n    toggle() {\n      this.isOpen = !this.isOpen;\n    },\n  },\n};\n</script>\n\n<style lang=\"css\" scoped>\n.icon {\n  width: 7px;\n  height: 6px;\n  margin-right: 10px;\n  border-top: 6px solid transparent;\n  border-left: 7px solid #555;\n  border-bottom: 6px solid transparent;\n}\n.icon--open {\n  transform: rotate(90deg);\n}\n.icon.empty {\n  border: none;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/sbomdependencytrees/dependencyTree.ts",
    "content": "/**\n * Copyright 2014-2024 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport * as d3 from 'd3';\nimport { HierarchyNode, HierarchyPointNode, Selection, TreeLayout } from 'd3';\n\nconst MARGIN = { top: 20, bottom: 20, left: 10 };\nconst MAX_ITEMS_IN_FRAME = 4;\n\nconst extractGroupId = (dependency: string): string =>\n  dependency?.match(/^(.*?)\\//)?.[1] ?? dependency;\n\nconst extractArtifactId = (dependency: string): string =>\n  dependency?.match(/^[^/]+\\/([^@]+)@?.*$/)?.[1] ?? dependency;\n\nexport type DependencyTreeData = {\n  name: string;\n  children?: DependencyTreeData[];\n};\n\ntype HierarchyNodeData = HierarchyNode<DependencyTreeData>;\n\ntype MyHierarchyNode = Omit<\n  HierarchyNodeData,\n  'id' | 'descendants' | 'children' | 'links' | 'ancestors' | 'leaves' | 'data'\n> & {\n  new (data: DependencyTreeData): MyHierarchyNode;\n  id?: string;\n  children?: MyHierarchyNode[];\n  _children?: MyHierarchyNode[];\n  data: DependencyTreeData;\n  x0?: number;\n  y0?: number;\n  links(): Array<MyHierarchyLink>;\n  ancestors(): MyHierarchyNode[];\n  descendants(): MyHierarchyNode[];\n  leaves(): MyHierarchyNode[];\n};\n\ntype MyHierarchyPointNode = HierarchyPointNode<DependencyTreeData> & {\n  links(): Array<MyHierarchyPointLink>;\n};\n\ntype MyHierarchyLink = {\n  source: MyHierarchyNode;\n  target: MyHierarchyNode;\n};\n\ntype MyHierarchyPointLink = {\n  source: MyHierarchyPointNode;\n  target: MyHierarchyPointNode;\n};\n\nexport type D3DependencyTree = {\n  treeContainer: HTMLElement;\n  root: MyHierarchyNode;\n  treeLayout: TreeLayout<DependencyTreeData>;\n  svg: Selection<SVGGElement, unknown, null, undefined>;\n  gNode: Selection<SVGGElement, unknown, null, undefined>;\n  gLink: Selection<SVGGElement, unknown, null, undefined>;\n};\n\n// Utility Functions\nconst linkNodesHorizontal = (d) => {\n  const sx = d.source.x;\n  const sy = d.source.y + (d.source.nodeWidth || 0); // right edge of source\n  const tx = d.target.x;\n  const ty = d.target.y; // left edge of target\n  const mx = (sy + ty) / 2; // horizontal midpoint for smooth curve\n  // Horizontal link: M sx at sy, with control points at horizontal midpoint\n  return `M${sy},${sx}C${mx},${sx} ${mx},${tx} ${ty},${tx}`;\n};\n\nconst createGlobalLinkAndNode = (\n  svg: Selection<SVGGElement, unknown, null, undefined>,\n) => {\n  const gLink = svg\n    .append('g')\n    .attr('fill', 'none')\n    .attr('stroke', '#999')\n    .attr('stroke-opacity', 0.4)\n    .attr('stroke-width', 2);\n\n  const gNode = svg\n    .append('g')\n    .attr('cursor', 'pointer')\n    .attr('pointer-events', 'all');\n\n  return { gLink, gNode };\n};\n\nconst initRootAndDescendants = (\n  tree: D3DependencyTree,\n  initFolding: boolean,\n): void => {\n  tree.root.x0 = tree.treeContainer.getBoundingClientRect().width / 2;\n  tree.root.y0 = 0;\n  tree.root.descendants().forEach((d, i) => {\n    d.id = '' + i;\n    d._children = d.children;\n\n    if (initFolding && d.depth >= 1) {\n      d.children = null;\n    }\n  });\n};\n\nconst updateDependencyTree = async (\n  dependencyTree: D3DependencyTree,\n  source: MyHierarchyNode,\n  removeNodes = false,\n): Promise<void> => {\n  const { root, treeLayout, svg, gNode, gLink, nodeWidth } = dependencyTree;\n  const nodes = root.descendants().reverse();\n  const links = root.links();\n\n  treeLayout(root);\n\n  const [left, right, leftWidth, rightWidth] = nodes.reduce(\n    ([left, right, leftWidth, rightWidth], node) => [\n      node.x < left.x ? node : left,\n      node.x > right.x ? node : right,\n      node.y < leftWidth.y ? node : leftWidth,\n      node.y > rightWidth.y ? node : rightWidth,\n    ],\n    [root, root, root, root],\n  );\n\n  const height = right.x - left.x + MARGIN.top + MARGIN.bottom;\n  const width = rightWidth.y - leftWidth.y + MARGIN.left + nodeWidth;\n  const treeContainerWidth =\n    dependencyTree.treeContainer.getBoundingClientRect().width;\n\n  svg\n    .transition()\n    .attr('height', height)\n    .attr('width', width)\n    .attr(\n      'viewBox',\n      `${-MARGIN.left}, ${left.x - MARGIN.top}, ${width}, ${height}`,\n    )\n    .tween(\n      'resize',\n      window.ResizeObserver ? null : () => () => svg.dispatch('toggle'),\n    );\n\n  if (removeNodes) {\n    if (!source.children) {\n      svg.attr('style', 'visibility: hidden');\n    } else {\n      svg.attr('style', 'font-size: .75rem;');\n    }\n\n    gNode.selectAll<SVGGElement, any>('g').remove();\n  }\n\n  const subGNodeSelection = gNode\n    .selectAll<SVGGElement, typeof root>('g')\n    .data(nodes, (d) => d.id);\n\n  const nodeEnter = subGNodeSelection\n    .enter()\n    .append('g')\n    .attr('transform', () => `translate(${source.y0},${source.x0})`)\n    .attr('fill-opacity', 0)\n    .on('click', (event, d) => {\n      d.children = d.children ? null : d._children;\n      updateDependencyTree(dependencyTree, d, false);\n    });\n\n  nodeEnter\n    .append('rect')\n    .attr('width', treeContainerWidth / (MAX_ITEMS_IN_FRAME + 1))\n    .attr('height', '2.5rem')\n    .attr('y', '-1.25rem')\n    .attr('rx', 6)\n    .attr('ry', 6)\n    .attr('stroke-width', 1)\n    .attr('fill-opacity', 0.8)\n    .attr('class', (d) => `node ${d._children ? 'node-with-children' : ''}`);\n\n  nodeEnter\n    .append('text')\n    .attr('dy', () => '-.25rem')\n    .attr('x', 12)\n    .attr('text-anchor', 'start')\n    .text((d) =>\n      d && d.data && d.data.name ? extractGroupId(d.data.name) : '',\n    );\n\n  nodeEnter\n    .append('text')\n    .attr('dy', '.75rem')\n    .attr('x', 12)\n    .attr('text-anchor', 'start')\n    .text((d) => extractArtifactId(d.data.name));\n\n  nodeEnter\n    .on('mouseover', (event, d) => {\n      d3.select('#tooltip').transition().style('opacity', 1).text(d.data.name);\n    })\n    .on('mouseout', () => {\n      d3.select('#tooltip').transition().style('opacity', 0);\n    })\n    .on('mousemove', (event) => {\n      d3.select('#tooltip')\n        .style('left', `${event.layerX + 10}px`)\n        .style('top', `${event.layerY + 10}px`);\n    });\n\n  nodeEnter.each((d) => {\n    d.nodeWidth = nodeWidth;\n  });\n\n  subGNodeSelection\n    .merge(nodeEnter)\n    .transition()\n    .attr('transform', (d) => `translate(${d.y},${d.x})`)\n    .attr('fill-opacity', 1)\n    .attr('stroke-opacity', 1);\n\n  subGNodeSelection\n    .exit()\n    .transition()\n    .remove()\n    .attr('transform', () => `translate(${source.y},${source.x})`)\n    .attr('fill-opacity', 0)\n    .attr('stroke-opacity', 0);\n\n  const link = gLink.selectAll('path').data(links);\n\n  const linkEnter = link\n    .enter()\n    .append('path')\n    .attr('class', 'edge')\n    .attr('d', linkNodesHorizontal);\n\n  link.merge(linkEnter).transition().attr('d', linkNodesHorizontal);\n\n  link.exit().transition().remove().attr('d', linkNodesHorizontal);\n\n  root.eachBefore((d: MyHierarchyNode) => {\n    d.x0 = d.x;\n    d.y0 = d.y;\n  });\n};\n\nexport const createDependencyTree = async (\n  treeContainer: HTMLElement,\n  treeData: DependencyTreeData,\n  initFolding = true,\n): Promise<D3DependencyTree> => {\n  d3.select(treeContainer).select('svg').remove();\n\n  if (!treeData) return;\n\n  const elementsWidth = treeContainer.getBoundingClientRect().width;\n  const dx = 48;\n  const dy = elementsWidth / MAX_ITEMS_IN_FRAME;\n  const nodeWidth = elementsWidth / (MAX_ITEMS_IN_FRAME + 1);\n\n  const root = d3.hierarchy(treeData) as MyHierarchyNode;\n  const treeLayout = d3.tree<DependencyTreeData>().nodeSize([dx, dy]);\n\n  root.sort((a, b) => d3.ascending(a.data.name, b.data.name));\n\n  const svg = d3\n    .select(treeContainer)\n    .append('svg')\n    .attr('data-testid', 'treecontainer-svg')\n    .attr('width', elementsWidth)\n    .attr('height', dx)\n    .attr('viewBox', [-MARGIN.left, -MARGIN.top, elementsWidth, dx])\n    .attr('style', 'font-size: .75rem;');\n\n  d3.select(treeContainer)\n    .append('div')\n    .attr('id', 'tooltip')\n    .attr('class', 'border bg-white rounded px-2 shadow')\n    .attr('style', 'position: absolute; opacity: 0; font-size: 0.85rem;');\n\n  const { gLink, gNode } = createGlobalLinkAndNode(svg);\n\n  const d3DependencyTree: D3DependencyTree = {\n    treeContainer,\n    gNode,\n    root,\n    svg,\n    treeLayout,\n    gLink,\n    nodeWidth,\n  };\n\n  initRootAndDescendants(d3DependencyTree, initFolding);\n  await updateDependencyTree(d3DependencyTree, root);\n\n  return d3DependencyTree;\n};\n\nexport const rerenderDependencyTree = async (\n  dependencyTree: D3DependencyTree,\n  data: DependencyTreeData,\n): Promise<void> => {\n  dependencyTree.root = d3.hierarchy(\n    data?.children ? data : {},\n  ) as MyHierarchyNode;\n\n  initRootAndDescendants(dependencyTree, false);\n  await updateDependencyTree(dependencyTree, dependencyTree.root, true);\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/sbomdependencytrees/i18n.en.json",
    "content": "{\n  \"instances\": {\n    \"sbom\": {\n      \"label\": \"Dependency trees\",\n      \"legend\": {\n        \"title\": \"Legend\",\n        \"node\": \"Dependency with no further dependencies\",\n        \"node_with_children\": \"Dependency with other dependencies\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/sbomdependencytrees/i18n.zh-TW.json",
    "content": "{\n  \"instances\": {\n    \"sbom\": {\n      \"label\": \"相依性樹狀圖\",\n      \"legend\": {\n        \"title\": \"圖例\",\n        \"node\": \"無其他相依元件的相依項\",\n        \"node_with_children\": \"有其他相依元件的相依項\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/sbomdependencytrees/index.spec.ts",
    "content": "import { screen, waitFor } from '@testing-library/vue';\nimport { describe, expect, it, vi } from 'vitest';\n\nimport Instance from '@/services/instance';\nimport { render } from '@/test-utils';\nimport SbomDependencyTrees from '@/views/instances/sbomdependencytrees/index.vue';\n\ntype Deferred<T> = {\n  promise: Promise<T>;\n  resolve: (value: T) => void;\n  reject: (reason?: unknown) => void;\n};\n\nconst deferred = <T>(): Deferred<T> => {\n  let resolve!: (value: T) => void;\n  let reject!: (reason?: unknown) => void;\n  const promise = new Promise<T>((res, rej) => {\n    resolve = res;\n    reject = rej;\n  });\n  return { promise, resolve, reject };\n};\n\ndescribe('SBOM dependency trees - empty state', () => {\n  it('hides warning while fetchSbomIds() is pending and shows it when resolved with empty list', async () => {\n    const d = deferred<{ data: { ids: string[] } }>();\n    const instance = new Instance({\n      id: '4711',\n      registration: {\n        name: 'test',\n        healthUrl: 'http://localhost:8080/actuator/health',\n        source: 'http-api',\n      },\n    });\n\n    const fetchSbomIds = vi.fn().mockReturnValue(d.promise);\n    instance.fetchSbomIds = fetchSbomIds;\n\n    render(SbomDependencyTrees, {\n      props: {\n        instance,\n      },\n    });\n\n    await waitFor(() => expect(fetchSbomIds).toHaveBeenCalledTimes(1));\n\n    expect(\n      screen.getByTestId('instance-section-loading-spinner'),\n    ).toBeInTheDocument();\n    expect(\n      screen.queryByText('instances.dependencies.no_data_provided'),\n    ).not.toBeInTheDocument();\n\n    d.resolve({ data: { ids: [] } });\n\n    await waitFor(() => {\n      expect(\n        screen.queryByTestId('instance-section-loading-spinner'),\n      ).not.toBeInTheDocument();\n      expect(\n        screen.getByText('instances.dependencies.no_data_provided'),\n      ).toBeInTheDocument();\n    });\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/sbomdependencytrees/index.vue",
    "content": "<!--\n  - Copyright 2014-2024 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-instance-section :error=\"error\" :loading=\"!hasLoaded\">\n    <template #before>\n      <sba-sticky-subnav>\n        <sba-input\n          v-model=\"filter\"\n          :placeholder=\"$t('term.filter')\"\n          name=\"filter\"\n          type=\"search\"\n        >\n          <template #prepend>\n            <font-awesome-icon icon=\"filter\" />\n          </template>\n        </sba-input>\n      </sba-sticky-subnav>\n    </template>\n\n    <template v-if=\"hasLoaded && sboms.length === 0\">\n      <sba-alert\n        severity=\"WARN\"\n        :error=\"$t('instances.dependencies.no_data_provided')\"\n      />\n    </template>\n    <tree-graph\n      v-for=\"sbomId in sboms\"\n      v-else\n      :key=\"sbomId\"\n      :instance=\"instance\"\n      :sbom-id=\"sbomId\"\n      :filter=\"filter\"\n    ></tree-graph>\n  </sba-instance-section>\n</template>\n\n<script>\nimport { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';\n\nimport SbaAlert from '@/components/sba-alert.vue';\nimport SbaInput from '@/components/sba-input';\nimport SbaStickySubnav from '@/components/sba-sticky-subnav.vue';\n\nimport Instance from '@/services/instance';\nimport { VIEW_GROUP } from '@/views/ViewGroup';\nimport TreeGraph from '@/views/instances/sbomdependencytrees/tree.vue';\nimport SbaInstanceSection from '@/views/instances/shell/sba-instance-section';\n\nexport default {\n  components: {\n    SbaAlert,\n    TreeGraph,\n    FontAwesomeIcon,\n    SbaStickySubnav,\n    SbaInstanceSection,\n    SbaInput,\n  },\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  data: () => ({\n    hasLoaded: false,\n    error: null,\n    contexts: [],\n    sboms: [],\n    filter: '',\n  }),\n  computed: {},\n  created() {\n    this.fetchSboms();\n  },\n  methods: {\n    async fetchSboms() {\n      this.error = null;\n      this.hasLoaded = false;\n      try {\n        const res = await this.instance.fetchSbomIds();\n        this.sboms = res.data.ids;\n      } catch (error) {\n        console.warn('Fetching sbom ids failed:', error);\n        this.error = error;\n      }\n      this.hasLoaded = true;\n    },\n  },\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      name: 'instances/sbom',\n      parent: 'instances',\n      path: 'sbom',\n      label: 'instances.sbom.label',\n      group: VIEW_GROUP.DEPENDENCIES,\n      component: this,\n      order: 2,\n      isEnabled: ({ instance }) => instance.hasEndpoint('sbom'),\n    });\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/sbomdependencytrees/sbomUtils.spec.ts",
    "content": "import { describe, expect, it } from 'vitest';\n\nimport {\n  SbomDependency,\n  filterTree,\n  normalizeNodeName,\n  retrieveChildren,\n} from './sbomUtils';\n\nimport { DependencyTreeData } from '@/views/instances/sbomdependencytrees/dependencyTree';\n\ndescribe('normalizeNodeName', () => {\n  it('should remove the path before the first forward slash', () => {\n    const input = 'path/to/resource';\n    const expectedOutput = 'to/resource';\n    expect(normalizeNodeName(input)).toBe(expectedOutput);\n  });\n\n  it('should remove query parameters after the first question mark', () => {\n    const input = 'resource?query=param';\n    const expectedOutput = 'resource';\n    expect(normalizeNodeName(input)).toBe(expectedOutput);\n  });\n\n  it('should remove both the path and query parameters', () => {\n    const input =\n      'pkg:maven/org.springframework.boot/spring-boot-starter@3.3.0-RC1?type=jar';\n    const expectedOutput =\n      'org.springframework.boot/spring-boot-starter@3.3.0-RC1';\n    expect(normalizeNodeName(input)).toBe(expectedOutput);\n  });\n\n  it('should handle strings without a forward slash', () => {\n    const input = 'resource';\n    const expectedOutput = 'resource';\n    expect(normalizeNodeName(input)).toBe(expectedOutput);\n  });\n\n  it('should handle strings without a question mark', () => {\n    const input = 'path/to/resource';\n    const expectedOutput = 'to/resource';\n    expect(normalizeNodeName(input)).toBe(expectedOutput);\n  });\n\n  describe('retrieveChildren', () => {\n    const sbomDependencies: SbomDependency[] = [\n      { ref: 'A', dependsOn: ['B', 'C'] },\n      { ref: 'B', dependsOn: ['D'] },\n      { ref: 'C', dependsOn: [] },\n      { ref: 'D', dependsOn: [] },\n    ];\n\n    it('should return undefined if dependsOn is empty', () => {\n      const result = retrieveChildren([], sbomDependencies);\n      expect(result).toBeUndefined();\n    });\n\n    it('should return children data correctly', () => {\n      const result = retrieveChildren(['B', 'C'], sbomDependencies);\n      expect(result).toEqual([\n        { name: 'B', children: [{ name: 'D', children: undefined }] },\n        { name: 'C', children: undefined },\n      ]);\n    });\n\n    it('should handle nested dependencies correctly', () => {\n      const result = retrieveChildren(['A'], sbomDependencies);\n      expect(result).toEqual([\n        {\n          name: 'A',\n          children: [\n            { name: 'B', children: [{ name: 'D', children: undefined }] },\n            { name: 'C', children: undefined },\n          ],\n        },\n      ]);\n    });\n\n    it('should return node with undefined children if the reference is not found', () => {\n      const result = retrieveChildren(['E'], sbomDependencies);\n      expect(result).toEqual([\n        {\n          name: 'E',\n          children: undefined,\n        },\n      ]);\n    });\n  });\n\n  describe('filterTree', () => {\n    const treeData: DependencyTreeData = {\n      name: 'A',\n      children: [\n        { name: 'B', children: [{ name: 'D', children: undefined }] },\n        { name: 'C', children: undefined },\n      ],\n    };\n\n    it('should return the same tree if filter is empty', () => {\n      const result = filterTree(treeData, '');\n      expect(result).toEqual(treeData);\n    });\n\n    it('should filter the tree by node name', () => {\n      const result = filterTree(treeData, 'B');\n      expect(result).toEqual({\n        children: [\n          {\n            children: [],\n            name: 'B',\n          },\n        ],\n        name: 'A',\n      });\n    });\n\n    it('should return null if no nodes match the filter', () => {\n      const result = filterTree(treeData, 'E');\n      expect(result).toBeNull();\n    });\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/sbomdependencytrees/sbomUtils.ts",
    "content": "import { DependencyTreeData } from '@/views/instances/sbomdependencytrees/dependencyTree';\n\nexport type SbomDependency = {\n  ref: string;\n  dependsOn?: string[];\n};\n\n// remove the path before the first forward slash and remove any query parameters after the first question mark from a ref.\nexport const normalizeNodeName = (name: string): string =>\n  name.replace(/^[^\\/]*\\//, '').replace(/\\?.*$/, '');\n\nfunction getChildren(sbomDependencies: SbomDependency[], item: string) {\n  return retrieveChildren(\n    sbomDependencies\n      .filter((node) => node.ref === item && node.dependsOn.length)\n      .flatMap((node) => node.dependsOn),\n    sbomDependencies,\n  );\n}\n\nexport const retrieveChildren = (\n  dependsOn: string[],\n  sbomDependencies: SbomDependency[],\n): DependencyTreeData[] | undefined => {\n  if (!dependsOn.length) return undefined;\n\n  return dependsOn.map((item) => ({\n    name: normalizeNodeName(item),\n    children: getChildren(sbomDependencies, item),\n  }));\n};\n\nexport const normalizeData = (\n  sbomDependencies: SbomDependency[],\n): DependencyTreeData => {\n  const children = sbomDependencies[0].dependsOn.map((item) => ({\n    name: normalizeNodeName(item),\n    children: getChildren(sbomDependencies, item),\n  }));\n\n  return {\n    name: normalizeNodeName(sbomDependencies[0].ref),\n    children,\n  };\n};\n\nexport const filterTree = (\n  treeData: DependencyTreeData,\n  filter: string,\n): DependencyTreeData | null => {\n  if (!filter || filter.trim() === '') {\n    return treeData;\n  }\n\n  const filterLowerCase = filter.trim().toLowerCase();\n  const matchesCurrentNode = treeData.name\n    .toLowerCase()\n    .includes(filterLowerCase);\n\n  const filteredChildren = treeData.children\n    ?.map((child) => filterTree(child, filter))\n    .filter((child) => child !== null) as DependencyTreeData[];\n\n  if (matchesCurrentNode || (filteredChildren && filteredChildren.length > 0)) {\n    return {\n      ...treeData,\n      children: filteredChildren,\n    };\n  }\n\n  return null;\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/sbomdependencytrees/tree.spec.ts",
    "content": "import { RenderResult, screen, waitFor } from '@testing-library/vue';\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport { applications } from '@/mocks/applications/data';\nimport Application from '@/services/application';\nimport Instance from '@/services/instance';\nimport { render } from '@/test-utils';\nimport TreeGraph from '@/views/instances/sbomdependencytrees/tree.vue';\n\nconst setUnknownFilter = async (\n  dependencyTree: RenderResult,\n  instance: Instance,\n): Promise<void> => {\n  expect(\n    await screen.findAllByText(/spring-boot-admin-sample-servlet/),\n  ).toBeDefined();\n  expect(screen.getByTestId('treecontainer-svg')).toBeVisible();\n\n  await dependencyTree.rerender({\n    sbomId: 'application',\n    instance: instance,\n    filter: 'unknowndependencyfilter',\n  });\n\n  vi.advanceTimersByTime(2000);\n\n  await waitFor(() => {\n    expect(screen.getByTestId('treecontainer-svg')).not.toBeVisible();\n  });\n};\n\ndescribe('TreeGraph', () => {\n  const application = new Application(applications[0]);\n  const instance: Instance = application.instances[0];\n\n  let dependencyTree: RenderResult;\n\n  const renderComponent = async (filter = '') => {\n    dependencyTree = render(TreeGraph, {\n      props: {\n        sbomId: 'application',\n        instance: instance,\n        filter,\n      },\n    });\n    await waitFor(() =>\n      expect(screen.getByTestId('treecontainer-svg')).toBeInTheDocument(),\n    );\n  };\n\n  beforeEach(async () => {\n    vi.useFakeTimers({ ignoreMissingTimers: true });\n    await renderComponent();\n  });\n\n  afterEach(() => {\n    vi.useRealTimers();\n  });\n\n  it('renders correctly with given props', async () => {\n    const expectedTexts = [\n      'spring-boot-admin-sample-servlet',\n      'spring-boot-admin-sample-custom-ui',\n      'spring-boot-admin-starter-server',\n      'org.hsqldb',\n      'spring-boot-starter-mail',\n      'spring-boot-starter-security',\n      'spring-boot-starter-webmvc',\n      'spring-cloud-starter-config',\n      'spring-session-core',\n      'spring-session-jdbc',\n    ];\n\n    for (const text of expectedTexts) {\n      expect(\n        await screen.findByText(new RegExp(text, 'i')),\n      ).toBeInTheDocument();\n    }\n\n    expect(screen.getByTestId('treecontainer-svg')).toBeVisible();\n  });\n\n  it('filters the tree by filter prop', async () => {\n    expect(await screen.findAllByText(/spring-session-jdbc/)).toBeDefined();\n    await dependencyTree.rerender({\n      sbomId: 'application',\n      instance: instance,\n      filter: 'webmvc',\n    });\n\n    vi.advanceTimersByTime(2000);\n\n    await waitFor(() => {\n      expect(\n        screen.queryByText(/spring-session-jdbc/i),\n      ).not.toBeInTheDocument();\n      expect(screen.queryByText(/spring-webmvc/i)).toBeInTheDocument();\n    });\n  });\n\n  it(\"shows empty tree if filter doesn't apply to dependencies in tree\", async () => {\n    expect(\n      await screen.findAllByText(/spring-boot-admin-sample-servlet/),\n    ).toBeDefined();\n\n    await dependencyTree.rerender({\n      sbomId: 'application',\n      instance: instance,\n      filter: 'unknowndependencyfilter',\n    });\n\n    vi.advanceTimersByTime(2000);\n\n    await waitFor(() => {\n      expect(\n        screen.queryByText(/spring-boot-admin-sample-servlet/i),\n      ).not.toBeInTheDocument();\n    });\n  });\n\n  it('should hide svg if no dependencies found for filter', async () => {\n    await setUnknownFilter(dependencyTree, instance);\n\n    await waitFor(() => {\n      expect(screen.getByTestId('treecontainer-svg')).not.toBeVisible();\n    });\n  });\n\n  it('should toggle svg visibility if dependencies found for filter', async () => {\n    await setUnknownFilter(dependencyTree, instance);\n\n    await dependencyTree.rerender({\n      sbomId: 'application',\n      instance: instance,\n      filter: 'webmvc',\n    });\n\n    vi.advanceTimersByTime(2000);\n\n    await waitFor(() => {\n      expect(screen.getByTestId('treecontainer-svg')).toBeVisible();\n      expect(screen.queryByText(/spring-webmvc/i)).toBeInTheDocument();\n    });\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/sbomdependencytrees/tree.vue",
    "content": "<!--\n  - Copyright 2014-2024 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-instance-section :error=\"error\" :loading=\"isLoading\">\n    <sba-panel :title=\"sbomId\">\n      <div ref=\"treeContainer\" class=\"x-scroller\"></div>\n    </sba-panel>\n\n    <div\n      class=\"flex flex-wrap items-center justify-center gap-6 p-4 bg-white rounded-lg shadow-sm border text-sm\"\n    >\n      <div class=\"flex items-center gap-2\">\n        {{ t('instances.sbom.legend.title') }}\n      </div>\n      <div class=\"flex items-center gap-2\">\n        <div class=\"node w-3 h-3 rounded-full shadow-sm\"></div>\n        <span>{{ t('instances.sbom.legend.node') }}</span>\n      </div>\n      <div class=\"flex items-center gap-2\">\n        <div class=\"node-with-children w-3 h-3 rounded-full shadow-sm\"></div>\n        <span>{{ t('instances.sbom.legend.node_with_children') }}</span>\n      </div>\n    </div>\n  </sba-instance-section>\n</template>\n\n<script setup lang=\"ts\">\nimport * as d3 from 'd3';\nimport { debounce } from 'lodash-es';\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';\nimport { useI18n } from 'vue-i18n';\n\nimport SbaPanel from '@/components/sba-panel.vue';\n\nimport Instance from '@/services/instance';\nimport {\n  D3DependencyTree,\n  createDependencyTree,\n  rerenderDependencyTree,\n} from '@/views/instances/sbomdependencytrees/dependencyTree';\nimport {\n  SbomDependency,\n  filterTree,\n  normalizeData,\n} from '@/views/instances/sbomdependencytrees/sbomUtils';\nimport SbaInstanceSection from '@/views/instances/shell/sba-instance-section.vue';\n\nconst props = defineProps<{\n  sbomId: string;\n  instance: Instance;\n  filter?: string;\n}>();\nconst { t } = useI18n();\n\nconst treeContainer = ref<HTMLElement | null>(null);\nconst dependencies = ref<SbomDependency[]>([]);\nconst rootNode = ref<D3DependencyTree | null>(null);\nconst error = ref<string | null>(null);\nconst isLoading = ref<boolean | null>(false);\n\nconst normalizedData = computed(() => normalizeData(dependencies.value));\nconst filteredData = computed(() =>\n  filterTree(normalizedData.value, props.filter || ''),\n);\n\nconst fetchSbomDependencies = async (sbomId: string): Promise<void> => {\n  error.value = null;\n  isLoading.value = true;\n  try {\n    const res = await props.instance.fetchSbom(sbomId);\n    dependencies.value = res.data.dependencies;\n    await renderTree();\n  } catch (err) {\n    console.warn('Fetching sbom failed:', err);\n    error.value = err;\n  } finally {\n    isLoading.value = false;\n  }\n};\n\nconst renderTree = async (): Promise<void> => {\n  rootNode.value = await createDependencyTree(\n    treeContainer.value!,\n    filteredData.value,\n  );\n};\n\nconst updateTree = async (): Promise<void> => {\n  isLoading.value = true;\n  await rerenderDependencyTree(rootNode.value, filteredData.value);\n  isLoading.value = false;\n};\n\nconst rerenderOrUpdateTree = async (\n  newVal: string,\n  oldVal: string,\n): Promise<void> => {\n  if (dependencies.value.length > 0) {\n    if (!newVal.trim() || newVal === oldVal) {\n      await renderTree();\n    } else {\n      await updateTree();\n    }\n  }\n};\n\nconst debouncedRerenderOrUpdateTree = debounce(rerenderOrUpdateTree, 1000);\n\nwatch(\n  () => props.filter,\n  (newVal, oldVal) => {\n    if (newVal !== oldVal && treeContainer.value !== null) {\n      debouncedRerenderOrUpdateTree(newVal || '', oldVal || '');\n    }\n  },\n  { immediate: true },\n);\n\nonMounted(() => {\n  fetchSbomDependencies(props.sbomId);\n});\n\nonBeforeUnmount(() => {\n  debouncedRerenderOrUpdateTree.cancel();\n\n  // Clean up D3 elements\n  if (rootNode.value && treeContainer.value) {\n    d3.select(treeContainer.value).select('#tooltip').remove();\n    d3.select(treeContainer.value).select('svg').selectAll('*').on('.', null);\n  }\n});\n</script>\n\n<style scoped>\n.x-scroller {\n  overflow-x: auto;\n}\n\n:deep(.node) {\n  --color: #d0f7df;\n  fill: var(--color);\n  background-color: var(--color);\n}\n\n:deep(.node-with-children) {\n  --color: #91e8e0;\n  fill: var(--color);\n  background-color: var(--color);\n}\n\n:deep(.edge) {\n  stroke: #cccccc;\n  stroke-width: 1;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/scheduledtasks/i18n.de.json",
    "content": "{\n  \"instances\": {\n    \"scheduledtasks\": {\n      \"label\": \"Geplante Tasks\",\n      \"no_scheduledtasks\": \"Keine geplanten Aufgaben.\",\n      \"cron\": {\n        \"expression\": \"Expression\",\n        \"runnable\": \"Runnable\",\n        \"title\": \"Cron\"\n      },\n\n      \"fixed_delay\": {\n        \"initial_delay_ms\": \"Verzögerung (ms)\",\n        \"interval_ms\": \"Intervall (ms)\",\n        \"runnable\": \"Runnable\",\n        \"title\": \"Feste Verzögerung\"\n      },\n      \"fixed_rate\": {\n        \"initial_delay_ms\": \"Verzögerung (ms)\",\n        \"interval_ms\": \"Intervall (ms)\",\n        \"runnable\": \"Runnable\",\n        \"title\": \"Fixed Rate\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/scheduledtasks/i18n.en.json",
    "content": "{\n  \"instances\": {\n    \"scheduledtasks\": {\n      \"label\": \"Scheduled Tasks\",\n      \"no_scheduledtasks\": \"No scheduled tasks present.\",\n      \"last_execution\": \"Last Execution\",\n      \"last_execution_status\": \"Last Execution State\",\n      \"next_execution\": \"Next Execution\",\n      \"cron\": {\n        \"expression\": \"Expression\",\n        \"runnable\": \"Runnable\",\n        \"title\": \"Cron\"\n      },\n\n      \"fixed_delay\": {\n        \"initial_delay_ms\": \"Initial Delay (ms)\",\n        \"interval_ms\": \"Interval (ms)\",\n        \"runnable\": \"Runnable\",\n        \"title\": \"Fixed Delay\"\n      },\n      \"fixed_rate\": {\n        \"initial_delay_ms\": \"Initial Delay (ms)\",\n        \"interval_ms\": \"Interval (ms)\",\n        \"runnable\": \"Runnable\",\n        \"title\": \"Fixed Rate\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/scheduledtasks/i18n.es.json",
    "content": "{\n  \"instances\": {\n    \"scheduledtasks\": {\n      \"label\": \"Tareas programadas\",\n      \"no_scheduledtasks\": \"No se encontraron tareas programadas.\",\n      \"cron\": {\n        \"expression\": \"Expresión\",\n        \"runnable\": \"Ejecutable\",\n        \"title\": \"Cron\"\n      },\n\n      \"fixed_delay\": {\n        \"initial_delay_ms\": \"Demora inicial (ms)\",\n        \"interval_ms\": \"Intervalo (ms)\",\n        \"runnable\": \"Ejecutable\",\n        \"title\": \"Fixed delay\"\n      },\n      \"fixed_rate\": {\n        \"initial_delay_ms\": \"Demora inicial (ms)\",\n        \"interval_ms\": \"Intervalo (ms)\",\n        \"runnable\": \"Ejecutable\",\n        \"title\": \"Fixed Rate\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/scheduledtasks/i18n.fr.json",
    "content": "{\n  \"instances\": {\n    \"scheduledtasks\": {\n      \"label\": \"Tâches programmées\",\n      \"no_scheduledtasks\": \"Aucune tâches présente.\",\n      \"cron\": {\n        \"expression\": \"Expression\",\n        \"runnable\": \"Exécutable\",\n        \"title\": \"Cron\"\n      },\n\n      \"fixed_delay\": {\n        \"initial_delay_ms\": \"Délai initial (ms)\",\n        \"interval_ms\": \"Intervalle (ms)\",\n        \"runnable\": \"Exécutable\",\n        \"title\": \"Délai fixe\"\n      },\n      \"fixed_rate\": {\n        \"initial_delay_ms\": \"Délai initial (ms)\",\n        \"interval_ms\": \"Intervalle (ms)\",\n        \"runnable\": \"Exécutable\",\n        \"title\": \"Taux fixe\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/scheduledtasks/i18n.is.json",
    "content": "{\n  \"instances\": {\n    \"scheduledtasks\": {\n      \"label\": \"Fastsett verkefni\",\n      \"no_scheduledtasks\": \"Engin fastsett verkefni\",\n      \"cron\": {\n        \"expression\": \"Expression\",\n        \"runnable\": \"Runnable\",\n        \"title\": \"Cron\"\n      },\n\n      \"fixed_delay\": {\n        \"initial_delay_ms\": \"Töf framan af (ms)\",\n        \"interval_ms\": \"Tíðni (ms)\",\n        \"runnable\": \"Runnable\",\n        \"title\": \"Föst töf\"\n      },\n      \"fixed_rate\": {\n        \"initial_delay_ms\": \"Töf framan af (ms)\",\n        \"interval_ms\": \"Tíðni (ms)\",\n        \"runnable\": \"Runnable\",\n        \"title\": \"Fixed rate\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/scheduledtasks/i18n.ko.json",
    "content": "{\n  \"instances\": {\n    \"scheduledtasks\": {\n      \"label\": \"스케쥴 작업\",\n      \"no_scheduledtasks\": \"스케쥴 작업이 없습니다.\",\n      \"cron\": {\n        \"expression\": \"표현식\",\n        \"runnable\": \"실행가능\",\n        \"title\": \"Cron\"\n      },\n\n      \"fixed_delay\": {\n        \"initial_delay_ms\": \"기본 지연 (ms)\",\n        \"interval_ms\": \"반복 주기 (ms)\",\n        \"runnable\": \"실행가능\",\n        \"title\": \"고정 지연\"\n      },\n      \"fixed_rate\": {\n        \"initial_delay_ms\": \"기본 지연(ms)\",\n        \"interval_ms\": \"반복 주기 (ms)\",\n        \"runnable\": \"실행가능\",\n        \"title\": \"고정 비율\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/scheduledtasks/i18n.pt-BR.json",
    "content": "{\n  \"instances\": {\n    \"scheduledtasks\": {\n      \"label\": \"Tarefas Agendados\",\n      \"no_scheduledtasks\": \"Nenhuma tarefa agendada presente.\",\n      \"cron\": {\n        \"expression\": \"Expressão\",\n        \"runnable\": \"Executável\",\n        \"title\": \"Cron\"\n      },\n\n      \"fixed_delay\": {\n        \"initial_delay_ms\": \"Delay Inicial (ms)\",\n        \"interval_ms\": \"Intervalo (ms)\",\n        \"runnable\": \"Executável\",\n        \"title\": \"Delay Fixo\"\n      },\n      \"fixed_rate\": {\n        \"initial_delay_ms\": \"Delay Inicial (ms)\",\n        \"interval_ms\": \"Intervalo (ms)\",\n        \"runnable\": \"Executável\",\n        \"title\": \"Taxa Fixa\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/scheduledtasks/i18n.ru.json",
    "content": "{\n  \"instances\": {\n    \"scheduledtasks\": {\n      \"label\": \"Планировщик заданий\",\n      \"no_scheduledtasks\": \"Нет запланированных заданий.\",\n      \"cron\": {\n        \"expression\": \"Выражение\",\n        \"runnable\": \"Выполняемый объект\",\n        \"title\": \"Крон\"\n      },\n      \"fixed_delay\": {\n        \"initial_delay_ms\": \"Начальная задержка (мс)\",\n        \"interval_ms\": \"Интервал (мс)\",\n        \"runnable\": \"Runnable\",\n        \"title\": \"Фиксированная задержка\"\n      },\n      \"fixed_rate\": {\n        \"initial_delay_ms\": \"Начальная задержка (мс)\",\n        \"interval_ms\": \"Интервал (мс)\",\n        \"runnable\": \"Runnable\",\n        \"title\": \"Фиксированный темп\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/scheduledtasks/i18n.zh-CN.json",
    "content": "{\n  \"instances\": {\n    \"scheduledtasks\": {\n      \"label\": \"计划任务\",\n      \"no_scheduledtasks\": \"没有计划任务。\",\n      \"cron\": {\n        \"expression\": \"表达式\",\n        \"runnable\": \"可运行\",\n        \"title\": \"计划任务\"\n      },\n\n      \"fixed_delay\": {\n        \"initial_delay_ms\": \"初始延迟(毫秒)\",\n        \"interval_ms\": \"间隔(毫秒)\",\n        \"runnable\": \"可运行\",\n        \"title\": \"固定延迟\"\n      },\n      \"fixed_rate\": {\n        \"initial_delay_ms\": \"初始延迟(毫秒)\",\n        \"interval_ms\": \"间隔(毫秒)\",\n        \"runnable\": \"可运行\",\n        \"title\": \"固定速度\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/scheduledtasks/i18n.zh-TW.json",
    "content": "{\n  \"instances\": {\n    \"scheduledtasks\": {\n      \"label\": \"排程任務\",\n      \"no_scheduledtasks\": \"沒有排程任務。\",\n      \"last_execution\": \"上次執行\",\n      \"last_execution_status\": \"上次執行狀態\",\n      \"next_execution\": \"下次執行\",\n      \"cron\": {\n        \"expression\": \"表達式\",\n        \"runnable\": \"可執行項目\",\n        \"title\": \"Cron\"\n      },\n\n      \"fixed_delay\": {\n        \"initial_delay_ms\": \"初始延遲 (毫秒)\",\n        \"interval_ms\": \"間隔 (毫秒)\",\n        \"runnable\": \"可執行項目\",\n        \"title\": \"Fixed Delay\"\n      },\n      \"fixed_rate\": {\n        \"initial_delay_ms\": \"初始延遲 (毫秒)\",\n        \"interval_ms\": \"間隔 (毫秒)\",\n        \"runnable\": \"可執行項目\",\n        \"title\": \"Fixed Rate\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/scheduledtasks/index.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-instance-section :error=\"err\" :loading=\"!hasLoaded\">\n    <div v-if=\"!hasData\" class=\"message is-warning\">\n      <div\n        class=\"message-body\"\n        v-text=\"$t('instances.scheduledtasks.no_scheduledtasks')\"\n      />\n    </div>\n\n    <div class=\"flex flex-col\">\n      <template v-if=\"hasCronData\">\n        <sba-panel :title=\"$t('instances.scheduledtasks.cron.title')\" seamless>\n          <table class=\"table w-full table-striped\">\n            <thead>\n              <tr>\n                <th v-text=\"$t('instances.scheduledtasks.cron.runnable')\" />\n                <th v-text=\"$t('instances.scheduledtasks.cron.expression')\" />\n                <th v-text=\"$t('instances.scheduledtasks.next_execution')\" />\n                <th v-text=\"$t('instances.scheduledtasks.last_execution')\" />\n                <th\n                  v-text=\"$t('instances.scheduledtasks.last_execution_status')\"\n                />\n              </tr>\n            </thead>\n            <tbody v-for=\"task in cron\" :key=\"task.runnable.target\">\n              <tr>\n                <td\n                  class=\"scheduledtasks__target\"\n                  :title=\"task.runnable.target\"\n                  v-text=\"truncateClassname(task.runnable.target)\"\n                />\n                <td\n                  class=\"font-mono text-sm\"\n                  :title=\"task.expression\"\n                  v-text=\"formatCron(task.expression)\"\n                />\n                <scheduled-task-executions :task=\"task\" />\n              </tr>\n            </tbody>\n          </table>\n        </sba-panel>\n      </template>\n\n      <template v-if=\"hasFixedDelayData\">\n        <sba-panel\n          :title=\"$t('instances.scheduledtasks.fixed_delay.title')\"\n          seamless\n        >\n          <table class=\"table w-full table-striped\">\n            <thead>\n              <tr>\n                <th\n                  v-text=\"$t('instances.scheduledtasks.fixed_delay.runnable')\"\n                />\n                <th\n                  v-text=\"\n                    $t('instances.scheduledtasks.fixed_delay.initial_delay_ms')\n                  \"\n                />\n                <th\n                  v-text=\"\n                    $t('instances.scheduledtasks.fixed_delay.interval_ms')\n                  \"\n                />\n                <th v-text=\"$t('instances.scheduledtasks.next_execution')\" />\n                <th v-text=\"$t('instances.scheduledtasks.last_execution')\" />\n                <th\n                  v-text=\"$t('instances.scheduledtasks.last_execution_status')\"\n                />\n              </tr>\n            </thead>\n            <tbody v-for=\"task in fixedDelay\" :key=\"task.runnable.target\">\n              <tr>\n                <td\n                  class=\"scheduledtasks__target\"\n                  :title=\"task.runnable.target\"\n                  v-text=\"truncateClassname(task.runnable.target)\"\n                />\n                <td\n                  :title=\"`${task.initialDelay}ms`\"\n                  v-text=\"formatTime(task.initialDelay)\"\n                />\n                <td\n                  :title=\"`${task.interval}ms`\"\n                  v-text=\"formatTime(task.interval)\"\n                />\n                <scheduled-task-executions :task=\"task\" />\n              </tr>\n            </tbody>\n          </table>\n        </sba-panel>\n      </template>\n\n      <template v-if=\"hasFixedRateData\">\n        <sba-panel\n          :title=\"$t('instances.scheduledtasks.fixed_rate.title')\"\n          seamless\n        >\n          <table class=\"table w-full table-striped\">\n            <thead>\n              <tr>\n                <th\n                  v-text=\"$t('instances.scheduledtasks.fixed_delay.runnable')\"\n                />\n                <th\n                  v-text=\"\n                    $t('instances.scheduledtasks.fixed_delay.initial_delay_ms')\n                  \"\n                />\n                <th\n                  v-text=\"\n                    $t('instances.scheduledtasks.fixed_delay.interval_ms')\n                  \"\n                />\n                <th v-text=\"$t('instances.scheduledtasks.next_execution')\" />\n                <th v-text=\"$t('instances.scheduledtasks.last_execution')\" />\n                <th\n                  v-text=\"$t('instances.scheduledtasks.last_execution_status')\"\n                />\n              </tr>\n            </thead>\n            <tbody v-for=\"task in fixedRate\" :key=\"task.runnable.target\">\n              <tr>\n                <td\n                  class=\"scheduledtasks__target\"\n                  :title=\"task.runnable.target\"\n                  v-text=\"truncateClassname(task.runnable.target)\"\n                />\n                <td\n                  :title=\"`${task.initialDelay}ms`\"\n                  v-text=\"formatTime(task.initialDelay)\"\n                />\n                <td\n                  :title=\"`${task.interval}ms`\"\n                  v-text=\"formatTime(task.interval)\"\n                />\n                <scheduled-task-executions :task=\"task\" />\n              </tr>\n            </tbody>\n          </table>\n        </sba-panel>\n      </template>\n    </div>\n  </sba-instance-section>\n</template>\n\n<script>\nimport cronstrue from 'cronstrue/i18n';\nimport { useI18n } from 'vue-i18n';\n\nimport SbaPanel from '@/components/sba-panel.vue';\n\nimport { useClassnameShortener } from '@/composables/useClassnameShortener';\nimport Instance from '@/services/instance';\nimport { usePrettyTime } from '@/utils/prettyTime';\nimport { VIEW_GROUP } from '@/views/ViewGroup';\nimport ScheduledTaskExecutions from '@/views/instances/scheduledtasks/scheduled-task-executions.vue';\nimport SbaInstanceSection from '@/views/instances/shell/sba-instance-section';\n\nexport default {\n  components: {\n    ScheduledTaskExecutions,\n    SbaPanel,\n    SbaInstanceSection,\n  },\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  setup() {\n    const { formatTime } = usePrettyTime();\n    const { locale } = useI18n();\n    const { truncateClassname } = useClassnameShortener({ maxLen: 80 });\n\n    return {\n      formatTime,\n      truncateClassname,\n      formatCron: (cron) =>\n        cronstrue.toString(cron, {\n          verbose: true,\n          locale: locale.value,\n          throwErrorOnParse: false,\n        }),\n    };\n  },\n  data: () => ({\n    hasLoaded: false,\n    error: null,\n    cron: null,\n    fixedDelay: null,\n    fixedRate: null,\n  }),\n  computed: {\n    hasCronData: function () {\n      return this.cron && this.cron.length;\n    },\n    hasFixedDelayData: function () {\n      return this.fixedDelay && this.fixedDelay.length;\n    },\n    hasFixedRateData: function () {\n      return this.fixedRate && this.fixedRate.length;\n    },\n    hasData: function () {\n      return (\n        this.hasCronData || this.hasFixedDelayData || this.hasFixedRateData\n      );\n    },\n  },\n  created() {\n    this.fetchScheduledTasks();\n  },\n  methods: {\n    async fetchScheduledTasks() {\n      this.error = null;\n      try {\n        const res = await this.instance.fetchScheduledTasks();\n        this.cron = res.data.cron;\n        this.fixedDelay = res.data.fixedDelay;\n        this.fixedRate = res.data.fixedRate;\n      } catch (error) {\n        console.warn('Fetching scheduled tasks failed:', error);\n        this.error = error;\n      }\n      this.hasLoaded = true;\n    },\n  },\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      name: 'instances/scheduledtasks',\n      parent: 'instances',\n      path: 'scheduledtasks',\n      component: this,\n      label: 'instances.scheduledtasks.label',\n      group: VIEW_GROUP.INSIGHTS,\n      order: 950,\n      isEnabled: ({ instance }) => instance.hasEndpoint('scheduledtasks'),\n    });\n  },\n};\n</script>\n<style lang=\"css\">\n.scheduledtasks__target {\n  width: 250px;\n  max-width: 750px;\n  overflow: hidden;\n  direction: rtl;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/scheduledtasks/scheduled-task-executions.spec.ts",
    "content": "import { screen, waitFor } from '@testing-library/vue';\nimport { afterEach, beforeEach, describe, expect, it } from 'vitest';\n\nimport { render } from '@/test-utils';\nimport ScheduledTaskExecutions from '@/views/instances/scheduledtasks/scheduled-task-executions.vue';\n\ndescribe('ScheduledTaskExecutions', () => {\n  const originalFormat = Intl.DateTimeFormat;\n\n  beforeEach(() => {\n    Intl.DateTimeFormat = function (locale, options) {\n      return new originalFormat(locale, {\n        ...options,\n        timeZone: 'Europe/Berlin',\n      });\n    } as any;\n  });\n\n  afterEach(() => {\n    Intl.DateTimeFormat = originalFormat;\n  });\n\n  const baseTask = {\n    nextExecution: { time: '2024-06-01T12:00:00Z' },\n    lastExecution: { time: '2024-05-31T12:00:00Z', status: 'SUCCESS' },\n  };\n\n  it('renders nextExecution time', async () => {\n    render(ScheduledTaskExecutions, {\n      props: { task: baseTask },\n    });\n    const nextExec = await screen.findByText('Jun 1, 2024, 2:00:00 PM');\n    expect(nextExec).toBeVisible();\n  });\n\n  it('renders lastExecution time and status when lastExecution exists', async () => {\n    render(ScheduledTaskExecutions, {\n      props: { task: baseTask },\n    });\n\n    const lastExec = await screen.findByText('Jun 1, 2024, 2:00:00 PM');\n    expect(lastExec).toBeVisible();\n\n    const statusBadge = await screen.findByRole('status');\n    expect(statusBadge).toHaveTextContent('SUCCESS');\n    expect(statusBadge).toHaveClass('status-badge success');\n  });\n\n  it('does not render lastExecution time or status if lastExecution is missing', async () => {\n    const task = { nextExecution: { time: '2024-06-01T12:00:00Z' } };\n    render(ScheduledTaskExecutions, {\n      props: { task },\n    });\n\n    const nextExec = await screen.findByText('Jun 1, 2024, 2:00:00 PM');\n    expect(nextExec).toBeVisible();\n\n    await waitFor(() =>\n      expect(screen.queryByRole('status')).not.toBeInTheDocument(),\n    );\n\n    await waitFor(() =>\n      expect(screen.queryByTestId('lastExecution')).not.toBeInTheDocument(),\n    );\n  });\n\n  it('applies classNames with lowercase status', async () => {\n    const task = {\n      nextExecution: { time: '2024-06-01T12:00:00Z' },\n      lastExecution: { time: '2024-05-31T12:00:00Z', status: 'ERROR' },\n    };\n    render(ScheduledTaskExecutions, {\n      props: { task },\n    });\n\n    const statusBadge = await screen.findByRole('status');\n    expect(statusBadge).toHaveTextContent('ERROR');\n    expect(statusBadge).toHaveClass('status-badge error');\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/scheduledtasks/scheduled-task-executions.vue",
    "content": "<template>\n  <td class=\"font-mono text-sm\">\n    <sba-formatted-obj\n      v-if=\"task.nextExecution\"\n      :value=\"formatDateTime(task.nextExecution.time)\"\n    />\n  </td>\n  <td class=\"font-mono text-sm\">\n    <sba-formatted-obj\n      v-if=\"task.lastExecution\"\n      data-testid=\"lastExecution\"\n      :value=\"formatDateTime(task.lastExecution.time)\"\n    />\n  </td>\n  <td class=\"font-mono text-sm\">\n    <span\n      v-if=\"task.lastExecution\"\n      role=\"status\"\n      :class=\"`status-badge ${task.lastExecution.status.toLowerCase()}`\"\n      v-text=\"task.lastExecution.status\"\n    />\n  </td>\n</template>\n\n<script setup lang=\"ts\">\nimport SbaFormattedObj from '@/components/sba-formatted-obj.vue';\n\nimport { usePrettyTime } from '@/utils/prettyTime';\n\ndefineProps({\n  task: {\n    type: Object,\n    required: true,\n  },\n});\n\nconst { formatDateTime } = usePrettyTime();\n</script>\n\n<style scoped>\n.status-badge {\n  @apply bg-gray-200 text-black text-xs inline-flex items-center uppercase  rounded overflow-hidden px-3 py-1;\n}\n.success {\n  @apply bg-green-200 text-green-700;\n}\n.error {\n  @apply bg-red-200 text-red-700;\n}\n.started {\n  @apply bg-yellow-200 text-yellow-700;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/sessions/i18n.de.json",
    "content": "{\n  \"instances\": {\n    \"sessions\": {\n      \"label\": \"Sitzungen\",\n      \"attributes\": \"Attribute\",\n      \"created_at\": \"Erstellt am\",\n      \"expired\": \"Abgelaufen\",\n      \"expiry\": \"Verfällt\",\n\n      \"last_accessed_at\": \"Letzter Zugriff\",\n      \"loading_sessions\": \"Sitzungen laden...\",\n      \"max_inactive_interval\": \"Max. Inaktivitäts-<br>intervall\",\n      \"no_sessions_found\": \"Keine Sitzungen gefunden\",\n      \"session_id\": \"Session Id\",\n      \"unlimited\": \"Unendlich\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/sessions/i18n.en.json",
    "content": "{\n  \"instances\": {\n    \"sessions\": {\n      \"label\": \"Sessions\",\n      \"attributes\": \"Attributes\",\n      \"created_at\": \"Created At\",\n      \"expired\": \"Expired\",\n      \"expiry\": \"Expiry\",\n\n      \"last_accessed_at\": \"Last Accessed At\",\n      \"loading_sessions\": \"Loading Sessions...\",\n      \"max_inactive_interval\": \"Max Inactive<br>Interval\",\n      \"no_sessions_found\": \"No Sessions found\",\n      \"session_id\": \"Session Id\",\n      \"unlimited\": \"Unlimited\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/sessions/i18n.es.json",
    "content": "{\n  \"instances\": {\n    \"sessions\": {\n      \"label\": \"Sesiones\",\n      \"attributes\": \"Atributos\",\n      \"created_at\": \"Creado\",\n      \"expired\": \"Expirado\",\n      \"expiry\": \"Expiración\",\n\n      \"last_accessed_at\": \"Último acceso\",\n      \"loading_sessions\": \"Cargando sesiones...\",\n      \"max_inactive_interval\": \"Intervalo de <br>Inactividad Máxima\",\n      \"no_sessions_found\": \"No se encontraron sesiones\",\n      \"session_id\": \"Sesión Id\",\n      \"unlimited\": \"Sin límites\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/sessions/i18n.fr.json",
    "content": "{\n  \"instances\": {\n    \"sessions\": {\n      \"label\": \"Sessions\",\n      \"attributes\": \"Attributs\",\n      \"created_at\": \"Crée le\",\n      \"expired\": \"Expiré\",\n      \"expiry\": \"Expiration\",\n\n      \"last_accessed_at\": \"Dernier accés le\",\n      \"loading_sessions\": \"Chargements des Sessions...\",\n      \"max_inactive_interval\": \"Max Inactifs<br>Intervalle\",\n      \"no_sessions_found\": \"Aucune Sessions trouvées\",\n      \"session_id\": \"Session Id\",\n      \"unlimited\": \"Illimité\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/sessions/i18n.is.json",
    "content": "{\n  \"instances\": {\n    \"sessions\": {\n      \"label\": \"Setur\",\n      \"attributes\": \"Einkenni\",\n      \"created_at\": \"Búið til á\",\n      \"expired\": \"Útrunnið\",\n      \"expiry\": \"Falla úr gildi\",\n\n      \"last_accessed_at\": \"Síðastur aðgangur á\",\n      \"loading_sessions\": \"Sækja setur…\",\n      \"max_inactive_interval\": \"Max Inactive<br>Interval\",\n      \"no_sessions_found\": \"Fundið engar setur\",\n      \"session_id\": \"Seta Id\",\n      \"unlimited\": \"Ótakmarkaður\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/sessions/i18n.ko.json",
    "content": "{\n  \"instances\": {\n    \"sessions\": {\n      \"label\": \"세션\",\n      \"attributes\": \"속성\",\n      \"created_at\": \"생성시간\",\n      \"expired\": \"만료됨\",\n      \"expiry\": \"만기\",\n\n      \"last_accessed_at\": \"마지막 접근시간\",\n      \"loading_sessions\": \"세션 불러오는 중...\",\n      \"max_inactive_interval\": \"최대 비활성<br>간격\",\n      \"no_sessions_found\": \"세션이 없습니다.\",\n      \"session_id\": \"세션 Id\",\n      \"unlimited\": \"무제한\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/sessions/i18n.pt-BR.json",
    "content": "{\n  \"instances\": {\n    \"sessions\": {\n      \"label\": \"Sessões\",\n      \"attributes\": \"Atributos\",\n      \"created_at\": \"Criado Em\",\n      \"expired\": \"Expirado\",\n      \"expiry\": \"Expiração\",\n\n      \"last_accessed_at\": \"Último Acesso Em\",\n      \"loading_sessions\": \"Carregando Sessões...\",\n      \"max_inactive_interval\": \"Intervalo Máximo<br>Inativo\",\n      \"no_sessions_found\": \"Nunhuma Sessão Encontrada\",\n      \"session_id\": \"Id de Sessão\",\n      \"unlimited\": \"Ilimitado\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/sessions/i18n.ru.json",
    "content": "{\n  \"instances\": {\n    \"sessions\": {\n      \"label\": \"Сессии\",\n      \"attributes\": \"Атрибуты\",\n      \"created_at\": \"Создано\",\n      \"expired\": \"Истекла\",\n      \"expiry\": \"Истечение\",\n      \"last_accessed_at\": \"Последний доступ\",\n      \"loading_sessions\": \"Загрузка сессий...\",\n      \"max_inactive_interval\": \"Максимальный неактивный<br>интервал\",\n      \"no_sessions_found\": \"Сессии не найдены\",\n      \"session_id\": \"Идентификатор сессии\",\n      \"unlimited\": \"Неограничен\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/sessions/i18n.zh-CN.json",
    "content": "{\n  \"instances\": {\n    \"sessions\": {\n      \"label\": \"Sessions\",\n      \"attributes\": \"属性\",\n      \"created_at\": \"创建于\",\n      \"expired\": \"已过期\",\n      \"expiry\": \"到期\",\n\n      \"last_accessed_at\": \"最后访问时间\",\n      \"loading_sessions\": \"加载Sessions中...\",\n      \"max_inactive_interval\": \"Max Inactive<br>Interval\",\n      \"no_sessions_found\": \"未找到Session\",\n      \"session_id\": \"Session Id\",\n      \"unlimited\": \"Unlimited\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/sessions/i18n.zh-TW.json",
    "content": "{\n  \"instances\": {\n    \"sessions\": {\n      \"label\": \"工作階段\",\n      \"attributes\": \"屬性\",\n      \"created_at\": \"建立時間\",\n      \"expired\": \"已過期\",\n      \"expiry\": \"到期時間\",\n\n      \"last_accessed_at\": \"最後存取時間\",\n      \"loading_sessions\": \"正在載入 Sessions...\",\n      \"max_inactive_interval\": \"最大閒置<br>間隔\",\n      \"no_sessions_found\": \"找不到 Session\",\n      \"session_id\": \"Session ID\",\n      \"unlimited\": \"無限制\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/sessions/index.vue",
    "content": "<!--\n  - Copyright 2014-2019 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-instance-section :error=\"error\" :loading=\"isLoading\">\n    <template #before>\n      <sba-sticky-subnav>\n        <div class=\"flex -space-x-px\">\n          <select\n            v-model=\"filter.type\"\n            class=\"relative focus:z-10 focus:ring-indigo-500 focus:border-indigo-500 block sm:text-sm border-gray-300 rounded-md rounded-r-none\"\n          >\n            <option value=\"username\" v-text=\"$t('term.username')\" />\n            <option\n              value=\"sessionId\"\n              v-text=\"$t('instances.sessions.session_id')\"\n            />\n          </select>\n          <sba-input\n            v-model=\"filter.value\"\n            input-class=\"!rounded-l-none\"\n            name=\"filter\"\n            type=\"search\"\n            @paste=\"handlePaste\"\n            @keyup.enter=\"fetchSessionsByUsername()\"\n          />\n        </div>\n      </sba-sticky-subnav>\n    </template>\n\n    <sba-panel :seamless=\"true\">\n      <sba-sessions-list\n        :instance=\"instance\"\n        :is-loading=\"isLoading\"\n        :sessions=\"sessions\"\n        @deleted=\"fetch\"\n      />\n    </sba-panel>\n  </sba-instance-section>\n</template>\n\n<script>\nimport { debounce, isEqual } from 'lodash-es';\nimport moment from 'moment';\n\nimport SbaPanel from '@/components/sba-panel';\nimport SbaStickySubnav from '@/components/sba-sticky-subnav';\n\nimport Instance from '@/services/instance';\nimport { VIEW_GROUP } from '@/views/ViewGroup';\nimport sbaSessionsList from '@/views/instances/sessions/sessions-list';\nimport SbaInstanceSection from '@/views/instances/shell/sba-instance-section';\n\nconst regexUuid =\n  /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/;\n\nclass Session {\n  constructor({ creationTime, lastAccessedTime, ...session }) {\n    Object.assign(this, session);\n    this.creationTime = moment(creationTime);\n    this.lastAccessedTime = moment(lastAccessedTime);\n  }\n}\n\nexport default {\n  components: {\n    SbaPanel,\n    SbaStickySubnav,\n    SbaInstanceSection,\n    sbaSessionsList,\n  },\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  data: () => ({\n    error: null,\n    filter: { value: '', type: null },\n    sessions: [],\n    isLoading: false,\n    currentRouteName: null,\n  }),\n  watch: {\n    '$route.query': {\n      immediate: true,\n      handler() {\n        this.filter = Object.entries(this.$route.query).reduce(\n          (acc, [name, value]) => {\n            acc.type = name;\n            acc.value = value;\n            return acc;\n          },\n          { type: 'username', value: '' },\n        );\n      },\n    },\n    filter: {\n      deep: true,\n      immediate: true,\n      handler() {\n        const oldQuery = { [this.filter.type]: this.filter.value };\n        if (\n          !isEqual(oldQuery, this.$route.query) &&\n          this.currentRouteName === this.$route.name\n        ) {\n          this.$router.replace({\n            name: 'instances/sessions',\n            query: oldQuery,\n          });\n        }\n        this.fetch();\n      },\n    },\n  },\n  mounted() {\n    this.currentRouteName = this.$route.name;\n  },\n  methods: {\n    fetch: debounce(async function () {\n      this.error = null;\n      if (!this.filter.value) {\n        this.sessions = [];\n        return;\n      }\n\n      this.isLoading = true;\n      try {\n        if (this.filter.type === 'sessionId') {\n          this.sessions = await this.fetchSession();\n        } else {\n          this.sessions = await this.fetchSessionsByUsername();\n        }\n      } catch (error) {\n        console.warn('Fetching sessions failed:', error);\n        this.error = error;\n      }\n      this.isLoading = false;\n    }, 250),\n    async fetchSession() {\n      try {\n        const response = await this.instance.fetchSession(this.filter.value);\n        return [new Session(response.data)];\n      } catch (error) {\n        if (error.response.status === 404) {\n          return [];\n        } else {\n          throw error;\n        }\n      }\n    },\n    async fetchSessionsByUsername() {\n      const response = await this.instance.fetchSessionsByUsername(\n        this.filter.value,\n      );\n      return response.data.sessions.map((session) => new Session(session));\n    },\n    handlePaste(event) {\n      const looksLikeSessionId = event.clipboardData\n        .getData('text')\n        .match(regexUuid);\n      if (looksLikeSessionId) {\n        this.filter.type = 'sessionId';\n      }\n    },\n  },\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      name: 'instances/sessions',\n      parent: 'instances',\n      path: 'sessions',\n      component: this,\n      label: 'instances.sessions.label',\n      group: VIEW_GROUP.SECURITY,\n      order: 700,\n      isEnabled: ({ instance }) => instance.hasEndpoint('sessions'),\n    });\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/sessions/sessions-list.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <table class=\"sessions table w-full\">\n    <thead>\n      <tr>\n        <th v-html=\"$t('instances.sessions.session_id')\" />\n        <th v-html=\"$t('instances.sessions.created_at')\" />\n        <th v-html=\"$t('instances.sessions.last_accessed_at')\" />\n        <th v-html=\"$t('instances.sessions.expiry')\" />\n        <th v-html=\"$t('instances.sessions.max_inactive_interval')\" />\n        <th v-html=\"$t('instances.sessions.attributes')\" />\n        <th>\n          <sba-confirm-button\n            v-if=\"sessions.length > 1\"\n            :class=\"{\n              'is-loading': deletingAll === 'executing',\n              'is-info': deletingAll === 'completed',\n              'is-danger': deletingAll === 'failed',\n            }\"\n            :disabled=\"deletingAll !== null\"\n            class=\"button\"\n            @click=\"deleteAllSessions()\"\n          >\n            <span\n              v-if=\"deletingAll === 'completed'\"\n              v-text=\"$t('term.deleted')\"\n            />\n            <span\n              v-else-if=\"deletingAll === 'failed'\"\n              v-text=\"$t('term.failed')\"\n            />\n            <span v-else>\n              <font-awesome-icon icon=\"trash\" />&nbsp;\n              <span v-text=\"$t('term.delete')\" />\n            </span>\n          </sba-confirm-button>\n        </th>\n      </tr>\n    </thead>\n    <tr v-for=\"session in sessions\" :key=\"session.id\">\n      <td>\n        <router-link\n          :to=\"{\n            name: 'instances/sessions',\n            params: { instanceId: instance.id },\n            query: { sessionId: session.id },\n          }\"\n        >\n          {{ session.id }}\n        </router-link>\n      </td>\n      <td v-text=\"formatDate(session.creationTime)\" />\n      <td v-text=\"formatDate(session.lastAccessedTime)\" />\n      <td>\n        <span\n          v-if=\"session.expired\"\n          class=\"tag is-info\"\n          v-text=\"$t('instances.sessions.expired')\"\n        />\n      </td>\n      <td>\n        <span\n          v-if=\"session.maxInactiveInterval >= 0\"\n          v-text=\"`${session.maxInactiveInterval}s`\"\n        />\n        <span v-else v-text=\"$t('instances.sessions.unlimited')\" />\n      </td>\n      <td>\n        <span\n          v-for=\"name in session.attributeNames\"\n          :key=\"`${session.id}-${name}`\"\n          class=\"tag\"\n          v-text=\"name\"\n        />\n      </td>\n      <td>\n        <button\n          :class=\"{\n            'is-loading': deleting[session.id] === 'executing',\n            'is-info': deleting[session.id] === 'completed',\n            'is-danger': deleting[session.id] === 'failed',\n          }\"\n          :disabled=\"session.id in deleting\"\n          class=\"button\"\n          @click=\"deleteSession(session.id)\"\n        >\n          <span\n            v-if=\"deleting[session.id] === 'completed'\"\n            v-text=\"$t('term.deleted')\"\n          />\n          <span\n            v-else-if=\"deleting[session.id] === 'failed'\"\n            v-text=\"$t('term.failed')\"\n          />\n          <span v-else>\n            <font-awesome-icon icon=\"trash\" />&nbsp;\n            <span v-text=\"$t('term.delete')\" />\n          </span>\n        </button>\n      </td>\n    </tr>\n    <tr v-if=\"sessions.length === 0\">\n      <td class=\"is-muted\" colspan=\"7\">\n        <p\n          v-if=\"isLoading\"\n          class=\"is-loading\"\n          v-text=\"$t('instances.sessions.loading_sessions')\"\n        />\n        <p v-else v-text=\"$t('instances.sessions.no_sessions_found')\" />\n      </td>\n    </tr>\n  </table>\n</template>\n\n<script>\nimport prettyBytes from 'pretty-bytes';\n\nimport { useDateTimeFormatter } from '@/composables/useDateTimeFormatter';\nimport Instance from '@/services/instance';\nimport { concatMap, from, listen, map, of, tap } from '@/utils/rxjs';\n\nexport default {\n  props: {\n    sessions: {\n      type: Array,\n      default: () => [],\n    },\n    instance: {\n      type: Instance,\n      required: true,\n    },\n    isLoading: {\n      type: Boolean,\n      default: false,\n    },\n  },\n  emits: ['deleted'],\n  setup() {\n    const { formatDateTime } = useDateTimeFormatter();\n\n    return {\n      formatDate: formatDateTime,\n    };\n  },\n  data: () => ({\n    deletingAll: null,\n    deleting: {},\n  }),\n  methods: {\n    prettyBytes,\n    deleteAllSessions() {\n      this.subscription = from(this.sessions)\n        .pipe(\n          map((session) => session.id),\n          concatMap(this._deleteSession),\n          listen((status) => (this.deletingAll = status)),\n        )\n        .subscribe({\n          complete: () => {\n            this.$emit('deleted', '*');\n          },\n        });\n    },\n    deleteSession(sessionId) {\n      this._deleteSession(sessionId)\n        .pipe(listen((status) => (this.deleting[sessionId] = status)))\n        .subscribe({\n          complete: () => this.$emit('deleted', sessionId),\n        });\n    },\n    _deleteSession(sessionId) {\n      return of(sessionId).pipe(\n        concatMap(async (sessionId) => {\n          await this.instance.deleteSession(sessionId);\n          return sessionId;\n        }),\n        tap({\n          error: (error) => {\n            console.warn(`Deleting session ${sessionId} failed:`, error);\n          },\n        }),\n      );\n    },\n  },\n};\n</script>\n<style lang=\"css\">\n.sessions td,\n.sessions th {\n  vertical-align: middle;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/shell/InstanceShell.vue",
    "content": "<!--\n  - Copyright 2014-2019 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div class=\"h-full\">\n    <sba-wave />\n    <div class=\"h-full\">\n      <Sidebar\n        v-if=\"instance\"\n        :key=\"instanceId\"\n        :application=\"application\"\n        :instance=\"instance\"\n        :views=\"sidebarViews\"\n      />\n      <main\n        class=\"min-h-full h-full relative z-0 ml-10 md:ml-60 transition-all\"\n      >\n        <router-view\n          v-if=\"instance\"\n          :application=\"application\"\n          :instance=\"instance\"\n        />\n      </main>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { computed } from 'vue';\nimport { useRoute } from 'vue-router';\n\nimport { useViewRegistry } from '@/composables/ViewRegistry';\nimport { useApplicationStore } from '@/composables/useApplicationStore';\nimport { findApplicationForInstance, findInstance } from '@/store';\nimport Sidebar from '@/views/instances/shell/sidebar';\n\nconst { applications } = useApplicationStore();\nconst { views } = useViewRegistry();\nconst route = useRoute();\n\nconst instanceId = computed(() => {\n  return route.params.instanceId;\n});\n\nconst activeMainViewName = computed(() => {\n  const currentView = route.meta.view;\n  return currentView && (currentView.parent || currentView.name);\n});\n\nconst sidebarViews = computed(() => {\n  return views.filter((v) => v.parent === activeMainViewName.value);\n});\n\nconst application = computed(() => {\n  return findApplicationForInstance(applications.value, instanceId.value);\n});\n\nconst instance = computed(() => {\n  return findInstance(applications.value, instanceId.value);\n});\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/shell/i18n.de.json",
    "content": "{\n  \"sidebar\": {\n    \"data\": {\n      \"title\": \"Daten\"\n    },\n    \"insights\": {\n      \"title\": \"Insights\"\n    },\n    \"instances_caches\": {\n      \"title\": \"Caches\"\n    },\n    \"dependencies\": {\n      \"title\": \"Dependencies\"\n    },\n    \"jvm\": {\n      \"title\": \"JVM\"\n    },\n    \"logging\": {\n      \"title\": \"Logging\"\n    },\n    \"security\": {\n      \"title\": \"Sicherheit\"\n    },\n    \"web\": {\n      \"title\": \"Web\"\n    },\n    \"custom-link\": {\n      \"title\": \"Links\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/shell/i18n.en.json",
    "content": "{\n  \"sidebar\": {\n    \"data\": {\n      \"title\": \"Data\"\n    },\n    \"insights\": {\n      \"title\": \"Insights\"\n    },\n    \"instances_caches\": {\n      \"title\": \"Caches\"\n    },\n    \"dependencies\": {\n      \"title\": \"Dependencies\"\n    },\n    \"jvm\": {\n      \"title\": \"JVM\"\n    },\n    \"logging\": {\n      \"title\": \"Logging\"\n    },\n    \"security\": {\n      \"title\": \"Security\"\n    },\n    \"web\": {\n      \"title\": \"Web\"\n    },\n    \"custom-link\": {\n      \"title\": \"Links\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/shell/i18n.es.json",
    "content": "{\n  \"sidebar\": {\n    \"data\": {\n      \"title\": \"Datos\"\n    },\n    \"insights\": {\n      \"title\": \"Insights\"\n    },\n    \"instances_caches\": {\n      \"title\": \"Caches\"\n    },\n    \"jvm\": {\n      \"title\": \"JVM\"\n    },\n    \"logging\": {\n      \"title\": \"Logging\"\n    },\n    \"security\": {\n      \"title\": \"Seguridad\"\n    },\n    \"web\": {\n      \"title\": \"Web\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/shell/i18n.fr.json",
    "content": "{\n  \"sidebar\": {\n    \"data\": {\n      \"title\": \"Données\"\n    },\n    \"insights\": {\n      \"title\": \"Aperçus\"\n    },\n    \"instances_caches\": {\n      \"title\": \"Caches\"\n    },\n    \"jvm\": {\n      \"title\": \"JVM\"\n    },\n    \"logging\": {\n      \"title\": \"Logging\"\n    },\n    \"security\": {\n      \"title\": \"Securité\"\n    },\n    \"web\": {\n      \"title\": \"Web\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/shell/i18n.is.json",
    "content": "{\n  \"sidebar\": {\n    \"data\": {\n      \"title\": \"Gögn\"\n    },\n    \"insights\": {\n      \"title\": \"Innsýn\"\n    },\n    \"instances_caches\": {\n      \"title\": \"Skyndiminni\"\n    },\n    \"jvm\": {\n      \"title\": \"JVM\"\n    },\n    \"logging\": {\n      \"title\": \"Skrá í annál\"\n    },\n    \"security\": {\n      \"title\": \"Öryggi\"\n    },\n    \"web\": {\n      \"title\": \"Vefur\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/shell/i18n.ko.json",
    "content": "{\n  \"sidebar\": {\n    \"data\": {\n      \"title\": \"데이터\"\n    },\n    \"insights\": {\n      \"title\": \"개요\"\n    },\n    \"instances_caches\": {\n      \"title\": \"캐시\"\n    },\n    \"jvm\": {\n      \"title\": \"JVM\"\n    },\n    \"logging\": {\n      \"title\": \"로그\"\n    },\n    \"security\": {\n      \"title\": \"보안\"\n    },\n    \"web\": {\n      \"title\": \"웹\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/shell/i18n.pt-BR.json",
    "content": "{\n  \"sidebar\": {\n    \"data\": {\n      \"title\": \"Dado\"\n    },\n    \"insights\": {\n      \"title\": \"Insights\"\n    },\n    \"instances_caches\": {\n      \"title\": \"Caches\"\n    },\n    \"jvm\": {\n      \"title\": \"JVM\"\n    },\n    \"logging\": {\n      \"title\": \"Logging\"\n    },\n    \"security\": {\n      \"title\": \"Segurança\"\n    },\n    \"web\": {\n      \"title\": \"Web\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/shell/i18n.ru.json",
    "content": "{\n  \"sidebar\": {\n    \"data\": {\n      \"title\": \"Данные\"\n    },\n    \"insights\": {\n      \"title\": \"Анализ\"\n    },\n    \"instances_caches\": {\n      \"title\": \"Кэши\"\n    },\n    \"jvm\": {\n      \"title\": \"JVM\"\n    },\n    \"logging\": {\n      \"title\": \"Журналирование\"\n    },\n    \"security\": {\n      \"title\": \"Безопасность\"\n    },\n    \"web\": {\n      \"title\": \"Web\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/shell/i18n.zh-CN.json",
    "content": "{\n  \"sidebar\": {\n    \"data\": {\n      \"title\": \"数据\"\n    },\n    \"insights\": {\n      \"title\": \"Insights\"\n    },\n    \"instances_caches\": {\n      \"title\": \"缓存\"\n    },\n    \"jvm\": {\n      \"title\": \"JVM\"\n    },\n    \"logging\": {\n      \"title\": \"日志\"\n    },\n    \"security\": {\n      \"title\": \"网络安全\"\n    },\n    \"web\": {\n      \"title\": \"Web\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/shell/i18n.zh-TW.json",
    "content": "{\n  \"sidebar\": {\n    \"data\": {\n      \"title\": \"資料\"\n    },\n    \"insights\": {\n      \"title\": \"洞察\"\n    },\n    \"instances_caches\": {\n      \"title\": \"快取\"\n    },\n    \"dependencies\": {\n      \"title\": \"相依元件\"\n    },\n    \"jvm\": {\n      \"title\": \"JVM\"\n    },\n    \"logging\": {\n      \"title\": \"日誌\"\n    },\n    \"security\": {\n      \"title\": \"安全性\"\n    },\n    \"web\": {\n      \"title\": \"Web\"\n    },\n    \"custom-link\": {\n      \"title\": \"連結\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/shell/index.ts",
    "content": "import InstanceShell from '@/views/instances/shell/InstanceShell.vue';\n\nexport default {\n  install({ viewRegistry }: { viewRegistry: ViewRegistry }) {\n    viewRegistry.addView({\n      name: 'instances',\n      path: '/instances/:instanceId',\n      component: InstanceShell,\n      isEnabled() {\n        return false;\n      },\n    });\n  },\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/shell/sba-instance-section.vue",
    "content": "<template>\n  <section :class=\"{ loading: loading }\" class=\"relative\">\n    <slot name=\"before\" />\n\n    <div\n      :class=\"\n        classNames({\n          'flex-1': true,\n          flex: layoutOptions.isFlex,\n          'px-2 md:px-6 py-6': !layoutOptions.noMargin,\n        })\n      \"\n    >\n      <sba-alert\n        v-if=\"error\"\n        :class=\"\n          classNames({\n            'p-4': layoutOptions.noMargin,\n          })\n        \"\n        :error=\"error\"\n        :title=\"$t('term.fetch_failed')\"\n        class=\"mb-6 w-full\"\n      />\n\n      <div\n        v-if=\"loading\"\n        class=\"loading-spinner-wrapper\"\n        data-testid=\"instance-section-loading-spinner\"\n      >\n        <div class=\"loading-spinner-wrapper-container\">\n          <sba-loading-spinner size=\"sm\" />\n          {{ $t('term.fetching_data') }}\n        </div>\n      </div>\n\n      <slot />\n    </div>\n  </section>\n</template>\n\n<script>\nimport classNames from 'classnames';\n\nimport SbaLoadingSpinner from '@/components/sba-loading-spinner';\n\nexport default {\n  name: 'SbaInstanceSection',\n  components: { SbaLoadingSpinner },\n  props: {\n    loading: {\n      type: Boolean,\n      default: false,\n    },\n    error: {\n      type: Error,\n      default: null,\n    },\n    layoutOptions: {\n      type: Object,\n      default: () => ({\n        flex: false,\n        noMargin: false,\n      }),\n    },\n  },\n  methods: {\n    classNames: classNames,\n  },\n};\n</script>\n\n<style scoped>\n.loading-spinner-wrapper {\n  @apply absolute w-full h-screen flex flex-col z-50 top-0 left-0 justify-center items-center opacity-0;\n\n  animation-name: show;\n  animation-duration: 0ms;\n  animation-fill-mode: forwards;\n  animation-delay: 250ms;\n  animation-iteration-count: 1;\n}\n\n.loading-spinner-wrapper-container {\n  @apply rounded-md bg-black/30 py-4 px-5 flex w-auto gap-4 items-center text-white;\n}\n\n@keyframes show {\n  from {\n    @apply opacity-0;\n  }\n\n  to {\n    @apply opacity-100;\n  }\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/shell/sidebar.stories.ts",
    "content": "import { vueRouter } from 'storybook-vue3-router';\n\nimport { applications } from '../../../mocks/applications/data';\nimport Instance from '../../../services/instance';\nimport Sidebar from './sidebar.vue';\n\nimport i18n from '@/i18n';\n\nexport default {\n  component: Sidebar,\n  title: 'Sidebar',\n};\n\nconst TemplateWithProps = (args) => ({\n  components: { Sidebar },\n  setup() {\n    return { args };\n  },\n  template: '<Sidebar v-bind=\"args\" />',\n  i18n,\n});\n\nexport const Test = {\n  render: TemplateWithProps,\n\n  args: {\n    instance: new Instance({\n      id: 'bba333956ae6',\n      ...applications[0].instances[0],\n    }),\n  },\n  decorators: [\n    vueRouter(\n      [\n        {\n          name: 'instances/details',\n          path: '/',\n          component: TemplateWithProps,\n        },\n      ],\n      { initialRoute: '/' },\n    ),\n  ],\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/shell/sidebar.vue",
    "content": "<!--\n  - Copyright 2014-2020 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <aside\n    :class=\"{ 'w-60': sidebarOpen }\"\n    class=\"h-full flex flex-col bg-white border-r backdrop-filter backdrop-blur-lg bg-opacity-80 z-40 w-10 md:w-60 transition-all left-0 pb-14 fixed\"\n  >\n    <ul class=\"relative px-1 py-1 overflow-y-auto\">\n      <!-- Instance info block -->\n      <li class=\"relative mb-1 hidden md:block\">\n        <router-link\n          :class=\"`instance-summary--${instance.statusInfo.status}`\"\n          :to=\"{\n            name: 'instances/details',\n            params: { instanceId: instance.id },\n          }\"\n          class=\"instance-info-block\"\n        >\n          <span class=\"overflow-hidden text-ellipsis\">\n            <span class=\"font-bold\" v-text=\"instance.registration.name\" /><br />\n            <small><em v-text=\"instance.id\" /></small>\n          </span>\n        </router-link>\n      </li>\n\n      <!-- sm: button toggle navigation -->\n      <li class=\"block md:hidden mb-1\">\n        <a class=\"navbar-link navbar-link__group\" @click.stop=\"toggleSidebar\">\n          <font-awesome-icon :icon=\"['fas', 'bars']\" />\n        </a>\n      </li>\n\n      <!-- The actual nav -->\n      <li\n        v-for=\"group in groups\"\n        :key=\"group.name\"\n        :data-sba-group=\"group.id\"\n        class=\"relative mb-1\"\n      >\n        <router-link\n          :class=\"{ 'navbar-link__active': isActiveGroup(group) }\"\n          :to=\"{\n            name: group.views[0].name,\n            params: { instanceId: instance.id },\n          }\"\n          class=\"navbar-link navbar-link__group\"\n        >\n          <span v-html=\"group.icon\" />\n          <span\n            v-text=\"\n              hasMultipleViews(group)\n                ? getGroupTitle(group.id)\n                : $t(group.views[0].label)\n            \"\n          />\n          <svg\n            v-if=\"hasMultipleViews(group)\"\n            :class=\"{\n              '-rotate-90': !isActiveGroup(group),\n              '': isActiveGroup(group),\n            }\"\n            aria-hidden=\"true\"\n            class=\"h-3 ml-auto hidden md:block transition\"\n            data-prefix=\"fas\"\n            focusable=\"false\"\n            role=\"img\"\n            viewBox=\"0 0 448 512\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <path\n              d=\"M207.029 381.476L12.686 187.132c-9.373-9.373-9.373-24.569 0-33.941l22.667-22.667c9.357-9.357 24.522-9.375 33.901-.04L224 284.505l154.745-154.021c9.379-9.335 24.544-9.317 33.901.04l22.667 22.667c9.373 9.373 9.373 24.569 0 33.941L240.971 381.476c-9.373 9.372-24.569 9.372-33.942 0z\"\n              fill=\"currentColor\"\n            />\n          </svg>\n        </router-link>\n\n        <!-- Le subnav -->\n        <ul\n          v-if=\"hasMultipleViews(group) && isActiveGroup(group)\"\n          :class=\"{ 'hidden md:block': !sidebarOpen }\"\n          class=\"relative block\"\n        >\n          <li\n            v-for=\"view in group.views\"\n            :key=\"view.name\"\n            :data-sba-view=\"view.name\"\n          >\n            <router-link\n              :to=\"{ name: view.name, params: { instanceId: instance.id } }\"\n              active-class=\"navbar-link__active\"\n              class=\"navbar-link navbar-link__group_item\"\n              exact-active-class=\"\"\n            >\n              <component :is=\"view.handle\" />\n            </router-link>\n          </li>\n        </ul>\n      </li>\n\n      <template v-if=\"customLinksFromMetadata?.length > 0\">\n        <Divider align=\"center\" class=\"!my-2\">\n          <small class=\"bold\">\n            {{ $t('sidebar.custom-link.title') }}\n          </small>\n        </Divider>\n\n        <li>\n          <ul\n            :class=\"{ 'hidden md:block': !sidebarOpen }\"\n            class=\"relative block\"\n          >\n            <li\n              v-for=\"view in customLinksFromMetadata\"\n              :key=\"view.name\"\n              :data-sba-view=\"view.name\"\n            >\n              <router-link\n                v-if=\"view.iframe\"\n                :to=\"{\n                  name: 'instances/custom-link-view',\n                  params: { instanceId: instance.id, url: view.href },\n                }\"\n                active-class=\"navbar-link__active\"\n                class=\"navbar-link navbar-link--custom navbar-link__group_item\"\n                exact-active-class=\"\"\n              >\n                <component :is=\"view.handle\" />\n              </router-link>\n              <a\n                v-else-if=\"view.href\"\n                :href=\"view.href\"\n                class=\"navbar-link navbar-link--custom navbar-link__group_item\"\n                target=\"_blank\"\n                rel=\"noopener\"\n              >\n                <component :is=\"view.handle\" />\n                <font-awesome-icon\n                  :icon=\"faArrowUpRightFromSquare\"\n                  class=\"w-2 ml-1\"\n                />\n              </a>\n            </li>\n          </ul>\n        </li>\n      </template>\n    </ul>\n  </aside>\n</template>\n\n<script lang=\"ts\" setup>\nimport { faArrowUpRightFromSquare } from '@fortawesome/free-solid-svg-icons';\nimport { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';\nimport { Divider } from 'primevue';\nimport { computed, h, ref, toRaw, watch } from 'vue';\nimport { useI18n } from 'vue-i18n';\nimport { useRoute } from 'vue-router';\n\nimport Application from '@/services/application';\nimport Instance from '@/services/instance';\nimport { compareBy } from '@/utils/collections';\nimport { VIEW_GROUP, VIEW_GROUP_ICON } from '@/views/ViewGroup';\n\nconst props = defineProps<{\n  views: any[];\n  instance: Instance;\n  application: Application;\n}>();\n\nconst { t } = useI18n();\nconst route = useRoute();\nconst sidebarOpen = ref(false);\n\nconst customLinksFromMetadata = computed(() => {\n  const newVar = props.instance.metadataParsed?.sidebar?.links || [];\n  return newVar.map((view) => {\n    return {\n      handle: () => h('span', { innerHTML: view.label }),\n      href: view.url,\n      iframe: view.iframe || false,\n      order: Number.MAX_SAFE_INTEGER,\n    } satisfies SbaView;\n  });\n});\n\nconst enabledViews = computed(() => {\n  if (!props.instance) {\n    return [];\n  }\n  return [...props.views]\n    .filter(\n      (view) =>\n        typeof view.isEnabled === 'undefined' ||\n        view.isEnabled({ instance: props.instance }),\n    )\n    .sort(compareBy((v: any) => v.order));\n});\n\nconst groups = computed(() => {\n  const groups = new Map();\n\n  [...enabledViews.value].forEach((view: SbaView) => {\n    const groupName = view.group;\n    const group = groups.get(groupName) || {\n      id: groupName,\n      order: Number.MAX_SAFE_INTEGER,\n      views: [],\n    };\n    groups.set(groupName, {\n      ...group,\n      order: Math.min(group.order, view.order),\n      icon: VIEW_GROUP_ICON[groupName],\n      views: [...group.views, view],\n    });\n  });\n  return Array.from(groups.values());\n});\n\nwatch(\n  () => route.fullPath,\n  () => {\n    sidebarOpen.value = false;\n  },\n);\n\nfunction toggleSidebar() {\n  sidebarOpen.value = !sidebarOpen.value;\n}\n\nfunction getGroupTitle(groupId: string) {\n  const key = 'sidebar.' + groupId + '.title';\n  const translated = t(key);\n  return key === translated ? groupId : translated;\n}\n\nfunction isActiveGroup(group: any) {\n  if (group.id === VIEW_GROUP.CUSTOM_LINK) {\n    return true;\n  }\n\n  const result = group.views.some(\n    (view: any) => toRaw(view) === route.meta.view,\n  );\n  return result;\n}\n\nfunction hasMultipleViews(group: any) {\n  return group.views.length > 1;\n}\n</script>\n\n<style scoped>\n.instance-info-block {\n  @apply bg-sba-50 bg-opacity-40 text-sba-900 flex items-center text-sm py-4 px-6 text-left overflow-hidden text-ellipsis rounded transition duration-300 ease-in-out cursor-pointer;\n}\n\na.navbar-link {\n  @apply cursor-pointer;\n}\n.navbar-link {\n  @apply bg-sba-50 bg-opacity-40 duration-300 ease-in-out flex  items-center overflow-hidden py-4 rounded text-sm transition whitespace-nowrap;\n  @apply text-gray-700;\n}\n.navbar-link--custom {\n  @apply !px-6;\n}\n\n.navbar-link:hover,\n.navbar-link__active {\n  @apply bg-sba-50 bg-opacity-80 text-sba-900;\n}\n\n.navbar-link__group_item {\n  @apply h-6 mb-1 mt-1 pl-12 pr-6;\n}\n\n.navbar-link__group {\n  @apply h-12 px-2 md:px-6;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/startup/i18n.de.json",
    "content": "{\n  \"instances\": {\n    \"startup\": {\n      \"label\": \"Startup\",\n      \"expand_all\": \"Alle aufklappen\",\n\n      \"column\": {\n        \"name\": \"Name\",\n        \"duration\": \"Dauer (ms)\",\n        \"details\": \"Details\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/startup/i18n.en.json",
    "content": "{\n  \"instances\": {\n    \"startup\": {\n      \"label\": \"Startup\",\n      \"expand_all\": \"Expand all\",\n\n      \"column\": {\n        \"name\": \"Name\",\n        \"duration\": \"Duration (ms)\",\n        \"details\": \"Details\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/startup/i18n.es.json",
    "content": "{\n  \"instances\": {\n    \"startup\": {\n      \"label\": \"Inicio\",\n      \"expand_all\": \"Expandir todo\",\n\n      \"column\": {\n        \"name\": \"Nombre\",\n        \"duration\": \"Duración (ms)\",\n        \"details\": \"Detalles\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/startup/i18n.is.json",
    "content": "{\n  \"instances\": {\n    \"startup\": {\n      \"label\": \"Ræsitími\",\n      \"expand_all\": \"Þenjast allt út\",\n\n      \"column\": {\n        \"name\": \"Nafn\",\n        \"duration\": \"Tímalengd (ms)\",\n        \"details\": \"Smáatriði\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/startup/i18n.zh-TW.json",
    "content": "{\n  \"instances\": {\n    \"startup\": {\n      \"label\": \"啟動\",\n      \"expand_all\": \"全部展開\",\n\n      \"column\": {\n        \"name\": \"名稱\",\n        \"duration\": \"時長 (毫秒)\",\n        \"details\": \"詳細資訊\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/startup/index.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-instance-section :error=\"error\">\n    <template #before>\n      <sba-sticky-subnav>\n        <div class=\"flex gap-2\">\n          <sba-button v-if=\"!isExpanded\" @click=\"expandTree\">\n            <svg\n              class=\"h-4 w-4\"\n              fill=\"currentColor\"\n              viewBox=\"0 0 16 16\"\n              xmlns=\"http://www.w3.org/2000/svg\"\n            >\n              <path\n                d=\"M1 8a.5.5 0 0 1 .5-.5h13a.5.5 0 0 1 0 1h-13A.5.5 0 0 1 1 8zM7.646.146a.5.5 0 0 1 .708 0l2 2a.5.5 0 0 1-.708.708L8.5 1.707V5.5a.5.5 0 0 1-1 0V1.707L6.354 2.854a.5.5 0 1 1-.708-.708l2-2zM8 10a.5.5 0 0 1 .5.5v3.793l1.146-1.147a.5.5 0 0 1 .708.708l-2 2a.5.5 0 0 1-.708 0l-2-2a.5.5 0 0 1 .708-.708L7.5 14.293V10.5A.5.5 0 0 1 8 10z\"\n                fill-rule=\"evenodd\"\n              />\n            </svg>\n          </sba-button>\n          <sba-button v-if=\"isExpanded\" @click=\"expandTree\">\n            <svg\n              class=\"h-4 w-4\"\n              fill=\"currentColor\"\n              viewBox=\"0 0 16 16\"\n              xmlns=\"http://www.w3.org/2000/svg\"\n            >\n              <path\n                d=\"M1 8a.5.5 0 0 1 .5-.5h13a.5.5 0 0 1 0 1h-13A.5.5 0 0 1 1 8zm7-8a.5.5 0 0 1 .5.5v3.793l1.146-1.147a.5.5 0 0 1 .708.708l-2 2a.5.5 0 0 1-.708 0l-2-2a.5.5 0 1 1 .708-.708L7.5 4.293V.5A.5.5 0 0 1 8 0zm-.5 11.707-1.146 1.147a.5.5 0 0 1-.708-.708l2-2a.5.5 0 0 1 .708 0l2 2a.5.5 0 0 1-.708.708L8.5 11.707V15.5a.5.5 0 0 1-1 0v-3.793z\"\n                fill-rule=\"evenodd\"\n              />\n            </svg>\n          </sba-button>\n          <div class=\"flex-1\">\n            <sba-input\n              v-model=\"filter\"\n              :placeholder=\"\n                $t('term.filter') +\n                ' by name/tags(key,value) or number for filter(duration)'\n              \"\n              name=\"filter\"\n              type=\"search\"\n            >\n              <template #prepend>\n                <font-awesome-icon icon=\"filter\" />\n              </template>\n              <template #append>\n                <span v-text=\"filteredSize\" /> /\n                <span v-text=\"totalSize\" />\n              </template>\n            </sba-input>\n          </div>\n        </div>\n      </sba-sticky-subnav>\n    </template>\n\n    <sba-panel>\n      <div class=\"-mx-4 -my-3\">\n        <tree-table\n          v-if=\"hasLoaded\"\n          :expand=\"expandedNodes\"\n          :tree=\"eventTree\"\n          :filter=\"filter\"\n          @after-filter-action=\"afterFilterAction\"\n          @change=\"saveTreeState\"\n        />\n      </div>\n    </sba-panel>\n  </sba-instance-section>\n</template>\n\n<script>\nimport Instance from '@/services/instance';\nimport { StartupActuatorService } from '@/services/startup-actuator';\nimport { VIEW_GROUP } from '@/views/ViewGroup';\nimport SbaInstanceSection from '@/views/instances/shell/sba-instance-section';\nimport TreeTable from '@/views/instances/startup/tree-table';\n\nexport default {\n  components: { SbaInstanceSection, TreeTable },\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  data: () => ({\n    error: null,\n    hasLoaded: false,\n    expandedNodes: null,\n    eventTree: null,\n    isExpanded: false,\n    filter: null,\n    filteredSize: '',\n    totalSize: '',\n  }),\n  async created() {\n    await this.fetchStartup();\n\n    this.loadTreeState();\n    this.expandEventId();\n\n    this.hasLoaded = true;\n  },\n  methods: {\n    expandTree() {\n      if (!this.isExpanded) {\n        this.expandedNodes = new Set(\n          this.eventTree.getEvents().map((e) => e.startupStep.id),\n        );\n        this.isExpanded = true;\n      } else {\n        this.expandedNodes = new Set();\n        this.isExpanded = false;\n      }\n    },\n    async fetchStartup() {\n      this.error = null;\n      try {\n        const res = await this.instance.fetchStartup();\n        this.eventTree = StartupActuatorService.parseAsTree(res.data);\n        this.totalSize = this.eventTree.getEvents().length;\n        this.filteredSize = this.totalSize;\n      } catch (error) {\n        console.warn('Fetching startup failed:', error);\n        this.error = error;\n      }\n    },\n    afterFilterAction(filteredSize) {\n      this.filteredSize = filteredSize;\n    },\n    expandEventId() {\n      let queryParams = this.$router.currentRoute.query;\n      if (queryParams && queryParams.id) {\n        this.expandedNodes = new Set(this.eventTree.getPath(+queryParams.id));\n      }\n    },\n    loadTreeState() {\n      if (window.localStorage) {\n        let value = localStorage.getItem(\n          `applications/${this.instance.registration.name}/startup`,\n        );\n        if (value) {\n          let parse = JSON.parse(value);\n          this.expandedNodes = new Set(parse.expandedNodes);\n        }\n      }\n    },\n    saveTreeState($event) {\n      if (window.localStorage) {\n        localStorage.setItem(\n          `applications/${this.instance.registration.name}/startup`,\n          JSON.stringify({\n            expandedNodes: [...$event.expandedNodes],\n          }),\n        );\n      }\n    },\n  },\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      name: 'instances/startup',\n      parent: 'instances',\n      path: 'startup',\n      component: this,\n      label: 'instances.startup.label',\n      group: VIEW_GROUP.LOGGING,\n      order: 600,\n      isEnabled: ({ instance }) => instance.hasEndpoint('startup'),\n    });\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/startup/tree-item.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <li\n    class=\"tree-item\"\n    :tree-item-depth=\"item.startupStep.depth\"\n    :class=\"{ 'is-open': isOpen }\"\n  >\n    <div class=\"row\">\n      <div class=\"column column--name\">\n        <a\n          class=\"icon\"\n          :class=\"{ empty: !hasChildren, 'icon--open': isOpen }\"\n          @click=\"toggle\"\n        >\n          <svg\n            v-if=\"hasChildren\"\n            aria-hidden=\"true\"\n            focusable=\"false\"\n            data-prefix=\"fas\"\n            role=\"img\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n            viewBox=\"0 0 448 512\"\n            class=\"w-3 h-3 -rotate-90\"\n          >\n            <path\n              fill=\"currentColor\"\n              d=\"M207.029 381.476L12.686 187.132c-9.373-9.373-9.373-24.569 0-33.941l22.667-22.667c9.357-9.357 24.522-9.375 33.901-.04L224 284.505l154.745-154.021c9.379-9.335 24.544-9.317 33.901.04l22.667 22.667c9.373 9.373 9.373 24.569 0 33.941L240.971 381.476c-9.373 9.372-24.569 9.372-33.942 0z\"\n            />\n          </svg>\n        </a>\n        <span v-text=\"item.startupStep.name\" />&nbsp;<small\n          >(#<span v-text=\"item.startupStep.id\" />)</small\n        >\n      </div>\n      <div\n        class=\"column column--duration\"\n        :title=\"item.duration + 'ms'\"\n        v-text=\"item.duration.toFixed(4)\"\n      />\n      <div class=\"column column--details\">\n        <span v-for=\"(tag, index) in item.startupStep.tags\" :key=\"index\">\n          <strong>{{ tag.key }}: </strong>\n          <span class=\"enforce-word-wrap\" v-text=\"tag.value\" />\n          <br />\n        </span>\n      </div>\n    </div>\n    <ul v-show=\"isOpen\">\n      <tree-item\n        v-for=\"(child, index) in item.startupStep.children\"\n        :key=\"index\"\n        :item=\"child\"\n        :expand=\"expand\"\n        @toggle=\"onToggle\"\n      />\n    </ul>\n  </li>\n</template>\n\n<script>\nexport default {\n  name: 'TreeItem',\n  props: {\n    item: {\n      type: Object,\n      required: true,\n    },\n    expand: Set,\n  },\n  emits: ['toggle'],\n  data: () => ({\n    isOpen: false,\n  }),\n  computed: {\n    hasChildren: function () {\n      return (\n        this.item.startupStep.children && this.item.startupStep.children.length\n      );\n    },\n  },\n  watch: {\n    expand: function (newVal) {\n      this.isOpen = newVal.has(this.item.startupStep.id);\n    },\n  },\n  created() {\n    this.isOpen = this.expand.has(this.item.startupStep.id);\n  },\n  methods: {\n    onToggle: function ($event) {\n      this.$emit('toggle', $event);\n    },\n    toggle: function () {\n      if (this.hasChildren) {\n        this.isOpen = !this.isOpen;\n        this.$emit('toggle', {\n          target: this.item,\n          isOpen: this.isOpen,\n        });\n      }\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/startup/tree-table.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div class=\"tree\">\n    <div class=\"row row--head\">\n      <div class=\"inline-flex items-center\">\n        <span v-text=\"$t('instances.startup.column.name')\" />\n      </div>\n      <div\n        class=\"text-right\"\n        v-text=\"$t('instances.startup.column.duration')\"\n      />\n      <div class=\"break-all\" v-text=\"$t('instances.startup.column.details')\" />\n    </div>\n    <ul v-if=\"tree\">\n      <tree-item\n        v-for=\"(eventNode, index) in filteredTree\"\n        :key=\"index\"\n        :item=\"eventNode\"\n        :expand=\"expandedNodes\"\n        @toggle=\"onToggle\"\n      />\n    </ul>\n  </div>\n</template>\n\n<script>\nimport { orderBy } from 'lodash-es';\n\nimport { StartupActuatorEventTree } from '@/services/startup-activator-tree';\nimport TreeItem from '@/views/instances/startup/tree-item';\n\nconst filterProperty = (needle) => (property, name) => {\n  if ('tags' == name && property && property.length > 0) {\n    return property.find((x) => findByFilter(needle, x));\n  }\n  return (\n    name.toString().toLowerCase().includes(needle) ||\n    (property && property.toString().toLowerCase().includes(needle))\n  );\n};\n\nconst findByFilter = (needle, properties, keys) => {\n  if (!properties) {\n    return false;\n  }\n  const fn1 = filterProperty(needle);\n  if (!keys) {\n    keys = Object.keys(properties);\n  }\n  return keys.find((key) => {\n    return fn1(properties[key], key);\n  });\n};\n\nconst filterResults = (needle) => (startupEvent) => {\n  if (!startupEvent || !startupEvent.startupStep) {\n    return null;\n  }\n  if (typeof needle == 'function') {\n    return needle(startupEvent) ? startupEvent : null;\n  }\n  var ret =\n    findByFilter(needle, startupEvent, ['name']) ||\n    findByFilter(needle, startupEvent.startupStep, ['name', 'tags']);\n  if (ret) {\n    return startupEvent;\n  }\n  return null;\n};\n\nexport default {\n  components: { TreeItem },\n  props: {\n    filter: {\n      type: String,\n      default: '',\n    },\n    tree: {\n      type: StartupActuatorEventTree,\n      required: true,\n    },\n    expand: {\n      type: Set,\n      required: false,\n      default: null,\n    },\n  },\n  emits: ['change', 'after-filter-action'],\n  data: () => ({\n    expandedNodes: new Set(),\n    isExpanded: false,\n    resultSize: '',\n  }),\n  computed: {\n    treeSize() {\n      return new Set(this.tree.getEvents()).size;\n    },\n    filteredTree() {\n      if (!this.tree) {\n        return [];\n      }\n      if (!this.filter) {\n        return this.tree.getRoots();\n      }\n      var xfilter;\n      if (/^[0-9.]+$/.test(this.filter)) {\n        const timeLimt = parseFloat(this.filter);\n        xfilter = function (x) {\n          return x.duration >= timeLimt;\n        };\n      } else {\n        xfilter = this.filter.toLowerCase();\n      }\n      const results = this.tree\n        .getEvents()\n        .map(filterResults(xfilter))\n        .filter((ps) => ps && Object.keys(ps.startupStep).length > 0);\n      return orderBy(results, (o) => -o.duration);\n    },\n  },\n  watch: {\n    expand(expandedNodes) {\n      this.expandedNodes = expandedNodes;\n    },\n    expandedNodes() {\n      this.$emit('change', {\n        expandedNodes: this.expandedNodes,\n      });\n    },\n    filteredTree: {\n      deep: true,\n      handler: function (newVal) {\n        this.$emit('after-filter-action', newVal.length);\n      },\n    },\n  },\n  created() {\n    if (this.expand) {\n      this.expandedNodes = this.expand;\n    }\n  },\n  methods: {\n    onToggle($event) {\n      if ($event.isOpen === true) {\n        this.expandedNodes.add($event.target.startupStep.id);\n        this.expandedNodes = new Set(this.expandedNodes);\n      } else {\n        this.expandedNodes.delete($event.target.startupStep.id);\n        this.expandedNodes = new Set(this.expandedNodes);\n      }\n\n      this.isExpanded = this.expandedNodes.size === this.treeSize;\n    },\n  },\n};\n</script>\n\n<style lang=\"css\">\n.text-right {\n  @apply justify-end;\n}\n.text-center {\n  @apply justify-center;\n}\n.enforce-word-wrap {\n  @apply break-all;\n}\n.tree .checkbox-expand-all {\n  margin-right: 5px;\n}\n.tree .row {\n  display: grid;\n  grid-template-columns: 1fr 140px 1fr;\n  grid-template-rows: 1fr;\n  grid-column-gap: 0;\n  grid-row-gap: 0;\n  border-bottom: 1px solid #dbdbdb;\n}\n.tree .row--head {\n  @apply bg-white;\n  padding: 0.5em 0.75em;\n  grid-template-rows: 1fr;\n  color: #363636;\n  border: none;\n  border-bottom: 2px solid #dbdbdb;\n  vertical-align: top;\n  font-weight: 700;\n}\n.tree .row .column {\n  @apply truncate inline-flex items-center w-full;\n  padding: 0.5em 0.75em;\n}\n.tree .row .column--details {\n  grid-area: 0.0416666667;\n  @apply break-all block;\n}\n.tree .row .column--duration {\n  @apply text-sm font-mono text-right;\n}\n.tree-item {\n  margin: 0;\n  transition-property: transform;\n  transition-duration: 125ms;\n}\n.tree-item .icon {\n  @apply w-4 h-4 flex items-center cursor-pointer;\n  margin-right: 10px;\n}\n.tree-item .icon--open {\n  transform: rotate(90deg);\n}\n.tree-item .icon.empty {\n}\n.tree-item[tree-item-depth='1'] {\n  background-color: rgba(66, 211, 165, 0.2);\n}\n.tree-item[tree-item-depth='1'] .column--name {\n  padding-left: 22px;\n}\n.tree-item[tree-item-depth='2'] {\n  background-color: rgba(66, 211, 165, 0.2);\n}\n.tree-item[tree-item-depth='2'] .column--name {\n  padding-left: 44px;\n}\n.tree-item[tree-item-depth='3'] {\n  background-color: rgba(66, 211, 165, 0.2);\n}\n.tree-item[tree-item-depth='3'] .column--name {\n  padding-left: 66px;\n}\n.tree-item[tree-item-depth='4'] {\n  background-color: rgba(66, 211, 165, 0.2);\n}\n.tree-item[tree-item-depth='4'] .column--name {\n  padding-left: 88px;\n}\n.tree-item[tree-item-depth='5'] {\n  background-color: rgba(66, 211, 165, 0.2);\n}\n.tree-item[tree-item-depth='5'] .column--name {\n  padding-left: 110px;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/threaddump/i18n.de.json",
    "content": "{\n  \"instances\": {\n    \"threaddump\": {\n      \"label\": \"Thread-Dump\",\n      \"download\": \"Thread-Dump herunterladen\",\n\n      \"download_failed\": \"Herunterladen des Thread-Dump ist fehlgeschlagen.\",\n      \"thread_details_blocked_count\": \"Blocked-Count\",\n      \"thread_details_blocked_time\": \"Blocked-Time\",\n      \"thread_details_lock_name\": \"Lock-Name\",\n      \"thread_details_lock_owner_id\": \"Lock-Besitzer-Id\",\n      \"thread_details_lock_owner_name\": \"Lock-Besitzer-Name\",\n      \"thread_details_waited_count\": \"Warteperioden\",\n      \"thread_details_waited_time\": \"gewartete Dauer\",\n      \"thread_id\": \"Thread-Id\",\n      \"thread_name\": \"Thread-Name\",\n      \"thread_state\": \"Thread-Status\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/threaddump/i18n.en.json",
    "content": "{\n  \"instances\": {\n    \"threaddump\": {\n      \"label\": \"Thread Dump\",\n      \"download\": \"Download Thread Dump\",\n\n      \"download_failed\": \"Downloading thread dump failed.\",\n      \"thread_details_blocked_count\": \"Blocked count\",\n      \"thread_details_blocked_time\": \"Blocked time\",\n      \"thread_details_lock_name\": \"Lock name\",\n      \"thread_details_lock_owner_id\": \"Lock owner id\",\n      \"thread_details_lock_owner_name\": \"Lock owner name\",\n      \"thread_details_waited_count\": \"Waited count\",\n      \"thread_details_waited_time\": \"Waited time\",\n      \"thread_id\": \"Thread Id\",\n      \"thread_name\": \"Thread name\",\n      \"thread_state\": \"Thread state\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/threaddump/i18n.es.json",
    "content": "{\n  \"instances\": {\n    \"threaddump\": {\n      \"label\": \"Thread Dump\",\n      \"download\": \"Descargar Thread Dump\",\n\n      \"download_failed\": \"Falla descargando el thread dump.\",\n      \"thread_details_blocked_count\": \"Bloqueados count\",\n      \"thread_details_blocked_time\": \"Tiempo de bloqueo\",\n      \"thread_details_lock_name\": \"Nombre del Lock\",\n      \"thread_details_lock_owner_id\": \"Id del dueño del Lock\",\n      \"thread_details_lock_owner_name\": \"Nombre del dueño del Lock\",\n      \"thread_details_waited_count\": \"Cantidad Waited\",\n      \"thread_details_waited_time\": \"Tiempo Waited\",\n      \"thread_id\": \"Id Thread\",\n      \"thread_name\": \"Nombre Thread \",\n      \"thread_state\": \"Estado Thread\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/threaddump/i18n.fr.json",
    "content": "{\n  \"instances\": {\n    \"threaddump\": {\n      \"label\": \"Thread Dump\",\n      \"download\": \"Télécharger Thread Dump\",\n\n      \"download_failed\": \"Échec du téléchargement de Thread Dump.\",\n      \"thread_details_blocked_count\": \"Nombre de blockés\",\n      \"thread_details_blocked_time\": \"Temps de blocage\",\n      \"thread_details_lock_name\": \"Nom du Lock\",\n      \"thread_details_lock_owner_id\": \"Id du propriétaire du Lock\",\n      \"thread_details_lock_owner_name\": \"Nom du propriétaire du Lock\",\n      \"thread_details_waited_count\": \"Nombre en attente\",\n      \"thread_details_waited_time\": \"Temps attendu\",\n      \"thread_id\": \"Thread Id\",\n      \"thread_name\": \"Nom du Thread\",\n      \"thread_state\": \"État du Thread\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/threaddump/i18n.is.json",
    "content": "{\n  \"instances\": {\n    \"threaddump\": {\n      \"label\": \"Thread Dump\",\n      \"download\": \"Hala Thread Dump niður\",\n\n      \"download_failed\": \"Mistókst að hala thread dump niður.\",\n      \"thread_details_blocked_count\": \"Fjöldi lokað\",\n      \"thread_details_blocked_time\": \"Tími lokað\",\n      \"thread_details_lock_name\": \"Nafn lokunar\",\n      \"thread_details_lock_owner_id\": \"Id eiganda lokunar\",\n      \"thread_details_lock_owner_name\": \"Nafn eiganda lokunar\",\n      \"thread_details_waited_count\": \"Fjöldi beðið\",\n      \"thread_details_waited_time\": \"Tími beðið\",\n      \"thread_id\": \"Id þráðar\",\n      \"thread_name\": \"Nafn þráðar\",\n      \"thread_state\": \"Staða þráðar\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/threaddump/i18n.ko.json",
    "content": "{\n  \"instances\": {\n    \"threaddump\": {\n      \"label\": \"스레드 덤프\",\n      \"download\": \"스레드 덤프 다운로드\",\n\n      \"download_failed\": \"스레드 덤프 다운로드 실패.\",\n      \"thread_details_blocked_count\": \"블록된 횟수\",\n      \"thread_details_blocked_time\": \"블록된 시간\",\n      \"thread_details_lock_name\": \"잠김 이름\",\n      \"thread_details_lock_owner_id\": \"잠김 소유자 id\",\n      \"thread_details_lock_owner_name\": \"잠김 소유자명\",\n      \"thread_details_waited_count\": \"대기 횟수\",\n      \"thread_details_waited_time\": \"대기 시간\",\n      \"thread_id\": \"스레드 Id\",\n      \"thread_name\": \"스레드 명\",\n      \"thread_state\": \"스레드 상태\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/threaddump/i18n.pt-BR.json",
    "content": "{\n  \"instances\": {\n    \"threaddump\": {\n      \"label\": \"Despejo de Thread\",\n      \"download\": \"Download do despejo de Thread\",\n\n      \"download_failed\": \"Falha no download do despejo de thread.\",\n      \"thread_details_blocked_count\": \"Contagem de bloqueio\",\n      \"thread_details_blocked_time\": \"Tempo bloqueado\",\n      \"thread_details_lock_name\": \"Nome do bloqueio\",\n      \"thread_details_lock_owner_id\": \"Bloquear ID do proprietário\",\n      \"thread_details_lock_owner_name\": \"Bloquear nome do proprietário\",\n      \"thread_details_waited_count\": \"Número pendente\",\n      \"thread_details_waited_time\": \"Tempo de espera\",\n      \"thread_id\": \"Id da Thread\",\n      \"thread_name\": \"Nome da Thread\",\n      \"thread_state\": \"Estado da Thread\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/threaddump/i18n.ru.json",
    "content": "{\n  \"instances\": {\n    \"threaddump\": {\n      \"label\": \"Дамп потоков\",\n      \"download\": \"Загрузить дамп потоков\",\n      \"download_failed\": \"Ошибка при загрузке дампа потоков.\",\n      \"thread_details_blocked_count\": \"Количество блокировок\",\n      \"thread_details_blocked_time\": \"Время блокировки\",\n      \"thread_details_lock_name\": \"Имя блокировки\",\n      \"thread_details_lock_owner_id\": \"Идентификатор владельца блокировки\",\n      \"thread_details_lock_owner_name\": \"Имя владельца блокировки\",\n      \"thread_details_waited_count\": \"Количество ожиданий\",\n      \"thread_details_waited_time\": \"Время ожиданий\",\n      \"thread_id\": \"Идентификатор потока\",\n      \"thread_name\": \"Имя потока\",\n      \"thread_state\": \"Статс потока\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/threaddump/i18n.zh-CN.json",
    "content": "{\n  \"instances\": {\n    \"threaddump\": {\n      \"label\": \"线程转储\",\n      \"download\": \"下载线程\",\n\n      \"download_failed\": \"线程无法下载。\",\n      \"thread_details_blocked_count\": \"阻塞数\",\n      \"thread_details_blocked_time\": \"阻塞时间\",\n      \"thread_details_lock_name\": \"锁名称\",\n      \"thread_details_lock_owner_id\": \"锁拥有者ID\",\n      \"thread_details_lock_owner_name\": \"锁拥有者名称\",\n      \"thread_details_waited_count\": \"等待数\",\n      \"thread_details_waited_time\": \"等待时间\",\n      \"thread_id\": \"线程ID\",\n      \"thread_name\": \"线程名称\",\n      \"thread_state\": \"线程状态\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/threaddump/i18n.zh-TW.json",
    "content": "{\n  \"instances\": {\n    \"threaddump\": {\n      \"label\": \"執行緒傾印\",\n      \"download\": \"下載執行緒傾印\",\n\n      \"download_failed\": \"下載執行緒傾印失敗。\",\n      \"thread_details_blocked_count\": \"阻塞次數\",\n      \"thread_details_blocked_time\": \"阻塞時間\",\n      \"thread_details_lock_name\": \"Lock 名稱\",\n      \"thread_details_lock_owner_id\": \"Lock 擁有者 ID\",\n      \"thread_details_lock_owner_name\": \"Lock 擁有者名稱\",\n      \"thread_details_waited_count\": \"等待次數\",\n      \"thread_details_waited_time\": \"等待時間\",\n      \"thread_id\": \"執行緒 ID\",\n      \"thread_name\": \"執行緒名稱\",\n      \"thread_state\": \"執行緒狀態\"\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/threaddump/index.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <sba-instance-section :error=\"errorFetch\" :loading=\"!hasLoaded\">\n    <template v-if=\"threads\" #before>\n      <sba-sticky-subnav>\n        <div class=\"text-right\">\n          <sba-button @click=\"downloadThreaddump\">\n            <font-awesome-icon icon=\"download\" />&nbsp;\n            <span v-text=\"$t('instances.threaddump.download')\" />\n          </sba-button>\n        </div>\n      </sba-sticky-subnav>\n    </template>\n    <sba-alert\n      v-if=\"errorDownload\"\n      :error=\"errorDownload\"\n      :title=\"$t('instances.threaddump.download_failed')\"\n    />\n    <sba-panel>\n      <threads-list v-if=\"threads\" :thread-timelines=\"threads\" />\n    </sba-panel>\n  </sba-instance-section>\n</template>\n\n<script>\nimport { remove } from 'lodash-es';\nimport moment from 'moment';\nimport { take } from 'rxjs/operators';\n\nimport subscribing from '@/mixins/subscribing';\nimport Instance from '@/services/instance';\nimport { concatMap, delay, retryWhen, timer } from '@/utils/rxjs';\nimport { VIEW_GROUP } from '@/views/ViewGroup';\nimport SbaInstanceSection from '@/views/instances/shell/sba-instance-section';\nimport threadsList from '@/views/instances/threaddump/threads-list';\n\nexport default {\n  components: { SbaInstanceSection, threadsList },\n  mixins: [subscribing],\n  props: {\n    instance: {\n      type: Instance,\n      required: true,\n    },\n  },\n  data: () => ({\n    hasLoaded: false,\n    errorFetch: null,\n    errorDownload: null,\n    threads: {},\n  }),\n  computed: {},\n  methods: {\n    updateTimelines(threads) {\n      const now = moment().valueOf();\n      //initialize with all known live threads, which will be removed from the list if still alive\n      const terminatedThreads = Object.entries(this.threads)\n        .filter(([, value]) => value.threadState !== 'TERMINATED')\n        .map(([threadId]) => parseInt(threadId));\n\n      threads.forEach((thread) => {\n        if (!this.threads[thread.threadId]) {\n          this.threads[thread.threadId] = {\n            threadId: thread.threadId,\n            threadState: thread.threadState,\n            threadName: thread.threadName,\n            timeline: [\n              {\n                start: now,\n                end: now,\n                details: thread,\n                threadState: thread.threadState,\n              },\n            ],\n          };\n        } else {\n          const entry = this.threads[thread.threadId];\n          if (entry.threadState !== thread.threadState) {\n            entry.threadState = thread.threadState;\n            entry.timeline[entry.timeline.length - 1].end = now;\n            entry.timeline.push({\n              start: now,\n              end: now,\n              details: thread,\n              threadState: thread.threadState,\n            });\n          } else {\n            entry.timeline[entry.timeline.length - 1].end = now;\n          }\n        }\n        remove(terminatedThreads, (threadId) => threadId === thread.threadId);\n      });\n\n      terminatedThreads.forEach((threadId) => {\n        const entry = this.threads[threadId];\n        entry.threadState = 'TERMINATED';\n        entry.timeline[entry.timeline.length - 1].end = now;\n      });\n    },\n    async fetchThreaddump() {\n      const response = await this.instance.fetchThreaddump();\n      return response.data.threads;\n    },\n    async downloadThreaddump() {\n      this.errorDownload = null;\n      try {\n        await this.instance.downloadThreaddump();\n      } catch (error) {\n        console.warn('Downloading thread dump failed.', error);\n        this.errorDownload = error;\n      }\n    },\n    createSubscription() {\n      this.errorFetch = null;\n      return timer(0, 1000)\n        .pipe(\n          concatMap(this.fetchThreaddump),\n          retryWhen((err) => {\n            return err.pipe(delay(1000), take(2));\n          }),\n        )\n        .subscribe({\n          next: (threads) => {\n            this.hasLoaded = true;\n            this.updateTimelines(threads);\n          },\n          error: (error) => {\n            this.hasLoaded = true;\n            console.warn('Fetching threaddump failed:', error);\n            this.errorFetch = error;\n          },\n        });\n    },\n  },\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      name: 'instances/threaddump',\n      parent: 'instances',\n      path: 'threaddump',\n      label: 'instances.threaddump.label',\n      component: this,\n      group: VIEW_GROUP.JVM,\n      order: 400,\n      isEnabled: ({ instance }) => instance.hasEndpoint('threaddump'),\n    });\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/threaddump/thread-list-item.vue",
    "content": "<template>\n  <table\n    class=\"table-auto table-wide table-striped w-full my-2 border shadow bg-white\"\n  >\n    <colgroup>\n      <col class=\"w-60\" />\n    </colgroup>\n    <tbody>\n      <tr>\n        <td class=\"label\" v-text=\"$t('instances.threaddump.thread_id')\" />\n        <td v-text=\"thread.threadId\" />\n      </tr>\n      <tr>\n        <td class=\"label\" v-text=\"$t('instances.threaddump.thread_name')\" />\n        <td v-text=\"thread.threadName\" />\n      </tr>\n      <template v-if=\"details !== null\">\n        <tr>\n          <td class=\"label\" v-text=\"$t('instances.threaddump.thread_state')\" />\n          <td v-text=\"details.threadState\" />\n        </tr>\n        <tr>\n          <td\n            class=\"label\"\n            v-text=\"$t('instances.threaddump.thread_details_blocked_count')\"\n          />\n          <td v-text=\"details.blockedCount\" />\n        </tr>\n        <tr>\n          <td\n            class=\"label\"\n            v-text=\"$t('instances.threaddump.thread_details_blocked_time')\"\n          />\n          <td v-text=\"details.blockedTime\" />\n        </tr>\n        <tr>\n          <td\n            class=\"label\"\n            v-text=\"$t('instances.threaddump.thread_details_waited_count')\"\n          />\n          <td v-text=\"details.waitedCount\" />\n        </tr>\n        <tr>\n          <td\n            class=\"label\"\n            v-text=\"$t('instances.threaddump.thread_details_waited_time')\"\n          />\n          <td v-text=\"details.waitedTime\" />\n        </tr>\n        <tr>\n          <td\n            class=\"label\"\n            v-text=\"$t('instances.threaddump.thread_details_lock_name')\"\n          />\n          <td v-text=\"details.lockName\" />\n        </tr>\n        <tr>\n          <td\n            class=\"label\"\n            v-text=\"$t('instances.threaddump.thread_details_lock_owner_id')\"\n          />\n          <td v-text=\"details.lockOwnerId\" />\n        </tr>\n        <tr>\n          <td\n            class=\"label\"\n            v-text=\"$t('instances.threaddump.thread_details_lock_owner_name')\"\n          />\n          <td v-text=\"details.lockOwnerName\" />\n        </tr>\n        <tr v-if=\"details.stackTrace.length > 0\">\n          <td colspan=\"2\">\n            <span class=\"label\" v-text=\"$t('term.stacktrace')\" />\n            <div class=\"text-sm\">\n              <template\n                v-for=\"(frame, idx) in details.stackTrace\"\n                :key=\"`frame-${thread.threadId}-${idx}`\"\n              >\n                <div class=\"whitespace-pre font-mono\">\n                  <span\n                    v-text=\"\n                      `${frame.className}.${frame.methodName}(${frame.fileName}:${frame.lineNumber})`\n                    \"\n                  />\n                  <sba-tag\n                    v-if=\"frame.nativeMethod\"\n                    :key=\"`frame-${thread.threadId}-${idx}-native`\"\n                    value=\"native\"\n                  />\n                </div>\n              </template>\n            </div>\n          </td>\n        </tr>\n      </template>\n    </tbody>\n  </table>\n</template>\n<script>\nexport default {\n  name: 'ThreadListItem',\n  props: {\n    thread: {\n      type: Object,\n      required: true,\n    },\n    details: {\n      type: Object,\n      required: true,\n    },\n  },\n};\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/threaddump/thread-tag.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <span\n    class=\"w-6 inline-block text-center mr-1 rounded\"\n    :class=\"`thread-tag--${threadState.toLowerCase()}`\"\n    :title=\"threadState\"\n  />\n</template>\n\n<script>\nexport default {\n  props: {\n    threadState: {\n      type: String,\n      required: true,\n    },\n  },\n};\n</script>\n\n<style lang=\"css\" scoped>\n.thread-tag--runnable {\n  color: black !important;\n  background-color: #48c78e !important;\n}\n.thread-tag--runnable:before {\n  content: 'R';\n}\n.thread-tag--timed_waiting,\n.thread-tag--waiting {\n  color: black !important;\n  background-color: #ffe08a !important;\n}\n.thread-tag--timed_waiting:before,\n.thread-tag--waiting:before {\n  content: 'W';\n}\n.thread-tag--blocked {\n  color: #fff !important;\n  background-color: #f14668 !important;\n}\n.thread-tag--blocked:before {\n  content: 'B';\n}\n.thread-tag--terminated {\n  color: black !important;\n  background-color: #f5f5f5 !important;\n}\n.thread-tag--terminated:before {\n  content: 'T';\n}\n.thread-tag--new {\n  color: black !important;\n  background-color: #f5f5f5 !important;\n}\n.thread-tag--new:before {\n  content: 'N';\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/instances/threaddump/threads-list.vue",
    "content": "<!--\n  - Copyright 2014-2020 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <table class=\"threads\">\n    <thead>\n      <tr>\n        <th class=\"threads__thread-name\" v-text=\"$t('term.name')\" />\n        <th class=\"threads__timeline\">\n          <svg class=\"threads__scale\" height=\"24px\" />\n        </th>\n      </tr>\n    </thead>\n    <template v-for=\"thread in threadTimelines\" :key=\"thread.threadId\">\n      <tbody>\n        <tr>\n          <td class=\"threads__thread-name\">\n            <thread-tag :thread-state=\"thread.threadState\" />\n            <span v-text=\"thread.threadName\" />\n          </td>\n          <td class=\"threads__timeline\">\n            <svg :id=\"`thread-${thread.threadId}`\" height=\"32px\" />\n          </td>\n        </tr>\n        <tr\n          v-if=\"showDetails[thread.threadId]\"\n          :key=\"`${thread.threadId}-detail`\"\n        >\n          <td colspan=\"2\">\n            <thread-list-item\n              :thread=\"thread\"\n              :details=\"getThreadDetails(thread, showDetails[thread.threadId])\"\n            />\n          </td>\n        </tr>\n      </tbody>\n    </template>\n  </table>\n</template>\n<script>\nimport moment from 'moment';\n\nimport d3 from '@/utils/d3';\nimport ThreadListItem from '@/views/instances/threaddump/thread-list-item';\nimport threadTag from '@/views/instances/threaddump/thread-tag';\n\nconst maxPixelsPerSeconds = 15;\n\nexport default {\n  components: { ThreadListItem, threadTag },\n  props: {\n    threadTimelines: {\n      type: Object,\n      required: true,\n    },\n  },\n  data: () => ({\n    showDetails: {},\n    lastEndPosition: 0,\n    resizeObserver: null,\n  }),\n  watch: {\n    threadTimelines: {\n      deep: true,\n      handler: 'drawTimelines',\n      immediate: true,\n    },\n  },\n  mounted() {\n    this.resizeObserver = new ResizeObserver(() => {\n      this.drawTimelines(this.threadTimelines);\n    });\n    const scaleElement = this.$el.querySelector('.threads__scale');\n    if (scaleElement) {\n      this.resizeObserver.observe(scaleElement);\n    }\n  },\n  beforeUnmount() {\n    if (this.resizeObserver) {\n      this.resizeObserver.disconnect();\n    }\n  },\n  methods: {\n    getThreadDetails(thread, start) {\n      return thread.timeline.find((entry) => entry.start === start).details;\n    },\n    getTimeExtent(timelines) {\n      return Object.entries(timelines)\n        .map(([, value]) => value.timeline)\n        .map((timeline) => ({\n          start: timeline[0].start,\n          end: timeline[timeline.length - 1].end,\n        }))\n        .reduce(\n          (current, next) => ({\n            start: Math.min(current.start, next.start),\n            end: Math.max(current.end, next.end),\n          }),\n          { start: Number.MAX_SAFE_INTEGER, end: Number.MIN_SAFE_INTEGER },\n        );\n    },\n    showThreadDetails({ threadId, start }) {\n      const previousSelectedStart = this.showDetails[threadId];\n      if (previousSelectedStart) {\n        d3.selectAll(\n          '#rect-threadid-' + threadId + '-start-' + previousSelectedStart,\n        ).attr('class', (d) => `thread thread--${d.threadState.toLowerCase()}`);\n      }\n\n      if (previousSelectedStart === start) {\n        this.showDetails = {\n          ...this.showDetails,\n          [threadId]: null,\n        };\n      } else {\n        this.showDetails = {\n          ...this.showDetails,\n          [threadId]: start,\n        };\n        d3.selectAll('#rect-threadid-' + threadId + '-start-' + start).attr(\n          'class',\n          (d) =>\n            `thread thread--${d.threadState.toLowerCase()} thread--clicked`,\n        );\n      }\n    },\n    async drawTimelines(timelines) {\n      if (timelines) {\n        const wasInView = this.isInView(this.lastEndPosition);\n        await this.$nextTick();\n\n        const { start, end } = this.getTimeExtent(timelines);\n        const width = this.$el\n          .querySelector('.threads__timeline')\n          .getBoundingClientRect().width;\n        const totalSeconds = Math.floor(width / maxPixelsPerSeconds);\n        const x = d3\n          .scaleTime()\n          .range([0, width])\n          .domain([start, Math.max(start + (totalSeconds + 1) * 1000, end)]);\n\n        const approxWidthOfDateLabel = 60;\n        const tickCount = Math.max(\n          2,\n          Math.floor((width - approxWidthOfDateLabel) / approxWidthOfDateLabel),\n        );\n        d3.select('.threads__scale')\n          .attr('width', width)\n          .call(\n            d3\n              .axisBottom(x)\n              .ticks(Math.min(tickCount, 20))\n              .tickFormat((d) => moment(d).format('HH:mm:ss')),\n          );\n\n        Object.entries(timelines).forEach(([threadId, value]) => {\n          const svg = d3.select(`#thread-${threadId}`).attr('width', width);\n          const d = svg.selectAll('rect').data(value.timeline);\n\n          d.enter()\n            .append('rect')\n            .attr(\n              'id',\n              'rect-threadid-' +\n                threadId +\n                '-start-' +\n                value.timeline[value.timeline.length - 1].start,\n            )\n            .attr(\n              'class',\n              (d) => `thread thread--${d.threadState.toLowerCase()}`,\n            )\n            .merge(d)\n            .attr('height', '2em')\n            .attr('x', (d) => x(d.start))\n            .transition(150)\n            .attr('width', (d) =>\n              Math.max(x(d.end) - x(d.start), x(d.start + 500) - x(d.start)),\n            );\n\n          d3.selectAll(\n            '#rect-threadid-' +\n              threadId +\n              '-start-' +\n              value.timeline[value.timeline.length - 1].start,\n          ).on('click', (event, d) =>\n            this.showThreadDetails({ threadId: threadId, start: d.start }),\n          );\n        });\n\n        this.lastEndPosition = x(end);\n        if (wasInView && !this.isInView(this.lastEndPosition)) {\n          const scrollable = this.$el;\n          scrollable.scroll(this.lastEndPosition, scrollable.scrollHeight);\n        }\n      }\n    },\n    isInView(xPos) {\n      const scrollable = this.$el;\n      return (\n        scrollable &&\n        xPos >= scrollable.scrollLeft &&\n        xPos <= scrollable.scrollLeft + scrollable.clientWidth\n      );\n    },\n  },\n};\n</script>\n<style lang=\"css\">\n.threads {\n  table-layout: fixed;\n  width: 100%;\n}\n.threads__thread-name {\n  width: 250px;\n  max-width: 750px;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.threads__thread-name:hover {\n  height: auto;\n  overflow: visible;\n  white-space: normal;\n}\n\n.threads__timeline {\n  width: auto;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  padding-left: 0 !important;\n  padding-right: 0 !important;\n}\n.threads__timeline svg {\n  display: block;\n}\n.threads__scale .domain {\n  display: none;\n}\n.thread {\n  stroke: #000;\n  stroke-width: 1px;\n  stroke-opacity: 0.1;\n}\n.thread--runnable {\n  fill: #48c78e;\n}\n.thread--runnable:hover,\n.thread--runnable.thread--clicked {\n  fill: #288159;\n}\n.thread--waiting {\n  fill: #ffe08a;\n}\n.thread--waiting:hover,\n.thread--waiting.thread--clicked {\n  fill: #ffc524;\n}\n.thread--timed_waiting {\n  fill: #ffe08a;\n}\n.thread--timed_waiting:hover,\n.thread--timed_waiting.thread--clicked {\n  fill: #ffc524;\n}\n.thread--blocked {\n  fill: #f14668;\n}\n.thread--blocked:hover,\n.thread--blocked.thread--clicked {\n  fill: #ffc524;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/journal/InstanceEvent.ts",
    "content": "/*!\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport interface IInstanceEvent {\n  instance: string;\n  version: number;\n  type: InstanceEventType;\n  timestamp: Date;\n  [key: string]: any;\n}\n\nexport class InstanceEvent implements IInstanceEvent {\n  public readonly instance: string;\n  public readonly version: number;\n  public readonly type: InstanceEventType;\n  public readonly timestamp: Date;\n\n  constructor({ instance, version, type, timestamp, ...rest }: IInstanceEvent) {\n    this.instance = instance;\n    this.version = version;\n    this.type = type;\n    this.timestamp = new Date(timestamp);\n\n    Object.assign(this, rest);\n  }\n\n  get key() {\n    return `${this.instance}-${this.version}-${this.type}-${this.timestamp.getTime()}`;\n  }\n}\n\nexport enum InstanceEventType {\n  STATUS_CHANGED = 'STATUS_CHANGED',\n  REGISTERED = 'REGISTERED',\n  DEREGISTERED = 'DEREGISTERED',\n  REGISTRATION_UPDATED = 'REGISTRATION_UPDATED',\n  INFO_CHANGED = 'INFO_CHANGED',\n  ENDPOINTS_DETECTED = 'ENDPOINTS_DETECTED',\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/journal/JournalTable.spec.ts",
    "content": "/*!\n * Copyright 2014-2025 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport userEvent from '@testing-library/user-event';\nimport { screen, within } from '@testing-library/vue';\nimport { beforeEach, describe, expect, it } from 'vitest';\n\nimport JournalTable from './JournalTable.vue';\n\nimport Application from '@/services/application';\nimport { render } from '@/test-utils';\nimport {\n  InstanceEvent,\n  InstanceEventType,\n} from '@/views/journal/InstanceEvent';\n\ndescribe('JournalTable', () => {\n  let events: InstanceEvent[];\n  let applications: Application[];\n\n  beforeEach(() => {\n    events = [\n      new InstanceEvent({\n        instance: 'instance-1',\n        version: 1,\n        type: InstanceEventType.REGISTERED,\n        timestamp: new Date('2024-01-01T10:00:00Z'),\n        registration: { name: 'Zebra App' },\n      }),\n      new InstanceEvent({\n        instance: 'instance-2',\n        version: 1,\n        type: InstanceEventType.REGISTERED,\n        timestamp: new Date('2024-01-01T10:05:00Z'),\n        registration: { name: 'Alpha App' },\n      }),\n      new InstanceEvent({\n        instance: 'instance-3',\n        version: 1,\n        type: InstanceEventType.STATUS_CHANGED,\n        timestamp: new Date('2024-01-01T10:10:00Z'),\n        registration: { name: 'Beta App' },\n      }),\n      new InstanceEvent({\n        instance: 'instance-4',\n        version: 1,\n        type: InstanceEventType.DEREGISTERED,\n        timestamp: new Date('2024-01-01T10:15:00Z'),\n        registration: { name: 'Gamma App' },\n      }),\n      new InstanceEvent({\n        instance: 'instance-5',\n        version: 2,\n        type: InstanceEventType.REGISTRATION_UPDATED,\n        timestamp: new Date('2024-01-01T10:20:00Z'),\n        registration: { name: 'Delta App' },\n      }),\n      new InstanceEvent({\n        instance: 'instance-6',\n        version: 1,\n        type: InstanceEventType.INFO_CHANGED,\n        timestamp: new Date('2024-01-01T10:25:00Z'),\n        registration: { name: 'Epsilon App' },\n      }),\n      new InstanceEvent({\n        instance: 'instance-7',\n        version: 1,\n        type: InstanceEventType.ENDPOINTS_DETECTED,\n        timestamp: new Date('2024-01-01T10:30:00Z'),\n        registration: { name: 'Theta App' },\n      }),\n      new InstanceEvent({\n        instance: 'instance-2',\n        version: 2,\n        type: InstanceEventType.INFO_CHANGED,\n        timestamp: new Date('2024-01-01T10:35:00Z'),\n        registration: { name: 'Alpha App' },\n      }),\n    ];\n\n    applications = [\n      new Application({\n        id: 'app-1',\n        name: 'Zebra App',\n        instances: [{ id: 'instance-1' }],\n      }),\n      new Application({\n        id: 'app-2',\n        name: 'Alpha App',\n        instances: [{ id: 'instance-2' }],\n      }),\n      new Application({\n        id: 'app-3',\n        name: 'Beta App',\n        instances: [{ id: 'instance-3' }],\n      }),\n      new Application({\n        id: 'app-4',\n        name: 'Gamma App',\n        instances: [{ id: 'instance-4' }],\n      }),\n      new Application({\n        id: 'app-5',\n        name: 'Delta App',\n        instances: [{ id: 'instance-5' }],\n      }),\n      new Application({\n        id: 'app-6',\n        name: 'Epsilon App',\n        instances: [{ id: 'instance-6' }],\n      }),\n      new Application({\n        id: 'app-7',\n        name: 'Theta App',\n        instances: [{ id: 'instance-7' }],\n      }),\n    ];\n  });\n\n  describe('Application name', () => {\n    it('should sort by application name', async () => {\n      const user = userEvent.setup();\n\n      render(JournalTable, {\n        props: {\n          events,\n          applications,\n        },\n      });\n\n      // Find the application column header and click it to sort\n      const applicationHeader = screen.getByText('Application');\n      await user.click(applicationHeader);\n\n      // Get all rows in the table\n      const rows = screen.getAllByRole('row');\n      // Skip header row\n      const dataRows = rows.slice(1);\n\n      // Extract application names from the rows\n      const applicationNames = dataRows\n        .map((row) => {\n          const cells = within(row).queryAllByRole('cell');\n          // Application column is the second column (after expander)\n          return cells[1]?.textContent?.trim();\n        })\n        .filter(Boolean);\n\n      // Verify they are sorted alphabetically\n      // The table shows all events, and sorting should order them alphabetically\n      expect(applicationNames.length).toBeGreaterThan(0);\n      // Check that the list is sorted alphabetically\n      const sortedNames = [...applicationNames].sort((a, b) =>\n        a!.localeCompare(b!),\n      );\n      expect(applicationNames).toEqual(sortedNames);\n    });\n\n    it('should sort by application name in descending order when clicked twice', async () => {\n      const user = userEvent.setup();\n\n      render(JournalTable, {\n        props: {\n          events,\n          applications,\n        },\n      });\n\n      // Find the application column header and click it twice\n      const applicationHeader = screen.getByText('Application');\n      await user.click(applicationHeader);\n      await user.click(applicationHeader);\n\n      // Get all rows in the table\n      const rows = screen.getAllByRole('row');\n      const dataRows = rows.slice(1);\n\n      // Extract application names from the rows\n      const applicationNames = dataRows\n        .map((row) => {\n          const cells = within(row).queryAllByRole('cell');\n          return cells[1]?.textContent?.trim();\n        })\n        .filter(Boolean);\n\n      // Verify they are sorted in descending order\n      expect(applicationNames.length).toBeGreaterThan(0);\n      // Check that the list is sorted in reverse alphabetical order\n      const sortedNames = [...applicationNames].sort((a, b) =>\n        b!.localeCompare(a!),\n      );\n      expect(applicationNames).toEqual(sortedNames);\n    });\n\n    it('should filter by application name', async () => {\n      const user = userEvent.setup();\n\n      render(JournalTable, {\n        props: {\n          events,\n          applications,\n        },\n      });\n\n      // Get all rows before filtering\n      let rows = screen.getAllByRole('row');\n      let dataRows = rows.slice(1);\n      expect(dataRows).toHaveLength(8);\n\n      // Find and click the application column filter button\n      const applicationHeader = screen.getByText('Application');\n      const filterButton = applicationHeader\n        .closest('th')\n        ?.querySelector('[data-pc-section=\"columnfilter\"]');\n\n      if (filterButton) {\n        await user.click(filterButton);\n\n        // Find and select \"Alpha App\" from the MultiSelect dropdown\n        const alphaOption = await screen.findByText('Alpha App');\n        await user.click(alphaOption);\n\n        // Close the filter menu by clicking outside or on the filter button again\n        await user.click(filterButton);\n\n        // Get rows after filtering\n        rows = screen.getAllByRole('row');\n        dataRows = rows.slice(1);\n\n        // Extract application names from visible rows\n        const applicationNames = dataRows\n          .map((row) => {\n            const cells = within(row).queryAllByRole('cell');\n            return cells[1]?.textContent?.trim();\n          })\n          .filter(Boolean);\n\n        // Verify only \"Alpha App\" events are shown\n        expect(applicationNames.every((name) => name === 'Alpha App')).toBe(\n          true,\n        );\n        expect(dataRows).toHaveLength(2);\n      }\n    });\n\n    it('should filter by multiple applications', async () => {\n      const user = userEvent.setup();\n\n      render(JournalTable, {\n        props: {\n          events,\n          applications,\n        },\n      });\n\n      // Find and click the application column filter button\n      const applicationHeader = screen.getByText('Application');\n      const filterButton = applicationHeader\n        .closest('th')\n        ?.querySelector('[data-pc-section=\"columnfilter\"]');\n\n      if (filterButton) {\n        await user.click(filterButton);\n\n        // Select multiple applications\n        const alphaOption = await screen.findByText('Alpha App');\n        await user.click(alphaOption);\n\n        const betaOption = await screen.findByText('Beta App');\n        await user.click(betaOption);\n\n        const gammaOption = await screen.findByText('Gamma App');\n        await user.click(gammaOption);\n\n        // Close the filter\n        await user.click(filterButton);\n\n        // Get rows after filtering\n        const rows = screen.getAllByRole('row');\n        const dataRows = rows.slice(1);\n\n        // Extract application names\n        const applicationNames = dataRows\n          .map((row) => {\n            const cells = within(row).queryAllByRole('cell');\n            return cells[1]?.textContent?.trim();\n          })\n          .filter(Boolean);\n\n        // Verify only selected apps are shown\n        expect(dataRows).toHaveLength(4);\n        expect(applicationNames).toContain('Alpha App');\n        expect(applicationNames).toContain('Beta App');\n        expect(applicationNames).toContain('Gamma App');\n        expect(applicationNames).not.toContain('Delta App');\n        expect(applicationNames).not.toContain('Epsilon App');\n        expect(applicationNames).not.toContain('Theta App');\n        expect(applicationNames).not.toContain('Zebra App');\n      }\n    });\n  });\n\n  describe('Instance id', () => {\n    it('should sort by instance id', async () => {\n      const user = userEvent.setup();\n\n      render(JournalTable, {\n        props: {\n          events,\n          applications,\n        },\n      });\n\n      // Find the instance column header and click it to sort\n      const instanceHeader = screen.getByText('Instance');\n      await user.click(instanceHeader);\n\n      // Get all rows in the table\n      const rows = screen.getAllByRole('row');\n      const dataRows = rows.slice(1);\n\n      // Extract instance IDs from the rows\n      const instanceIds = dataRows\n        .map((row) => {\n          const cells = within(row).queryAllByRole('cell');\n          // Instance column is the third column (after expander and application)\n          return cells[2]?.textContent?.trim();\n        })\n        .filter(Boolean);\n\n      // Verify they are sorted alphabetically\n      expect(instanceIds).toEqual([\n        'instance-1',\n        'instance-2',\n        'instance-2',\n        'instance-3',\n        'instance-4',\n        'instance-5',\n        'instance-6',\n        'instance-7',\n      ]);\n    });\n\n    it('should sort by instance id in descending order when clicked twice', async () => {\n      const user = userEvent.setup();\n\n      render(JournalTable, {\n        props: {\n          events,\n          applications,\n        },\n      });\n\n      // Find the instance column header and click it twice\n      const instanceHeader = screen.getByText('Instance');\n      await user.click(instanceHeader);\n      await user.click(instanceHeader);\n\n      // Get all rows in the table\n      const rows = screen.getAllByRole('row');\n      const dataRows = rows.slice(1);\n\n      // Extract instance IDs from the rows\n      const instanceIds = dataRows\n        .map((row) => {\n          const cells = within(row).queryAllByRole('cell');\n          return cells[2]?.textContent?.trim();\n        })\n        .filter(Boolean);\n\n      // Verify they are sorted in descending order\n      expect(instanceIds).toEqual([\n        'instance-7',\n        'instance-6',\n        'instance-5',\n        'instance-4',\n        'instance-3',\n        'instance-2',\n        'instance-2',\n        'instance-1',\n      ]);\n    });\n\n    it('should filter by instance id', async () => {\n      const user = userEvent.setup();\n\n      render(JournalTable, {\n        props: {\n          events,\n          applications,\n        },\n      });\n\n      // Get all rows before filtering\n      let rows = screen.getAllByRole('row');\n      let dataRows = rows.slice(1);\n      expect(dataRows).toHaveLength(8);\n\n      // Find and click the instance column filter button\n      const instanceHeader = screen.getByText('Instance');\n      const filterButton = instanceHeader\n        .closest('th')\n        ?.querySelector('[data-pc-section=\"columnfilter\"]');\n\n      if (filterButton) {\n        await user.click(filterButton);\n\n        // Find and select \"instance-2\" from the MultiSelect dropdown\n        const instance2Option = await screen.findByText('instance-2');\n        await user.click(instance2Option);\n\n        // Close the filter menu\n        await user.click(filterButton);\n\n        // Get rows after filtering\n        rows = screen.getAllByRole('row');\n        dataRows = rows.slice(1);\n\n        // Extract instance IDs from visible rows\n        const instanceIds = dataRows\n          .map((row) => {\n            const cells = within(row).queryAllByRole('cell');\n            return cells[2]?.textContent?.trim();\n          })\n          .filter(Boolean);\n\n        // Verify only \"instance-2\" events are shown\n        expect(instanceIds.every((id) => id === 'instance-2')).toBe(true);\n        expect(dataRows).toHaveLength(2);\n      }\n    });\n\n    it('should filter by multiple instances', async () => {\n      const user = userEvent.setup();\n\n      render(JournalTable, {\n        props: {\n          events,\n          applications,\n        },\n      });\n\n      // Find and click the instance column filter button\n      const instanceHeader = screen.getByText('Instance');\n      const filterButton = instanceHeader\n        .closest('th')\n        ?.querySelector('[data-pc-section=\"columnfilter\"]');\n\n      if (filterButton) {\n        await user.click(filterButton);\n\n        // Select multiple instances\n        const instance1Option = await screen.findByText('instance-1');\n        await user.click(instance1Option);\n\n        const instance3Option = await screen.findByText('instance-3');\n        await user.click(instance3Option);\n\n        const instance5Option = await screen.findByText('instance-5');\n        await user.click(instance5Option);\n\n        // Close the filter\n        await user.click(filterButton);\n\n        // Get rows after filtering\n        const rows = screen.getAllByRole('row');\n        const dataRows = rows.slice(1);\n\n        // Extract instance IDs\n        const instanceIds = dataRows\n          .map((row) => {\n            const cells = within(row).queryAllByRole('cell');\n            return cells[2]?.textContent?.trim();\n          })\n          .filter(Boolean);\n\n        // Verify only selected instances are shown\n        expect(dataRows).toHaveLength(3);\n        expect(instanceIds).toContain('instance-1');\n        expect(instanceIds).toContain('instance-3');\n        expect(instanceIds).toContain('instance-5');\n        expect(instanceIds).not.toContain('instance-2');\n        expect(instanceIds).not.toContain('instance-4');\n        expect(instanceIds).not.toContain('instance-6');\n        expect(instanceIds).not.toContain('instance-7');\n      }\n    });\n  });\n\n  describe('Time', () => {\n    it('should sort by time', async () => {\n      const user = userEvent.setup();\n\n      render(JournalTable, {\n        props: {\n          events,\n          applications,\n        },\n      });\n\n      // Find the time column header and click it to sort\n      const timeHeader = screen.getByText('Time');\n      await user.click(timeHeader);\n\n      // Get all rows in the table\n      const rows = screen.getAllByRole('row');\n      const dataRows = rows.slice(1);\n\n      // Extract timestamps from the rows\n      const timestamps = dataRows.map((row) => {\n        const cells = within(row).queryAllByRole('cell');\n        // Time column is the fourth column (after expander, application, and instance)\n        return cells[3]?.textContent?.trim();\n      });\n\n      // Compare by checking the order is ascending\n      for (let i = 0; i < timestamps.length - 1; i++) {\n        const current = new Date(timestamps[i] || '');\n        const next = new Date(timestamps[i + 1] || '');\n        expect(current.getTime()).toBeLessThanOrEqual(next.getTime());\n      }\n    });\n\n    it('should sort by time in descending order when clicked twice', async () => {\n      const user = userEvent.setup();\n\n      render(JournalTable, {\n        props: {\n          events,\n          applications,\n        },\n      });\n\n      // Find the time column header and click it twice\n      const timeHeader = screen.getByText('Time');\n      await user.click(timeHeader);\n      await user.click(timeHeader);\n\n      // Get all rows in the table\n      const rows = screen.getAllByRole('row');\n      const dataRows = rows.slice(1);\n\n      // Extract timestamps from the rows\n      const timestamps = dataRows.map((row) => {\n        const cells = within(row).queryAllByRole('cell');\n        return cells[3]?.textContent?.trim();\n      });\n\n      // Verify they are sorted in descending order (newest first)\n      for (let i = 0; i < timestamps.length - 1; i++) {\n        const current = new Date(timestamps[i] || '');\n        const next = new Date(timestamps[i + 1] || '');\n        expect(current.getTime()).toBeGreaterThanOrEqual(next.getTime());\n      }\n    });\n  });\n\n  describe('Event type', () => {\n    it('should sort by event type', async () => {\n      const user = userEvent.setup();\n\n      render(JournalTable, {\n        props: {\n          events,\n          applications,\n        },\n      });\n\n      // Find the event column header and click it to sort\n      const eventHeader = screen.getByText('Event');\n      await user.click(eventHeader);\n\n      // Get all rows in the table\n      const rows = screen.getAllByRole('row');\n      const dataRows = rows.slice(1);\n\n      // Extract event types from the rows\n      const eventTypes = dataRows\n        .map((row) => {\n          const cells = within(row).queryAllByRole('cell');\n          // Event column is the fifth column\n          return cells[4]?.textContent?.trim();\n        })\n        .filter(Boolean);\n\n      // Verify they are sorted alphabetically\n      expect(eventTypes).toEqual([\n        'DEREGISTERED',\n        'ENDPOINTS_DETECTED',\n        'INFO_CHANGED',\n        'INFO_CHANGED',\n        'REGISTERED',\n        'REGISTERED',\n        'REGISTRATION_UPDATED',\n        'STATUS_CHANGED',\n      ]);\n    });\n\n    it('should sort by event type in descending order when clicked twice', async () => {\n      const user = userEvent.setup();\n\n      render(JournalTable, {\n        props: {\n          events,\n          applications,\n        },\n      });\n\n      // Find the event column header and click it twice\n      const eventHeader = screen.getByText('Event');\n      await user.click(eventHeader);\n      await user.click(eventHeader);\n\n      // Get all rows in the table\n      const rows = screen.getAllByRole('row');\n      const dataRows = rows.slice(1);\n\n      // Extract event types from the rows\n      const eventTypes = dataRows\n        .map((row) => {\n          const cells = within(row).queryAllByRole('cell');\n          return cells[4]?.textContent?.trim();\n        })\n        .filter(Boolean);\n\n      // Verify they are sorted in descending order\n      expect(eventTypes).toEqual([\n        'STATUS_CHANGED',\n        'REGISTRATION_UPDATED',\n        'REGISTERED',\n        'REGISTERED',\n        'INFO_CHANGED',\n        'INFO_CHANGED',\n        'ENDPOINTS_DETECTED',\n        'DEREGISTERED',\n      ]);\n    });\n\n    it('should filter by event type', async () => {\n      const user = userEvent.setup();\n\n      render(JournalTable, {\n        props: {\n          events,\n          applications,\n        },\n      });\n\n      // Get all rows before filtering\n      let rows = screen.getAllByRole('row');\n      let dataRows = rows.slice(1);\n      expect(dataRows).toHaveLength(8);\n\n      // Find and click the event column filter button\n      const eventHeader = screen.getByText('Event');\n      const filterButton = eventHeader\n        .closest('th')\n        ?.querySelector('[data-pc-section=\"columnfilter\"]');\n\n      if (filterButton) {\n        await user.click(filterButton);\n\n        // Find and select \"INFO_CHANGED\" from the MultiSelect dropdown\n        const infoChangedOption = await screen.findByText('INFO_CHANGED');\n        await user.click(infoChangedOption);\n\n        // Close the filter menu\n        await user.click(filterButton);\n\n        // Get rows after filtering\n        rows = screen.getAllByRole('row');\n        dataRows = rows.slice(1);\n\n        // Extract event types from visible rows\n        const eventTypes = dataRows\n          .map((row) => {\n            const cells = within(row).queryAllByRole('cell');\n            return cells[4]?.textContent?.trim();\n          })\n          .filter(Boolean);\n\n        // Verify only \"INFO_CHANGED\" events are shown\n        expect(eventTypes.every((type) => type === 'INFO_CHANGED')).toBe(true);\n        expect(dataRows).toHaveLength(2);\n      }\n    });\n\n    it('should filter by multiple event types', async () => {\n      const user = userEvent.setup();\n\n      render(JournalTable, {\n        props: {\n          events,\n          applications,\n        },\n      });\n\n      // Find and click the event column filter button\n      const eventHeader = screen.getByText('Event');\n      const filterButton = eventHeader\n        .closest('th')\n        ?.querySelector('[data-pc-section=\"columnfilter\"]');\n\n      if (filterButton) {\n        await user.click(filterButton);\n\n        // Select multiple event types\n        const registeredOption = await screen.findByText('REGISTERED');\n        await user.click(registeredOption);\n\n        const deregisteredOption = await screen.findByText('DEREGISTERED');\n        await user.click(deregisteredOption);\n\n        const statusChangedOption = await screen.findByText('STATUS_CHANGED');\n        await user.click(statusChangedOption);\n\n        // Close the filter\n        await user.click(filterButton);\n\n        // Get rows after filtering\n        const rows = screen.getAllByRole('row');\n        const dataRows = rows.slice(1);\n\n        // Extract event types\n        const eventTypes = dataRows\n          .map((row) => {\n            const cells = within(row).queryAllByRole('cell');\n            return cells[4]?.textContent?.trim();\n          })\n          .filter(Boolean);\n\n        // Verify only selected event types are shown\n        expect(dataRows).toHaveLength(4);\n        expect(eventTypes).toContain('REGISTERED');\n        expect(eventTypes).toContain('DEREGISTERED');\n        expect(eventTypes).toContain('STATUS_CHANGED');\n        expect(eventTypes).not.toContain('INFO_CHANGED');\n        expect(eventTypes).not.toContain('REGISTRATION_UPDATED');\n        expect(eventTypes).not.toContain('ENDPOINTS_DETECTED');\n      }\n    });\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/journal/JournalTable.vue",
    "content": "<!--\n  - Copyright 2014-2025 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <DataTable\n    v-model:filters=\"filters\"\n    v-model:expanded-rows=\"expandedRows\"\n    striped-rows\n    data-key=\"key\"\n    :value=\"mappedEvents\"\n    paginator\n    sort-field=\"date\"\n    :sort-order=\"-1\"\n    :rows=\"50\"\n    :rows-per-page-options=\"[5, 10, 20, 50, 100, 250, 500]\"\n    filter-display=\"menu\"\n    :global-filter-fields=\"['application', 'instance', 'type']\"\n  >\n    <template #header>\n      <div class=\"flex justify-between\">\n        <Button\n          type=\"button\"\n          icon=\"pi pi-filter-slash\"\n          :label=\"t('term.filter_action.reset')\"\n          variant=\"outlined\"\n          @click=\"clearFilter()\"\n        />\n\n        <IconField>\n          <InputIcon>\n            <font-awesome-icon :icon=\"faSearch\" />\n          </InputIcon>\n          <InputText\n            v-model=\"filters.global.value\"\n            :placeholder=\"t('term.keyword_search')\"\n          />\n        </IconField>\n      </div>\n    </template>\n\n    <Column expander style=\"width: 2rem\" />\n\n    <Column\n      :header=\"$t('term.application')\"\n      :sortable=\"true\"\n      field=\"application\"\n      :show-filter-match-modes=\"false\"\n      filter-field=\"application\"\n      :filter-menu-style=\"{\n        minWidth: '16rem',\n      }\"\n    >\n      <template #body=\"{ data }\">\n        {{ data.application }}\n      </template>\n      <template #filter=\"{ filterModel }\">\n        <MultiSelect\n          v-model=\"filterModel.value\"\n          :options=\"applicationNames\"\n          :placeholder=\"t('journal.filter.application.any')\"\n          :show-toggle-all=\"false\"\n          :max-selected-labels=\"1\"\n        >\n          <template #option=\"slotProps\">\n            <div class=\"flex items-center gap-2\">\n              <span>{{ slotProps.option }}</span>\n            </div>\n          </template>\n        </MultiSelect>\n      </template>\n    </Column>\n\n    <Column\n      :header=\"$t('term.instance')\"\n      :sortable=\"true\"\n      field=\"instance\"\n      :show-filter-match-modes=\"false\"\n      filter-field=\"instance\"\n      :filter-menu-style=\"{\n        minWidth: '16rem',\n      }\"\n      class=\"w-48\"\n    >\n      <template #body=\"{ data }\">\n        <router-link\n          :to=\"{\n            name: 'instances/details',\n            params: { instanceId: data.instance },\n          }\"\n        >\n          {{ data.instance }}\n        </router-link>\n      </template>\n      <template #filter=\"{ filterModel }\">\n        <MultiSelect\n          v-model=\"filterModel.value\"\n          :options=\"instanceIds\"\n          :placeholder=\"t('journal.filter.instance_id.any')\"\n          :show-toggle-all=\"false\"\n          :max-selected-labels=\"1\"\n        >\n          <template #option=\"slotProps\">\n            <div class=\"flex items-center gap-2\">\n              <span>{{ slotProps.option }}</span>\n            </div>\n          </template>\n        </MultiSelect>\n      </template>\n    </Column>\n\n    <Column\n      :header=\"$t('term.time')\"\n      :sortable=\"true\"\n      field=\"date\"\n      filter-field=\"date\"\n      data-type=\"date\"\n      :filter-menu-style=\"{\n        minWidth: '16rem',\n      }\"\n      class=\"w-72\"\n    >\n      <template #body=\"{ data }\">\n        {{ formatDateTime(data.date) }}\n      </template>\n      <template #filter=\"{ filterModel }\">\n        <DatePicker v-model=\"filterModel.value\" :manual-input=\"false\" />\n      </template>\n    </Column>\n\n    <Column\n      :header=\"$t('term.event')\"\n      :sortable=\"true\"\n      field=\"type\"\n      :show-filter-match-modes=\"false\"\n      filter-field=\"type\"\n      :filter-menu-style=\"{\n        minWidth: '16rem',\n      }\"\n      class=\"w-1/4\"\n    >\n      <template #body=\"{ data }\">\n        {{ data.type }}\n      </template>\n      <template #filter=\"{ filterModel }\">\n        <MultiSelect\n          v-model=\"filterModel.value\"\n          :options=\"eventTypes\"\n          :placeholder=\"t('journal.filter.event_type.any')\"\n          :show-toggle-all=\"false\"\n          :max-selected-labels=\"1\"\n        >\n          <template #option=\"slotProps\">\n            <div class=\"flex items-center gap-2\">\n              <span>{{ slotProps.option }}</span>\n            </div>\n          </template>\n        </MultiSelect>\n      </template>\n    </Column>\n    <template #expansion=\"slotProps\">\n      <pre class=\"whitespace-pre-wrap text-sm\">{{\n        JSON.stringify(slotProps, null, 2)\n      }}</pre>\n    </template>\n  </DataTable>\n</template>\n\n<script setup lang=\"ts\">\nimport { faSearch } from '@fortawesome/free-solid-svg-icons/faSearch';\nimport { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';\nimport { FilterMatchMode, FilterOperator } from '@primevue/core/api';\nimport {\n  Button,\n  Column,\n  DataTable,\n  DatePicker,\n  IconField,\n  InputIcon,\n  InputText,\n  MultiSelect,\n} from 'primevue';\nimport { computed, onMounted, ref, watch } from 'vue';\nimport { useI18n } from 'vue-i18n';\nimport { useRoute, useRouter } from 'vue-router';\n\nimport { useDateTimeFormatter } from '@/composables/useDateTimeFormatter';\nimport Application from '@/services/application';\nimport { pushFlattened } from '@/utils/array';\nimport { InstanceEvent } from '@/views/journal/InstanceEvent';\nimport {\n  getApplicationNames,\n  getApplicationNamesByInstanceId,\n} from '@/views/journal/utils';\n\nconst route = useRoute();\nconst router = useRouter();\n\nconst { events = [] } = defineProps<{\n  events?: Array<InstanceEvent>;\n  applications: Array<Application>;\n}>();\n\nconst { formatDateTime } = useDateTimeFormatter();\nconst { t } = useI18n();\nconst expandedRows = ref([]);\n\nconst applicationNames = computed(() => {\n  return getApplicationNames(events);\n});\nconst applicationNamesByInstanceId = computed(() => {\n  return getApplicationNamesByInstanceId(events);\n});\nconst mappedEvents = computed(() => {\n  return events.map((e) => ({\n    ...e,\n    key: e.key,\n    date: new Date(e.timestamp),\n    application: applicationNamesByInstanceId.value[e.instance],\n  }));\n});\n\nconst eventTypes = computed(() => {\n  return [...new Set(mappedEvents.value.map((e) => e.type))].sort((a, b) =>\n    a.localeCompare(b),\n  );\n});\n\nconst instanceIds = computed(() => {\n  return [...new Set(mappedEvents.value.map((e) => e.instance))].sort((a, b) =>\n    a.localeCompare(b),\n  );\n});\n\nconst filters = ref();\nconst initFilters = () => {\n  filters.value = {\n    global: { value: null, matchMode: FilterMatchMode.CONTAINS },\n    application: {\n      value: null,\n      matchMode: FilterMatchMode.IN,\n    },\n    instance: {\n      value: null,\n      matchMode: FilterMatchMode.IN,\n    },\n    date: {\n      operator: FilterOperator.AND,\n      constraints: [{ value: null, matchMode: FilterMatchMode.DATE_IS }],\n    },\n    type: {\n      value: null,\n      matchMode: FilterMatchMode.IN,\n    },\n  };\n};\ninitFilters();\n\nconst clearFilter = () => {\n  initFilters();\n};\n\nwatch(\n  filters,\n  () => {\n    const query: Record<string, string | string[]> = {};\n    if (filters.value.global.value) {\n      query.q = filters.value.global.value;\n    }\n    if (filters.value.application?.value?.length) {\n      query.application = filters.value.application.value;\n    }\n    if (filters.value.instance?.value?.length) {\n      query.instanceId = filters.value.instance.value;\n    }\n    if (filters.value.type?.value?.length) {\n      query.type = filters.value.type.value;\n    }\n    router.push({ query });\n  },\n  { deep: true },\n);\n\nonMounted(() => {\n  filters.value.global.value = route.query.q || null;\n  filters.value.application.value = pushFlattened([], route.query.application);\n  filters.value.instance.value = pushFlattened([], route.query.instanceId);\n  filters.value.type.value = pushFlattened([], route.query.type);\n});\n</script>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/journal/deduplicate-events.spec.ts",
    "content": "import { describe, expect, it } from 'vitest';\n\nimport { deduplicateInstanceEvents } from './deduplicate-events';\n\nimport {\n  InstanceEvent,\n  InstanceEventType,\n} from '@/views/journal/InstanceEvent';\n\nconst createEvent = (\n  instance: string,\n  version: number,\n  type = InstanceEventType.REGISTERED,\n) =>\n  new InstanceEvent({\n    instance,\n    version,\n    type,\n    timestamp: '2024-01-01T10:00:00Z',\n    registration: { name: instance },\n  });\n\ndescribe('deduplicateInstanceEvents', () => {\n  it('removes events with identical instance, type and version', () => {\n    const events = [\n      createEvent('instance-1', 1),\n      createEvent('instance-1', 1, InstanceEventType.DEREGISTERED),\n      createEvent('instance-2', 3),\n      createEvent('instance-2', 3),\n      createEvent('instance-3', 2),\n      createEvent('instance-3', 2, InstanceEventType.INFO_CHANGED),\n      createEvent('instance-3', 2, InstanceEventType.INFO_CHANGED),\n    ];\n\n    const result = deduplicateInstanceEvents(events);\n\n    expect(result).toHaveLength(5);\n    expect(result.map((event) => event.key)).toEqual([\n      'instance-1-1-REGISTERED-1704103200000',\n      'instance-1-1-DEREGISTERED-1704103200000',\n      'instance-2-3-REGISTERED-1704103200000',\n      'instance-3-2-REGISTERED-1704103200000',\n      'instance-3-2-INFO_CHANGED-1704103200000',\n    ]);\n  });\n\n  it('preserves the order of the first occurrences', () => {\n    const events = [\n      createEvent('instance-1', 2),\n      createEvent('instance-2', 1),\n      createEvent('instance-1', 2),\n      createEvent('instance-3', 4),\n    ];\n\n    const result = deduplicateInstanceEvents(events);\n\n    expect(result.map((event) => event.key)).toEqual([\n      'instance-1-2-REGISTERED-1704103200000',\n      'instance-2-1-REGISTERED-1704103200000',\n      'instance-3-4-REGISTERED-1704103200000',\n    ]);\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/journal/deduplicate-events.ts",
    "content": "import { InstanceEvent } from '@/views/journal/InstanceEvent';\n\n/**\n * Removes duplicate instance events from an array based on their key property.\n *\n * This function filters an array of InstanceEvent objects, keeping only the first occurrence\n * of each unique event key. Subsequent events with the same key are filtered out.\n *\n * @param events - Array of InstanceEvent objects to deduplicate\n * @returns A new array containing only unique events (by key), preserving the order of first occurrence\n *\n * @example\n * const events = [\n *   { key: 'event1', ... },\n *   { key: 'event2', ... },\n *   { key: 'event1', ... } // duplicate\n * ];\n * const unique = deduplicateInstanceEvents(events);\n * // Returns first two events only\n */\nexport function deduplicateInstanceEvents(events: InstanceEvent[]) {\n  const seen = new Set<string>();\n  return events.filter((event) => {\n    if (seen.has(event.key)) {\n      return false;\n    }\n    seen.add(event.key);\n    return true;\n  });\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/journal/i18n.de.json",
    "content": "{\n  \"journal\": {\n    \"title\": \"Ereignis-Journal\",\n    \"label\": \"Journal\",\n    \"auto_update\": \"Ereignisse automatisch aktualisieren\",\n    \"error\": {\n      \"generic\": \"Fehler beim Abrufen neuer Ereignisse vom Server\"\n    },\n    \"filter\": {\n      \"application\": {\n        \"any\": \"Alle Anwendungen\"\n      },\n      \"event_type\": {\n        \"any\": \"Jeder Typ\"\n      },\n      \"instance_id\": {\n        \"any\": \"Alle Instanzen\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/journal/i18n.en.json",
    "content": "{\n  \"journal\": {\n    \"title\": \"Event Journal\",\n    \"label\": \"Journal\",\n    \"auto_update\": \"Auto-update events\",\n    \"error\": {\n      \"generic\": \"Error retrieving new events from server\"\n    },\n    \"filter\": {\n      \"application\": {\n        \"any\": \"Any\"\n      },\n      \"event_type\": {\n        \"any\": \"Any\"\n      },\n      \"instance_id\": {\n        \"any\": \"Any\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/journal/i18n.es.json",
    "content": "{\n  \"journal\": {\n    \"title\": \"Bitácora de eventos\",\n    \"label\": \"Eventos\",\n    \"auto_update\": \"Actualizar eventos automáticamente\",\n    \"error\": {\n      \"generic\": \"Error al recuperar nuevos eventos del servidor\"\n    },\n    \"filter\": {\n      \"application\": {\n        \"any\": \"Cualquiera\"\n      },\n      \"event_type\": {\n        \"any\": \"Cualquiera\"\n      },\n      \"instance_id\": {\n        \"any\": \"Cualquiera\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/journal/i18n.fr.json",
    "content": "{\n  \"journal\": {\n    \"title\": \"Journal d'évènements\",\n    \"label\": \"Journal\",\n    \"auto_update\": \"Mise à jour automatique des événements\",\n    \"error\": {\n      \"generic\": \"Erreur lors de la récupération de nouveaux événements du serveur\"\n    },\n    \"filter\": {\n      \"application\": {\n        \"any\": \"Tous\"\n      },\n      \"event_type\": {\n        \"any\": \"Tous\"\n      },\n      \"instance_id\": {\n        \"any\": \"Tous\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/journal/i18n.is.json",
    "content": "{\n  \"journal\": {\n    \"title\": \"Atburðaannáll\",\n    \"label\": \"Annáll\",\n    \"auto_update\": \"Sjálfvirk uppfærsla atburða\",\n    \"error\": {\n      \"generic\": \"Villa við að sækja nýja atburði frá þjóni\"\n    },\n    \"filter\": {\n      \"application\": {\n        \"any\": \"Allt\"\n      },\n      \"event_type\": {\n        \"any\": \"Allt\"\n      },\n      \"instance_id\": {\n        \"any\": \"Allt\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/journal/i18n.ko.json",
    "content": "{\n  \"journal\": {\n    \"title\": \"이벤트 일지\",\n    \"label\": \"일지\",\n    \"auto_update\": \"이벤트 자동 업데이트\",\n    \"error\": {\n      \"generic\": \"서버에서 새 이벤트를 가져오는 중 오류가 발생했습니다\"\n    },\n    \"filter\": {\n      \"application\": {\n        \"any\": \"전체\"\n      },\n      \"event_type\": {\n        \"any\": \"전체\"\n      },\n      \"instance_id\": {\n        \"any\": \"전체\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/journal/i18n.pt-BR.json",
    "content": "{\n  \"journal\": {\n    \"title\": \"Registro de Eventos\",\n    \"label\": \"Registro\",\n    \"auto_update\": \"Atualizar eventos automaticamente\",\n    \"error\": {\n      \"generic\": \"Erro ao recuperar novos eventos do servidor\"\n    },\n    \"filter\": {\n      \"application\": {\n        \"any\": \"Qualquer\"\n      },\n      \"event_type\": {\n        \"any\": \"Qualquer\"\n      },\n      \"instance_id\": {\n        \"any\": \"Qualquer\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/journal/i18n.ru.json",
    "content": "{\n  \"journal\": {\n    \"title\": \"Журнал событий\",\n    \"label\": \"Журнал\",\n    \"auto_update\": \"Автоматическое обновление событий\",\n    \"error\": {\n      \"generic\": \"Ошибка при получении новых событий с сервера\"\n    },\n    \"filter\": {\n      \"application\": {\n        \"any\": \"Любое\"\n      },\n      \"event_type\": {\n        \"any\": \"Любой\"\n      },\n      \"instance_id\": {\n        \"any\": \"Любой\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/journal/i18n.zh-CN.json",
    "content": "{\n  \"journal\": {\n    \"title\": \"事件日志\",\n    \"label\": \"日志报表\",\n    \"auto_update\": \"自动更新事件\",\n    \"error\": {\n      \"generic\": \"从服务器检索新事件时出错\"\n    },\n    \"filter\": {\n      \"application\": {\n        \"any\": \"任意\"\n      },\n      \"event_type\": {\n        \"any\": \"任意\"\n      },\n      \"instance_id\": {\n        \"any\": \"任意\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/journal/i18n.zh-TW.json",
    "content": "{\n  \"journal\": {\n    \"title\": \"事件日誌\",\n    \"label\": \"日誌\",\n    \"auto_update\": \"自動更新事件\",\n    \"error\": {\n      \"generic\": \"從伺服器檢索新事件時發生錯誤\"\n    },\n    \"filter\": {\n      \"application\": {\n        \"any\": \"全部\"\n      },\n      \"event_type\": {\n        \"any\": \"全部\"\n      },\n      \"instance_id\": {\n        \"any\": \"全部\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/journal/index.spec.ts",
    "content": "/*!\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { screen } from '@testing-library/vue';\nimport { HttpResponse, http } from 'msw';\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\nimport { ref } from 'vue';\n\nimport JournalView from './index.vue';\n\nimport { useApplicationStore } from '@/composables/useApplicationStore';\nimport { server } from '@/mocks/server';\nimport Application from '@/services/application';\nimport { render } from '@/test-utils';\nimport {\n  InstanceEvent,\n  InstanceEventType,\n} from '@/views/journal/InstanceEvent';\n\nvi.mock('@/composables/useApplicationStore', () => ({\n  useApplicationStore: vi.fn(),\n}));\n\ndescribe('Journal View', () => {\n  beforeEach(() => {\n    // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n    // @ts-ignore\n    useApplicationStore.mockReturnValue({\n      applicationsInitialized: ref(true),\n      applications: ref([\n        new Application({\n          id: 'app-1',\n          name: 'App 1',\n          instances: [{ id: 'instance-1' }],\n        }),\n        new Application({\n          id: 'app-2',\n          name: 'App 2',\n          instances: [{ id: 'instance-2' }],\n        }),\n      ]),\n      error: ref(null),\n    });\n  });\n\n  it('should update instance names when registration is updated', async () => {\n    server.use(\n      http.get('**/instances/events', () => {\n        return HttpResponse.json([\n          new InstanceEvent({\n            instance: 'instance-1',\n            version: 1,\n            type: InstanceEventType.REGISTERED,\n            timestamp: new Date('2023-01-01T10:00:00Z'),\n            registration: { name: 'OLD APP NAME' },\n          }),\n          new InstanceEvent({\n            instance: 'instance-1',\n            version: 2,\n            type: InstanceEventType.REGISTRATION_UPDATED,\n            timestamp: new Date('2023-01-01T11:00:00Z'),\n            registration: { name: 'NEW APP NAME' },\n          }),\n        ]);\n      }),\n    );\n    render(JournalView);\n\n    const allByNewText = await screen.findAllByText('NEW APP NAME');\n    expect(allByNewText).toBeDefined();\n  });\n\n  it('should handle both REGISTERED and REGISTRATION_UPDATED events', async () => {\n    server.use(\n      http.get('**/instances/events', () => {\n        return HttpResponse.json([\n          new InstanceEvent({\n            instance: 'instance-1',\n            version: 1,\n            type: InstanceEventType.REGISTERED,\n            timestamp: new Date('2023-01-01T10:00:00Z'),\n            registration: { name: 'App One' },\n          }),\n          new InstanceEvent({\n            instance: 'instance-2',\n            version: 1,\n            type: InstanceEventType.REGISTERED,\n            timestamp: new Date('2023-01-01T10:05:00Z'),\n            registration: { name: 'App Two' },\n          }),\n          new InstanceEvent({\n            instance: 'instance-2',\n            version: 2,\n            type: InstanceEventType.REGISTRATION_UPDATED,\n            timestamp: new Date('2023-01-01T11:00:00Z'),\n            registration: { name: 'App Two Updated' },\n          }),\n        ]);\n      }),\n    );\n    render(JournalView);\n\n    const appOneText = await screen.findAllByText('App One');\n    expect(appOneText).toBeDefined();\n    const appTwoText = await screen.findAllByText('App Two Updated');\n    expect(appTwoText).toBeDefined();\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/journal/index.vue",
    "content": "<!--\n  - Copyright 2014-2019 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div class=\"px-12 pt-10 pb-6\">\n    <div class=\"flex mb-6\">\n      <div class=\"flex-1\">\n        <h1 class=\"title\" v-text=\"$t('journal.title')\" />\n        <h2\n          v-if=\"filter.application\"\n          class=\"subtitle\"\n          v-text=\"filter.application\"\n        />\n        <h1\n          v-else-if=\"filter.instanceId\"\n          class=\"subtitle\"\n          v-text=\"`${getName(filter.instanceId)} (${filter.instanceId})`\"\n        />\n      </div>\n      <div class=\"flex items-center gap-2\">\n        <span class=\"hidden md:inline\" v-text=\"$t('journal.auto_update')\" />\n        <sba-button\n          :title=\"$t('journal.auto_update')\"\n          :primary=\"autoUpdate\"\n          @click=\"autoUpdate = !autoUpdate\"\n        >\n          <font-awesome-icon :icon=\"faArrowsDownToLine\" />\n        </sba-button>\n      </div>\n    </div>\n\n    <sba-alert :error=\"error\" />\n\n    <JournalTable :events=\"listedEvents\" :applications=\"applications\" />\n  </div>\n</template>\n\n<script>\nimport { faArrowsDownToLine } from '@fortawesome/free-solid-svg-icons';\nimport { useI18n } from 'vue-i18n';\n\nimport SbaAlert from '@/components/sba-alert';\n\nimport { useApplicationStore } from '@/composables/useApplicationStore';\nimport { useDateTimeFormatter } from '@/composables/useDateTimeFormatter';\nimport subscribing from '@/mixins/subscribing';\nimport Instance from '@/services/instance';\nimport { compareBy } from '@/utils/collections';\nimport {\n  InstanceEvent,\n  InstanceEventType,\n} from '@/views/journal/InstanceEvent';\nimport JournalTable from '@/views/journal/JournalTable.vue';\nimport { deduplicateInstanceEvents } from '@/views/journal/deduplicate-events';\n\nexport default {\n  components: { JournalTable, SbaAlert },\n  mixins: [subscribing],\n  setup() {\n    const { formatDateTime } = useDateTimeFormatter();\n    const { applications } = useApplicationStore();\n    const i18n = useI18n();\n\n    return {\n      t: i18n.t,\n      formatDate: formatDateTime,\n      faArrowsDownToLine,\n      applications,\n    };\n  },\n  data: () => ({\n    Event,\n    events: [],\n    seenEventKeys: new Set(),\n    listOffset: 0,\n    showPayload: {},\n    pageSize: 25,\n    current: 1,\n    error: null,\n    autoUpdate: true,\n    filter: {\n      application: undefined,\n      instanceId: undefined,\n    },\n  }),\n  computed: {\n    listedEvents() {\n      return this.events.slice(this.listOffset);\n    },\n    instanceNames() {\n      return this.events\n        .filter(\n          (event) =>\n            event.type === InstanceEventType.REGISTERED ||\n            event.type === InstanceEventType.REGISTRATION_UPDATED,\n        )\n        .sort((a, b) => b.timestamp - a.timestamp)\n        .reduceRight((names, event) => {\n          names[event.instance] = event.payload.registration.name;\n          return names;\n        }, {});\n    },\n  },\n  watch: {\n    autoUpdate: function (value) {\n      if (value) {\n        this.listOffset = 0;\n      }\n    },\n  },\n  async created() {\n    try {\n      const response = await Instance.fetchEvents();\n      const events = response.data\n        .map((e, idx) => ({\n          ...e,\n          version: idx,\n        }))\n        .sort(compareBy((v) => v.timestamp))\n        .reverse()\n        .map((e) => new InstanceEvent(e));\n\n      const deduplicated = deduplicateInstanceEvents(events);\n      this.seenEventKeys = new Set(deduplicated.map((event) => event.key));\n      this.events = Object.freeze(deduplicated);\n      this.listOffset = events.length - deduplicated.length;\n      this.error = null;\n    } catch (error) {\n      console.warn('Fetching events failed:', error);\n      this.error = this.t('journal.error.generic');\n    }\n  },\n  methods: {\n    getName(instanceId) {\n      return this.instanceNames[instanceId] || '?';\n    },\n    createSubscription() {\n      return Instance.getEventStream().subscribe({\n        next: (message) => {\n          this.error = null;\n          const incomingEvent = new InstanceEvent(message.data);\n          if (this.seenEventKeys.has(incomingEvent.key)) {\n            return;\n          }\n          this.seenEventKeys.add(incomingEvent.key);\n          this.events = Object.freeze([incomingEvent, ...this.events]);\n          if (!this.autoUpdate) {\n            this.listOffset += 1;\n          }\n        },\n        error: (error) => {\n          console.warn('Listening for events failed:', error);\n          this.error = this.t('journal.error.generic');\n        },\n      });\n    },\n  },\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      path: '/journal',\n      name: 'journal',\n      label: 'journal.label',\n      order: 100,\n      component: this,\n    });\n  },\n};\n</script>\n\n<style scoped>\n.label {\n  white-space: nowrap;\n}\n\n.title {\n  @apply text-3xl;\n}\n\n.subtitle {\n  @apply text-xl;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/journal/utils.ts",
    "content": "/*!\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n  IInstanceEvent,\n  InstanceEventType,\n} from '@/views/journal/InstanceEvent';\n\n/**\n * Extracts application names from instance events.\n *\n * @param events\n */\nexport const getApplicationNamesByInstanceId = (events: IInstanceEvent[]) => {\n  return events\n    .filter(\n      (event) =>\n        event.type === InstanceEventType.REGISTERED ||\n        event.type === InstanceEventType.REGISTRATION_UPDATED,\n    )\n    .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())\n    .reduceRight((names, event) => {\n      names[event.instance] = event.registration?.name;\n      return names;\n    }, {});\n};\n\nexport const getApplicationNames = (events: IInstanceEvent[]): string[] => {\n  const applicationNames = events\n    .filter(\n      (event) =>\n        event.type === InstanceEventType.REGISTERED ||\n        event.type === InstanceEventType.REGISTRATION_UPDATED,\n    )\n    .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())\n    .map((event) => event.registration?.name)\n    .sort((a, b) => a.localeCompare(b));\n\n  return [...new Set(applicationNames)];\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/wallboard/hex-mesh.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <div ref=\"root\" v-on-resize=\"onResize\" class=\"hex-mesh\">\n    <svg\n      :height=\"meshHeight\"\n      :width=\"meshWidth\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <defs>\n        <clipPath id=\"hex-clip\">\n          <path :d=\"hexPath\" />\n        </clipPath>\n      </defs>\n      <template v-for=\"row in rows\">\n        <g\n          v-for=\"col in cols + (row % 2 ? 0 : -1)\"\n          :key=\"`${col}-${row}`\"\n          :class=\"classForItem(item(col, row))\"\n          :transform=\"translate(col, row)\"\n          class=\"hex\"\n          @click=\"click($event, col, row)\"\n        >\n          <path :d=\"hexPath\" />\n          <foreignObject\n            v-if=\"item(col, row)\"\n            :height=\"hexHeight\"\n            :width=\"hexWidth\"\n            style=\"pointer-events: none\"\n            x=\"0\"\n            y=\"0\"\n          >\n            <slot :item=\"item(col, row)\" name=\"item\" />\n          </foreignObject>\n        </g>\n      </template>\n    </svg>\n  </div>\n</template>\n\n<script>\nimport { ref } from 'vue';\n\nimport onResize from '@/directives/on-resize';\nimport { calcLayout } from '@/views/wallboard/utils';\n\nexport default {\n  directives: { onResize },\n  props: {\n    items: {\n      type: Array,\n      default: () => [],\n    },\n    classForItem: {\n      type: Function,\n      default: () => void 0,\n    },\n  },\n  emits: ['click'],\n  setup() {\n    const root = ref(null);\n\n    return {\n      root,\n    };\n  },\n  data: () => ({\n    cols: 1,\n    rows: 1,\n    sideLength: 1,\n  }),\n  computed: {\n    itemCount() {\n      return this.items.length;\n    },\n    hexPath() {\n      const points = [\n        this.point(0),\n        this.point(1),\n        this.point(2),\n        this.point(3),\n        this.point(4),\n        this.point(5),\n      ];\n\n      // Radius for the rounded corners\n      const cornerRadius = this.sideLength * 0.05;\n\n      // Parse points into coordinate pairs\n      const coords = points.map((p) => {\n        const [x, y] = p.split(',').map(Number);\n        return { x, y };\n      });\n\n      // Helper function to calculate distance between two points\n      const distance = (p1, p2) =>\n        Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));\n\n      // Helper function to move along a line from p1 towards p2 by a given distance\n      const moveAlong = (p1, p2, dist) => {\n        const d = distance(p1, p2);\n        const ratio = dist / d;\n        return {\n          x: p1.x + (p2.x - p1.x) * ratio,\n          y: p1.y + (p2.y - p1.y) * ratio,\n        };\n      };\n\n      // Build the path\n      let path = '';\n\n      for (let i = 0; i < coords.length; i++) {\n        const current = coords[i];\n        const prev = coords[(i - 1 + coords.length) % coords.length];\n        const next = coords[(i + 1) % coords.length];\n\n        // Point after current corner (moving from current towards next)\n        const nextEdgeLength = distance(current, next);\n        const afterCorner = moveAlong(\n          current,\n          next,\n          Math.min(cornerRadius, nextEdgeLength / 2),\n        );\n\n        // Point before current corner (moving from current towards prev)\n        const prevEdgeLength = distance(current, prev);\n        const beforeCorner = moveAlong(\n          current,\n          prev,\n          Math.min(cornerRadius, prevEdgeLength / 2),\n        );\n\n        if (i === 0) {\n          // Start at the point after the first corner\n          path += `M ${afterCorner.x},${afterCorner.y} `;\n        } else {\n          // Draw line to the point before this corner\n          path += `L ${beforeCorner.x},${beforeCorner.y} `;\n          // Draw quadratic bezier curve around this corner using the corner as control point\n          path += `Q ${current.x},${current.y} ${afterCorner.x},${afterCorner.y} `;\n        }\n      }\n\n      // Close the path (draws line back to start and bezier around first corner)\n      const firstCorner = coords[0];\n      const lastCorner = coords[coords.length - 1];\n      const firstEdgeLength = distance(firstCorner, lastCorner);\n      const beforeFirstCorner = moveAlong(\n        firstCorner,\n        lastCorner,\n        Math.min(cornerRadius, firstEdgeLength / 2),\n      );\n      path += `L ${beforeFirstCorner.x},${beforeFirstCorner.y} `;\n\n      const afterFirstCorner = moveAlong(\n        firstCorner,\n        coords[1],\n        Math.min(cornerRadius, distance(firstCorner, coords[1]) / 2),\n      );\n      path += `Q ${firstCorner.x},${firstCorner.y} ${afterFirstCorner.x},${afterFirstCorner.y} `;\n\n      path += 'Z';\n      return path;\n    },\n    hexHeight() {\n      return this.sideLength * 2;\n    },\n    hexWidth() {\n      return this.sideLength * Math.sqrt(3);\n    },\n    meshWidth() {\n      return this.hexWidth * this.cols;\n    },\n    meshHeight() {\n      return this.sideLength * (2 + (this.rows - 1) * 1.5);\n    },\n  },\n  watch: {\n    sideLength(newVal) {\n      this.root.style['font-size'] = `${newVal / 9.5}px`;\n    },\n    itemCount: {\n      handler: 'updateLayout',\n      immediate: true,\n    },\n  },\n  methods: {\n    translate(col, row) {\n      const x = (col - 1) * this.hexWidth + (row % 2 ? 0 : this.hexWidth / 2);\n      const y = (row - 1) * this.sideLength * 1.5;\n      return `translate(${x},${y})`;\n    },\n    item(col, row) {\n      const rowOffset =\n        (row - 1) * this.cols - Math.max(Math.floor((row - 1) / 2), 0);\n      const index = rowOffset + col - 1;\n      return this.items[index];\n    },\n    point(i) {\n      const innerSideLength = this.sideLength * 0.95;\n      const marginTop = this.hexHeight / 2;\n      const marginLeft = this.hexWidth / 2;\n      const x =\n        marginLeft + innerSideLength * Math.cos(((1 + i * 2) * Math.PI) / 6);\n      const y =\n        marginTop + innerSideLength * Math.sin(((1 + i * 2) * Math.PI) / 6);\n      return `${x},${y}`;\n    },\n    click(event, col, row) {\n      const item = this.item(col, row);\n      if (item) {\n        this.$emit('click', item, event);\n      }\n    },\n    updateLayout() {\n      if (this.root) {\n        const boundingClientRect = this.root.getBoundingClientRect();\n        const layout = calcLayout(\n          this.itemCount,\n          boundingClientRect.width,\n          boundingClientRect.height,\n        );\n        this.cols = layout.cols;\n        this.rows = layout.rows;\n        this.sideLength = layout.sideLength;\n      }\n    },\n    onResize(entries) {\n      for (let e of entries) {\n        if (e.target === this.root) {\n          this.updateLayout();\n        }\n      }\n    },\n  },\n};\n</script>\n\n<style>\n.hex-mesh {\n  background-color: #4a4a4a;\n  width: 100%;\n  height: 100%;\n  display: flex;\n  justify-content: space-around;\n  align-items: center;\n}\n\n.hex {\n  cursor: pointer;\n  fill: transparent;\n  fill-opacity: 0.05;\n  stroke-width: 0.5;\n  stroke-opacity: 0.8;\n\n  &:has(foreignObject) {\n    fill: black;\n    stroke: black;\n  }\n}\n\n.hex:hover path {\n  fill-opacity: 0.25;\n  stroke-opacity: 1;\n  stroke-width: 2;\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/wallboard/i18n.de.json",
    "content": "{\n  \"wallboard\": {\n    \"instances_count\": \"{count} Instanz | {count} Instanzen\",\n    \"label\": \"Wallboard\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/wallboard/i18n.en.json",
    "content": "{\n  \"wallboard\": {\n    \"instances_count\": \"no instances | {count} instance | {count} instances\",\n    \"label\": \"Wallboard\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/wallboard/i18n.es.json",
    "content": "{\n  \"wallboard\": {\n    \"instances_count\": \"no hay instancias | {count} instancia | {count} instancias\",\n    \"label\": \"Tablero\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/wallboard/i18n.fr.json",
    "content": "{\n  \"wallboard\": {\n    \"instances_count\": \"pas d'instances | {count} instance | {count} instances\",\n    \"label\": \"Tableau de bord\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/wallboard/i18n.is.json",
    "content": "{\n  \"wallboard\": {\n    \"instances_count\": \"{count} eintak | {count} eintök\",\n    \"label\": \"Wallboard\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/wallboard/i18n.ko.json",
    "content": "{\n  \"wallboard\": {\n    \"instances_count\": \"인스턴스 없음 | {count} 인스턴스 | {count} 인스턴스\",\n    \"label\": \"대시보드\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/wallboard/i18n.pt-BR.json",
    "content": "{\n  \"wallboard\": {\n    \"instances_count\": \"sem instâncias | {count} instância | {count} instâncias\",\n    \"label\": \"Wallboard\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/wallboard/i18n.ru.json",
    "content": "{\n  \"wallboard\": {\n    \"instances_count\": \"нет экземпляров | {count} экземпляр | {count} экземпляров\",\n    \"label\": \"Доска\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/wallboard/i18n.zh-CN.json",
    "content": "{\n  \"wallboard\": {\n    \"instances_count\": \"没有实例 | {count} 个实例 | {count} 个实例\",\n    \"label\": \"应用墙\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/wallboard/i18n.zh-TW.json",
    "content": "{\n  \"wallboard\": {\n    \"instances_count\": \"沒有執行個體 | {count} 個執行個體 | {count} 個執行個體\",\n    \"label\": \"資訊看板\"\n  }\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/wallboard/index.vue",
    "content": "<!--\n  - Copyright 2014-2018 the original author or authors.\n  -\n  - Licensed under the Apache License, Version 2.0 (the \"License\");\n  - you may not use this file except in compliance with the License.\n  - You may obtain a copy of the License at\n  -\n  -     http://www.apache.org/licenses/LICENSE-2.0\n  -\n  - Unless required by applicable law or agreed to in writing, software\n  - distributed under the License is distributed on an \"AS IS\" BASIS,\n  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  - See the License for the specific language governing permissions and\n  - limitations under the License.\n  -->\n\n<template>\n  <section class=\"wallboard section\">\n    <div\n      class=\"flex gap-2 justify-end absolute w-full md:w-[28rem] top-14 right-0 bg-black/20 py-3 px-4 rounded-bl\"\n    >\n      <sba-input\n        v-model=\"routerState.termFilter\"\n        class=\"flex-1\"\n        :placeholder=\"$t('term.filter')\"\n        name=\"filter\"\n        type=\"search\"\n      >\n        <template #prepend>\n          <font-awesome-icon icon=\"filter\" />\n        </template>\n      </sba-input>\n\n      <select\n        v-if=\"healthStatus.size > 1\"\n        v-model=\"routerState.statusFilter\"\n        aria-label=\"status-filter\"\n        class=\"relative focus:z-10 focus:ring-indigo-500 focus:border-indigo-500 block sm:text-sm border-gray-300 rounded\"\n      >\n        <option selected value=\"none\" v-text=\"$t('term.all')\" />\n        <optgroup :label=\"t('health.label')\">\n          <option\n            v-for=\"status in healthStatus\"\n            :key=\"status\"\n            :value=\"status\"\n            v-text=\"t('health.status.' + status)\"\n          />\n        </optgroup>\n      </select>\n    </div>\n\n    <sba-alert\n      v-if=\"error\"\n      :error=\"error\"\n      :title=\"t('applications.server_connection_failed')\"\n      class=\"my-0 fixed w-full\"\n      severity=\"WARN\"\n    />\n\n    <sba-loading-spinner v-if=\"!applicationsInitialized\" />\n\n    <template v-if=\"applicationsInitialized\">\n      <div\n        v-if=\"routerState.termFilter.length > 0 && applications.length === 0\"\n        class=\"flex w-full h-full items-center text-center text-white text-xl\"\n        v-text=\"\n          t('term.no_results_for_term', {\n            term: routerState.termFilter,\n          })\n        \"\n      />\n      <hex-mesh\n        v-if=\"applicationsInitialized\"\n        :class-for-item=\"classForApplication\"\n        :items=\"applications\"\n        @click=\"select\"\n      >\n        <template #item=\"{ item: application }\">\n          <div :key=\"application.name\" class=\"hex__body application\">\n            <div class=\"application__status-indicator\" />\n            <div class=\"application__header application__time-ago is-muted\">\n              <sba-time-ago\n                :date=\"application.statusTimestamp\"\n                :precision=\"true\"\n              />\n            </div>\n            <div class=\"application__body\">\n              <h1 class=\"application__name\" v-text=\"application.name\" />\n              <p\n                class=\"application__instances is-muted\"\n                v-text=\"\n                  t('wallboard.instances_count', application.instances.length)\n                \"\n              />\n            </div>\n            <h2\n              class=\"application__footer application__version\"\n              v-text=\"application.buildVersion\"\n            />\n          </div>\n        </template>\n      </hex-mesh>\n    </template>\n  </section>\n</template>\n\n<script lang=\"ts\">\nimport classNames from 'classnames';\nimport Fuse from 'fuse.js';\nimport { computed } from 'vue';\nimport { useI18n } from 'vue-i18n';\n\nimport { HealthStatus } from '@/HealthStatus';\nimport { useApplicationStore } from '@/composables/useApplicationStore';\nimport Application from '@/services/application';\nimport { useRouterState } from '@/utils/useRouterState';\nimport hexMesh from '@/views/wallboard/hex-mesh.vue';\n\nexport default {\n  components: { hexMesh },\n  setup() {\n    const { t } = useI18n();\n\n    const routerState = useRouterState({\n      termFilter: '',\n      wordWrap: true,\n      statusFilter: 'none',\n    });\n\n    const { applications, applicationsInitialized, error } =\n      useApplicationStore();\n\n    const fuse = computed(\n      () =>\n        new Fuse<Application>(applications.value, {\n          includeScore: true,\n          useExtendedSearch: true,\n          threshold: 0.4,\n          keys: ['name', 'buildVersion', 'instances.name', 'instances.id'],\n        }),\n    );\n\n    const filteredApplications = computed(() => {\n      function filterByTerm(): Application[] {\n        if (routerState.termFilter.length > 0) {\n          return fuse.value.search(routerState.termFilter).map((sr) => sr.item);\n        } else {\n          return applications.value;\n        }\n      }\n\n      function filterByStatus(result: Application[]) {\n        if (routerState.statusFilter !== 'none') {\n          return result.filter(\n            (application: Application) =>\n              application.status === routerState.statusFilter,\n          );\n        }\n\n        return result;\n      }\n\n      let result = filterByTerm();\n      result = filterByStatus(result);\n\n      return result;\n    });\n\n    const healthStatus = computed(() => {\n      return new Set(\n        applications.value.map((application) => application.status),\n      );\n    });\n\n    return {\n      applications: filteredApplications,\n      applicationsInitialized,\n      error,\n      t,\n      healthStatus,\n      routerState,\n    };\n  },\n  methods: {\n    classNames,\n    classForApplication(application: Application) {\n      if (!application) {\n        return null;\n      }\n      if (application.status === HealthStatus.UP) {\n        return 'up';\n      }\n      if (application.status === HealthStatus.RESTRICTED) {\n        return 'restricted';\n      }\n      if (application.status === HealthStatus.DOWN) {\n        return 'down';\n      }\n      if (application.status === HealthStatus.OUT_OF_SERVICE) {\n        return 'down';\n      }\n      if (application.status === HealthStatus.OFFLINE) {\n        return 'down';\n      }\n      if (application.status === HealthStatus.UNKNOWN) {\n        return 'unknown';\n      }\n      return '';\n    },\n    select(application: Application) {\n      if (application.instances.length === 1) {\n        this.$router.push({\n          name: 'instances/details',\n          params: { instanceId: application.instances[0].id },\n        });\n      } else {\n        this.$router.push({\n          name: 'applications',\n          params: { selected: application.name },\n        });\n      }\n    },\n  },\n  install({ viewRegistry }) {\n    viewRegistry.addView({\n      path: '/wallboard',\n      name: 'wallboard',\n      label: 'wallboard.label',\n      order: -100,\n      component: this,\n    });\n  },\n};\n</script>\n\n<style lang=\"postcss\">\n.wallboard {\n  background-color: #4a4a4a;\n  height: calc(100vh - 52px);\n  width: 100%;\n}\n\n.wallboard .application {\n  color: #f5f5f5;\n  font-size: 1em;\n  font-weight: 400;\n  line-height: 1;\n  text-align: center;\n  overflow: hidden;\n  display: flex;\n  flex-direction: column;\n}\n\n.wallboard .application__name {\n  width: 100%;\n  padding: 2.5%;\n  color: #fff;\n  font-size: 2em;\n  font-weight: 600;\n  line-height: 1.125;\n}\n\n.wallboard .application__version {\n  color: #f5f5f5;\n  font-size: 1.25em;\n  line-height: 1.25;\n}\n\n.wallboard .application__header {\n  width: 90%;\n  margin-bottom: 0.5em;\n  font-style: italic;\n}\n\n.wallboard .application__footer {\n  width: 90%;\n  margin-top: 0.5em;\n}\n\n.up > path {\n  stroke: theme('colors.green.400');\n  fill: theme('colors.green.400');\n}\n\n.down > path,\n.offline > path {\n  stroke: theme('colors.red.400');\n  fill: theme('colors.red.400');\n  stroke-width: 2;\n}\n\n.hex .hex__body::after {\n  display: flex;\n  justify-content: center;\n  align-content: center;\n  font-size: 15em;\n  position: absolute;\n  z-index: -1;\n  width: 100%;\n}\n\n.hex .hex__body {\n  position: fixed;\n  z-index: 10;\n  width: 100%;\n  height: 100%;\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  align-items: center;\n}\n\n.hex.down .hex__body::after {\n  content: '!';\n  color: theme('colors.red.500');\n}\n\n.hex.unknown .hex__body::after {\n  content: '?';\n  color: theme('colors.gray.500');\n}\n\n.restricted > path {\n  stroke: theme('colors.yellow.500');\n  fill: theme('colors.yellow.500');\n}\n</style>\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/wallboard/utils.spec.ts",
    "content": "/*\n * Copyright 2014-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { describe, expect, it } from 'vitest';\n\nimport * as hm from './utils';\n\ndescribe('utils', () => {\n  it('should calculate optimum layout for 12 in 1594x879', () => {\n    const result = hm.calcLayout(12, 1594, 879);\n\n    expect(result).toEqual({\n      rows: 3,\n      cols: 5,\n      sideLength: 175.8,\n    });\n  });\n\n  it('should calculate optimum layout for 1 in 100x100', () => {\n    const result = hm.calcLayout(1, 100, 100);\n\n    expect(result).toEqual({\n      rows: 1,\n      cols: 1,\n      sideLength: 50,\n    });\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/wallboard/utils.ts",
    "content": "const tileCount = (cols, rows) => {\n  const shorterRows = Math.floor(rows / 2);\n  return rows * cols - shorterRows;\n};\n\nconst calcSideLength = (width, height, cols, rows) => {\n  const fitToWidth = width / cols / Math.sqrt(3);\n  const fitToHeight = (height * 2) / (3 * rows + 1);\n  return Math.min(fitToWidth, fitToHeight);\n};\n\nexport const calcLayout = (minTileCount, width, height) => {\n  let cols = 1,\n    rows = 1;\n  let sideLength = calcSideLength(width, height, cols, rows);\n\n  while (minTileCount > tileCount(cols, rows)) {\n    const sidelengthExtraCol = calcSideLength(width, height, cols + 1, rows);\n    const sidelengthExtraRow = calcSideLength(width, height, cols, rows + 1);\n    if (sidelengthExtraCol > sidelengthExtraRow) {\n      sideLength = sidelengthExtraCol;\n      cols++;\n    } else {\n      sideLength = sidelengthExtraRow;\n      rows++;\n    }\n  }\n  return {\n    cols,\n    rows,\n    sideLength,\n  };\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/wallboard/wallboard.spec.ts",
    "content": "import { screen, waitFor, within } from '@testing-library/vue';\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\nimport { Ref, ref } from 'vue';\n\nimport { useApplicationStore } from '@/composables/useApplicationStore';\nimport Application from '@/services/application';\nimport Instance from '@/services/instance';\nimport { render } from '@/test-utils';\nimport Wallboard from '@/views/wallboard/index.vue';\n\nvi.mock('@/composables/useApplicationStore', () => ({\n  useApplicationStore: vi.fn(),\n}));\n\ndescribe('Wallboard', () => {\n  let applicationsInitialized: Ref<boolean>;\n  let applications: Ref<Application[]>;\n  let error: Ref<any>;\n\n  beforeEach(async () => {\n    applicationsInitialized = ref(false);\n    applications = ref([]);\n    error = ref(null);\n\n    // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n    // @ts-ignore\n    useApplicationStore.mockReturnValue({\n      applicationsInitialized,\n      applications,\n      error,\n    });\n\n    render(Wallboard);\n  });\n\n  it('shows message when applications list is loaded', async () => {\n    screen.findByText('applications.loading_applications');\n  });\n\n  it('shows application', async () => {\n    applicationsInitialized.value = true;\n    applications.value = [\n      new Application({\n        name: 'Test Application',\n        statusTimestamp: Date.now(),\n        instances: [new Instance({ id: '4711' })],\n      }),\n    ];\n    await waitFor(() => {\n      expect(screen.getByText('Test Application')).toBeVisible();\n    });\n  });\n\n  it('shows error, when connection is lost', async () => {\n    applicationsInitialized.value = true;\n    applications.value = [];\n    error.value = new Error('Connection lost');\n    await waitFor(() => {\n      expect(screen.getByText('Server connection failed.')).toBeVisible();\n    });\n  });\n\n  it('does not show duplicated entries in status filter', async () => {\n    applications.value = [\n      new Application({\n        name: 'Test Application',\n        statusTimestamp: Date.now(),\n        instances: [new Instance({ id: '4711' })],\n        status: 'UP',\n      }),\n      new Application({\n        name: 'Test Application 2',\n        statusTimestamp: Date.now(),\n        instances: [new Instance({ id: '4712' })],\n        status: 'UP',\n      }),\n      new Application({\n        name: 'Test Application Down',\n        statusTimestamp: Date.now(),\n        instances: [new Instance({ id: '4713' })],\n        status: 'DOWN',\n      }),\n    ];\n\n    await waitFor(() => {\n      const dropdown = screen.queryByLabelText('status-filter');\n      expect(within(dropdown).getAllByRole('option')).toHaveLength(3);\n      expect(\n        within(dropdown).getByRole('option', { name: 'all' }),\n      ).toBeVisible();\n      expect(\n        within(dropdown).getByRole('option', { name: 'up' }),\n      ).toBeVisible(); // expecting only one!\n      expect(\n        within(dropdown).getByRole('option', { name: 'down' }),\n      ).toBeVisible();\n    });\n  });\n});\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/views/wallboard/wallboard.stories.ts",
    "content": "import { vueRouter } from 'storybook-vue3-router';\n\nimport Application from '../../services/application';\nimport Wallboard from './index.vue';\n\nimport { HealthStatus } from '@/HealthStatus';\nimport { useApplicationStore } from '@/composables/useApplicationStore';\nimport Instance from '@/services/instance';\n\nexport default {\n  component: Wallboard,\n  title: 'Views/Wallboard',\n};\n\nconst Template = (args) => ({\n  components: { Wallboard },\n  setup() {\n    const { applicationStore } = useApplicationStore();\n    applicationStore._dispatchEvent(\n      'changed',\n      Object.keys(HealthStatus).map((status) => {\n        return new Application({\n          status: status,\n          statusTimestamp: '2023-05-02',\n          name: `controller${status}`,\n          group: 'group-' + Math.floor(Math.random() * 10),\n          buildVersion: '1.2.3',\n          instances: [new Instance({ id: '123' })],\n        });\n      }),\n    );\n    return { args };\n  },\n  template: '<Wallboard />',\n});\n\nexport const Default = {\n  render: Template,\n  decorators: [\n    vueRouter(\n      [\n        {\n          name: 'wallboard',\n          path: '/',\n          component: Template,\n        },\n      ],\n      { initialRoute: '/' },\n    ),\n  ],\n};\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/frontend/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n/// <reference types=\"./global\" />\ndeclare module '*.vue' {\n  import { DefineComponent } from 'vue';\n  const component: DefineComponent<object, object, any>;\n  export default component;\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/java/de/codecentric/boot/admin/server/ui/config/AdminServerUiAutoConfiguration.java",
    "content": "/*\n * Copyright 2014-2024 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui.config;\n\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.boot.autoconfigure.AutoConfigureAfter;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.boot.webflux.autoconfigure.WebFluxProperties;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Conditional;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.http.MediaType;\nimport org.springframework.web.reactive.config.WebFluxConfigurer;\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\nimport org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver;\nimport org.thymeleaf.templatemode.TemplateMode;\n\nimport de.codecentric.boot.admin.server.config.AdminServerMarkerConfiguration;\nimport de.codecentric.boot.admin.server.config.AdminServerProperties;\nimport de.codecentric.boot.admin.server.config.AdminServerWebConfiguration;\nimport de.codecentric.boot.admin.server.config.SpringBootAdminServerEnabledCondition;\nimport de.codecentric.boot.admin.server.notify.filter.web.NotificationFilterController;\nimport de.codecentric.boot.admin.server.ui.extensions.UiExtensions;\nimport de.codecentric.boot.admin.server.ui.extensions.UiExtensionsScanner;\nimport de.codecentric.boot.admin.server.ui.extensions.UiRoutesScanner;\nimport de.codecentric.boot.admin.server.ui.web.HomepageForwardingFilterConfig;\nimport de.codecentric.boot.admin.server.ui.web.UiController;\nimport de.codecentric.boot.admin.server.ui.web.UiController.Settings;\nimport de.codecentric.boot.admin.server.web.PathUtils;\n\nimport static java.util.Arrays.asList;\n\n@Configuration(proxyBeanMethods = false)\n@Conditional(SpringBootAdminServerEnabledCondition.class)\n@ConditionalOnBean(AdminServerMarkerConfiguration.Marker.class)\n@AutoConfigureAfter(AdminServerWebConfiguration.class)\n@EnableConfigurationProperties(AdminServerUiProperties.class)\npublic class AdminServerUiAutoConfiguration {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(AdminServerUiAutoConfiguration.class);\n\n\t/**\n\t * path patterns that will be forwarded to the homepage (webapp)\n\t */\n\tprivate static final List<String> DEFAULT_UI_ROUTES = asList(\"/about/**\", \"/applications/**\", \"/instances/**\",\n\t\t\t\"/journal/**\", \"/wallboard/**\", \"/external/**\");\n\n\t/**\n\t * path patterns that will be excluded from forwarding to the homepage (webapp), can\n\t * be extended via property: spring.boot.admin.ui.additionalRouteExcludes\n\t */\n\tprivate static final List<String> DEFAULT_UI_ROUTE_EXCLUDES = asList(\"/extensions/**\", \"/instances/*/actuator/**\");\n\n\tprivate final AdminServerUiProperties adminUi;\n\n\tprivate final AdminServerProperties adminServer;\n\n\tprivate final ApplicationContext applicationContext;\n\n\tpublic AdminServerUiAutoConfiguration(AdminServerUiProperties adminUi, AdminServerProperties serverProperties,\n\t\t\tApplicationContext applicationContext) {\n\t\tthis.adminUi = adminUi;\n\t\tthis.adminServer = serverProperties;\n\t\tthis.applicationContext = applicationContext;\n\t}\n\n\t@Bean\n\tpublic CssColorUtils cssColorUtils() {\n\t\treturn new CssColorUtils();\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean\n\tpublic UiController homeUiController(UiExtensions uiExtensions) throws IOException {\n\t\tList<String> extensionRoutes = new UiRoutesScanner(this.applicationContext)\n\t\t\t.scan(this.adminUi.getExtensionResourceLocations());\n\t\tList<String> routes = Stream.concat(DEFAULT_UI_ROUTES.stream(), extensionRoutes.stream()).toList();\n\n\t\tSettings uiSettings = Settings.builder()\n\t\t\t.brand(this.adminUi.getBrand())\n\t\t\t.title(this.adminUi.getTitle())\n\t\t\t.loginIcon(this.adminUi.getLoginIcon())\n\t\t\t.favicon(this.adminUi.getFavicon())\n\t\t\t.faviconDanger(this.adminUi.getFaviconDanger())\n\t\t\t.enableToasts(this.adminUi.getEnableToasts())\n\t\t\t.hideInstanceUrl(this.adminUi.getHideInstanceUrl())\n\t\t\t.disableInstanceUrl(this.adminUi.getDisableInstanceUrl())\n\t\t\t.notificationFilterEnabled(\n\t\t\t\t\t!this.applicationContext.getBeansOfType(NotificationFilterController.class).isEmpty())\n\t\t\t.routes(routes)\n\t\t\t.rememberMeEnabled(this.adminUi.isRememberMeEnabled())\n\t\t\t.availableLanguages(this.adminUi.getAvailableLanguages())\n\t\t\t.externalViews(this.adminUi.getExternalViews())\n\t\t\t.pollTimer(this.adminUi.getPollTimer())\n\t\t\t.viewSettings(this.adminUi.getViewSettings())\n\t\t\t.theme(this.adminUi.getTheme())\n\t\t\t.build();\n\n\t\tString publicUrl = (this.adminUi.getPublicUrl() != null) ? this.adminUi.getPublicUrl()\n\t\t\t\t: this.adminServer.getContextPath();\n\t\treturn new UiController(publicUrl, uiExtensions, uiSettings);\n\t}\n\n\t@Bean\n\tUiExtensions uiExtensions() throws IOException {\n\t\tUiExtensionsScanner scanner = new UiExtensionsScanner(this.applicationContext);\n\t\tUiExtensions uiExtensions = scanner.scan(this.adminUi.getExtensionResourceLocations());\n\t\tuiExtensions.forEach((e) -> log.info(\"Loaded Spring Boot Admin UI Extension: {}\", e));\n\t\treturn uiExtensions;\n\t}\n\n\t@Bean\n\tpublic SpringResourceTemplateResolver adminTemplateResolver() {\n\t\tSpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();\n\t\tresolver.setApplicationContext(this.applicationContext);\n\t\tresolver.setPrefix(this.adminUi.getTemplateLocation());\n\t\tresolver.setSuffix(\".html\");\n\t\tresolver.setTemplateMode(TemplateMode.HTML);\n\t\tresolver.setCharacterEncoding(StandardCharsets.UTF_8.name());\n\t\tresolver.setCacheable(this.adminUi.isCacheTemplates());\n\t\tresolver.setOrder(10);\n\t\tresolver.setCheckExistence(true);\n\t\treturn resolver;\n\t}\n\n\tstatic String normalizeHomepageUrl(String homepage) {\n\t\tif (!\"/\".equals(homepage)) {\n\t\t\thomepage = PathUtils.normalizePath(homepage);\n\t\t}\n\t\treturn homepage;\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)\n\tpublic static class ReactiveUiConfiguration {\n\n\t\t@Configuration(proxyBeanMethods = false)\n\t\tpublic static class AdminUiWebfluxConfig implements WebFluxConfigurer {\n\n\t\t\tprivate final AdminServerUiProperties adminUi;\n\n\t\t\tprivate final AdminServerProperties adminServer;\n\n\t\t\tprivate final WebFluxProperties webFluxProperties;\n\n\t\t\tprivate final ApplicationContext applicationContext;\n\n\t\t\tpublic AdminUiWebfluxConfig(AdminServerUiProperties adminUi, AdminServerProperties adminServer,\n\t\t\t\t\tWebFluxProperties webFluxProperties, ApplicationContext applicationContext) {\n\t\t\t\tthis.adminUi = adminUi;\n\t\t\t\tthis.adminServer = adminServer;\n\t\t\t\tthis.webFluxProperties = webFluxProperties;\n\t\t\t\tthis.applicationContext = applicationContext;\n\t\t\t}\n\n\t\t\t@Bean\n\t\t\tpublic HomepageForwardingFilterConfig homepageForwardingFilterConfig() throws IOException {\n\t\t\t\tString webFluxBasePath = webFluxProperties.getBasePath();\n\t\t\t\tboolean webfluxBasePathSet = webFluxBasePath != null;\n\t\t\t\tString homepage = normalizeHomepageUrl(\n\t\t\t\t\t\twebfluxBasePathSet ? webFluxBasePath + \"/\" : this.adminServer.path(\"/\"));\n\n\t\t\t\tList<String> extensionRoutes = new UiRoutesScanner(this.applicationContext)\n\t\t\t\t\t.scan(this.adminUi.getExtensionResourceLocations());\n\t\t\t\tList<String> routesIncludes = Stream.concat(DEFAULT_UI_ROUTES.stream(), extensionRoutes.stream())\n\t\t\t\t\t.map((path) -> webfluxBasePathSet ? webFluxBasePath + path : this.adminServer.path(path))\n\t\t\t\t\t.collect(Collectors.toList());\n\t\t\t\troutesIncludes.add(\"\");\n\n\t\t\t\tList<String> routesExcludes = Stream\n\t\t\t\t\t.concat(DEFAULT_UI_ROUTE_EXCLUDES.stream(), this.adminUi.getAdditionalRouteExcludes().stream())\n\t\t\t\t\t.map((path) -> webfluxBasePathSet ? webFluxBasePath + path : this.adminServer.path(path))\n\t\t\t\t\t.toList();\n\n\t\t\t\treturn new HomepageForwardingFilterConfig(homepage, routesIncludes, routesExcludes);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void addResourceHandlers(org.springframework.web.reactive.config.ResourceHandlerRegistry registry) {\n\t\t\t\tregistry.addResourceHandler(this.adminServer.path(\"/**\"))\n\t\t\t\t\t.addResourceLocations(this.adminUi.getResourceLocations())\n\t\t\t\t\t.setCacheControl(this.adminUi.getCache().toCacheControl())\n\t\t\t\t\t.setMediaTypes(Map.of(\"js\", new MediaType(\"application\", \"javascript\")));\n\n\t\t\t\tregistry.addResourceHandler(this.adminServer.path(\"/extensions/**\"))\n\t\t\t\t\t.addResourceLocations(this.adminUi.getExtensionResourceLocations())\n\t\t\t\t\t.setCacheControl(this.adminUi.getCache().toCacheControl())\n\t\t\t\t\t.setMediaTypes(Map.of(\"js\", new MediaType(\"application\", \"javascript\")));\n\t\t\t}\n\n\t\t\t@Bean\n\t\t\t@ConditionalOnMissingBean\n\t\t\tpublic de.codecentric.boot.admin.server.ui.web.reactive.HomepageForwardingFilter homepageForwardFilter(\n\t\t\t\t\tHomepageForwardingFilterConfig homepageForwardingFilterConfig) {\n\t\t\t\treturn new de.codecentric.boot.admin.server.ui.web.reactive.HomepageForwardingFilter(\n\t\t\t\t\t\thomepageForwardingFilterConfig);\n\t\t\t}\n\n\t\t}\n\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)\n\tpublic static class ServletUiConfiguration {\n\n\t\t@Configuration(proxyBeanMethods = false)\n\t\tpublic static class AdminUiWebMvcConfig implements WebMvcConfigurer {\n\n\t\t\tprivate final AdminServerUiProperties adminUi;\n\n\t\t\tprivate final AdminServerProperties adminServer;\n\n\t\t\tprivate final ApplicationContext applicationContext;\n\n\t\t\tpublic AdminUiWebMvcConfig(AdminServerUiProperties adminUi, AdminServerProperties adminServer,\n\t\t\t\t\tApplicationContext applicationContext) {\n\t\t\t\tthis.adminUi = adminUi;\n\t\t\t\tthis.adminServer = adminServer;\n\t\t\t\tthis.applicationContext = applicationContext;\n\t\t\t}\n\n\t\t\t@Bean\n\t\t\tpublic HomepageForwardingFilterConfig homepageForwardingFilterConfig() throws IOException {\n\t\t\t\tString homepage = normalizeHomepageUrl(this.adminServer.path(\"/\"));\n\n\t\t\t\tList<String> extensionRoutes = new UiRoutesScanner(this.applicationContext)\n\t\t\t\t\t.scan(this.adminUi.getExtensionResourceLocations());\n\t\t\t\tList<String> routesIncludes = Stream\n\t\t\t\t\t.concat(DEFAULT_UI_ROUTES.stream(), Stream.concat(extensionRoutes.stream(), Stream.of(\"/\")))\n\t\t\t\t\t.map(this.adminServer::path)\n\t\t\t\t\t.toList();\n\n\t\t\t\tList<String> routesExcludes = Stream\n\t\t\t\t\t.concat(DEFAULT_UI_ROUTE_EXCLUDES.stream(), this.adminUi.getAdditionalRouteExcludes().stream())\n\t\t\t\t\t.map(this.adminServer::path)\n\t\t\t\t\t.toList();\n\n\t\t\t\treturn new HomepageForwardingFilterConfig(homepage, routesIncludes, routesExcludes);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void addResourceHandlers(\n\t\t\t\t\torg.springframework.web.servlet.config.annotation.ResourceHandlerRegistry registry) {\n\t\t\t\tregistry.addResourceHandler(this.adminServer.path(\"/**\"))\n\t\t\t\t\t.addResourceLocations(this.adminUi.getResourceLocations())\n\t\t\t\t\t.setCacheControl(this.adminUi.getCache().toCacheControl());\n\t\t\t\tregistry.addResourceHandler(this.adminServer.path(\"/extensions/**\"))\n\t\t\t\t\t.addResourceLocations(this.adminUi.getExtensionResourceLocations())\n\t\t\t\t\t.setCacheControl(this.adminUi.getCache().toCacheControl());\n\t\t\t}\n\n\t\t\t@Bean\n\t\t\t@ConditionalOnMissingBean\n\t\t\tpublic de.codecentric.boot.admin.server.ui.web.servlet.HomepageForwardingFilter homepageForwardFilter(\n\t\t\t\t\tHomepageForwardingFilterConfig homepageForwardingFilterConfig) {\n\t\t\t\treturn new de.codecentric.boot.admin.server.ui.web.servlet.HomepageForwardingFilter(\n\t\t\t\t\t\thomepageForwardingFilterConfig);\n\t\t\t}\n\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/java/de/codecentric/boot/admin/server/ui/config/AdminServerUiProperties.java",
    "content": "/*\n * Copyright 2014-2024 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui.config;\n\nimport java.time.Duration;\nimport java.time.temporal.ChronoUnit;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\n\nimport lombok.Data;\nimport lombok.Getter;\nimport org.jspecify.annotations.Nullable;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.boot.convert.DurationUnit;\nimport org.springframework.http.CacheControl;\n\nimport de.codecentric.boot.admin.server.ui.web.UiController;\n\n@lombok.Data\n@ConfigurationProperties(\"spring.boot.admin.ui\")\npublic class AdminServerUiProperties {\n\n\tprivate static final String[] CLASSPATH_RESOURCE_LOCATIONS = { \"classpath:/META-INF/spring-boot-admin-server-ui/\" };\n\n\tprivate static final String[] CLASSPATH_EXTENSION_RESOURCE_LOCATIONS = {\n\t\t\t\"classpath:/META-INF/spring-boot-admin-server-ui/extensions/\" };\n\n\t/**\n\t * Locations of SBA ui resources.\n\t */\n\tprivate String[] resourceLocations = CLASSPATH_RESOURCE_LOCATIONS;\n\n\t/**\n\t * Locations of SBA ui exentsion resources.\n\t */\n\tprivate String[] extensionResourceLocations = CLASSPATH_EXTENSION_RESOURCE_LOCATIONS;\n\n\t/**\n\t * Locations of SBA ui template.\n\t */\n\tprivate String templateLocation = CLASSPATH_RESOURCE_LOCATIONS[0];\n\n\t/**\n\t * Icon used as image on login page\n\t */\n\tprivate String loginIcon = \"assets/img/icon-spring-boot-admin.svg\";\n\n\t/**\n\t * Icon used as default favicon and icon for desktop notifications.\n\t */\n\tprivate String favicon = \"assets/img/favicon.png\";\n\n\t/**\n\t * Icon used as default favicon and icon for desktop notifications.\n\t */\n\tprivate String faviconDanger = \"assets/img/favicon-danger.png\";\n\n\t/**\n\t * Page-Title to be shown.\n\t */\n\tprivate String title = \"Spring Boot Admin\";\n\n\t/**\n\t * Brand to be shown in then navbar.\n\t */\n\tprivate String brand = \"<img src=\\\"assets/img/icon-spring-boot-admin.svg\\\"><span>Spring Boot Admin</span>\";\n\n\t/**\n\t * If running behind a reverse proxy (using path rewriting) this can be used to output\n\t * correct self references. If the host/port is omitted it will be inferred from the\n\t * request.\n\t */\n\t@Nullable private String publicUrl = null;\n\n\t/**\n\t * Wether the thymeleaf templates should be cached.\n\t */\n\tprivate boolean cacheTemplates = true;\n\n\t/**\n\t * Cache-Http-Header settings.\n\t */\n\tprivate Cache cache = new Cache();\n\n\t/**\n\t * External views shown in the navbar.\n\t */\n\tprivate List<UiController.ExternalView> externalViews = new ArrayList<>();\n\n\t/**\n\t * External views shown in the navbar.\n\t */\n\tprivate List<UiController.ViewSettings> viewSettings = new ArrayList<>();\n\n\t/**\n\t * Whether the option to remember a user should be available.\n\t */\n\tprivate boolean rememberMeEnabled = true;\n\n\t/**\n\t * Limit languages to this list. Intersection of all supported languages and this list\n\t * will be used.\n\t */\n\tprivate List<String> availableLanguages = new ArrayList<>();\n\n\tprivate PollTimer pollTimer = new PollTimer();\n\n\t/**\n\t * Additional routes to exclude from home page redirecting filter. Requests to these\n\t * routes will not redirected to home page\n\t */\n\tprivate List<String> additionalRouteExcludes = new ArrayList<>();\n\n\t/**\n\t * Allows to enable toast notifications in SBA.\n\t */\n\tprivate Boolean enableToasts = false;\n\n\t/**\n\t * Set to <code>true</code> to hide service URLs as well as actions that require them\n\t * in UI (e.g. jump to /health or /actuator).\n\t */\n\tprivate Boolean hideInstanceUrl = false;\n\n\t/**\n\t * Set to <code>true</code> to disable service URLs as well as actions that require\n\t * them in UI.\n\t */\n\tprivate Boolean disableInstanceUrl = false;\n\n\tprivate UiTheme theme = new UiTheme();\n\n\t@lombok.Data\n\tpublic static class PollTimer {\n\n\t\t/**\n\t\t * Time in milliseconds to refresh data in caches view.\n\t\t */\n\t\tprivate int cache = 2500;\n\n\t\t/**\n\t\t * Time in milliseconds to refresh data in datasource view.\n\t\t */\n\t\tprivate int datasource = 2500;\n\n\t\t/**\n\t\t * Time in milliseconds to refresh data in gc view.\n\t\t */\n\t\tprivate int gc = 2500;\n\n\t\t/**\n\t\t * Time in milliseconds to refresh data in process view.\n\t\t */\n\t\tprivate int process = 2500;\n\n\t\t/**\n\t\t * Time in milliseconds to refresh data in memory view.\n\t\t */\n\t\tprivate int memory = 2500;\n\n\t\t/**\n\t\t * Time in milliseconds to refresh data in threads view.\n\t\t */\n\t\tprivate int threads = 2500;\n\n\t\t/**\n\t\t * Time in milliseconds to refresh data in logfile view.\n\t\t */\n\t\tprivate int logfile = 1000;\n\n\t}\n\n\t@lombok.Data\n\tpublic static class Cache {\n\n\t\t/**\n\t\t * include \"max-age\" directive in Cache-Control http header.\n\t\t */\n\t\t@Nullable\n\t\t@DurationUnit(ChronoUnit.SECONDS)\n\t\tprivate Duration maxAge = Duration.ofSeconds(3600);\n\n\t\t/**\n\t\t * include \"no-cache\" directive in Cache-Control http header.\n\t\t */\n\t\tprivate Boolean noCache = false;\n\n\t\t/**\n\t\t * include \"no-store\" directive in Cache-Control http header.\n\t\t */\n\t\tprivate Boolean noStore = false;\n\n\t\tpublic CacheControl toCacheControl() {\n\t\t\tif (Boolean.TRUE.equals(this.noStore)) {\n\t\t\t\treturn CacheControl.noStore();\n\t\t\t}\n\t\t\tif (Boolean.TRUE.equals(this.noCache)) {\n\t\t\t\treturn CacheControl.noCache();\n\t\t\t}\n\t\t\tif (this.maxAge != null) {\n\t\t\t\treturn CacheControl.maxAge(this.maxAge.getSeconds(), TimeUnit.SECONDS);\n\t\t\t}\n\t\t\treturn CacheControl.empty();\n\t\t}\n\n\t}\n\n\t@Data\n\tpublic static class UiTheme {\n\n\t\tprivate Boolean backgroundEnabled = true;\n\n\t\tprivate Palette palette = new Palette();\n\n\t\tprivate String color = \"#14615A\";\n\n\t}\n\n\t/**\n\t * Color shades are based on Tailwind's color palettes:\n\t * <a href=\"https://tailwindcss.com/docs/customizing-colors\">tailwindcss.com</a>\n\t * <p>\n\t * name shade number mainColorLighter 50 mainColorLight 300 mainColor 500\n\t * mainColorDark 700 mainColorDarker 800\n\t */\n\t@Getter\n\tpublic static class Palette {\n\n\t\tprivate String shade50 = \"#EEFCFA\";\n\n\t\tprivate String shade100 = \"#D9F7F4\";\n\n\t\tprivate String shade200 = \"#B7F0EA\";\n\n\t\tprivate String shade300 = \"#91E8E0\";\n\n\t\tprivate String shade400 = \"#6BE0D5\";\n\n\t\tprivate String shade500 = \"#47D9CB\";\n\n\t\tprivate String shade600 = \"#27BEAF\";\n\n\t\tprivate String shade700 = \"#1E9084\";\n\n\t\tprivate String shade800 = \"#14615A\";\n\n\t\tprivate String shade900 = \"#0A2F2B\";\n\n\t\tpublic void set50(String shade50) {\n\t\t\tthis.shade50 = shade50;\n\t\t}\n\n\t\tpublic void set100(String shade100) {\n\t\t\tthis.shade100 = shade100;\n\t\t}\n\n\t\tpublic void set200(String shade200) {\n\t\t\tthis.shade200 = shade200;\n\t\t}\n\n\t\tpublic void set300(String shade300) {\n\t\t\tthis.shade300 = shade300;\n\t\t}\n\n\t\tpublic void set400(String shade400) {\n\t\t\tthis.shade400 = shade400;\n\t\t}\n\n\t\tpublic void set500(String shade500) {\n\t\t\tthis.shade500 = shade500;\n\t\t}\n\n\t\tpublic void set600(String shade600) {\n\t\t\tthis.shade600 = shade600;\n\t\t}\n\n\t\tpublic void set700(String shade700) {\n\t\t\tthis.shade700 = shade700;\n\t\t}\n\n\t\tpublic void set800(String shade800) {\n\t\t\tthis.shade800 = shade800;\n\t\t}\n\n\t\tpublic void set900(String shade900) {\n\t\t\tthis.shade900 = shade900;\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/java/de/codecentric/boot/admin/server/ui/config/CssColorUtils.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui.config;\n\nimport java.util.regex.Pattern;\n\npublic class CssColorUtils {\n\n\tprivate static final Pattern HEX_RGB_PATTERN = Pattern.compile(\"^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$\");\n\n\tpublic String hexToRgb(String color) {\n\t\tif (!HEX_RGB_PATTERN.matcher(color).matches()) {\n\t\t\tthrow new IllegalArgumentException(String.format(\"Invalid hex rgb format %s\", color));\n\t\t}\n\t\treturn String.format(\"%s, %s, %s\", Integer.valueOf(color.substring(1, 3), 16),\n\t\t\t\tInteger.valueOf(color.substring(3, 5), 16), Integer.valueOf(color.substring(5, 7), 16));\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/java/de/codecentric/boot/admin/server/ui/config/ServerRuntimeHints.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui.config;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport lombok.SneakyThrows;\nimport org.springframework.aot.hint.ExecutableMode;\nimport org.springframework.aot.hint.MemberCategory;\nimport org.springframework.aot.hint.RuntimeHints;\nimport org.springframework.aot.hint.RuntimeHintsRegistrar;\nimport org.springframework.aot.hint.TypeHint;\nimport org.springframework.aot.hint.TypeReference;\nimport org.springframework.context.annotation.Configuration;\nimport tools.jackson.databind.ser.jackson.TokenBufferSerializer;\nimport tools.jackson.databind.ser.jdk.JDKMiscSerializers;\n\nimport de.codecentric.boot.admin.server.domain.entities.Instance;\nimport de.codecentric.boot.admin.server.domain.events.InstanceDeregisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEndpointsDetectedEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceInfoChangedEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegisteredEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceRegistrationUpdatedEvent;\nimport de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;\nimport de.codecentric.boot.admin.server.domain.values.BuildVersion;\nimport de.codecentric.boot.admin.server.domain.values.Endpoint;\nimport de.codecentric.boot.admin.server.domain.values.Endpoints;\nimport de.codecentric.boot.admin.server.domain.values.Info;\nimport de.codecentric.boot.admin.server.domain.values.InstanceId;\nimport de.codecentric.boot.admin.server.domain.values.Registration;\nimport de.codecentric.boot.admin.server.domain.values.StatusInfo;\nimport de.codecentric.boot.admin.server.domain.values.Tags;\nimport de.codecentric.boot.admin.server.ui.web.UiController;\nimport de.codecentric.boot.admin.server.utils.jackson.BuildVersionMixin;\nimport de.codecentric.boot.admin.server.utils.jackson.EndpointMixin;\nimport de.codecentric.boot.admin.server.utils.jackson.EndpointsMixin;\nimport de.codecentric.boot.admin.server.utils.jackson.InfoMixin;\nimport de.codecentric.boot.admin.server.utils.jackson.InstanceDeregisteredEventMixin;\nimport de.codecentric.boot.admin.server.utils.jackson.InstanceEndpointsDetectedEventMixin;\nimport de.codecentric.boot.admin.server.utils.jackson.InstanceEventMixin;\nimport de.codecentric.boot.admin.server.utils.jackson.InstanceIdMixin;\nimport de.codecentric.boot.admin.server.utils.jackson.InstanceInfoChangedEventMixin;\nimport de.codecentric.boot.admin.server.utils.jackson.InstanceRegisteredEventMixin;\nimport de.codecentric.boot.admin.server.utils.jackson.InstanceRegistrationUpdatedEventMixin;\nimport de.codecentric.boot.admin.server.utils.jackson.InstanceStatusChangedEventMixin;\nimport de.codecentric.boot.admin.server.utils.jackson.StatusInfoMixin;\nimport de.codecentric.boot.admin.server.utils.jackson.TagsMixin;\nimport de.codecentric.boot.admin.server.web.InstanceWebProxy;\n\n@Configuration\npublic class ServerRuntimeHints implements RuntimeHintsRegistrar {\n\n\t@Override\n\tpublic void registerHints(RuntimeHints hints, ClassLoader classLoader) {\n\t\tregisterReflectionHints(hints);\n\n\t\tregisterResourcesHints(hints);\n\n\t\tregisterSerializationHints(hints);\n\t}\n\n\tprivate static void registerSerializationHints(RuntimeHints hints) {\n\t\thints.serialization()\n\t\t\t.registerType(HashMap.class)\n\t\t\t.registerType(ArrayList.class)\n\t\t\t.registerType(Registration.class)\n\t\t\t.registerType(InstanceId.class)\n\t\t\t.registerType(Instance.class)\n\t\t\t.registerType(BuildVersion.class)\n\t\t\t.registerType(Endpoint.class)\n\t\t\t.registerType(Endpoints.class)\n\t\t\t.registerType(Info.class)\n\t\t\t.registerType(StatusInfo.class)\n\t\t\t.registerType(Tags.class);\n\t}\n\n\tprivate static void registerResourcesHints(org.springframework.aot.hint.RuntimeHints hints) {\n\t\thints.resources()\n\t\t\t.registerPattern(\"**/spring-boot-admin-server-ui/**.*\")\n\t\t\t.registerPattern(\"**/sba-settings.js\")\n\t\t\t.registerPattern(\"**/variables.css\");\n\t}\n\n\t@SneakyThrows\n\tprivate static void registerReflectionHints(org.springframework.aot.hint.RuntimeHints hints) {\n\t\tClass<?> queryEndpointStrategyResponse = Class\n\t\t\t.forName(\"de.codecentric.boot.admin.server.services.endpoints.QueryIndexEndpointStrategy$Response\");\n\t\tClass<?> queryEndpointStrategyResponseEndpointRef = Class.forName(\n\t\t\t\t\"de.codecentric.boot.admin.server.services.endpoints.QueryIndexEndpointStrategy$Response$EndpointRef\");\n\n\t\thints.reflection()\n\t\t\t.registerType(queryEndpointStrategyResponse, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(queryEndpointStrategyResponseEndpointRef, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(UiController.Settings.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(UiController.ExternalView.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(AdminServerUiProperties.UiTheme.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(AdminServerUiProperties.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(AdminServerUiProperties.Palette.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(AdminServerUiProperties.Cache.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(AdminServerUiProperties.PollTimer.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(CssColorUtils.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(InstanceDeregisteredEvent.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(InstanceEndpointsDetectedEvent.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(InstanceEvent.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(InstanceInfoChangedEvent.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(InstanceRegisteredEvent.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(InstanceRegistrationUpdatedEvent.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(InstanceStatusChangedEvent.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(InstanceId.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(Endpoint.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(Instance.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(InstanceId.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(InstanceWebProxy.InstanceResponse.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(InstanceWebProxy.ForwardRequest.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\n\t\t\t.registerType(BuildVersionMixin.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(EndpointMixin.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(EndpointsMixin.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(InfoMixin.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(InstanceDeregisteredEventMixin.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(InstanceEndpointsDetectedEventMixin.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(InstanceEventMixin.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(InstanceIdMixin.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(InstanceInfoChangedEventMixin.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(InstanceRegisteredEventMixin.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(InstanceRegistrationUpdatedEventMixin.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(InstanceStatusChangedEventMixin.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(StatusInfoMixin.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\t\t\t.registerType(TagsMixin.class, MemberCategory.INVOKE_PUBLIC_METHODS,\n\t\t\t\t\tMemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)\n\n\t\t\t.registerConstructor(queryEndpointStrategyResponse.getConstructors()[0], ExecutableMode.INVOKE)\n\t\t\t.registerConstructor(\n\t\t\t\t\tqueryEndpointStrategyResponseEndpointRef.getDeclaredConstructor(String.class, boolean.class),\n\t\t\t\t\tExecutableMode.INVOKE)\n\n\t\t\t.registerConstructor(Registration.class.getDeclaredConstructor(String.class, String.class, String.class,\n\t\t\t\t\tString.class, String.class, Map.class), ExecutableMode.INVOKE)\n\t\t\t.registerConstructor(Registration.Builder.class.getDeclaredConstructor(), ExecutableMode.INVOKE)\n\t\t\t.registerMethod(Registration.Builder.class.getMethod(\"build\"), ExecutableMode.INVOKE)\n\t\t\t.registerMethod(Registration.class.getMethod(\"toBuilder\"), ExecutableMode.INVOKE)\n\t\t\t.registerTypes(\n\t\t\t\t\tTypeReference.listOf(JDKMiscSerializers.AtomicBooleanSerializer.class,\n\t\t\t\t\t\t\tJDKMiscSerializers.AtomicIntegerSerializer.class,\n\t\t\t\t\t\t\tJDKMiscSerializers.AtomicLongSerializer.class, TokenBufferSerializer.class),\n\t\t\t\t\tTypeHint.builtWith(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS));\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/java/de/codecentric/boot/admin/server/ui/config/SpringNativeServerAutoConfiguration.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui.config;\n\nimport org.springframework.boot.autoconfigure.AutoConfigureAfter;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnBean;\nimport org.springframework.context.annotation.Conditional;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.ImportRuntimeHints;\n\nimport de.codecentric.boot.admin.server.config.AdminServerMarkerConfiguration;\nimport de.codecentric.boot.admin.server.config.AdminServerWebConfiguration;\nimport de.codecentric.boot.admin.server.config.SpringBootAdminServerEnabledCondition;\n\n@Configuration(proxyBeanMethods = false)\n@Conditional(SpringBootAdminServerEnabledCondition.class)\n@ConditionalOnBean(AdminServerMarkerConfiguration.Marker.class)\n@AutoConfigureAfter(AdminServerWebConfiguration.class)\n@ImportRuntimeHints({ ServerRuntimeHints.class })\npublic class SpringNativeServerAutoConfiguration {\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/java/de/codecentric/boot/admin/server/ui/config/package-info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n@NullMarked\npackage de.codecentric.boot.admin.server.ui.config;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/java/de/codecentric/boot/admin/server/ui/extensions/UiExtension.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui.extensions;\n\n@lombok.Data\npublic class UiExtension {\n\n\tprivate final String resourcePath;\n\n\tprivate final String resourceLocation;\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/java/de/codecentric/boot/admin/server/ui/extensions/UiExtensions.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui.extensions;\n\nimport java.util.Collections;\nimport java.util.Iterator;\nimport java.util.List;\n\n@lombok.Data\npublic class UiExtensions implements Iterable<UiExtension> {\n\n\tpublic static final UiExtensions EMPTY = new UiExtensions(Collections.emptyList());\n\n\tprivate final List<UiExtension> extensions;\n\n\tpublic UiExtensions(List<UiExtension> extensions) {\n\t\tthis.extensions = Collections.unmodifiableList(extensions);\n\t}\n\n\t@Override\n\tpublic Iterator<UiExtension> iterator() {\n\t\treturn this.extensions.iterator();\n\t}\n\n\tpublic List<UiExtension> getCssExtensions() {\n\t\treturn this.extensions.stream().filter((e) -> e.getResourcePath().endsWith(\".css\")).toList();\n\t}\n\n\tpublic List<UiExtension> getJsExtensions() {\n\t\treturn this.extensions.stream().filter((e) -> e.getResourcePath().endsWith(\".js\")).toList();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/java/de/codecentric/boot/admin/server/ui/extensions/UiExtensionsScanner.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui.extensions;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport java.util.stream.Stream;\n\nimport org.jspecify.annotations.Nullable;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.core.io.Resource;\nimport org.springframework.core.io.support.ResourcePatternResolver;\n\npublic class UiExtensionsScanner {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(UiExtensionsScanner.class);\n\n\tprivate final ResourcePatternResolver resolver;\n\n\tpublic UiExtensionsScanner(ResourcePatternResolver resolver) {\n\t\tthis.resolver = resolver;\n\t}\n\n\tpublic UiExtensions scan(String... locations) throws IOException {\n\t\tList<UiExtension> extensions = new ArrayList<>();\n\t\tfor (String location : locations) {\n\t\t\tfor (Resource resource : resolveAssets(location)) {\n\t\t\t\tString resourcePath = this.getResourcePath(location, resource);\n\t\t\t\tif (resourcePath != null && resource.isReadable()) {\n\t\t\t\t\tUiExtension extension = new UiExtension(resourcePath, location + resourcePath);\n\t\t\t\t\tlog.debug(\"Found UiExtension {}\", extension);\n\t\t\t\t\textensions.add(extension);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn new UiExtensions(extensions);\n\t}\n\n\tprivate List<Resource> resolveAssets(String location) throws IOException {\n\t\tString widerLocation = toPattern(location);\n\t\treturn Stream\n\t\t\t.concat(Arrays.stream(this.resolver.getResources(widerLocation + \"**/*.js\")),\n\t\t\t\t\tArrays.stream(this.resolver.getResources(widerLocation + \"**/*.css\")))\n\t\t\t.toList();\n\t}\n\n\tprivate String toPattern(String location) {\n\t\t// replace the classpath pattern to search all locations and not just the first\n\t\treturn location.replace(\"classpath:\", \"classpath*:\");\n\t}\n\n\t@Nullable private String getResourcePath(String location, Resource resource) throws IOException {\n\t\tString locationWithoutPrefix = location.replaceFirst(\"^[^:]+:\", \"\");\n\t\tMatcher m = Pattern.compile(Pattern.quote(locationWithoutPrefix) + \"(.+)$\")\n\t\t\t.matcher(resource.getURI().toString());\n\t\tif (m.find()) {\n\t\t\treturn m.group(1);\n\t\t}\n\t\telse {\n\t\t\treturn null;\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/java/de/codecentric/boot/admin/server/ui/extensions/UiRoutesScanner.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui.extensions;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.core.io.Resource;\nimport org.springframework.core.io.support.ResourcePatternResolver;\nimport org.springframework.util.StringUtils;\n\npublic class UiRoutesScanner {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(UiRoutesScanner.class);\n\n\tprivate final ResourcePatternResolver resolver;\n\n\tpublic UiRoutesScanner(ResourcePatternResolver resolver) {\n\t\tthis.resolver = resolver;\n\t}\n\n\tpublic List<String> scan(String... locations) throws IOException {\n\t\tList<String> routes = new ArrayList<>();\n\t\tfor (String location : locations) {\n\t\t\tfor (Resource resource : this.resolver.getResources(toPattern(location) + \"**/routes.txt\")) {\n\t\t\t\tif (resource.isReadable()) {\n\t\t\t\t\troutes.addAll(readLines(resource.getInputStream()));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn routes;\n\t}\n\n\tprivate List<String> readLines(InputStream input) {\n\t\ttry (BufferedReader buffer = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {\n\t\t\treturn buffer.lines().map(String::trim).filter(StringUtils::hasText).toList();\n\t\t}\n\t\tcatch (IOException ex) {\n\t\t\tlog.warn(\"Couldn't read routes from\", ex);\n\t\t\treturn Collections.emptyList();\n\t\t}\n\t}\n\n\tprivate String toPattern(String location) {\n\t\t// replace the classpath pattern to search all locations and not just the first\n\t\treturn location.replace(\"classpath:\", \"classpath*:\");\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/java/de/codecentric/boot/admin/server/ui/extensions/package-info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n@NullMarked\npackage de.codecentric.boot.admin.server.ui.extensions;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/java/de/codecentric/boot/admin/server/ui/web/HomepageForwardingFilterConfig.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui.web;\n\nimport java.util.List;\n\nimport lombok.Value;\n\n@Value\npublic class HomepageForwardingFilterConfig {\n\n\tString homepage;\n\n\tList<String> routesIncludes;\n\n\t/**\n\t * routes which are excluded intentionally (for instance downloads)\n\t */\n\tList<String> routesExcludes;\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/java/de/codecentric/boot/admin/server/ui/web/HomepageForwardingMatcher.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui.web;\n\nimport java.util.List;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\nimport java.util.regex.Pattern;\n\nimport org.springframework.http.HttpMethod;\nimport org.springframework.http.MediaType;\n\npublic class HomepageForwardingMatcher<T> implements Predicate<T> {\n\n\tprivate final List<Pattern> includeRoutes;\n\n\tprivate final List<Pattern> excludeRoutes;\n\n\tprivate final Function<T, String> methodAccessor;\n\n\tprivate final Function<T, String> pathAccessor;\n\n\tprivate final Function<T, List<MediaType>> acceptsAccessor;\n\n\tpublic HomepageForwardingMatcher(List<String> includeRoutes, List<String> excludeRoutes,\n\t\t\tFunction<T, String> methodAccessor, Function<T, String> pathAccessor,\n\t\t\tFunction<T, List<MediaType>> acceptsAccessor) {\n\t\tthis.includeRoutes = toPatterns(includeRoutes);\n\t\tthis.excludeRoutes = toPatterns(excludeRoutes);\n\t\tthis.methodAccessor = methodAccessor;\n\t\tthis.pathAccessor = pathAccessor;\n\t\tthis.acceptsAccessor = acceptsAccessor;\n\t}\n\n\tpublic boolean test(T request) {\n\t\tif (!HttpMethod.GET.matches(this.methodAccessor.apply(request))) {\n\t\t\treturn false;\n\t\t}\n\n\t\tString path = this.pathAccessor.apply(request);\n\t\tboolean isExcludedRoute = this.excludeRoutes.stream().anyMatch((p) -> p.matcher(path).matches());\n\t\tboolean isIncludedRoute = this.includeRoutes.stream().anyMatch((p) -> p.matcher(path).matches());\n\t\tif (isExcludedRoute || !isIncludedRoute) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn this.acceptsAccessor.apply(request).stream().anyMatch((t) -> t.includes(MediaType.TEXT_HTML));\n\t}\n\n\tprivate List<Pattern> toPatterns(List<String> routes) {\n\t\treturn routes.stream()\n\t\t\t.map((r) -> \"^\" + r.replaceAll(\"/[*][*]\", \"(/.*)?\").replaceAll(\"/[*]/\", \"/[^/]+/\") + \"$\")\n\t\t\t.map(Pattern::compile)\n\t\t\t.toList();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/java/de/codecentric/boot/admin/server/ui/web/UiController.java",
    "content": "/*\n * Copyright 2014-2024 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui.web;\n\nimport java.security.Principal;\nimport java.util.List;\nimport java.util.Map;\n\nimport com.fasterxml.jackson.annotation.JsonInclude;\nimport com.fasterxml.jackson.annotation.JsonInclude.Include;\nimport org.jspecify.annotations.Nullable;\nimport org.springframework.aot.hint.annotation.RegisterReflectionForBinding;\nimport org.springframework.http.MediaType;\nimport org.springframework.util.Assert;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.ModelAttribute;\nimport org.springframework.web.util.UriComponents;\nimport org.springframework.web.util.UriComponentsBuilder;\n\nimport de.codecentric.boot.admin.server.ui.config.AdminServerUiProperties.PollTimer;\nimport de.codecentric.boot.admin.server.ui.config.AdminServerUiProperties.UiTheme;\nimport de.codecentric.boot.admin.server.ui.extensions.UiExtension;\nimport de.codecentric.boot.admin.server.ui.extensions.UiExtensions;\nimport de.codecentric.boot.admin.server.web.AdminController;\n\nimport static java.util.Collections.emptyMap;\nimport static java.util.Collections.singletonMap;\nimport static org.springframework.util.CollectionUtils.isEmpty;\n\n@AdminController\npublic class UiController {\n\n\tprivate final String publicUrl;\n\n\tprivate final UiExtensions uiExtensions;\n\n\tprivate final Settings uiSettings;\n\n\tpublic UiController(String publicUrl, UiExtensions uiExtensions, Settings uiSettings) {\n\t\tthis.publicUrl = publicUrl;\n\t\tthis.uiExtensions = uiExtensions;\n\t\tthis.uiSettings = uiSettings;\n\t}\n\n\t@ModelAttribute(value = \"baseUrl\", binding = false)\n\tpublic String getBaseUrl(UriComponentsBuilder uriBuilder) {\n\t\tUriComponents publicComponents = UriComponentsBuilder.fromUriString(this.publicUrl).build();\n\t\tif (publicComponents.getScheme() != null) {\n\t\t\turiBuilder.scheme(publicComponents.getScheme());\n\t\t}\n\t\tif (publicComponents.getHost() != null) {\n\t\t\turiBuilder.host(publicComponents.getHost());\n\t\t}\n\t\tif (publicComponents.getPort() != -1) {\n\t\t\turiBuilder.port(publicComponents.getPort());\n\t\t}\n\t\tif (publicComponents.getPath() != null) {\n\t\t\turiBuilder.path(publicComponents.getPath());\n\t\t}\n\t\treturn uriBuilder.path(\"/\").toUriString();\n\t}\n\n\t@ModelAttribute(value = \"uiSettings\", binding = false)\n\tpublic Settings getUiSettings() {\n\t\treturn this.uiSettings;\n\t}\n\n\t@ModelAttribute(value = \"cssExtensions\", binding = false)\n\tpublic List<UiExtension> getCssExtensions() {\n\t\treturn this.uiExtensions.getCssExtensions();\n\t}\n\n\t@ModelAttribute(value = \"jsExtensions\", binding = false)\n\tpublic List<UiExtension> getJsExtensions() {\n\t\treturn this.uiExtensions.getJsExtensions();\n\t}\n\n\t@ModelAttribute(value = \"user\", binding = false)\n\tpublic Map<String, Object> getUser(@Nullable Principal principal) {\n\t\tif (principal != null) {\n\t\t\treturn singletonMap(\"name\", principal.getName());\n\t\t}\n\t\treturn emptyMap();\n\t}\n\n\t@GetMapping(path = \"/\", produces = MediaType.TEXT_HTML_VALUE)\n\t@RegisterReflectionForBinding(String.class)\n\tpublic String index() {\n\t\treturn \"index\";\n\t}\n\n\t@GetMapping(path = \"/sba-settings.js\", produces = \"application/javascript\")\n\tpublic String sbaSettings() {\n\t\treturn \"sba-settings.js\";\n\t}\n\n\t@GetMapping(path = \"/variables.css\", produces = \"text/css\")\n\tpublic String variablesCss() {\n\t\treturn \"variables.css\";\n\t}\n\n\t@GetMapping(path = \"/login\", produces = MediaType.TEXT_HTML_VALUE)\n\tpublic String login() {\n\t\treturn \"login\";\n\t}\n\n\t@lombok.Data\n\t@lombok.Builder\n\tpublic static class Settings {\n\n\t\tprivate final String title;\n\n\t\tprivate final String brand;\n\n\t\tprivate final String loginIcon;\n\n\t\tprivate final String favicon;\n\n\t\tprivate final String faviconDanger;\n\n\t\tprivate final PollTimer pollTimer;\n\n\t\tprivate final UiTheme theme;\n\n\t\tprivate final boolean notificationFilterEnabled;\n\n\t\tprivate final boolean rememberMeEnabled;\n\n\t\tprivate final List<String> availableLanguages;\n\n\t\tprivate final List<String> routes;\n\n\t\tprivate final List<ExternalView> externalViews;\n\n\t\tprivate final List<ViewSettings> viewSettings;\n\n\t\tprivate final Boolean enableToasts;\n\n\t\tprivate final Boolean hideInstanceUrl;\n\n\t\tprivate final Boolean disableInstanceUrl;\n\n\t}\n\n\t@lombok.Data\n\t@JsonInclude(Include.NON_EMPTY)\n\tpublic static class ExternalView {\n\n\t\t/**\n\t\t * Label to be shown in the navbar.\n\t\t */\n\t\tprivate final String label;\n\n\t\t/**\n\t\t * Url for the external view to be linked\n\t\t */\n\t\tprivate final String url;\n\n\t\t/**\n\t\t * Order in the navbar.\n\t\t */\n\t\tprivate final Integer order;\n\n\t\t/**\n\t\t * Should the page shown as an iframe or open in a new window.\n\t\t */\n\t\tprivate final boolean iframe;\n\n\t\t/**\n\t\t * A list of child views.\n\t\t */\n\t\tprivate final List<ExternalView> children;\n\n\t\tpublic ExternalView(String label, String url, Integer order, boolean iframe, List<ExternalView> children) {\n\t\t\tAssert.hasText(label, \"'label' must not be empty\");\n\t\t\tif (isEmpty(children)) {\n\t\t\t\tAssert.hasText(url, \"'url' must not be empty\");\n\t\t\t}\n\t\t\tthis.label = label;\n\t\t\tthis.url = url;\n\t\t\tthis.order = order;\n\t\t\tthis.iframe = iframe;\n\t\t\tthis.children = children;\n\t\t}\n\n\t}\n\n\t@lombok.Data\n\t@JsonInclude(Include.NON_EMPTY)\n\tpublic static class ViewSettings {\n\n\t\t/**\n\t\t * Name of the view to address.\n\t\t */\n\t\tprivate final String name;\n\n\t\t/**\n\t\t * Set view enabled.\n\t\t */\n\t\tprivate boolean enabled;\n\n\t\tpublic ViewSettings(String name, boolean enabled) {\n\t\t\tAssert.hasText(name, \"'name' must not be empty\");\n\t\t\tthis.name = name;\n\t\t\tthis.enabled = enabled;\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/java/de/codecentric/boot/admin/server/ui/web/package-info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n@NullMarked\npackage de.codecentric.boot.admin.server.ui.web;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/java/de/codecentric/boot/admin/server/ui/web/reactive/HomepageForwardingFilter.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui.web.reactive;\n\nimport java.util.List;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.http.server.reactive.ServerHttpRequest;\nimport org.springframework.web.server.ServerWebExchange;\nimport org.springframework.web.server.WebFilter;\nimport org.springframework.web.server.WebFilterChain;\nimport reactor.core.publisher.Mono;\n\nimport de.codecentric.boot.admin.server.ui.web.HomepageForwardingFilterConfig;\nimport de.codecentric.boot.admin.server.ui.web.HomepageForwardingMatcher;\n\npublic class HomepageForwardingFilter implements WebFilter {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(HomepageForwardingFilter.class);\n\n\tprivate final String homepage;\n\n\tprivate final HomepageForwardingMatcher<ServerHttpRequest> matcher;\n\n\tpublic HomepageForwardingFilter(String homepage, List<String> routeIncludes, List<String> routeExcludes) {\n\t\tthis.homepage = homepage;\n\t\tthis.matcher = new HomepageForwardingMatcher<>(routeIncludes, routeExcludes,\n\t\t\t\t(request) -> request.getMethod().name(),\n\t\t\t\t(request) -> request.getPath().pathWithinApplication().toString(),\n\t\t\t\t(request) -> request.getHeaders().getAccept());\n\t}\n\n\tpublic HomepageForwardingFilter(HomepageForwardingFilterConfig filterConfig) {\n\t\tthis(filterConfig.getHomepage(), filterConfig.getRoutesIncludes(), filterConfig.getRoutesExcludes());\n\t}\n\n\t@Override\n\tpublic Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {\n\t\tif (this.matcher.test(exchange.getRequest())) {\n\t\t\tlog.trace(\"Forwarding request with URL {} to index\", exchange.getRequest().getURI());\n\t\t\texchange = exchange.mutate().request((request) -> request.path(this.homepage)).build();\n\t\t}\n\t\treturn chain.filter(exchange);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/java/de/codecentric/boot/admin/server/ui/web/reactive/package-info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n@NullMarked\npackage de.codecentric.boot.admin.server.ui.web.reactive;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/java/de/codecentric/boot/admin/server/ui/web/servlet/HomepageForwardingFilter.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui.web.servlet;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport jakarta.servlet.Filter;\nimport jakarta.servlet.FilterChain;\nimport jakarta.servlet.FilterConfig;\nimport jakarta.servlet.ServletException;\nimport jakarta.servlet.ServletRequest;\nimport jakarta.servlet.ServletResponse;\nimport jakarta.servlet.http.HttpServletRequest;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.MediaType;\nimport org.springframework.web.util.UrlPathHelper;\n\nimport de.codecentric.boot.admin.server.ui.web.HomepageForwardingFilterConfig;\nimport de.codecentric.boot.admin.server.ui.web.HomepageForwardingMatcher;\n\n/**\n * A servlet filter that forwards matching HTML requests to the home page. Further routing\n * will is done in the browser by VueRouter using history mode.\n *\n * @see <a href=\"https://router.vuejs.org/guide/essentials/history-mode.html\">VueRouter\n * history mode</a>\n */\npublic class HomepageForwardingFilter implements Filter {\n\n\tprivate static final Logger log = LoggerFactory.getLogger(HomepageForwardingFilter.class);\n\n\tprivate final String homepage;\n\n\tprivate final HomepageForwardingMatcher<HttpServletRequest> matcher;\n\n\tpublic HomepageForwardingFilter(String homepage, List<String> routes, List<String> excludedRoutes) {\n\t\tthis.homepage = homepage;\n\t\tUrlPathHelper urlPathHelper = new UrlPathHelper();\n\t\tthis.matcher = new HomepageForwardingMatcher<>(routes, excludedRoutes, HttpServletRequest::getMethod,\n\t\t\t\turlPathHelper::getPathWithinApplication,\n\t\t\t\t(r) -> MediaType.parseMediaTypes(r.getHeader(HttpHeaders.ACCEPT)));\n\t}\n\n\tpublic HomepageForwardingFilter(HomepageForwardingFilterConfig filterConfig) {\n\t\tthis(filterConfig.getHomepage(), filterConfig.getRoutesIncludes(), filterConfig.getRoutesExcludes());\n\t}\n\n\t@Override\n\tpublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)\n\t\t\tthrows IOException, ServletException {\n\t\tif (request instanceof HttpServletRequest httpRequest && this.matcher.test(httpRequest)) {\n\t\t\tlog.trace(\"Forwarding request with URL {} to index\", httpRequest.getRequestURI());\n\t\t\trequest.getRequestDispatcher(this.homepage).forward(request, response);\n\t\t\treturn;\n\t\t}\n\n\t\tchain.doFilter(request, response);\n\t}\n\n\t@Override\n\tpublic void init(FilterConfig filterConfig) {\n\t}\n\n\t@Override\n\tpublic void destroy() {\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/java/de/codecentric/boot/admin/server/ui/web/servlet/package-info.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n@NullMarked\npackage de.codecentric.boot.admin.server.ui.web.servlet;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "de.codecentric.boot.admin.server.ui.config.AdminServerUiAutoConfiguration\nde.codecentric.boot.admin.server.ui.config.SpringNativeServerAutoConfiguration\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/test/java/de/codecentric/boot/admin/server/ui/AbstractAdminUiApplicationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.MediaType;\nimport org.springframework.test.web.reactive.server.WebTestClient;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic abstract class AbstractAdminUiApplicationTest {\n\n\tprivate WebTestClient webClient;\n\n\tprotected void setUp(int port) {\n\t\tthis.webClient = createWebClient(port);\n\t}\n\n\t@Test\n\tpublic void should_return_index() {\n\t\t//@formatter:off\n\t\tthis.webClient.get()\n\t\t\t\t\t.uri(\"/\")\n\t\t\t\t\t.accept(MediaType.TEXT_HTML, MediaType.ALL)\n\t\t\t\t\t.exchange()\n\t\t\t\t\t.expectStatus().isOk()\n\t\t\t\t\t.expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML)\n\t\t\t\t\t.expectBody(String.class)\n\t\t\t\t\t.value((body) -> assertThat(body).contains(\"<title>Spring Boot Admin</title>\"));\n\t\t//@formatter:on\n\t}\n\n\t@Test\n\tpublic void should_not_return_index_for_logfile() {\n\t\t//@formatter:off\n\t\tthis.webClient.get()\n\t\t\t\t\t\t.uri(\"/instances/a973ff14be49/actuator/logfile\")\n\t\t\t\t\t\t.accept(MediaType.TEXT_HTML, MediaType.ALL)\n\t\t\t\t\t\t.exchange()\n\t\t\t\t\t\t.expectStatus().isEqualTo(HttpStatus.SERVICE_UNAVAILABLE);\n\t\t//@formatter:on\n\t}\n\n\t@Test\n\tpublic void should_return_api() {\n\t\t//@formatter:off\n\t\tthis.webClient.get()\n\t\t\t\t\t.uri(\"/applications\")\n\t\t\t\t\t.accept(MediaType.APPLICATION_JSON)\n\t\t\t\t\t.exchange()\n\t\t\t\t\t.expectStatus().isOk()\n\t\t\t\t\t.expectHeader().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)\n\t\t\t\t\t.expectBody(String.class)\n\t\t\t\t\t.value((body) -> assertThat(body).contains(\"[]\"));\n\t\t//@formatter:on\n\t}\n\n\t@Test\n\tpublic void should_return_index_for_ui_path() {\n\t\t//@formatter:off\n\t\tthis.webClient.get()\n\t\t\t\t\t.uri(\"/applications\")\n\t\t\t\t\t.accept(MediaType.TEXT_HTML)\n\t\t\t\t\t.exchange()\n\t\t\t\t\t.expectStatus().isOk()\n\t\t\t\t\t.expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML)\n\t\t\t\t\t.expectBody(String.class)\n\t\t\t\t\t.value((body) -> assertThat(body).contains(\"<title>Spring Boot Admin</title>\"));\n\t\t//@formatter:on\n\t}\n\n\t@Test\n\tpublic void should_return_404_for_unknown_path() {\n\t\t//@formatter:off\n\t\tthis.webClient.get()\n\t\t\t\t\t.uri(\"/unknown\")\n\t\t\t\t\t.accept(MediaType.TEXT_HTML)\n\t\t\t\t\t.exchange()\n\t\t\t\t\t.expectStatus().isNotFound();\n\t\t//@formatter:on\n\t}\n\n\t@Test\n\tpublic void should_return_correct_content_type_for_js_extensions() {\n\t\t//@formatter:off\n\t\tthis.webClient.get()\n\t\t\t\t\t.uri(\"/extensions/custom/custom.abcdef.js\")\n\t\t\t\t\t.header(\"Accept\", \"*/*\")\n\t\t\t\t\t.exchange()\n\t\t\t\t\t.expectStatus().isOk()\n\t\t\t\t\t.expectHeader().contentType(getExpectedMediaTypeForJavaScript());\n\t\t//@formatter:on\n\t}\n\n\tabstract MediaType getExpectedMediaTypeForJavaScript();\n\n\t@Test\n\tpublic void should_return_correct_content_type_for_css_extensions() {\n\t\t//@formatter:off\n\t\tthis.webClient.get()\n\t\t\t\t\t.uri(\"/extensions/custom/custom.abcdef.css\")\n\t\t\t\t\t.header(\"Accept\", \"text/css,*/*;q=0.1\")\n\t\t\t\t\t.exchange()\n\t\t\t\t\t.expectStatus().isOk()\n\t\t\t\t\t.expectHeader().contentType(MediaType.parseMediaType(\"text/css\"));\n\t\t//@formatter:on\n\t}\n\n\t@Test\n\tpublic void should_contain_only_one_language() {\n\t\t//@formatter:off\n\t\tthis.webClient.get()\n\t\t\t\t\t.uri(\"/sba-settings.js\")\n\t\t\t\t\t.accept(MediaType.ALL)\n\t\t\t\t\t.exchange()\n\t\t\t\t\t.expectStatus().isOk()\n\t\t\t\t\t.expectHeader().contentTypeCompatibleWith(\"application/javascript\")\n\t\t\t\t\t.expectBody(String.class)\n\t\t\t\t\t.value((body) -> assertThat(body).contains(\"\\\"availableLanguages\\\":[\\\"de\\\"]\"));\n\t\t//@formatter:on\n\t}\n\n\t@Test\n\tpublic void should_return_defaults_for_pollTimers() {\n\t\t//@formatter:off\n\t\tthis.webClient.get()\n\t\t\t.uri(\"/sba-settings.js\")\n\t\t\t.accept(MediaType.ALL)\n\t\t\t.exchange()\n\t\t\t.expectStatus().isOk()\n\t\t\t.expectHeader().contentTypeCompatibleWith(\"application/javascript\")\n\t\t\t.expectBody(String.class)\n\t\t\t.value((body) -> assertThat(body).contains(\"\\\"pollTimer\\\"\"))\n\t\t\t.value((body) -> assertThat(body).contains(\"\\\"cache\\\":2500\"));\n\t\t//@formatter:on\n\t}\n\n\tprotected WebTestClient createWebClient(int port) {\n\t\treturn WebTestClient.bindToServer().baseUrl(\"http://localhost:\" + port).build();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/test/java/de/codecentric/boot/admin/server/ui/AdminUiReactiveApplicationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui;\n\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.springframework.boot.SpringBootConfiguration;\nimport org.springframework.boot.WebApplicationType;\nimport org.springframework.boot.autoconfigure.EnableAutoConfiguration;\nimport org.springframework.boot.builder.SpringApplicationBuilder;\nimport org.springframework.context.ConfigurableApplicationContext;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.http.MediaType;\nimport org.springframework.security.config.web.server.ServerHttpSecurity;\nimport org.springframework.security.web.server.SecurityWebFilterChain;\n\nimport de.codecentric.boot.admin.server.config.EnableAdminServer;\n\nclass AdminUiReactiveApplicationTest extends AbstractAdminUiApplicationTest {\n\n\tprivate static ConfigurableApplicationContext instance;\n\n\tprivate static Integer port;\n\n\t@BeforeAll\n\tstatic void setUp() {\n\t\tinstance = new SpringApplicationBuilder().sources(TestAdminApplication.class)\n\t\t\t.web(WebApplicationType.REACTIVE)\n\t\t\t.run(\"--server.port=0\",\n\t\t\t\t\t\"--spring.boot.admin.ui.extension-resource-locations=classpath:/META-INF/test-extensions/\",\n\t\t\t\t\t\"--spring.boot.admin.ui.available-languages=de\");\n\n\t\tport = instance.getEnvironment().getProperty(\"local.server.port\", Integer.class, 0);\n\t}\n\n\t@AfterAll\n\tstatic void shutdown() {\n\t\tinstance.close();\n\t}\n\n\t@BeforeEach\n\tvoid setupEach() {\n\t\tsuper.setUp(port);\n\t}\n\n\t@Override\n\tMediaType getExpectedMediaTypeForJavaScript() {\n\t\treturn MediaType.parseMediaType(\"application/javascript\");\n\t}\n\n\t@EnableAdminServer\n\t@EnableAutoConfiguration\n\t@SpringBootConfiguration\n\tpublic static class TestAdminApplication {\n\n\t\t@Bean\n\t\tpublic SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {\n\t\t\treturn http.authorizeExchange((authorizeExchange) -> authorizeExchange.anyExchange().permitAll())\n\t\t\t\t.csrf(ServerHttpSecurity.CsrfSpec::disable)\n\t\t\t\t.build();\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/test/java/de/codecentric/boot/admin/server/ui/AdminUiServletApplicationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.springframework.boot.SpringBootConfiguration;\nimport org.springframework.boot.WebApplicationType;\nimport org.springframework.boot.autoconfigure.EnableAutoConfiguration;\nimport org.springframework.boot.builder.SpringApplicationBuilder;\nimport org.springframework.context.ConfigurableApplicationContext;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.http.MediaType;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;\nimport org.springframework.security.web.SecurityFilterChain;\n\nimport de.codecentric.boot.admin.server.config.EnableAdminServer;\n\nclass AdminUiServletApplicationTest extends AbstractAdminUiApplicationTest {\n\n\tprivate ConfigurableApplicationContext instance;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tthis.instance = new SpringApplicationBuilder().sources(TestAdminApplication.class)\n\t\t\t.web(WebApplicationType.SERVLET)\n\t\t\t.run(\"--server.port=0\",\n\t\t\t\t\t\"--spring.boot.admin.ui.extension-resource-locations=classpath:/META-INF/test-extensions/\",\n\t\t\t\t\t\"--spring.boot.admin.ui.available-languages=de\");\n\n\t\tsuper.setUp(this.instance.getEnvironment().getProperty(\"local.server.port\", Integer.class, 0));\n\t}\n\n\t@AfterEach\n\tvoid shutdown() {\n\t\tthis.instance.close();\n\t}\n\n\t@Override\n\tMediaType getExpectedMediaTypeForJavaScript() {\n\t\treturn MediaType.parseMediaType(\"text/javascript\");\n\t}\n\n\t@EnableAdminServer\n\t@EnableAutoConfiguration\n\t@SpringBootConfiguration\n\tpublic static class TestAdminApplication {\n\n\t\t@Configuration(proxyBeanMethods = false)\n\t\tpublic static class SecurityConfiguration {\n\n\t\t\t@Bean\n\t\t\tprotected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n\t\t\t\treturn http.csrf(AbstractHttpConfigurer::disable)\n\t\t\t\t\t.authorizeHttpRequests((authz) -> authz.anyRequest().permitAll())\n\t\t\t\t\t.anonymous((config) -> config.principal(\"anonymousUser\"))\n\t\t\t\t\t.build();\n\t\t\t}\n\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/test/java/de/codecentric/boot/admin/server/ui/config/AdminServerUiAutoConfigurationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui.config;\n\nimport jakarta.servlet.FilterChain;\nimport jakarta.servlet.ServletResponse;\nimport org.assertj.core.api.WithAssertions;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.springframework.boot.autoconfigure.AutoConfigurations;\nimport org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;\nimport org.springframework.boot.test.context.runner.WebApplicationContextRunner;\nimport org.springframework.boot.webflux.autoconfigure.WebFluxProperties;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.MediaType;\nimport org.springframework.mock.http.server.reactive.MockServerHttpRequest;\nimport org.springframework.mock.web.MockHttpServletRequest;\nimport org.springframework.mock.web.MockHttpServletResponse;\nimport org.springframework.mock.web.server.MockServerWebExchange;\nimport org.springframework.web.server.ServerWebExchange;\nimport org.springframework.web.server.WebFilterChain;\n\nimport de.codecentric.boot.admin.server.config.AdminServerMarkerConfiguration;\nimport de.codecentric.boot.admin.server.config.AdminServerProperties;\nimport de.codecentric.boot.admin.server.config.SpringBootAdminServerEnabledCondition;\nimport de.codecentric.boot.admin.server.ui.web.reactive.HomepageForwardingFilter;\n\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.atMostOnce;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.verify;\n\n@ExtendWith(MockitoExtension.class)\nclass AdminServerUiAutoConfigurationTest implements WithAssertions {\n\n\t@Test\n\tvoid testNormalizeHomepageUrl() {\n\t\tassertThat(AdminServerUiAutoConfiguration.normalizeHomepageUrl(\"/test/\")).isEqualTo(\"/test\");\n\t}\n\n\t@Nested\n\tclass ReactiveUiConfigurationTest {\n\n\t\tprivate final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()\n\t\t\t.withPropertyValues(\"--spring.boot.admin.ui.available-languages=de\", \"--spring.webflux.base-path=test\")\n\t\t\t.withBean(AdminServerProperties.class)\n\t\t\t.withBean(WebFluxProperties.class)\n\t\t\t.withConfiguration(AutoConfigurations.of(AdminServerUiAutoConfiguration.class));\n\n\t\t@Mock\n\t\tWebFilterChain webFilterChain;\n\n\t\t@ParameterizedTest\n\t\t@CsvSource({ \"/test/extensions/myextension\", \"/test/instances/1/actuator/heapdump\",\n\t\t\t\t\"/test/instances/1/actuator/logfile\" })\n\t\tvoid contextPathIsRespectedInExcludedRoutes(String routeExcludes) {\n\t\t\tMockServerHttpRequest serverHttpRequest = MockServerHttpRequest.get(routeExcludes)\n\t\t\t\t.header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE)\n\t\t\t\t.build();\n\n\t\t\tServerWebExchange serverWebExchange = spy(MockServerWebExchange.from(serverHttpRequest));\n\n\t\t\tthis.contextRunner\n\t\t\t\t.withUserConfiguration(SpringBootAdminServerEnabledCondition.class,\n\t\t\t\t\t\tAdminServerMarkerConfiguration.Marker.class)\n\t\t\t\t.run((context) -> {\n\t\t\t\t\tHomepageForwardingFilter bean = context.getBean(HomepageForwardingFilter.class);\n\t\t\t\t\tbean.filter(serverWebExchange, webFilterChain);\n\n\t\t\t\t\tverify(serverWebExchange, never()).mutate();\n\t\t\t\t});\n\t\t}\n\n\t\t@ParameterizedTest\n\t\t@CsvSource({ \"/test/about\", \"/test/applications\", \"/test/instances\", \"/test/journal\", \"/test/wallboard\",\n\t\t\t\t\"/test/external\" })\n\t\tvoid contextPathIsRespectedInIncludedRoutes(String routeIncludes) {\n\t\t\tMockServerHttpRequest serverHttpRequest = MockServerHttpRequest.get(routeIncludes)\n\t\t\t\t.header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE)\n\t\t\t\t.build();\n\n\t\t\tServerWebExchange serverWebExchange = spy(MockServerWebExchange.from(serverHttpRequest));\n\n\t\t\tthis.contextRunner\n\t\t\t\t.withUserConfiguration(SpringBootAdminServerEnabledCondition.class,\n\t\t\t\t\t\tAdminServerMarkerConfiguration.Marker.class)\n\t\t\t\t.run((context) -> {\n\t\t\t\t\tHomepageForwardingFilter bean = context.getBean(HomepageForwardingFilter.class);\n\t\t\t\t\tbean.filter(serverWebExchange, webFilterChain);\n\n\t\t\t\t\tverify(serverWebExchange, atMostOnce()).mutate();\n\t\t\t\t});\n\t\t}\n\n\t}\n\n\t@Nested\n\tclass ServletUiConfiguration {\n\n\t\tprivate final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()\n\t\t\t.withPropertyValues(\"--spring.boot.admin.ui.available-languages=de\", \"--spring.boot.admin.contextPath=test\")\n\t\t\t.withBean(AdminServerProperties.class)\n\t\t\t.withConfiguration(AutoConfigurations.of(AdminServerUiAutoConfiguration.class));\n\n\t\t@ParameterizedTest\n\t\t@CsvSource({ \"/test/extensions/myextension\", \"/test/instances/1/actuator/heapdump\",\n\t\t\t\t\"/test/instances/1/actuator/logfile\" })\n\t\tvoid contextPathIsRespectedInExcludedRoutes(String routeExcludes) {\n\t\t\tMockHttpServletRequest httpServletRequest = spy(new MockHttpServletRequest(\"GET\", routeExcludes));\n\t\t\thttpServletRequest.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE);\n\n\t\t\tthis.contextRunner\n\t\t\t\t.withUserConfiguration(SpringBootAdminServerEnabledCondition.class,\n\t\t\t\t\t\tAdminServerMarkerConfiguration.Marker.class)\n\t\t\t\t.run((context) -> {\n\t\t\t\t\tde.codecentric.boot.admin.server.ui.web.servlet.HomepageForwardingFilter bean = context\n\t\t\t\t\t\t.getBean(de.codecentric.boot.admin.server.ui.web.servlet.HomepageForwardingFilter.class);\n\t\t\t\t\tbean.doFilter(httpServletRequest, mock(ServletResponse.class), mock(FilterChain.class));\n\n\t\t\t\t\tverify(httpServletRequest, never()).getRequestDispatcher(any());\n\t\t\t\t});\n\t\t}\n\n\t\t@ParameterizedTest\n\t\t@CsvSource({ \"/test/about\", \"/test/applications\", \"/test/instances\", \"/test/journal\", \"/test/wallboard\",\n\t\t\t\t\"/test/external\" })\n\t\tvoid contextPathIsRespectedInIncludedRoutes(String routeIncludes) {\n\t\t\tMockHttpServletRequest httpServletRequest = spy(new MockHttpServletRequest(\"GET\", routeIncludes));\n\t\t\thttpServletRequest.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE);\n\n\t\t\tthis.contextRunner\n\t\t\t\t.withUserConfiguration(SpringBootAdminServerEnabledCondition.class,\n\t\t\t\t\t\tAdminServerMarkerConfiguration.Marker.class)\n\t\t\t\t.run((context) -> {\n\t\t\t\t\tde.codecentric.boot.admin.server.ui.web.servlet.HomepageForwardingFilter bean = context\n\t\t\t\t\t\t.getBean(de.codecentric.boot.admin.server.ui.web.servlet.HomepageForwardingFilter.class);\n\t\t\t\t\tbean.doFilter(httpServletRequest, new MockHttpServletResponse(), mock(FilterChain.class));\n\n\t\t\t\t\tverify(httpServletRequest, atMostOnce()).getRequestDispatcher(any());\n\t\t\t\t});\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/test/java/de/codecentric/boot/admin/server/ui/config/AdminServerUiPropertiesTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui.config;\n\nimport java.time.Duration;\n\nimport org.assertj.core.api.WithAssertions;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.TestPropertySource;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\n\n@SpringBootTest(classes = AdminServerUiProperties.class)\n@ExtendWith(SpringExtension.class)\n@TestPropertySource(properties = { \"spring.boot.admin.ui.cache.maxAge: -1s\", \"spring.boot.admin.ui.cache.noCache: true\",\n\t\t\"spring.boot.admin.ui.cache.noStore: true\", \"spring.boot.admin.ui.theme.color: #ffffff\",\n\t\t\"spring.boot.admin.ui.theme.palette.50: #50\", \"spring.boot.admin.ui.theme.palette.100: #100\",\n\t\t\"spring.boot.admin.ui.theme.palette.200: #200\", \"spring.boot.admin.ui.theme.palette.300: #300\",\n\t\t\"spring.boot.admin.ui.theme.palette.400: #400\", \"spring.boot.admin.ui.theme.palette.500: #500\",\n\t\t\"spring.boot.admin.ui.theme.palette.600: #600\", \"spring.boot.admin.ui.theme.palette.700: #700\",\n\t\t\"spring.boot.admin.ui.theme.palette.800: #800\", \"spring.boot.admin.ui.theme.palette.900: #900\" })\n@EnableConfigurationProperties({ AdminServerUiProperties.class })\nclass AdminServerUiPropertiesTest implements WithAssertions {\n\n\t@Autowired\n\tprivate AdminServerUiProperties adminServerUiProperties;\n\n\t@Nested\n\tclass CacheTest {\n\n\t\t@Test\n\t\tvoid maxAge() {\n\t\t\tassertThat(adminServerUiProperties.getCache().getMaxAge()).isEqualTo(Duration.ofSeconds(-1));\n\t\t}\n\n\t\t@Test\n\t\tvoid noCache() {\n\t\t\tassertThat(adminServerUiProperties.getCache().getNoCache()).isEqualTo(true);\n\t\t}\n\n\t\t@Test\n\t\tvoid noStore() {\n\t\t\tassertThat(adminServerUiProperties.getCache().getNoStore()).isEqualTo(true);\n\t\t}\n\n\t}\n\n\t@Nested\n\tclass ThemeTest {\n\n\t\t@Test\n\t\tvoid color() {\n\t\t\tassertThat(adminServerUiProperties.getTheme().getColor()).isEqualTo(\"#ffffff\");\n\t\t}\n\n\t\t@Nested\n\t\tclass PaletteTest {\n\n\t\t\t@Test\n\t\t\tvoid shades() {\n\t\t\t\tAdminServerUiProperties.UiTheme theme = adminServerUiProperties.getTheme();\n\t\t\t\tAdminServerUiProperties.Palette palette = theme.getPalette();\n\n\t\t\t\tassertThat(palette.getShade50()).isEqualTo(\"#50\");\n\t\t\t\tassertThat(palette.getShade100()).isEqualTo(\"#100\");\n\t\t\t\tassertThat(palette.getShade200()).isEqualTo(\"#200\");\n\t\t\t\tassertThat(palette.getShade300()).isEqualTo(\"#300\");\n\t\t\t\tassertThat(palette.getShade400()).isEqualTo(\"#400\");\n\t\t\t\tassertThat(palette.getShade500()).isEqualTo(\"#500\");\n\t\t\t\tassertThat(palette.getShade600()).isEqualTo(\"#600\");\n\t\t\t\tassertThat(palette.getShade700()).isEqualTo(\"#700\");\n\t\t\t\tassertThat(palette.getShade800()).isEqualTo(\"#800\");\n\t\t\t\tassertThat(palette.getShade900()).isEqualTo(\"#900\");\n\t\t\t}\n\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/test/java/de/codecentric/boot/admin/server/ui/config/CssColorUtilsTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui.config;\n\nimport org.assertj.core.api.WithAssertions;\nimport org.junit.jupiter.api.Test;\n\nclass CssColorUtilsTest implements WithAssertions {\n\n\t@Test\n\tvoid hexToRgb() {\n\t\tCssColorUtils cssColorUtils = new CssColorUtils();\n\n\t\tassertThat(cssColorUtils.hexToRgb(new AdminServerUiProperties.Palette().getShade50()))\n\t\t\t.isEqualTo(\"238, 252, 250\");\n\t}\n\n\t@Test\n\tvoid hexToRgb_throws_exception_on_invalid_format() {\n\t\tCssColorUtils cssColorUtils = new CssColorUtils();\n\n\t\tassertThatThrownBy(() -> cssColorUtils.hexToRgb(\"EEFCFA\")).isInstanceOf(IllegalArgumentException.class);\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/test/java/de/codecentric/boot/admin/server/ui/config/ReactiveAdminServerUiAutoConfigurationAdminContextPathTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui.config;\n\nimport org.springframework.boot.autoconfigure.AutoConfigurations;\nimport org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;\nimport org.springframework.boot.webflux.autoconfigure.WebFluxProperties;\n\nimport de.codecentric.boot.admin.server.config.AdminServerProperties;\n\npublic class ReactiveAdminServerUiAutoConfigurationAdminContextPathTest\n\t\textends ReactiveAdminServerUiAutoConfigurationTest {\n\n\t@Override\n\tprotected ReactiveWebApplicationContextRunner getContextRunner() {\n\t\treturn new ReactiveWebApplicationContextRunner()\n\t\t\t.withPropertyValues(\"--spring.boot.admin.ui.available-languages=de\", \"--spring.boot.admin.contextPath=test\",\n\t\t\t\t\t\"--spring.boot.admin.ui.additional-route-excludes[0]=/instances/*/actuator/some-extension/**\")\n\t\t\t.withBean(AdminServerProperties.class)\n\t\t\t.withBean(WebFluxProperties.class)\n\t\t\t.withConfiguration(AutoConfigurations.of(AdminServerUiAutoConfiguration.class));\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/test/java/de/codecentric/boot/admin/server/ui/config/ReactiveAdminServerUiAutoConfigurationBothPathsTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui.config;\n\nimport org.springframework.boot.autoconfigure.AutoConfigurations;\nimport org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;\nimport org.springframework.boot.webflux.autoconfigure.WebFluxProperties;\n\nimport de.codecentric.boot.admin.server.config.AdminServerProperties;\n\npublic class ReactiveAdminServerUiAutoConfigurationBothPathsTest extends ReactiveAdminServerUiAutoConfigurationTest {\n\n\t@Override\n\tprotected ReactiveWebApplicationContextRunner getContextRunner() {\n\t\treturn new ReactiveWebApplicationContextRunner()\n\t\t\t.withPropertyValues(\"--spring.boot.admin.ui.available-languages=de\",\n\t\t\t\t\t\"--spring.boot.admin.contextPath=different\", \"--spring.webflux.base-path=test\",\n\t\t\t\t\t\"--spring.boot.admin.ui.additional-route-excludes[0]=/instances/*/actuator/some-extension/**\")\n\t\t\t.withBean(AdminServerProperties.class)\n\t\t\t.withBean(WebFluxProperties.class)\n\t\t\t.withConfiguration(AutoConfigurations.of(AdminServerUiAutoConfiguration.class));\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/test/java/de/codecentric/boot/admin/server/ui/config/ReactiveAdminServerUiAutoConfigurationTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui.config;\n\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.MediaType;\nimport org.springframework.mock.http.server.reactive.MockServerHttpRequest;\nimport org.springframework.mock.web.server.MockServerWebExchange;\nimport org.springframework.web.server.ServerWebExchange;\nimport org.springframework.web.server.WebFilterChain;\n\nimport de.codecentric.boot.admin.server.config.AdminServerMarkerConfiguration;\nimport de.codecentric.boot.admin.server.config.SpringBootAdminServerEnabledCondition;\nimport de.codecentric.boot.admin.server.ui.web.reactive.HomepageForwardingFilter;\n\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.verify;\n\n@ExtendWith(MockitoExtension.class)\npublic abstract class ReactiveAdminServerUiAutoConfigurationTest {\n\n\tprotected abstract ReactiveWebApplicationContextRunner getContextRunner();\n\n\t@Mock\n\tWebFilterChain webFilterChain;\n\n\t@ParameterizedTest\n\t@CsvSource({ \"/test/extensions/myextension\", \"/test/instances/1/actuator/heapdump\",\n\t\t\t\"/test/instances/1/actuator/logfile\", \"/test/instances/1/actuator/some-extension/file.html\" })\n\tpublic void contextPathIsRespectedInExcludedRoutes(String routeExcludes) {\n\t\tMockServerHttpRequest serverHttpRequest = MockServerHttpRequest.get(routeExcludes)\n\t\t\t.header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE)\n\t\t\t.build();\n\n\t\tServerWebExchange serverWebExchange = spy(MockServerWebExchange.from(serverHttpRequest));\n\n\t\tthis.getContextRunner()\n\t\t\t.withUserConfiguration(SpringBootAdminServerEnabledCondition.class,\n\t\t\t\t\tAdminServerMarkerConfiguration.Marker.class)\n\t\t\t.run((context) -> {\n\t\t\t\tHomepageForwardingFilter bean = context.getBean(HomepageForwardingFilter.class);\n\t\t\t\tbean.filter(serverWebExchange, webFilterChain);\n\n\t\t\t\tverify(serverWebExchange, never()).mutate();\n\t\t\t});\n\t}\n\n\t@ParameterizedTest\n\t@CsvSource({ \"/test/about\", \"/test/applications\", \"/test/instances\", \"/test/journal\", \"/test/wallboard\",\n\t\t\t\"/test/external\" })\n\tpublic void contextPathIsRespectedInIncludedRoutes(String routeIncludes) {\n\t\tMockServerHttpRequest serverHttpRequest = MockServerHttpRequest.get(routeIncludes)\n\t\t\t.header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE)\n\t\t\t.build();\n\n\t\tServerWebExchange serverWebExchange = spy(MockServerWebExchange.from(serverHttpRequest));\n\n\t\tthis.getContextRunner()\n\t\t\t.withUserConfiguration(SpringBootAdminServerEnabledCondition.class,\n\t\t\t\t\tAdminServerMarkerConfiguration.Marker.class)\n\t\t\t.run((context) -> {\n\t\t\t\tHomepageForwardingFilter bean = context.getBean(HomepageForwardingFilter.class);\n\t\t\t\tbean.filter(serverWebExchange, webFilterChain);\n\n\t\t\t\tverify(serverWebExchange).mutate();\n\t\t\t});\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/test/java/de/codecentric/boot/admin/server/ui/config/ReactiveAdminServerUiAutoConfigurationWebfluxBasePathTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui.config;\n\nimport org.springframework.boot.autoconfigure.AutoConfigurations;\nimport org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;\nimport org.springframework.boot.webflux.autoconfigure.WebFluxProperties;\n\nimport de.codecentric.boot.admin.server.config.AdminServerProperties;\n\npublic class ReactiveAdminServerUiAutoConfigurationWebfluxBasePathTest\n\t\textends ReactiveAdminServerUiAutoConfigurationTest {\n\n\t@Override\n\tprotected ReactiveWebApplicationContextRunner getContextRunner() {\n\t\treturn new ReactiveWebApplicationContextRunner()\n\t\t\t.withPropertyValues(\"--spring.boot.admin.ui.available-languages=de\", \"--spring.webflux.base-path=test\",\n\t\t\t\t\t\"--spring.boot.admin.ui.additional-route-excludes[0]=/instances/*/actuator/some-extension/**\")\n\t\t\t.withBean(AdminServerProperties.class)\n\t\t\t.withBean(WebFluxProperties.class)\n\t\t\t.withConfiguration(AutoConfigurations.of(AdminServerUiAutoConfiguration.class));\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/test/java/de/codecentric/boot/admin/server/ui/extensions/UiExtensionsScannerTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui.extensions;\n\nimport java.io.IOException;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.core.io.support.PathMatchingResourcePatternResolver;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass UiExtensionsScannerTest {\n\n\tprivate final UiExtensionsScanner scanner = new UiExtensionsScanner(new PathMatchingResourcePatternResolver());\n\n\t@Test\n\tvoid should_find_extensions() throws IOException {\n\t\tUiExtensions extensions = this.scanner.scan(\"classpath:/META-INF/test-extensions/\");\n\t\tassertThat(extensions.getCssExtensions()).contains(new UiExtension(\"custom/custom.abcdef.css\",\n\t\t\t\t\"classpath:/META-INF/test-extensions/custom/custom.abcdef.css\"));\n\n\t\tassertThat(extensions.getJsExtensions()).contains(new UiExtension(\"custom/custom.abcdef.js\",\n\t\t\t\t\"classpath:/META-INF/test-extensions/custom/custom.abcdef.js\"));\n\t}\n\n\t@Test\n\tvoid should_not_find_extensions() throws IOException {\n\t\tUiExtensions extensions = this.scanner.scan(\"classpath:/META-INF/NO-test-extensions/\");\n\t\tassertThat(extensions.getCssExtensions()).isEmpty();\n\t\tassertThat(extensions.getJsExtensions()).isEmpty();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/test/java/de/codecentric/boot/admin/server/ui/extensions/UiRoutesScannerTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui.extensions;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.core.io.support.PathMatchingResourcePatternResolver;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass UiRoutesScannerTest {\n\n\tprivate final UiRoutesScanner scanner = new UiRoutesScanner(new PathMatchingResourcePatternResolver());\n\n\t@Test\n\tvoid should_find_route() throws IOException {\n\t\tList<String> routes = this.scanner.scan(\"classpath:/META-INF/test-extensions/\");\n\t\tassertThat(routes).containsExactlyInAnyOrder(\"/custom/**\");\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/test/java/de/codecentric/boot/admin/server/ui/web/HomepageForwardingMatcherTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui.web;\n\nimport java.util.List;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.http.MediaType;\n\nimport static java.util.Collections.singletonList;\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass HomepageForwardingMatcherTest {\n\n\tprivate final HomepageForwardingMatcher<MockRequest> matcher = new HomepageForwardingMatcher<>(\n\t\t\tsingletonList(\"/viewRoute/**\"), singletonList(\"/viewRoute/*/exclude\"), MockRequest::method,\n\t\t\tMockRequest::path, MockRequest::accepts);\n\n\t@Test\n\tvoid should_return_false_when_method_is_not_get() {\n\t\tassertThat(this.matcher.test(new MockRequest(\"POST\", \"/viewRoute\", singletonList(MediaType.TEXT_HTML))))\n\t\t\t.isFalse();\n\t}\n\n\t@Test\n\tvoid should_return_false_when_path_does_not_match() {\n\t\tassertThat(this.matcher.test(new MockRequest(\"GET\", \"/api\", singletonList(MediaType.TEXT_HTML)))).isFalse();\n\t}\n\n\t@Test\n\tvoid should_return_false_when_accepts_does_not_match() {\n\t\tassertThat(this.matcher.test(new MockRequest(\"GET\", \"/viewRoute\", singletonList(MediaType.APPLICATION_XML))))\n\t\t\t.isFalse();\n\t}\n\n\t@Test\n\tvoid should_return_false_when_path_is_excluded() {\n\t\tassertThat(this.matcher\n\t\t\t.test(new MockRequest(\"GET\", \"/viewRoute/12345/exclude\", singletonList(MediaType.TEXT_HTML)))).isFalse();\n\t}\n\n\t@Test\n\tvoid should_return_true() {\n\t\tassertThat(this.matcher\n\t\t\t.test(new MockRequest(\"GET\", \"/viewRoute/detail?query\", singletonList(MediaType.TEXT_HTML)))).isTrue();\n\t}\n\n\tprivate record MockRequest(String method, String path, List<MediaType> accepts) {\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/test/java/de/codecentric/boot/admin/server/ui/web/UiControllerTest.java",
    "content": "/*\n * Copyright 2014-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage de.codecentric.boot.admin.server.ui.web;\n\nimport java.util.List;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.springframework.test.web.servlet.MockMvc;\nimport org.springframework.test.web.servlet.setup.MockMvcBuilders;\n\nimport de.codecentric.boot.admin.server.ui.extensions.UiExtensions;\nimport de.codecentric.boot.admin.server.web.servlet.AdminControllerHandlerMapping;\n\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;\nimport static org.springframework.util.CollectionUtils.isEmpty;\n\nclass UiControllerTest {\n\n\t@Test\n\tvoid should_use_default_url() throws Exception {\n\t\tMockMvc mockMvc = setupController(\"\", List.of());\n\n\t\tmockMvc.perform(get(\"http://example/\"))\n\t\t\t.andExpect(status().isOk())\n\t\t\t.andExpect(view().name(\"index\"))\n\t\t\t.andExpect(model().attribute(\"baseUrl\", \"http://example/\"));\n\t}\n\n\t@Test\n\tvoid should_use_path_from_public_url() throws Exception {\n\t\tMockMvc mockMvc = setupController(\"/public\", List.of());\n\n\t\tmockMvc.perform(get(\"http://example/\"))\n\t\t\t.andExpect(status().isOk())\n\t\t\t.andExpect(view().name(\"index\"))\n\t\t\t.andExpect(model().attribute(\"baseUrl\", \"http://example/public/\"));\n\t}\n\n\t@Test\n\tvoid should_use_host_and_path_from_public_url() throws Exception {\n\t\tMockMvc mockMvc = setupController(\"http://public/public\", List.of());\n\n\t\tmockMvc.perform(get(\"http://example/\"))\n\t\t\t.andExpect(status().isOk())\n\t\t\t.andExpect(view().name(\"index\"))\n\t\t\t.andExpect(model().attribute(\"baseUrl\", \"http://public/public/\"));\n\t}\n\n\t@Test\n\tvoid should_use_scheme_host_and_path_from_public_url() throws Exception {\n\t\tMockMvc mockMvc = setupController(\"https://public/public\", List.of());\n\n\t\tmockMvc.perform(get(\"http://example/\"))\n\t\t\t.andExpect(status().isOk())\n\t\t\t.andExpect(view().name(\"index\"))\n\t\t\t.andExpect(model().attribute(\"baseUrl\", \"https://public/public/\"));\n\t}\n\n\t@ParameterizedTest\n\t@CsvSource(value = { //\n\t\t\t\"link without children with url, https://codecentric.de, false, false\", //\n\t\t\t\"link without children without url, null, false, true\", //\n\t\t\t\"link with children, null, true, false\" }, //\n\t\t\tnullValues = { \"null\" })\n\tvoid should_validate_external_views(String label, String url, boolean hasChildren, boolean shouldFail) {\n\t\ttry {\n\t\t\tUiController.ExternalView externalView = new UiController.ExternalView(label, url, 1, false,\n\t\t\t\t\thasChildren\n\t\t\t\t\t\t\t? List.of(new UiController.ExternalView(\"child\", \"https://urli.com\", 1, false, List.of()))\n\t\t\t\t\t\t\t: List.of());\n\n\t\t\tsetupController(\"https://mysba.com\", List.of(externalView));\n\t\t}\n\t\tcatch (Exception ex) {\n\t\t\tif (!shouldFail) {\n\t\t\t\tfail(\"Initialization of External View should have failed\");\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate MockMvc setupController(String publicUrl, List<UiController.ExternalView> externalViews) {\n\t\tvar uiControllerSettings = UiController.Settings.builder();\n\t\tif (!isEmpty(externalViews)) {\n\t\t\tuiControllerSettings.externalViews(externalViews);\n\t\t}\n\t\treturn MockMvcBuilders\n\t\t\t.standaloneSetup(new UiController(publicUrl, UiExtensions.EMPTY, uiControllerSettings.build()))\n\t\t\t.setCustomHandlerMapping(() -> new AdminControllerHandlerMapping(\"\"))\n\t\t\t.build();\n\t}\n\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/test/resources/META-INF/test-extensions/custom/custom.abcdef.css",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/test/resources/META-INF/test-extensions/custom/custom.abcdef.js",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/test/resources/META-INF/test-extensions/custom/custom.txt",
    "content": "/*\n * Copyright 2014-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/test/resources/META-INF/test-extensions/custom/routes.txt",
    "content": "/custom/**\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/test/resources/application.yml",
    "content": "server:\n  shutdown: immediate\n"
  },
  {
    "path": "spring-boot-admin-server-ui/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker",
    "content": "mock-maker-inline\n"
  },
  {
    "path": "spring-boot-admin-server-ui/tailwind.config.js",
    "content": "module.exports = {\n  mode: 'jit',\n  darkMode: 'class',\n  content: ['./index.html', './login.html', './src/**/*.{vue,js,ts,jsx,tsx}'],\n  safelist: [\n    {\n      pattern: /.*-sba-.*/,\n    },\n  ],\n  theme: {\n    extend: {\n      colors: {\n        sba: {\n          DEFAULT: 'var(--main-500)',\n          50: withOpacity('--main-50'),\n          100: withOpacity('--main-100'),\n          200: withOpacity('--main-200'),\n          300: withOpacity('--main-300'),\n          400: withOpacity('--main-400'),\n          500: withOpacity('--main-500'),\n          600: withOpacity('--main-600'),\n          700: withOpacity('--main-700'),\n          800: withOpacity('--main-800'),\n          900: withOpacity('--main-900'),\n        },\n        orange: {\n          DEFAULT: '#ED8936',\n          50: '#FCECDF',\n          100: '#FAE1CC',\n          200: '#F7CBA6',\n          300: '#F4B581',\n          400: '#F09F5B',\n          500: '#ED8936',\n          600: '#D86C13',\n          700: '#A4520F',\n          800: '#71390A',\n          900: '#3D1F05',\n        },\n        teal: {\n          DEFAULT: '#38B2AC',\n          50: '#B9E9E7',\n          100: '#A9E4E1',\n          200: '#8ADAD6',\n          300: '#6BD0CB',\n          400: '#4CC7C1',\n          500: '#38B2AC',\n          600: '#2B8783',\n          700: '#1D5D5A',\n          800: '#103230',\n          900: '#020707',\n        },\n      },\n      maxWidth: {\n        sm: '24rem',\n      },\n      columns: {\n        2: '14rem',\n      },\n      fontSize: {\n        '9xl': '7rem',\n      },\n    },\n  },\n  plugins: [require('@tailwindcss/forms'), require('@tailwindcss/typography')],\n};\n\nfunction withOpacity(variableName) {\n  return ({ opacityValue }) => {\n    if (opacityValue !== undefined) {\n      return `rgba(var(${variableName}), ${opacityValue})`;\n    }\n    return `rgb(var(${variableName}))`;\n  };\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Node\",\n    \"strict\": true,\n    \"jsx\": \"preserve\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"lib\": [\n      \"ESNext\",\n      \"DOM\"\n    ],\n    \"skipLibCheck\": true,\n    \"noEmit\": true,\n    \"types\": [\"vitest/globals\"],\n    \"paths\": {\n      \"@/*\": [\n        \"./src/main/frontend/*\"\n      ]\n    }\n  },\n  \"include\": [\n    \"**/*.ts\",\n    \"**/*.d.ts\",\n    \"**/*.tsx\",\n    \"**/*.vue\",\n    \"vite.config.mts\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"./tsconfig.node.json\"\n    }\n  ]\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"composite\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Node\",\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"paths\": {\n      \"@/*\": [\n        \"./src/main/frontend/*\"\n      ]\n    }\n  },\n  \"include\": [\n    \"**/*.ts\",\n    \"**/*.d.ts\",\n    \"**/*.tsx\",\n    \"**/*.vue\",\n    \"vite.config.mts\"\n  ]\n}\n"
  },
  {
    "path": "spring-boot-admin-server-ui/vite.config.mts",
    "content": "/// <reference types=\"vitest\" />\nimport vue from '@vitejs/plugin-vue';\nimport { resolve } from 'path';\nimport { visualizer } from 'rollup-plugin-visualizer';\nimport { defineConfig, loadEnv } from 'vite';\nimport { viteStaticCopy } from 'vite-plugin-static-copy';\n\nimport postcss from './postcss.config';\n\nconst frontendDir = resolve(__dirname, 'src/main/frontend');\nconst outDir = resolve(__dirname, 'target/dist');\n\nexport default defineConfig(({ mode }) => {\n  process.env = { ...process.env, ...loadEnv(mode, process.cwd()) };\n\n  const isDevMode = process.env.NODE_ENV === 'development';\n  const isTestMode = process.env.TEST === 'true';\n\n  return {\n    base: './',\n    define: {\n      __VUE_PROD_DEVTOOLS__: isDevMode,\n      __PROJECT_VERSION__: JSON.stringify(\n        `${process.env.PROJECT_VERSION || '0.0.0'}`,\n      ),\n    },\n    plugins: [\n      vue(),\n      visualizer(() => {\n        return {\n          filename: resolve(__dirname, 'target/vite.bundle-size-analyzer.html'),\n        };\n      }),\n      viteStaticCopy({\n        targets: isTestMode\n          ? []\n          : [\n              {\n                src: ['sba-settings.js', 'assets/'],\n                dest: outDir,\n              },\n            ],\n      }),\n    ],\n    css: {\n      postcss,\n      preprocessorOptions: {\n        scss: {\n          api: 'modern-compiler',\n        },\n      },\n    },\n    test: {\n      root: __dirname,\n      globals: true,\n      environment: 'jsdom',\n      setupFiles: [resolve(frontendDir, 'tests/setup.ts')],\n      env: {\n        LANG: 'de_DE.UTF-8',\n        LC_ALL: 'de_DE.UTF-8',\n        TZ: 'Europe/Berlin',\n      },\n      include: [\n        resolve(\n          frontendDir,\n          '**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}',\n        ),\n      ],\n    },\n    root: frontendDir,\n    build: {\n      target: 'es2022',\n      outDir,\n      rollupOptions: {\n        input: {\n          sba: resolve(frontendDir, './index.html'),\n          login: resolve(frontendDir, './login.html'),\n        },\n        external: ['sba-settings.js', 'public/variables.css'],\n      },\n    },\n    resolve: {\n      alias: {\n        '@': frontendDir,\n      },\n      extensions: ['.vue', '.js', '.json', '.ts'],\n    },\n    server: {\n      proxy: {\n        '^(/sba-settings.js|/variables.css)': {\n          target: 'http://localhost:8080',\n          changeOrigin: true,\n        },\n        '^/(applications|instances|notifications|extensions)(?:/.*)?$': {\n          target: 'http://localhost:8080',\n          changeOrigin: true,\n          bypass: (req) => {\n            const isEventStream = req.headers.accept === 'text/event-stream';\n            const isAjaxCall =\n              req.headers['x-requested-with'] === 'XMLHttpRequest';\n            const isFile = req.url.indexOf('.js') !== -1;\n            const redirectToIndex = !(isAjaxCall || isEventStream) && !isFile;\n            if (redirectToIndex) {\n              return '/index.html';\n            }\n          },\n        },\n      },\n    },\n  };\n});\n"
  },
  {
    "path": "spring-boot-admin-starter-client/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright 2014-2018 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>spring-boot-admin-starter-client</artifactId>\n\n    <name>Spring Boot Admin Client Starter</name>\n    <description>Spring Boot Admin Client Starter</description>\n\n    <parent>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-build</artifactId>\n        <version>${revision}</version>\n        <relativePath>../spring-boot-admin-build</relativePath>\n    </parent>\n\n    <dependencies>\n        <dependency>\n            <groupId>de.codecentric</groupId>\n            <artifactId>spring-boot-admin-client</artifactId>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "spring-boot-admin-starter-server/pom.xml",
    "content": "<?xml version=\"1.0\"?>\n<!--\n  ~ Copyright 2014-2018 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>spring-boot-admin-starter-server</artifactId>\n\n    <name>Spring Boot Admin Server Starter</name>\n    <description>Spring Boot Admin Server Starter</description>\n\n    <parent>\n        <groupId>de.codecentric</groupId>\n        <artifactId>spring-boot-admin-build</artifactId>\n        <version>${revision}</version>\n        <relativePath>../spring-boot-admin-build</relativePath>\n    </parent>\n\n    <dependencies>\n        <dependency>\n            <groupId>de.codecentric</groupId>\n            <artifactId>spring-boot-admin-server</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>de.codecentric</groupId>\n            <artifactId>spring-boot-admin-server-ui</artifactId>\n        </dependency>\n    </dependencies>\n\n    <profiles>\n        <profile>\n            <id>include-cloud</id>\n            <activation>\n                <property>\n                    <name>!excludeSpringCloud</name>\n                </property>\n            </activation>\n            <dependencies>\n                <dependency>\n                    <groupId>de.codecentric</groupId>\n                    <artifactId>spring-boot-admin-server-cloud</artifactId>\n                    <!--fix for https://github.com/mojohaus/flatten-maven-plugin/issues/70 -->\n                    <version>${revision}</version>\n                    <scope>compile</scope>\n                </dependency>\n            </dependencies>\n        </profile>\n    </profiles>\n</project>\n"
  },
  {
    "path": "src/checkstyle/checkstyle-header.txt",
    "content": "^\\Q/*\\E$\n^\\Q * Copyright \\E2014-20\\d\\d\\Q the original author or authors.\\E$\n^\\Q *\\E$\n^\\Q * Licensed under the Apache License, Version 2.0 (the \"License\");\\E$\n^\\Q * you may not use this file except in compliance with the License.\\E$\n^\\Q * You may obtain a copy of the License at\\E$\n^\\Q *\\E$\n^\\Q *     https://www.apache.org/licenses/LICENSE-2.0\\E$\n^\\Q *\\E$\n^\\Q * Unless required by applicable law or agreed to in writing, software\\E$\n^\\Q * distributed under the License is distributed on an \"AS IS\" BASIS,\\E$\n^\\Q * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\\E$\n^\\Q * See the License for the specific language governing permissions and\\E$\n^\\Q * limitations under the License.\\E$\n^\\Q */\\E$\n^$\n^.*$\n"
  },
  {
    "path": "src/checkstyle/checkstyle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright 2014-2018 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<!--\n This configuration is basically a copy of spring-javaformat.\n\n https://github.com/spring-io/spring-javaformat/blob/master/spring-javaformat/spring-javaformat-checkstyle/src/main/resources/io/spring/javaformat/checkstyle/spring-checkstyle.xml\n\n Changes as follows\n - Own HeaderCheck\n - RequireThisCheck: disabled\n - AvoidStaticImportCheck: disabled\n - JavadocTypeCheck: disabled\n - JavadocVariableCheck: disabled\n - JavadocStyleCheck: disabled\n - BDDMockito imports: disabled, BDD-Style not used at the moment\n - root-package for SpringImportOrderCheck\n-->\n\n<!DOCTYPE module PUBLIC\n    \"-//Puppy Crawl//DTD Check Configuration 1.3//EN\"\n    \"http://www.puppycrawl.com/dtds/configuration_1_3.dtd\">\n<module name=\"com.puppycrawl.tools.checkstyle.Checker\">\n    <!-- Root Checks -->\n    <module name=\"com.puppycrawl.tools.checkstyle.checks.header.RegexpHeaderCheck\">\n        <property name=\"headerFile\" value=\"${checkstyle.header.file}\"/>\n        <property name=\"fileExtensions\" value=\"java\"/>\n    </module>\n    <module name=\"com.puppycrawl.tools.checkstyle.checks.NewlineAtEndOfFileCheck\"/>\n\n    <!-- TreeWalker Checks -->\n    <module name=\"com.puppycrawl.tools.checkstyle.TreeWalker\">\n        <!-- Annotations -->\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck\">\n            <property name=\"elementStyle\" value=\"compact\"/>\n        </module>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.annotation.MissingOverrideCheck\"/>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.annotation.PackageAnnotationCheck\"/>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationLocationCheck\"/>\n\n        <!-- Block Checks -->\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.blocks.EmptyBlockCheck\">\n            <property name=\"option\" value=\"text\"/>\n        </module>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.blocks.LeftCurlyCheck\"/>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.blocks.RightCurlyCheck\">\n            <property name=\"option\" value=\"alone\"/>\n        </module>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.blocks.NeedBracesCheck\"/>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.blocks.AvoidNestedBlocksCheck\"/>\n\n        <!-- Class Design -->\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.design.FinalClassCheck\"/>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.design.InterfaceIsTypeCheck\"/>\n        <module name=\"io.spring.javaformat.checkstyle.check.SpringHideUtilityClassConstructor\"/>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.design.MutableExceptionCheck\"/>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.design.InnerTypeLastCheck\"/>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.design.OneTopLevelClassCheck\"/>\n\n        <!-- Coding -->\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.coding.CovariantEqualsCheck\"/>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.coding.EmptyStatementCheck\"/>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.coding.EqualsHashCodeCheck\"/>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheck\"/>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.coding.SimplifyBooleanExpressionCheck\"/>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.coding.SimplifyBooleanReturnCheck\"/>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.coding.StringLiteralEqualityCheck\"/>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.coding.NestedForDepthCheck\">\n            <property name=\"max\" value=\"3\"/>\n        </module>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.coding.NestedIfDepthCheck\">\n            <property name=\"max\" value=\"3\"/>\n        </module>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.coding.NestedTryDepthCheck\">\n            <property name=\"max\" value=\"3\"/>\n        </module>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.coding.MultipleVariableDeclarationsCheck\"/>\n        <!--        <module name=\"com.puppycrawl.tools.checkstyle.checks.coding.RequireThisCheck\">-->\n        <!--            <property name=\"checkMethods\" value=\"false\"/>-->\n        <!--            <property name=\"validateOnlyOverlapping\" value=\"false\"/>-->\n        <!--        </module>-->\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.coding.OneStatementPerLineCheck\"/>\n\n        <!-- Imports -->\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.imports.AvoidStarImportCheck\"/>\n        <!--        <module name=\"com.puppycrawl.tools.checkstyle.checks.imports.AvoidStaticImportCheck\">-->\n        <!--            <property name=\"excludes\"-->\n        <!--                      value=\"io.restassured.RestAssured.*, org.assertj.core.api.Assertions.*, org.junit.jupiter.api.Assertions.*, org.junit.jupiter.api.Assumptions.*, org.junit.Assert.*, org.junit.Assume.*, org.junit.internal.matchers.ThrowableMessageMatcher.*, org.junit.jupiter.api.Assertions.*, org.hamcrest.CoreMatchers.*, org.hamcrest.Matchers.*, org.springframework.boot.configurationprocessor.ConfigurationMetadataMatchers.*, org.springframework.boot.configurationprocessor.TestCompiler.*, org.springframework.boot.test.autoconfigure.AutoConfigurationImportedCondition.*, org.mockito.Mockito.*, org.mockito.BDDMockito.*, org.mockito.ArgumentMatchers.*, org.mockito.Matchers.*, org.springframework.restdocs.headers.HeaderDocumentation.*, org.springframework.restdocs.hypermedia.HypermediaDocumentation.*, org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*, org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*, org.springframework.restdocs.operation.preprocess.Preprocessors.*, org.springframework.restdocs.payload.PayloadDocumentation.*, org.springframework.restdocs.request.RequestDocumentation.*, org.springframework.restdocs.restassured3.operation.preprocess.RestAssuredPreprocessors.*, org.springframework.restdocs.restassured3.RestAssuredRestDocumentation.*, org.springframework.restdocs.snippet.Attributes.*, org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.*, org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*, org.springframework.test.web.servlet.result.MockMvcResultMatchers.*, org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.*, org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*, org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*, org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo, org.springframework.test.web.client.ExpectedCount.*, org.springframework.test.web.client.match.MockRestRequestMatchers.*, org.springframework.test.web.client.response.MockRestResponseCreators.*, org.springframework.test.web.servlet.result.MockMvcResultHandlers.*, org.springframework.web.reactive.function.BodyInserters.*, org.springframework.web.reactive.function.server.RequestPredicates.*, org.springframework.web.reactive.function.server.RouterFunctions.*, org.springframework.test.web.servlet.setup.MockMvcBuilders.*\"/>-->\n        <!--        </module>-->\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.imports.RedundantImportCheck\"/>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.imports.UnusedImportsCheck\">\n            <property name=\"processJavadoc\" value=\"true\"/>\n        </module>\n        <module name=\"io.spring.javaformat.checkstyle.check.SpringImportOrderCheck\">\n            <property name=\"projectRootPackage\" value=\"de.codecentric.boot.admin\"/>\n        </module>\n\n        <!-- Javadoc Comments -->\n        <!--        <module name=\"com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTypeCheck\">-->\n        <!--            <property name=\"scope\" value=\"package\"/>-->\n        <!--            <property name=\"authorFormat\" value=\".+\\s.+\"/>-->\n        <!--        </module>-->\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocMethodCheck\"/>\n        <!--        <module name=\"com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocVariableCheck\">-->\n        <!--            <property name=\"scope\" value=\"public\"/>-->\n        <!--        </module>-->\n        <!--        <module name=\"com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocStyleCheck\">-->\n        <!--            <property name=\"checkEmptyJavadoc\" value=\"true\"/>-->\n        <!--        </module>-->\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.javadoc.NonEmptyAtclauseDescriptionCheck\"/>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTagContinuationIndentationCheck\">\n            <property name=\"offset\" value=\"0\"/>\n        </module>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.javadoc.AtclauseOrderCheck\">\n            <property name=\"target\" value=\"CLASS_DEF, INTERFACE_DEF, ENUM_DEF\"/>\n            <property name=\"tagOrder\" value=\"@param, @author, @since, @see, @version, @serial, @deprecated\"/>\n        </module>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.javadoc.AtclauseOrderCheck\">\n            <property name=\"target\" value=\"METHOD_DEF, CTOR_DEF, VARIABLE_DEF\"/>\n            <property name=\"tagOrder\" value=\"@param, @return, @throws, @since, @deprecated, @see\"/>\n        </module>\n\n        <!-- Miscellaneous -->\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.indentation.CommentsIndentationCheck\">\n            <property name=\"tokens\" value=\"BLOCK_COMMENT_BEGIN\"/>\n        </module>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.UpperEllCheck\"/>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.ArrayTypeStyleCheck\"/>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.OuterTypeFilenameCheck\"/>\n\n        <!-- Modifiers -->\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.modifier.RedundantModifierCheck\"/>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.modifier.ModifierOrderCheck\"/>\n\n        <!-- Regexp -->\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck\">\n            <property name=\"format\" value=\"^\\t* +\\t*\\S\"/>\n            <property name=\"message\"\n                      value=\"Line has leading space characters; indentation should be performed with tabs only.\"/>\n            <property name=\"ignoreComments\" value=\"true\"/>\n        </module>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck\">\n            <property name=\"maximum\" value=\"0\"/>\n            <property name=\"format\" value=\"org\\.junit\\.(Assert|jupiter\\.api\\.Assertions)\\.assert\"/>\n            <property name=\"message\"\n                      value=\"Please use AssertJ imports.\"/>\n            <property name=\"ignoreComments\" value=\"true\"/>\n        </module>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.regexp.RegexpCheck\">\n            <property name=\"format\" value=\"[ \\t]+$\"/>\n            <property name=\"illegalPattern\" value=\"true\"/>\n            <property name=\"message\" value=\"Trailing whitespace\"/>\n        </module>\n\n        <!-- Whitespace -->\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.whitespace.GenericWhitespaceCheck\"/>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.whitespace.MethodParamPadCheck\"/>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.whitespace.NoWhitespaceAfterCheck\">\n            <property name=\"tokens\" value=\"BNOT, DEC, DOT, INC, LNOT, UNARY_MINUS, UNARY_PLUS, ARRAY_DECLARATOR\"/>\n        </module>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.whitespace.NoWhitespaceBeforeCheck\"/>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.whitespace.ParenPadCheck\"/>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.whitespace.TypecastParenPadCheck\"/>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.whitespace.WhitespaceAfterCheck\"/>\n        <module name=\"com.puppycrawl.tools.checkstyle.checks.whitespace.WhitespaceAroundCheck\"/>\n\n        <!-- Spring Conventions -->\n        <module name=\"io.spring.javaformat.checkstyle.check.SpringLambdaCheck\"/>\n        <module name=\"io.spring.javaformat.checkstyle.check.SpringTernaryCheck\"/>\n        <module name=\"io.spring.javaformat.checkstyle.check.SpringCatchCheck\"/>\n        <module name=\"io.spring.javaformat.checkstyle.check.SpringJavadocCheck\"/>\n        <module name=\"io.spring.javaformat.checkstyle.check.SpringMethodOrderCheck\"/>\n        <module name=\"io.spring.javaformat.checkstyle.check.SpringMethodVisibilityCheck\"/>\n    </module>\n</module>\n"
  }
]