シェルの場合(こっちはいちおう動くこと確認できた。Kibana 8.18.8で動くこと確認済み)

#!/bin/bash
set -e

TOKEN="MWMxMmhab0JWNUp6TGlPTUtFbHc6LW13NHpJbmhzenN3WWJqVzdBVVBMdw=="

KIBANA_URL="http://localhost:5601"
DASHBOARD_ID="dfd3b3b4-41bc-44f3-a459-bad56dab9b59"
VIS_IDS_FILE="$1"

if [ -z "$KIBANA_URL" ] || [ -z "$DASHBOARD_ID" ] || [ -z "$VIS_IDS_FILE" ]; then
  echo "Usage: $0 <kibana_url> <dashboard_id> <vis_ids.json>"
  exit 1
fi

echo "Loading VIS_IDS from $VIS_IDS_FILE ..."
VIS_IDS=$(jq -r '.[]' "$VIS_IDS_FILE")

echo "VISUALIZATION IDs:"
echo "$VIS_IDS"
echo "----------------------------------------"

# ================
# STEP 1: Dashboard を取得
# ================
echo "Fetching dashboard: $DASHBOARD_ID ..."
DASH=$(curl -s -X GET "$KIBANA_URL/api/saved_objects/dashboard/$DASHBOARD_ID" \
  -H "kbn-xsrf: true")

PANELS=$(echo "$DASH" | jq -r '.attributes.panelsJSON')

if [ "$PANELS" = "null" ] || [ -z "$PANELS" ]; then
  PANELS="[]"
fi

NEW_PANELS=$(echo "$PANELS" | jq '.')

# 自動レイアウト(2カラム)
X=0
Y=0

# 既存 index 数
CURRENT_COUNT=$(echo "$NEW_PANELS" | jq length)
NEXT_INDEX=$((CURRENT_COUNT + 1))

# ================
# STEP 2: panelJSON を追加
# ================
for VIS_ID in $VIS_IDS; do

  PANEL=$(cat <<EOF
{
  "panelIndex": "$NEXT_INDEX",
  "type": "visualization",
  "id": "$VIS_ID",
  "gridData": {
    "x": $X,
    "y": $Y,
    "w": 48,
    "h": 15,
    "i": "$NEXT_INDEX"
  },
  "version": "1"
}
EOF
)

  NEW_PANELS=$(echo "$NEW_PANELS" | jq ". + [ $PANEL ]")

  # 次のパネル配置(2カラム)
  if [ $X -eq 0 ]; then
    X=24
  else
    X=0
    Y=$((Y + 15))
  fi

  NEXT_INDEX=$((NEXT_INDEX + 1))

done

# ================
# STEP 3: Dashboard を更新
# ================
ATTR_ONLY=$(echo "$DASH" | jq '.attributes')

UPDATED_ATTR=$(echo "$ATTR_ONLY" | jq --arg panels "$(echo "$NEW_PANELS" | jq -c '.')" \
  '.panelsJSON = $panels')

echo "Updating dashboard ..."
curl -v -X PUT "$KIBANA_URL/api/saved_objects/dashboard/$DASHBOARD_ID" \
  -H "kbn-xsrf: true" \
  -H "Authorization: ApiKey ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d "{\"attributes\": $UPDATED_ATTR }"

echo "Done! Panels added to dashboard."

Javaの場合(こっちは動くか確認できていない)

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;

import java.io.File;
import java.io.IOException;
import java.util.*;

public class AddPanelsToDashboard {

    private static final ObjectMapper mapper = new ObjectMapper();
    private static final OkHttpClient client = new OkHttpClient();

    public static void main(String[] args) throws Exception {

        if (args.length < 3) {
            System.out.println("Usage: java AddPanelsToDashboard <kibana_url> <dashboard_id> <vis_ids.json> [api_key]");
            return;
        }

        String kibanaUrl = args[0];
        String dashboardId = args[1];
        String visJsonPath = args[2];
        String apiKey = args.length >= 4 ? args[3] : null;

        // VIS IDS 読み込み
        List<String> visIds = mapper.readValue(new File(visJsonPath), new TypeReference<List<String>>() {});
        System.out.println("Loaded VIS IDS: " + visIds);

        // Dashboard 取得
        String dashApi = kibanaUrl + "/api/saved_objects/dashboard/" + dashboardId;
        String dashJson = httpGet(dashApi, apiKey);

        Map<String, Object> dashMap = mapper.readValue(dashJson, new TypeReference<Map<String, Object>>() {});
        Map<String, Object> attributes = (Map<String, Object>) dashMap.get("attributes");

        // panelsJSON を取得
        List<Object> panels = new ArrayList<>();

        if (attributes.get("panelsJSON") != null && !attributes.get("panelsJSON").toString().isEmpty()) {
            panels = mapper.readValue(attributes.get("panelsJSON").toString(), new TypeReference<List<Object>>() {});
        }

        int nextIndex = panels.size() + 1;

        int x = 0;
        int y = 0;

        // VIS_ID ごとに panel を追加(横幅 48 → 全幅)
        for (String id : visIds) {
            Map<String, Object> panel = new LinkedHashMap<>();
            panel.put("panelIndex", String.valueOf(nextIndex));
            panel.put("type", "visualization");
            panel.put("id", id);

            Map<String, Object> grid = new LinkedHashMap<>();
            grid.put("x", 0);
            grid.put("y", y);
            grid.put("w", 48); // 全幅
            grid.put("h", 15);
            grid.put("i", String.valueOf(nextIndex));

            panel.put("gridData", grid);
            panel.put("version", "1");

            panels.add(panel);

            y += 15;
            nextIndex++;
        }

        // 新 panelsJSON をセット
        attributes.put("panelsJSON", mapper.writeValueAsString(panels));

        // Dashboard 更新
        Map<String, Object> updateBody = new LinkedHashMap<>();
        updateBody.put("attributes", attributes);

        String updateJson = mapper.writeValueAsString(updateBody);

        String putApi = kibanaUrl + "/api/saved_objects/dashboard/" + dashboardId;
        String result = httpPut(putApi, updateJson, apiKey);

        System.out.println("Update Result: " + result);
        System.out.println("Done!");
    }

    // =======================
    // HTTP GET
    // =======================
    private static String httpGet(String url, String apiKey) throws IOException {
        Request.Builder builder = new Request.Builder()
                .url(url)
                .addHeader("kbn-xsrf", "true");

        if (apiKey != null) {
            builder.addHeader("Authorization", "ApiKey " + apiKey);
        }

        Request request = builder.build();
        Response response = client.newCall(request).execute();
        return response.body().string();
    }

    // =======================
    // HTTP PUT
    // =======================
    private static String httpPut(String url, String json, String apiKey) throws IOException {
        RequestBody body = RequestBody.create(json, MediaType.parse("application/json"));

        Request.Builder builder = new Request.Builder()
                .url(url)
                .put(body)
                .addHeader("kbn-xsrf", "true");

        if (apiKey != null) {
            builder.addHeader("Authorization", "ApiKey " + apiKey);
        }

        Request request = builder.build();
        Response response = client.newCall(request).execute();
        return response.body().string();
    }
}

投稿者 MJfr6SPbeaJRee8Y