🛠️ Splunk 既存ダッシュボード 図形の追加・修正・編集ガイド
~チャート上に図形を自由に追加・変更する方法~
📚 この記事で学べること
項目 内容 図形の追加 新しい四角・丸・棒・三角を追加 位置の変更 図形を好きな場所に配置 色の変更 ステータスや条件で色を変える サイズ変更 図形の大きさを調整 グループ化 複数図形をまとめて管理
🔷 Part 1: 図形追加の基本パターン
1-1. 追加の全体像
【既存ダッシュボード】 【追加後】
┌─────────────────────┐ ┌─────────────────────┐
│ ■ ● ▬ ▼ │ → │ ■ ● ▬ ▼ ★ ◆ │
│ A B C D │ │ A B C D E F │
└─────────────────────┘ └─────────────────────┘
追加に必要な作業:
1. SPL: 新しいデータ行を追加
2. CSS: 新しい図形のスタイルを定義
3. JavaScript: 図形生成ロジックを修正
1-2. 新しい図形を追加する3ステップ
ステップ1: SPLにデータを追加
| makeresults count=6
| streamstats count as id
| eval device_name = case(
id==1, "サーバーA",
id==2, "サーバーB",
id==3, "ネットワーク機器C",
id==4, "データベースD",
id==5, "ストレージE", /* ★追加★ */
id==6, "ファイアウォールF" /* ★追加★ */
)
| eval status = case(
id==1, "正常",
id==2, "警告",
id==3, "異常",
id==4, "停止",
id==5, "正常", /* ★追加★ */
id==6, "警告" /* ★追加★ */
)
| eval status_code = case(
status=="正常", 0,
status=="警告", 1,
status=="異常", 2,
status=="停止", 3
)
| table device_name, status, status_code
【変更ポイント】
┌────────────────────────────────────────────────────────┐
│ count=4 → count=6 │
│ ↑ 生成する行数を増やす │
│ │
│ case文に新しい条件を追加 │
│ id==5, "ストレージE" │
│ id==6, "ファイアウォールF" │
└────────────────────────────────────────────────────────┘
ステップ2: CSSに新しい図形スタイルを追加
/* ===== 既存の図形 ===== */
.rectangle { ... }
.circle { ... }
.bar { ... }
.triangle { ... }
/* ===== ★新規追加★ 星形 ===== */
.star {
/* 星形はSVGまたはクリップパスで作成 */
width: 60px;
height: 60px;
background-color: #808080 ;
clip-path: polygon(
50% 0%, /* 上の頂点 */
61% 35%, /* 右上内側 */
98% 35%, /* 右上外側 */
68% 57%, /* 右下内側 */
79% 91%, /* 右下外側 */
50% 70%, /* 下の内側 */
21% 91%, /* 左下外側 */
32% 57%, /* 左下内側 */
2% 35%, /* 左上外側 */
39% 35% /* 左上内側 */
);
display: flex;
justify-content: center;
align-items: center;
transition: background-color 0.3s ease;
}
/* ===== ★新規追加★ ひし形 ===== */
.diamond {
width: 50px;
height: 50px;
background-color: #808080 ;
/* 45度回転させて正方形をひし形に */
transform: rotate(45deg);
display: flex;
justify-content: center;
align-items: center;
transition: background-color 0.3s ease;
}
/* ひし形の中のテキストを正位置に */
.diamond span {
transform: rotate(-45deg);
color: white;
font-size: 10px;
font-weight: bold;
}
/* ===== ★新規追加★ 六角形 ===== */
.hexagon {
width: 60px;
height: 52px;
background-color: #808080 ;
clip-path: polygon(
25% 0%, /* 左上 */
75% 0%, /* 右上 */
100% 50%, /* 右 */
75% 100%, /* 右下 */
25% 100%, /* 左下 */
0% 50% /* 左 */
);
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 11px;
font-weight: bold;
transition: background-color 0.3s ease;
}
ステップ3: JavaScriptの図形配列を更新
// 【変更前】4種類
var shapeTypes = ['rectangle', 'circle', 'bar', 'triangle'];
// 【変更後】6種類に増やす
var shapeTypes = ['rectangle', 'circle', 'bar', 'triangle', 'star', 'diamond'];
// ↑追加 ↑追加
// 図形生成部分も追加
if (shapeType === 'rectangle') {
shapeHtml = '<div class="rectangle ' + statusClass + '">' + status + '</div>';
} else if (shapeType === 'circle') {
shapeHtml = '<div class="circle ' + statusClass + '">' + status + '</div>';
} else if (shapeType === 'bar') {
shapeHtml = '<div class="bar ' + statusClass + '">' + status + '</div>';
} else if (shapeType === 'triangle') {
shapeHtml = '<div class="triangle ' + statusClass + '"></div>';
}
// ★追加★
else if (shapeType === 'star') {
shapeHtml = '<div class="star ' + statusClass + '"></div>';
}
// ★追加★
else if (shapeType === 'diamond') {
shapeHtml = '<div class="diamond ' + statusClass + '"><span>' + status + '</span></div>';
}
🔷 Part 2: 図形の位置変更
2-1. Flexboxで配置を変更
左寄せ(デフォルト)
.shapes-container {
display: flex;
justify-content: flex-start; /* ← 左寄せ */
gap: 20px;
}
┌─────────────────────────────────────────┐
│ ■ ● ▬ ▼ │
│ │
└─────────────────────────────────────────┘
中央寄せ
.shapes-container {
display: flex;
justify-content: center; /* ← 中央寄せ */
gap: 20px;
}
┌─────────────────────────────────────────┐
│ ■ ● ▬ ▼ │
│ │
└─────────────────────────────────────────┘
右寄せ
.shapes-container {
display: flex;
justify-content: flex-end; /* ← 右寄せ */
gap: 20px;
}
┌─────────────────────────────────────────┐
│ ■ ● ▬ ▼ │
│ │
└─────────────────────────────────────────┘
均等配置
.shapes-container {
display: flex;
justify-content: space-between; /* ← 両端に配置、間を均等 */
gap: 20px;
}
┌─────────────────────────────────────────┐
│ ■ ● ▬ ▼ │
│ │
└─────────────────────────────────────────┘
均等配置(周囲も含む)
.shapes-container {
display: flex;
justify-content: space-around; /* ← 周囲も含めて均等 */
gap: 20px;
}
┌─────────────────────────────────────────┐
│ ■ ● ▬ ▼ │
│ │
└─────────────────────────────────────────┘
2-2. CSS Gridで格子状に配置
3列のグリッド
.shapes-container {
display: grid;
grid-template-columns: repeat(3, 1fr); /* 3列 */
gap: 20px;
padding: 20px;
}
┌─────────────────────────────────────────┐
│ ■ ● ▬ │
│ A B C │
│ │
│ ▼ ★ ◆ │
│ D E F │
└─────────────────────────────────────────┘
4列のグリッド
.shapes-container {
display: grid;
grid-template-columns: repeat(4, 1fr); /* 4列 */
gap: 15px;
padding: 20px;
}
┌─────────────────────────────────────────┐
│ ■ ● ▬ ▼ │
│ A B C D │
│ │
│ ★ ◆ │
│ E F │
└─────────────────────────────────────────┘
列幅を固定
.shapes-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
/* ↑ 最小120px、最大1frで自動的に列数を調整 */
gap: 20px;
}
2-3. 絶対位置指定(完全自由配置)
基本設定
/* コンテナを基準点にする */
.shapes-container {
position: relative; /* ★重要★ 子要素の基準点になる */
width: 100%;
height: 400px; /* 高さを固定 */
background-color: #f5f5f5 ;
border: 1px solid #ddd ;
}
/* 各図形アイテムを絶対位置指定 */
.shape-item {
position: absolute; /* ★重要★ 親要素を基準に配置 */
}
個別の位置指定(CSS)
/* 1番目の図形:左上 */
.shape-item:nth-child(1) {
top: 20px;
left: 30px;
}
/* 2番目の図形:右上 */
.shape-item:nth-child(2) {
top: 20px;
right: 30px;
}
/* 3番目の図形:中央 */
.shape-item:nth-child(3) {
top: 50%;
left: 50%;
transform: translate(-50%, -50%); /* 中心に配置 */
}
/* 4番目の図形:左下 */
.shape-item:nth-child(4) {
bottom: 20px;
left: 30px;
}
┌─────────────────────────────────────────┐
│ ■ ● │
│ A B │
│ │
│ ▬ │
│ C │
│ │
│ ▼ │
│ D │
└─────────────────────────────────────────┘
JavaScriptで動的に位置指定
// データに位置情報を含める方法
| makeresults count=4
| streamstats count as id
| eval device_name = case(id==1,"A", id==2,"B", id==3,"C", id==4,"D")
| eval status = case(id==1,"正常", id==2,"警告", id==3,"異常", id==4,"停止")
| eval status_code = case(status=="正常",0, status=="警告",1, status=="異常",2, status=="停止",3)
| eval pos_top = case(id==1,"20px", id==2,"20px", id==3,"150px", id==4,"150px")
| eval pos_left = case(id==1,"30px", id==2,"250px", id==3,"30px", id==4,"250px")
| table device_name, status, status_code, pos_top, pos_left
// JavaScript側で位置を適用
var topIndex = fields.indexOf('pos_top');
var leftIndex = fields.indexOf('pos_left');
var posTop = row[topIndex];
var posLeft = row[leftIndex];
// スタイル属性で位置を指定
var itemHtml =
'<div class="shape-item" style="top: ' + posTop + '; left: ' + posLeft + ';">' +
shapeHtml +
'<div class="device-label">' + deviceName + '</div>' +
'</div>';
🔷 Part 3: 色の変更方法
3-1. 新しいステータス色を追加
CSS定義
/* ===== 既存のステータス色 ===== */
.status-normal { background-color: #28a745 ; } /* 緑:正常 */
.status-warning { background-color: #ffc107 ; } /* 黄:警告 */
.status-error { background-color: #dc3545 ; } /* 赤:異常 */
.status-stop { background-color: #6c757d ; } /* 灰:停止 */
/* ===== ★新規追加★ ===== */
.status-info { background-color: #17a2b8 ; } /* 水色:情報 */
.status-success { background-color: #20c997 ; } /* ティール:成功 */
.status-danger { background-color: #e83e8c ; } /* ピンク:危険 */
.status-dark { background-color: #343a40 ; } /* 濃灰:不明 */
.status-primary { background-color: #007bff ; } /* 青:メイン */
.status-secondary { background-color: #6610f2 ; } /* 紫:サブ */
SPL側でステータスコードを追加
| eval status_code = case(
status=="正常", 0,
status=="警告", 1,
status=="異常", 2,
status=="停止", 3,
status=="情報", 4, /* ★追加★ */
status=="成功", 5, /* ★追加★ */
status=="危険", 6, /* ★追加★ */
status=="不明", 7 /* ★追加★ */
)
JavaScript側でswitch文を追加
var statusClass = '';
switch(statusCode) {
case 0: statusClass = 'status-normal'; break;
case 1: statusClass = 'status-warning'; break;
case 2: statusClass = 'status-error'; break;
case 3: statusClass = 'status-stop'; break;
// ★追加★
case 4: statusClass = 'status-info'; break;
case 5: statusClass = 'status-success'; break;
case 6: statusClass = 'status-danger'; break;
case 7: statusClass = 'status-dark'; break;
default: statusClass = 'status-stop';
}
3-2. 数値範囲で色を変える
SPLで数値に応じたステータスを設定
| makeresults count=5
| streamstats count as id
| eval value = case(id==1, 95, id==2, 75, id==3, 50, id==4, 25, id==5, 10)
| eval status = case(
value >= 80, "正常", /* 80以上:緑 */
value >= 60, "注意", /* 60-79:黄緑 */
value >= 40, "警告", /* 40-59:黄 */
value >= 20, "異常", /* 20-39:オレンジ */
value < 20, "危険" /* 20未満:赤 */
)
| eval status_code = case(
status=="正常", 0,
status=="注意", 1,
status=="警告", 2,
status=="異常", 3,
status=="危険", 4
)
| table id, value, status, status_code
対応するCSS
.status-0 { background-color: #28a745 ; } /* 緑:80以上 */
.status-1 { background-color: #7cb342 ; } /* 黄緑:60-79 */
.status-2 { background-color: #ffc107 ; } /* 黄:40-59 */
.status-3 { background-color: #ff9800 ; } /* オレンジ:20-39 */
.status-4 { background-color: #dc3545 ; } /* 赤:20未満 */
3-3. グラデーション色
/* グラデーション背景 */
.status-gradient-good {
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
}
.status-gradient-warning {
background: linear-gradient(135deg, #ffc107 0%, #ff9800 100%);
}
.status-gradient-bad {
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
}
3-4. 点滅アニメーション(異常時)
/* 点滅アニメーション定義 */
@keyframes blink {
0% { opacity: 1; }
50% { opacity: 0.3; }
100% { opacity: 1; }
}
/* 異常ステータスに点滅を適用 */
.status-error {
background-color: #dc3545 ;
animation: blink 1s ease-in-out infinite;
/* ↑ 1秒間隔で無限に点滅 */
}
/* 危険ステータスは高速点滅 */
.status-danger {
background-color: #e83e8c ;
animation: blink 0.5s ease-in-out infinite;
/* ↑ 0.5秒間隔で高速点滅 */
}
🔷 Part 4: サイズ変更
4-1. 図形サイズの変更
小サイズ
.shape-small {
transform: scale(0.7); /* 70%に縮小 */
}
/* または個別に指定 */
.rectangle-small {
width: 70px; /* 100px → 70px */
height: 42px; /* 60px → 42px */
}
.circle-small {
width: 50px; /* 70px → 50px */
height: 50px;
}
大サイズ
.shape-large {
transform: scale(1.5); /* 150%に拡大 */
}
/* または個別に指定 */
.rectangle-large {
width: 150px; /* 100px → 150px */
height: 90px; /* 60px → 90px */
}
.circle-large {
width: 100px; /* 70px → 100px */
height: 100px;
}
SPLでサイズを指定
| eval size_class = case(
status=="正常", "shape-small",
status=="警告", "", /* 通常サイズ */
status=="異常", "shape-large", /* 異常は大きく目立たせる */
status=="停止", "shape-small"
)
| table device_name, status, status_code, size_class
JavaScript側でサイズクラスを適用
var sizeIndex = fields.indexOf('size_class');
var sizeClass = row[sizeIndex] || ''; // nullの場合は空文字
// 図形にサイズクラスを追加
shapeHtml = '<div class="rectangle ' + statusClass + ' ' + sizeClass + '">' + status + '</div>';
🔷 Part 5: 図形のグループ化
5-1. カテゴリ別にグループ化
SPLでカテゴリを追加
| makeresults count=6
| streamstats count as id
| eval device_name = case(
id==1, "Webサーバー1",
id==2, "Webサーバー2",
id==3, "DBサーバー1",
id==4, "DBサーバー2",
id==5, "ネットワーク1",
id==6, "ネットワーク2"
)
| eval category = case(
id<=2, "Web層",
id<=4, "DB層",
id<=6, "NW層"
)
| eval status = case(id==1,"正常", id==2,"警告", id==3,"正常", id==4,"異常", id==5,"正常", id==6,"停止")
| eval status_code = case(status=="正常",0, status=="警告",1, status=="異常",2, status=="停止",3)
| table category, device_name, status, status_code
CSS:グループスタイル
/* グループコンテナ */
.category-group {
border: 2px solid #ddd ;
border-radius: 10px;
padding: 15px;
margin-bottom: 20px;
background-color: #f9f9f9 ;
}
/* グループタイトル */
.category-title {
font-size: 16px;
font-weight: bold;
color: #333 ;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid #007bff ;
}
/* グループ内の図形コンテナ */
.category-shapes {
display: flex;
gap: 15px;
flex-wrap: wrap;
}
JavaScript:グループ別に描画
results.on("data", function() {
if (results.hasData()) {
var rows = results.data().rows;
var fields = results.data().fields;
var container = document.getElementById('shapesContainer');
container.innerHTML = '';
// カテゴリごとにデータをグループ化
var groups = {};
rows.forEach(function(row) {
var categoryIndex = fields.indexOf('category');
var category = row[categoryIndex];
// グループが存在しなければ作成
if (!groups[category]) {
groups[category] = [];
}
// グループにデータを追加
groups[category].push(row);
});
// グループごとにHTMLを生成
Object.keys(groups).forEach(function(category) {
// グループコンテナ開始
var groupHtml = '<div class="category-group">';
groupHtml += '<div class="category-title">' + category + '</div>';
groupHtml += '<div class="category-shapes">';
// グループ内の各図形を生成
groups[category].forEach(function(row, index) {
var deviceIndex = fields.indexOf('device_name');
var statusIndex = fields.indexOf('status');
var codeIndex = fields.indexOf('status_code');
var deviceName = row[deviceIndex];
var status = row[statusIndex];
var statusCode = parseInt(row[codeIndex]);
var statusClass = '';
switch(statusCode) {
case 0: statusClass = 'status-normal'; break;
case 1: statusClass = 'status-warning'; break;
case 2: statusClass = 'status-error'; break;
case 3: statusClass = 'status-stop'; break;
}
// 図形HTML
groupHtml += '<div class="shape-item">';
groupHtml += '<div class="circle ' + statusClass + '">' + status + '</div>';
groupHtml += '<div class="device-label">' + deviceName + '</div>';
groupHtml += '</div>';
});
groupHtml += '</div>'; // category-shapes終了
groupHtml += '</div>'; // category-group終了
container.innerHTML += groupHtml;
});
}
});
表示結果
┌─────────────────────────────────────────────────────┐
│ Web層 │
│ ───────────────────────────────── │
│ │ ● 正常 ● 警告 │ │
│ │ Web1 Web2 │ │
│ └────────────────────┘ │
├─────────────────────────────────────────────────────┤
│ DB層 │
│ ───────────────────────────────── │
│ │ ● 正常 ● 異常 │ │
│ │ DB1 DB2 │ │
│ └────────────────────┘ │
├─────────────────────────────────────────────────────┤
│ NW層 │
│ ───────────────────────────────── │
│ │ ● 正常 ● 停止 │ │
│ │ NW1 NW2 │ │
│ └────────────────────┘ │
└─────────────────────────────────────────────────────┘
🔷 Part 6: 既存コードの修正チェックリスト
6-1. 図形を追加するときのチェックリスト
□ 1. SPL修正
□ makeresults count の数を増やした
□ eval の case文に新しいデータを追加した
□ 2. CSS修正
□ 新しい図形のクラスを定義した
□ 新しいステータス色を定義した(必要な場合)
□ 三角形など特殊図形の場合、border-colorも定義した
□ 3. JavaScript修正
□ shapeTypes配列に新しい図形タイプを追加した
□ if-else文に新しい図形の生成コードを追加した
□ switch文に新しいステータスコードを追加した(必要な場合)
6-2. よくある修正パターン
パターン1: 図形の色だけ変えたい
修正箇所: CSSのみ
.status-normal { background-color: #28a745 ; }
↓ 変更
.status-normal { background-color: #00ff00 ; } /* より明るい緑に */
パターン2: 図形のサイズだけ変えたい
修正箇所: CSSのみ
.rectangle {
width: 100px; → width: 150px;
height: 60px; → height: 90px;
}
パターン3: 図形の配置を変えたい
修正箇所: CSSのみ
.shapes-container {
justify-content: flex-start; → justify-content: center;
}
パターン4: 新しいデバイスを追加したい
修正箇所: SPLのみ
count=4 → count=5
case文に id==5 の条件を追加
パターン5: 新しい図形タイプを追加したい
修正箇所: CSS + JavaScript
1. CSSに .newShape { ... } を追加
2. JSの shapeTypes に 'newShape' を追加
3. JSの if-else に newShape の分岐を追加
🔷 Part 7: 完全版テンプレート(カスタマイズ用)
7-1. フル機能版コード
<dashboard>
<label>カスタマイズ可能図形ダッシュボード</label>
<!-- ========== データ取得サーチ ========== -->
<search id="mainSearch">
<query>
| makeresults count=8
| streamstats count as id
| eval device_name = case(
id==1, "Webサーバー1",
id==2, "Webサーバー2",
id==3, "APサーバー1",
id==4, "APサーバー2",
id==5, "DBサーバー1",
id==6, "DBサーバー2",
id==7, "FW1",
id==8, "FW2"
)
| eval category = case(
id<=2, "Web層",
id<=4, "AP層",
id<=6, "DB層",
id<=8, "NW層"
)
| eval status = case(
id==1, "正常",
id==2, "正常",
id==3, "警告",
id==4, "正常",
id==5, "異常",
id==6, "正常",
id==7, "停止",
id==8, "正常"
)
| eval status_code = case(
status=="正常", 0,
status=="警告", 1,
status=="異常", 2,
status=="停止", 3
)
| eval shape_type = case(
id<=2, "circle",
id<=4, "rectangle",
id<=6, "hexagon",
id<=8, "diamond"
)
| eval size_class = case(
status=="異常", "shape-large",
status=="停止", "shape-large",
1==1, ""
)
| table category, device_name, status, status_code, shape_type, size_class
</query>
<earliest>-24h@h</earliest>
<latest>now</latest>
</search>
<row>
<panel>
<title>システム監視ダッシュボード</title>
<html>
<style>
/* ========== メインコンテナ ========== */
.main-container {
padding: 20px;
background-color: #f8f9fa ;
}
/* ========== グループスタイル ========== */
.category-group {
border: 2px solid #dee2e6 ;
border-radius: 12px;
padding: 20px;
margin-bottom: 25px;
background-color: white;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.category-title {
font-size: 18px;
font-weight: bold;
color: #495057 ;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 3px solid #007bff ;
}
.category-shapes {
display: flex;
gap: 25px;
flex-wrap: wrap;
justify-content: flex-start;
}
/* ========== 図形アイテム共通 ========== */
.shape-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 10px;
border-radius: 8px;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.shape-item:hover {
transform: translateY(-5px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
/* ========== 四角形 ========== */
.rectangle {
width: 90px;
height: 55px;
background-color: #808080 ;
border-radius: 6px;
border: 2px solid #333 ;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-weight: bold;
font-size: 13px;
transition: all 0.3s ease;
}
/* ========== 丸 ========== */
.circle {
width: 65px;
height: 65px;
background-color: #808080 ;
border-radius: 50%;
border: 2px solid #333 ;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-weight: bold;
font-size: 12px;
transition: all 0.3s ease;
}
/* ========== 六角形 ========== */
.hexagon {
width: 70px;
height: 60px;
background-color: #808080 ;
clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
display: flex;
justify-content: center;
align-items: center;
color: white;
font-weight: bold;
font-size: 11px;
transition: all 0.3s ease;
}
/* ========== ひし形 ========== */
.diamond {
width: 50px;
height: 50px;
background-color: #808080 ;
transform: rotate(45deg);
display: flex;
justify-content: center;
align-items: center;
transition: all 0.3s ease;
}
.diamond span {
transform: rotate(-45deg);
color: white;
font-size: 10px;
font-weight: bold;
}
/* ========== ラベル ========== */
.device-label {
font-size: 12px;
color: #333 ;
font-weight: bold;
text-align: center;
}
.status-label {
font-size: 11px;
color: #666 ;
}
/* ========== ステータス色 ========== */
.status-normal { background-color: #28a745 !important; }
.status-warning { background-color: #ffc107 !important; color: #333 !important; }
.status-error { background-color: #dc3545 !important; }
.status-stop { background-color: #6c757d !important; }
/* ========== サイズ変更 ========== */
.shape-large { transform: scale(1.3); }
.shape-small { transform: scale(0.8); }
/* ========== 点滅(異常・停止時) ========== */
@keyframes pulse {
0% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.05); opacity: 0.8; }
100% { transform: scale(1); opacity: 1; }
}
.status-error, .status-stop {
animation: pulse 1.5s ease-in-out infinite;
}
.shape-large.status-error,
.shape-large.status-stop {
animation: pulse 1s ease-in-out infinite;
}
</style>
<div class="main-container" id="mainContainer">
<!-- JavaScriptで生成 -->
</div>
<script>
require([
'splunkjs/mvc',
'splunkjs/mvc/simplexml/ready!'
], function(mvc) {
var searchManager = mvc.Components.get("mainSearch");
var results = searchManager.data("results");
results.on("data", function() {
if (results.hasData()) {
var rows = results.data().rows;
var fields = results.data().fields;
var container = document.getElementById('mainContainer');
container.innerHTML = '';
// カテゴリごとにグループ化
var groups = {};
rows.forEach(function(row) {
var catIndex = fields.indexOf('category');
var category = row[catIndex];
if (!groups[category]) {
groups[category] = [];
}
groups[category].push(row);
});
// グループごとに描画
Object.keys(groups).forEach(function(category) {
var groupHtml = '<div class="category-group">';
groupHtml += '<div class="category-title">' + category + '</div>';
groupHtml += '<div class="category-shapes">';
groups[category].forEach(function(row) {
var deviceIndex = fields.indexOf('device_name');
var statusIndex = fields.indexOf('status');
var codeIndex = fields.indexOf('status_code');
var shapeIndex = fields.indexOf('shape_type');
var sizeIndex = fields.indexOf('size_class');
var deviceName = row[deviceIndex];
var status = row[statusIndex];
var statusCode = parseInt(row[codeIndex]);
var shapeType = row[shapeIndex];
var sizeClass = row[sizeIndex] || '';
var statusClass = '';
switch(statusCode) {
case 0: statusClass = 'status-normal'; break;
case 1: statusClass = 'status-warning'; break;
case 2: statusClass = 'status-error'; break;
case 3: statusClass = 'status-stop'; break;
}
var shapeHtml = '';
var allClasses = statusClass + ' ' + sizeClass;
if (shapeType === 'rectangle') {
shapeHtml = '<div class="rectangle ' + allClasses + '">' + status + '</div>';
} else if (shapeType === 'circle') {
shapeHtml = '<div class="circle ' + allClasses + '">' + status + '</div>';
} else if (shapeType === 'hexagon') {
shapeHtml = '<div class="hexagon ' + allClasses + '">' + status + '</div>';
} else if (shapeType === 'diamond') {
shapeHtml = '<div class="diamond ' + allClasses + '"><span>' + status + '</span></div>';
}
groupHtml += '<div class="shape-item">';
groupHtml += shapeHtml;
groupHtml += '<div class="device-label">' + deviceName + '</div>';
groupHtml += '</div>';
});
groupHtml += '</div></div>';
container.innerHTML += groupHtml;
});
}
});
});
</script>
</html>
</panel>
</row>
</dashboard>
🎯 クイックリファレンス
追加・修正早見表
やりたいこと 修正箇所 修正内容 データを増やす SPL count=N を増やす、case文に追加 新しい図形を追加 CSS + JS クラス定義 + shapeTypes配列 + if-else 色を変える CSS .status-xxx { background-color: #xxx ; } サイズを変える CSS width / height または transform: scale() 配置を変える CSS justify-content / grid-template-columns グループ化する SPL + JS categoryフィールド追加 + グループ処理 点滅させる CSS @keyframes + animation ホバー効果を追加 CSS .shape-item:hover { ... }
これで既存ダッシュボードへの図形追加・修正・編集が自由自在です!🎉
★コピペセクション
# 🛠️ Splunk 既存ダッシュボード 図形の追加・修正・編集ガイド
## ~チャート上に図形を自由に追加・変更する方法~
---
## 📚 この記事で学べること
| 項目 | 内容 |
|------|------|
| 図形の追加 | 新しい四角・丸・棒・三角を追加 |
| 位置の変更 | 図形を好きな場所に配置 |
| 色の変更 | ステータスや条件で色を変える |
| サイズ変更 | 図形の大きさを調整 |
| グループ化 | 複数図形をまとめて管理 |
---
# 🔷 Part 1: 図形追加の基本パターン
## 1-1. 追加の全体像
```
【既存ダッシュボード】 【追加後】
┌─────────────────────┐ ┌─────────────────────┐
│ ■ ● ▬ ▼ │ → │ ■ ● ▬ ▼ ★ ◆ │
│ A B C D │ │ A B C D E F │
└─────────────────────┘ └─────────────────────┘
追加に必要な作業:
1. SPL: 新しいデータ行を追加
2. CSS: 新しい図形のスタイルを定義
3. JavaScript: 図形生成ロジックを修正
```
---
## 1-2. 新しい図形を追加する3ステップ
### ステップ1: SPLにデータを追加
```spl
| makeresults count=6
| streamstats count as id
| eval device_name = case(
id==1, "サーバーA",
id==2, "サーバーB",
id==3, "ネットワーク機器C",
id==4, "データベースD",
id==5, "ストレージE", /* ★追加★ */
id==6, "ファイアウォールF" /* ★追加★ */
)
| eval status = case(
id==1, "正常",
id==2, "警告",
id==3, "異常",
id==4, "停止",
id==5, "正常", /* ★追加★ */
id==6, "警告" /* ★追加★ */
)
| eval status_code = case(
status=="正常", 0,
status=="警告", 1,
status=="異常", 2,
status=="停止", 3
)
| table device_name, status, status_code
```
```
【変更ポイント】
┌────────────────────────────────────────────────────────┐
│ count=4 → count=6 │
│ ↑ 生成する行数を増やす │
│ │
│ case文に新しい条件を追加 │
│ id==5, "ストレージE" │
│ id==6, "ファイアウォールF" │
└────────────────────────────────────────────────────────┘
```
### ステップ2: CSSに新しい図形スタイルを追加
```css
/* ===== 既存の図形 ===== */
.rectangle { ... }
.circle { ... }
.bar { ... }
.triangle { ... }
/* ===== ★新規追加★ 星形 ===== */
.star {
/* 星形はSVGまたはクリップパスで作成 */
width: 60px;
height: 60px;
background-color: #808080 ;
clip-path: polygon(
50% 0%, /* 上の頂点 */
61% 35%, /* 右上内側 */
98% 35%, /* 右上外側 */
68% 57%, /* 右下内側 */
79% 91%, /* 右下外側 */
50% 70%, /* 下の内側 */
21% 91%, /* 左下外側 */
32% 57%, /* 左下内側 */
2% 35%, /* 左上外側 */
39% 35% /* 左上内側 */
);
display: flex;
justify-content: center;
align-items: center;
transition: background-color 0.3s ease;
}
/* ===== ★新規追加★ ひし形 ===== */
.diamond {
width: 50px;
height: 50px;
background-color: #808080 ;
/* 45度回転させて正方形をひし形に */
transform: rotate(45deg);
display: flex;
justify-content: center;
align-items: center;
transition: background-color 0.3s ease;
}
/* ひし形の中のテキストを正位置に */
.diamond span {
transform: rotate(-45deg);
color: white;
font-size: 10px;
font-weight: bold;
}
/* ===== ★新規追加★ 六角形 ===== */
.hexagon {
width: 60px;
height: 52px;
background-color: #808080 ;
clip-path: polygon(
25% 0%, /* 左上 */
75% 0%, /* 右上 */
100% 50%, /* 右 */
75% 100%, /* 右下 */
25% 100%, /* 左下 */
0% 50% /* 左 */
);
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 11px;
font-weight: bold;
transition: background-color 0.3s ease;
}
```
### ステップ3: JavaScriptの図形配列を更新
```javascript
// 【変更前】4種類
var shapeTypes = ['rectangle', 'circle', 'bar', 'triangle'];
// 【変更後】6種類に増やす
var shapeTypes = ['rectangle', 'circle', 'bar', 'triangle', 'star', 'diamond'];
// ↑追加 ↑追加
```
```javascript
// 図形生成部分も追加
if (shapeType === 'rectangle') {
shapeHtml = '<div class="rectangle ' + statusClass + '">' + status + '</div>';
} else if (shapeType === 'circle') {
shapeHtml = '<div class="circle ' + statusClass + '">' + status + '</div>';
} else if (shapeType === 'bar') {
shapeHtml = '<div class="bar ' + statusClass + '">' + status + '</div>';
} else if (shapeType === 'triangle') {
shapeHtml = '<div class="triangle ' + statusClass + '"></div>';
}
// ★追加★
else if (shapeType === 'star') {
shapeHtml = '<div class="star ' + statusClass + '"></div>';
}
// ★追加★
else if (shapeType === 'diamond') {
shapeHtml = '<div class="diamond ' + statusClass + '"><span>' + status + '</span></div>';
}
```
---
# 🔷 Part 2: 図形の位置変更
## 2-1. Flexboxで配置を変更
### 左寄せ(デフォルト)
```css
.shapes-container {
display: flex;
justify-content: flex-start; /* ← 左寄せ */
gap: 20px;
}
```
```
┌─────────────────────────────────────────┐
│ ■ ● ▬ ▼ │
│ │
└─────────────────────────────────────────┘
```
### 中央寄せ
```css
.shapes-container {
display: flex;
justify-content: center; /* ← 中央寄せ */
gap: 20px;
}
```
```
┌─────────────────────────────────────────┐
│ ■ ● ▬ ▼ │
│ │
└─────────────────────────────────────────┘
```
### 右寄せ
```css
.shapes-container {
display: flex;
justify-content: flex-end; /* ← 右寄せ */
gap: 20px;
}
```
```
┌─────────────────────────────────────────┐
│ ■ ● ▬ ▼ │
│ │
└─────────────────────────────────────────┘
```
### 均等配置
```css
.shapes-container {
display: flex;
justify-content: space-between; /* ← 両端に配置、間を均等 */
gap: 20px;
}
```
```
┌─────────────────────────────────────────┐
│ ■ ● ▬ ▼ │
│ │
└─────────────────────────────────────────┘
```
### 均等配置(周囲も含む)
```css
.shapes-container {
display: flex;
justify-content: space-around; /* ← 周囲も含めて均等 */
gap: 20px;
}
```
```
┌─────────────────────────────────────────┐
│ ■ ● ▬ ▼ │
│ │
└─────────────────────────────────────────┘
```
---
## 2-2. CSS Gridで格子状に配置
### 3列のグリッド
```css
.shapes-container {
display: grid;
grid-template-columns: repeat(3, 1fr); /* 3列 */
gap: 20px;
padding: 20px;
}
```
```
┌─────────────────────────────────────────┐
│ ■ ● ▬ │
│ A B C │
│ │
│ ▼ ★ ◆ │
│ D E F │
└─────────────────────────────────────────┘
```
### 4列のグリッド
```css
.shapes-container {
display: grid;
grid-template-columns: repeat(4, 1fr); /* 4列 */
gap: 15px;
padding: 20px;
}
```
```
┌─────────────────────────────────────────┐
│ ■ ● ▬ ▼ │
│ A B C D │
│ │
│ ★ ◆ │
│ E F │
└─────────────────────────────────────────┘
```
### 列幅を固定
```css
.shapes-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
/* ↑ 最小120px、最大1frで自動的に列数を調整 */
gap: 20px;
}
```
---
## 2-3. 絶対位置指定(完全自由配置)
### 基本設定
```css
/* コンテナを基準点にする */
.shapes-container {
position: relative; /* ★重要★ 子要素の基準点になる */
width: 100%;
height: 400px; /* 高さを固定 */
background-color: #f5f5f5 ;
border: 1px solid #ddd ;
}
/* 各図形アイテムを絶対位置指定 */
.shape-item {
position: absolute; /* ★重要★ 親要素を基準に配置 */
}
```
### 個別の位置指定(CSS)
```css
/* 1番目の図形:左上 */
.shape-item:nth-child(1) {
top: 20px;
left: 30px;
}
/* 2番目の図形:右上 */
.shape-item:nth-child(2) {
top: 20px;
right: 30px;
}
/* 3番目の図形:中央 */
.shape-item:nth-child(3) {
top: 50%;
left: 50%;
transform: translate(-50%, -50%); /* 中心に配置 */
}
/* 4番目の図形:左下 */
.shape-item:nth-child(4) {
bottom: 20px;
left: 30px;
}
```
```
┌─────────────────────────────────────────┐
│ ■ ● │
│ A B │
│ │
│ ▬ │
│ C │
│ │
│ ▼ │
│ D │
└─────────────────────────────────────────┘
```
### JavaScriptで動的に位置指定
```javascript
// データに位置情報を含める方法
| makeresults count=4
| streamstats count as id
| eval device_name = case(id==1,"A", id==2,"B", id==3,"C", id==4,"D")
| eval status = case(id==1,"正常", id==2,"警告", id==3,"異常", id==4,"停止")
| eval status_code = case(status=="正常",0, status=="警告",1, status=="異常",2, status=="停止",3)
| eval pos_top = case(id==1,"20px", id==2,"20px", id==3,"150px", id==4,"150px")
| eval pos_left = case(id==1,"30px", id==2,"250px", id==3,"30px", id==4,"250px")
| table device_name, status, status_code, pos_top, pos_left
```
```javascript
// JavaScript側で位置を適用
var topIndex = fields.indexOf('pos_top');
var leftIndex = fields.indexOf('pos_left');
var posTop = row[topIndex];
var posLeft = row[leftIndex];
// スタイル属性で位置を指定
var itemHtml =
'<div class="shape-item" style="top: ' + posTop + '; left: ' + posLeft + ';">' +
shapeHtml +
'<div class="device-label">' + deviceName + '</div>' +
'</div>';
```
---
# 🔷 Part 3: 色の変更方法
## 3-1. 新しいステータス色を追加
### CSS定義
```css
/* ===== 既存のステータス色 ===== */
.status-normal { background-color: #28a745 ; } /* 緑:正常 */
.status-warning { background-color: #ffc107 ; } /* 黄:警告 */
.status-error { background-color: #dc3545 ; } /* 赤:異常 */
.status-stop { background-color: #6c757d ; } /* 灰:停止 */
/* ===== ★新規追加★ ===== */
.status-info { background-color: #17a2b8 ; } /* 水色:情報 */
.status-success { background-color: #20c997 ; } /* ティール:成功 */
.status-danger { background-color: #e83e8c ; } /* ピンク:危険 */
.status-dark { background-color: #343a40 ; } /* 濃灰:不明 */
.status-primary { background-color: #007bff ; } /* 青:メイン */
.status-secondary { background-color: #6610f2 ; } /* 紫:サブ */
```
### SPL側でステータスコードを追加
```spl
| eval status_code = case(
status=="正常", 0,
status=="警告", 1,
status=="異常", 2,
status=="停止", 3,
status=="情報", 4, /* ★追加★ */
status=="成功", 5, /* ★追加★ */
status=="危険", 6, /* ★追加★ */
status=="不明", 7 /* ★追加★ */
)
```
### JavaScript側でswitch文を追加
```javascript
var statusClass = '';
switch(statusCode) {
case 0: statusClass = 'status-normal'; break;
case 1: statusClass = 'status-warning'; break;
case 2: statusClass = 'status-error'; break;
case 3: statusClass = 'status-stop'; break;
// ★追加★
case 4: statusClass = 'status-info'; break;
case 5: statusClass = 'status-success'; break;
case 6: statusClass = 'status-danger'; break;
case 7: statusClass = 'status-dark'; break;
default: statusClass = 'status-stop';
}
```
---
## 3-2. 数値範囲で色を変える
### SPLで数値に応じたステータスを設定
```spl
| makeresults count=5
| streamstats count as id
| eval value = case(id==1, 95, id==2, 75, id==3, 50, id==4, 25, id==5, 10)
| eval status = case(
value >= 80, "正常", /* 80以上:緑 */
value >= 60, "注意", /* 60-79:黄緑 */
value >= 40, "警告", /* 40-59:黄 */
value >= 20, "異常", /* 20-39:オレンジ */
value < 20, "危険" /* 20未満:赤 */
)
| eval status_code = case(
status=="正常", 0,
status=="注意", 1,
status=="警告", 2,
status=="異常", 3,
status=="危険", 4
)
| table id, value, status, status_code
```
### 対応するCSS
```css
.status-0 { background-color: #28a745 ; } /* 緑:80以上 */
.status-1 { background-color: #7cb342 ; } /* 黄緑:60-79 */
.status-2 { background-color: #ffc107 ; } /* 黄:40-59 */
.status-3 { background-color: #ff9800 ; } /* オレンジ:20-39 */
.status-4 { background-color: #dc3545 ; } /* 赤:20未満 */
```
---
## 3-3. グラデーション色
```css
/* グラデーション背景 */
.status-gradient-good {
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
}
.status-gradient-warning {
background: linear-gradient(135deg, #ffc107 0%, #ff9800 100%);
}
.status-gradient-bad {
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
}
```
---
## 3-4. 点滅アニメーション(異常時)
```css
/* 点滅アニメーション定義 */
@keyframes blink {
0% { opacity: 1; }
50% { opacity: 0.3; }
100% { opacity: 1; }
}
/* 異常ステータスに点滅を適用 */
.status-error {
background-color: #dc3545 ;
animation: blink 1s ease-in-out infinite;
/* ↑ 1秒間隔で無限に点滅 */
}
/* 危険ステータスは高速点滅 */
.status-danger {
background-color: #e83e8c ;
animation: blink 0.5s ease-in-out infinite;
/* ↑ 0.5秒間隔で高速点滅 */
}
```
---
# 🔷 Part 4: サイズ変更
## 4-1. 図形サイズの変更
### 小サイズ
```css
.shape-small {
transform: scale(0.7); /* 70%に縮小 */
}
/* または個別に指定 */
.rectangle-small {
width: 70px; /* 100px → 70px */
height: 42px; /* 60px → 42px */
}
.circle-small {
width: 50px; /* 70px → 50px */
height: 50px;
}
```
### 大サイズ
```css
.shape-large {
transform: scale(1.5); /* 150%に拡大 */
}
/* または個別に指定 */
.rectangle-large {
width: 150px; /* 100px → 150px */
height: 90px; /* 60px → 90px */
}
.circle-large {
width: 100px; /* 70px → 100px */
height: 100px;
}
```
### SPLでサイズを指定
```spl
| eval size_class = case(
status=="正常", "shape-small",
status=="警告", "", /* 通常サイズ */
status=="異常", "shape-large", /* 異常は大きく目立たせる */
status=="停止", "shape-small"
)
| table device_name, status, status_code, size_class
```
### JavaScript側でサイズクラスを適用
```javascript
var sizeIndex = fields.indexOf('size_class');
var sizeClass = row[sizeIndex] || ''; // nullの場合は空文字
// 図形にサイズクラスを追加
shapeHtml = '<div class="rectangle ' + statusClass + ' ' + sizeClass + '">' + status + '</div>';
```
---
# 🔷 Part 5: 図形のグループ化
## 5-1. カテゴリ別にグループ化
### SPLでカテゴリを追加
```spl
| makeresults count=6
| streamstats count as id
| eval device_name = case(
id==1, "Webサーバー1",
id==2, "Webサーバー2",
id==3, "DBサーバー1",
id==4, "DBサーバー2",
id==5, "ネットワーク1",
id==6, "ネットワーク2"
)
| eval category = case(
id<=2, "Web層",
id<=4, "DB層",
id<=6, "NW層"
)
| eval status = case(id==1,"正常", id==2,"警告", id==3,"正常", id==4,"異常", id==5,"正常", id==6,"停止")
| eval status_code = case(status=="正常",0, status=="警告",1, status=="異常",2, status=="停止",3)
| table category, device_name, status, status_code
```
### CSS:グループスタイル
```css
/* グループコンテナ */
.category-group {
border: 2px solid #ddd ;
border-radius: 10px;
padding: 15px;
margin-bottom: 20px;
background-color: #f9f9f9 ;
}
/* グループタイトル */
.category-title {
font-size: 16px;
font-weight: bold;
color: #333 ;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid #007bff ;
}
/* グループ内の図形コンテナ */
.category-shapes {
display: flex;
gap: 15px;
flex-wrap: wrap;
}
```
### JavaScript:グループ別に描画
```javascript
results.on("data", function() {
if (results.hasData()) {
var rows = results.data().rows;
var fields = results.data().fields;
var container = document.getElementById('shapesContainer');
container.innerHTML = '';
// カテゴリごとにデータをグループ化
var groups = {};
rows.forEach(function(row) {
var categoryIndex = fields.indexOf('category');
var category = row[categoryIndex];
// グループが存在しなければ作成
if (!groups[category]) {
groups[category] = [];
}
// グループにデータを追加
groups[category].push(row);
});
// グループごとにHTMLを生成
Object.keys(groups).forEach(function(category) {
// グループコンテナ開始
var groupHtml = '<div class="category-group">';
groupHtml += '<div class="category-title">' + category + '</div>';
groupHtml += '<div class="category-shapes">';
// グループ内の各図形を生成
groups[category].forEach(function(row, index) {
var deviceIndex = fields.indexOf('device_name');
var statusIndex = fields.indexOf('status');
var codeIndex = fields.indexOf('status_code');
var deviceName = row[deviceIndex];
var status = row[statusIndex];
var statusCode = parseInt(row[codeIndex]);
var statusClass = '';
switch(statusCode) {
case 0: statusClass = 'status-normal'; break;
case 1: statusClass = 'status-warning'; break;
case 2: statusClass = 'status-error'; break;
case 3: statusClass = 'status-stop'; break;
}
// 図形HTML
groupHtml += '<div class="shape-item">';
groupHtml += '<div class="circle ' + statusClass + '">' + status + '</div>';
groupHtml += '<div class="device-label">' + deviceName + '</div>';
groupHtml += '</div>';
});
groupHtml += '</div>'; // category-shapes終了
groupHtml += '</div>'; // category-group終了
container.innerHTML += groupHtml;
});
}
});
```
### 表示結果
```
┌─────────────────────────────────────────────────────┐
│ Web層 │
│ ───────────────────────────────── │
│ │ ● 正常 ● 警告 │ │
│ │ Web1 Web2 │ │
│ └────────────────────┘ │
├─────────────────────────────────────────────────────┤
│ DB層 │
│ ───────────────────────────────── │
│ │ ● 正常 ● 異常 │ │
│ │ DB1 DB2 │ │
│ └────────────────────┘ │
├─────────────────────────────────────────────────────┤
│ NW層 │
│ ───────────────────────────────── │
│ │ ● 正常 ● 停止 │ │
│ │ NW1 NW2 │ │
│ └────────────────────┘ │
└─────────────────────────────────────────────────────┘
```
---
# 🔷 Part 6: 既存コードの修正チェックリスト
## 6-1. 図形を追加するときのチェックリスト
```
□ 1. SPL修正
□ makeresults count の数を増やした
□ eval の case文に新しいデータを追加した
□ 2. CSS修正
□ 新しい図形のクラスを定義した
□ 新しいステータス色を定義した(必要な場合)
□ 三角形など特殊図形の場合、border-colorも定義した
□ 3. JavaScript修正
□ shapeTypes配列に新しい図形タイプを追加した
□ if-else文に新しい図形の生成コードを追加した
□ switch文に新しいステータスコードを追加した(必要な場合)
```
## 6-2. よくある修正パターン
### パターン1: 図形の色だけ変えたい
```
修正箇所: CSSのみ
.status-normal { background-color: #28a745 ; }
↓ 変更
.status-normal { background-color: #00ff00 ; } /* より明るい緑に */
```
### パターン2: 図形のサイズだけ変えたい
```
修正箇所: CSSのみ
.rectangle {
width: 100px; → width: 150px;
height: 60px; → height: 90px;
}
```
### パターン3: 図形の配置を変えたい
```
修正箇所: CSSのみ
.shapes-container {
justify-content: flex-start; → justify-content: center;
}
```
### パターン4: 新しいデバイスを追加したい
```
修正箇所: SPLのみ
count=4 → count=5
case文に id==5 の条件を追加
```
### パターン5: 新しい図形タイプを追加したい
```
修正箇所: CSS + JavaScript
1. CSSに .newShape { ... } を追加
2. JSの shapeTypes に 'newShape' を追加
3. JSの if-else に newShape の分岐を追加
```
---
# 🔷 Part 7: 完全版テンプレート(カスタマイズ用)
## 7-1. フル機能版コード
```xml
<dashboard>
<label>カスタマイズ可能図形ダッシュボード</label>
<!-- ========== データ取得サーチ ========== -->
<search id="mainSearch">
<query>
| makeresults count=8
| streamstats count as id
| eval device_name = case(
id==1, "Webサーバー1",
id==2, "Webサーバー2",
id==3, "APサーバー1",
id==4, "APサーバー2",
id==5, "DBサーバー1",
id==6, "DBサーバー2",
id==7, "FW1",
id==8, "FW2"
)
| eval category = case(
id<=2, "Web層",
id<=4, "AP層",
id<=6, "DB層",
id<=8, "NW層"
)
| eval status = case(
id==1, "正常",
id==2, "正常",
id==3, "警告",
id==4, "正常",
id==5, "異常",
id==6, "正常",
id==7, "停止",
id==8, "正常"
)
| eval status_code = case(
status=="正常", 0,
status=="警告", 1,
status=="異常", 2,
status=="停止", 3
)
| eval shape_type = case(
id<=2, "circle",
id<=4, "rectangle",
id<=6, "hexagon",
id<=8, "diamond"
)
| eval size_class = case(
status=="異常", "shape-large",
status=="停止", "shape-large",
1==1, ""
)
| table category, device_name, status, status_code, shape_type, size_class
</query>
<earliest>-24h@h</earliest>
<latest>now</latest>
</search>
<row>
<panel>
<title>システム監視ダッシュボード</title>
<html>
<style>
/* ========== メインコンテナ ========== */
.main-container {
padding: 20px;
background-color: #f8f9fa ;
}
/* ========== グループスタイル ========== */
.category-group {
border: 2px solid #dee2e6 ;
border-radius: 12px;
padding: 20px;
margin-bottom: 25px;
background-color: white;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.category-title {
font-size: 18px;
font-weight: bold;
color: #495057 ;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 3px solid #007bff ;
}
.category-shapes {
display: flex;
gap: 25px;
flex-wrap: wrap;
justify-content: flex-start;
}
/* ========== 図形アイテム共通 ========== */
.shape-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 10px;
border-radius: 8px;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.shape-item:hover {
transform: translateY(-5px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
/* ========== 四角形 ========== */
.rectangle {
width: 90px;
height: 55px;
background-color: #808080 ;
border-radius: 6px;
border: 2px solid #333 ;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-weight: bold;
font-size: 13px;
transition: all 0.3s ease;
}
/* ========== 丸 ========== */
.circle {
width: 65px;
height: 65px;
background-color: #808080 ;
border-radius: 50%;
border: 2px solid #333 ;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-weight: bold;
font-size: 12px;
transition: all 0.3s ease;
}
/* ========== 六角形 ========== */
.hexagon {
width: 70px;
height: 60px;
background-color: #808080 ;
clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
display: flex;
justify-content: center;
align-items: center;
color: white;
font-weight: bold;
font-size: 11px;
transition: all 0.3s ease;
}
/* ========== ひし形 ========== */
.diamond {
width: 50px;
height: 50px;
background-color: #808080 ;
transform: rotate(45deg);
display: flex;
justify-content: center;
align-items: center;
transition: all 0.3s ease;
}
.diamond span {
transform: rotate(-45deg);
color: white;
font-size: 10px;
font-weight: bold;
}
/* ========== ラベル ========== */
.device-label {
font-size: 12px;
color: #333 ;
font-weight: bold;
text-align: center;
}
.status-label {
font-size: 11px;
color: #666 ;
}
/* ========== ステータス色 ========== */
.status-normal { background-color: #28a745 !important; }
.status-warning { background-color: #ffc107 !important; color: #333 !important; }
.status-error { background-color: #dc3545 !important; }
.status-stop { background-color: #6c757d !important; }
/* ========== サイズ変更 ========== */
.shape-large { transform: scale(1.3); }
.shape-small { transform: scale(0.8); }
/* ========== 点滅(異常・停止時) ========== */
@keyframes pulse {
0% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.05); opacity: 0.8; }
100% { transform: scale(1); opacity: 1; }
}
.status-error, .status-stop {
animation: pulse 1.5s ease-in-out infinite;
}
.shape-large.status-error,
.shape-large.status-stop {
animation: pulse 1s ease-in-out infinite;
}
</style>
<div class="main-container" id="mainContainer">
<!-- JavaScriptで生成 -->
</div>
<script>
require([
'splunkjs/mvc',
'splunkjs/mvc/simplexml/ready!'
], function(mvc) {
var searchManager = mvc.Components.get("mainSearch");
var results = searchManager.data("results");
results.on("data", function() {
if (results.hasData()) {
var rows = results.data().rows;
var fields = results.data().fields;
var container = document.getElementById('mainContainer');
container.innerHTML = '';
// カテゴリごとにグループ化
var groups = {};
rows.forEach(function(row) {
var catIndex = fields.indexOf('category');
var category = row[catIndex];
if (!groups[category]) {
groups[category] = [];
}
groups[category].push(row);
});
// グループごとに描画
Object.keys(groups).forEach(function(category) {
var groupHtml = '<div class="category-group">';
groupHtml += '<div class="category-title">' + category + '</div>';
groupHtml += '<div class="category-shapes">';
groups[category].forEach(function(row) {
var deviceIndex = fields.indexOf('device_name');
var statusIndex = fields.indexOf('status');
var codeIndex = fields.indexOf('status_code');
var shapeIndex = fields.indexOf('shape_type');
var sizeIndex = fields.indexOf('size_class');
var deviceName = row[deviceIndex];
var status = row[statusIndex];
var statusCode = parseInt(row[codeIndex]);
var shapeType = row[shapeIndex];
var sizeClass = row[sizeIndex] || '';
var statusClass = '';
switch(statusCode) {
case 0: statusClass = 'status-normal'; break;
case 1: statusClass = 'status-warning'; break;
case 2: statusClass = 'status-error'; break;
case 3: statusClass = 'status-stop'; break;
}
var shapeHtml = '';
var allClasses = statusClass + ' ' + sizeClass;
if (shapeType === 'rectangle') {
shapeHtml = '<div class="rectangle ' + allClasses + '">' + status + '</div>';
} else if (shapeType === 'circle') {
shapeHtml = '<div class="circle ' + allClasses + '">' + status + '</div>';
} else if (shapeType === 'hexagon') {
shapeHtml = '<div class="hexagon ' + allClasses + '">' + status + '</div>';
} else if (shapeType === 'diamond') {
shapeHtml = '<div class="diamond ' + allClasses + '"><span>' + status + '</span></div>';
}
groupHtml += '<div class="shape-item">';
groupHtml += shapeHtml;
groupHtml += '<div class="device-label">' + deviceName + '</div>';
groupHtml += '</div>';
});
groupHtml += '</div></div>';
container.innerHTML += groupHtml;
});
}
});
});
</script>
</html>
</panel>
</row>
</dashboard>
```
---
# 🎯 クイックリファレンス
## 追加・修正早見表
| やりたいこと | 修正箇所 | 修正内容 |
|-------------|---------|---------|
| データを増やす | SPL | `count=N` を増やす、`case`文に追加 |
| 新しい図形を追加 | CSS + JS | クラス定義 + `shapeTypes`配列 + `if-else` |
| 色を変える | CSS | `.status-xxx { background-color: #xxx ; }` |
| サイズを変える | CSS | `width` / `height` または `transform: scale()` |
| 配置を変える | CSS | `justify-content` / `grid-template-columns` |
| グループ化する | SPL + JS | `category`フィールド追加 + グループ処理 |
| 点滅させる | CSS | `@keyframes` + `animation` |
| ホバー効果を追加 | CSS | `.shape-item:hover { ... }` |
---
**これで既存ダッシュボードへの図形追加・修正・編集が自由自在です!🎉**~新人エンジニア向け・実務で使えるコード徹底解説~
# 🎓 Splunk 図形追加・編集コード 一行ずつ完全解説
## ~新人エンジニア向け・実務で使えるコード徹底解説~
---
## 📚 この記事の目的
| 項目 | 内容 |
|------|------|
| 対象者 | Splunk初心者・新人エンジニア |
| 目標 | コードの意味を完全理解し、実務で修正・追加できるようになる |
| 構成 | SPL → CSS → HTML → JavaScript の順に解説 |
---
# 🔷 Part 1: SPL(データ取得)完全解説
## 1-1. SPL全体コード
```spl
| makeresults count=8
| streamstats count as id
| eval device_name = case(
id==1, "Webサーバー1",
id==2, "Webサーバー2",
id==3, "APサーバー1",
id==4, "APサーバー2",
id==5, "DBサーバー1",
id==6, "DBサーバー2",
id==7, "FW1",
id==8, "FW2"
)
| eval category = case(
id<=2, "Web層",
id<=4, "AP層",
id<=6, "DB層",
id<=8, "NW層"
)
| eval status = case(
id==1, "正常",
id==2, "正常",
id==3, "警告",
id==4, "正常",
id==5, "異常",
id==6, "正常",
id==7, "停止",
id==8, "正常"
)
| eval status_code = case(
status=="正常", 0,
status=="警告", 1,
status=="異常", 2,
status=="停止", 3
)
| eval shape_type = case(
id<=2, "circle",
id<=4, "rectangle",
id<=6, "hexagon",
id<=8, "diamond"
)
| eval size_class = case(
status=="異常", "shape-large",
status=="停止", "shape-large",
1==1, ""
)
| table category, device_name, status, status_code, shape_type, size_class
```
---
## 1-2. SPL一行ずつ解説
### データ生成部分
```spl
| makeresults count=8
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【コマンド】makeresults │
│ 【役割】テスト用の空データを生成する │
│ 【引数】count=8 → 8行のデータを作成 │
│ │
│ 【実務での使い方】 │
│ ・テスト時: makeresults で仮データを作成 │
│ ・本番時: index=your_index に置き換える │
│ │
│ 【修正例:デバイスを増やしたい場合】 │
│ count=8 → count=10 (10個に増やす) │
│ count=8 → count=20 (20個に増やす) │
│ │
│ 【この時点のデータ】 │
│ ┌──────────────────┐ │
│ │ _time │ ← 8行の空データ(時刻のみ) │
│ ├──────────────────┤ │
│ │ 2024-01-01 00:00 │ │
│ │ 2024-01-01 00:00 │ │
│ │ ... (8行) │ │
│ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
```spl
| streamstats count as id
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【コマンド】streamstats │
│ 【役割】各行に連番を振る │
│ 【引数】count as id → カウント結果を「id」フィールドに保存 │
│ │
│ 【stats vs streamstats の違い】 │
│ ・stats: 全データを集計して1行にまとめる │
│ ・streamstats: 各行ごとに累計を計算(行数は変わらない) │
│ │
│ 【この時点のデータ】 │
│ ┌──────────────────┬─────┐ │
│ │ _time │ id │ │
│ ├──────────────────┼─────┤ │
│ │ 2024-01-01 00:00 │ 1 │ ← 連番が振られた │
│ │ 2024-01-01 00:00 │ 2 │ │
│ │ 2024-01-01 00:00 │ 3 │ │
│ │ ... (8行) │ ... │ │
│ └──────────────────┴─────┘ │
└─────────────────────────────────────────────────────────────────┘
```
### デバイス名の設定
```spl
| eval device_name = case(
id==1, "Webサーバー1",
id==2, "Webサーバー2",
id==3, "APサーバー1",
id==4, "APサーバー2",
id==5, "DBサーバー1",
id==6, "DBサーバー2",
id==7, "FW1",
id==8, "FW2"
)
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【コマンド】eval │
│ 【役割】新しいフィールドを作成し、値を計算・設定する │
│ 【書式】eval フィールド名 = 値または関数 │
│ │
│ 【関数】case(条件1, 値1, 条件2, 値2, ...) │
│ 【役割】条件に応じた値を返す(if-else文のようなもの) │
│ │
│ 【書き方のルール】 │
│ case( │
│ 条件, 結果, ← カンマで区切る │
│ 条件, 結果, │
│ ... │
│ ) │
│ │
│ 【演算子】 │
│ == 等しい(比較) │
│ = 代入(evalの左辺で使う) │
│ != 等しくない │
│ < より小さい │
│ <= 以下 │
│ > より大きい │
│ >= 以上 │
│ │
│ 【実務での修正例:デバイスを追加】 │
│ id==9, "ロードバランサー1", │
│ id==10, "ロードバランサー2" │
│ ↑ case文の最後に追加(カンマを忘れずに!) │
│ │
│ 【この時点のデータ】 │
│ ┌─────┬─────────────────┐ │
│ │ id │ device_name │ │
│ ├─────┼─────────────────┤ │
│ │ 1 │ Webサーバー1 │ │
│ │ 2 │ Webサーバー2 │ │
│ │ 3 │ APサーバー1 │ │
│ │ ... │ ... │ │
│ └─────┴─────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
### カテゴリの設定
```spl
| eval category = case(
id<=2, "Web層",
id<=4, "AP層",
id<=6, "DB層",
id<=8, "NW層"
)
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【役割】デバイスをグループ(カテゴリ)に分類する │
│ │
│ 【条件の評価順序】★重要★ │
│ case文は上から順に評価され、最初にマッチした条件の値を返す │
│ │
│ id=1 の場合: │
│ id<=2 (1<=2) → true → "Web層" を返す → 終了 │
│ │
│ id=3 の場合: │
│ id<=2 (3<=2) → false → 次へ │
│ id<=4 (3<=4) → true → "AP層" を返す → 終了 │
│ │
│ 【カテゴリの対応表】 │
│ id 1〜2 → Web層 │
│ id 3〜4 → AP層 │
│ id 5〜6 → DB層 │
│ id 7〜8 → NW層 │
│ │
│ 【実務での修正例:カテゴリを追加】 │
│ id<=10, "LB層" ← 新しいカテゴリを追加 │
│ │
│ 【注意】XMLでは < を < と書く必要がある │
│ id<=2 → id<=2 │
└─────────────────────────────────────────────────────────────────┘
```
### ステータスの設定
```spl
| eval status = case(
id==1, "正常",
id==2, "正常",
id==3, "警告",
id==4, "正常",
id==5, "異常",
id==6, "正常",
id==7, "停止",
id==8, "正常"
)
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【役割】各デバイスのステータス(状態)を設定 │
│ │
│ 【ステータスの種類】 │
│ "正常" → 問題なく稼働中(緑色で表示) │
│ "警告" → 注意が必要(黄色で表示) │
│ "異常" → 問題発生中(赤色で表示) │
│ "停止" → 稼働していない(灰色で表示) │
│ │
│ 【実務での本番データ取得例】 │
│ テストではなく実際のデータを使う場合: │
│ │
│ index=monitoring sourcetype=server_status │
│ | stats latest(status) as status by device_name │
│ | eval status = case( │
│ status_code==0, "正常", │
│ status_code==1, "警告", │
│ status_code==2, "異常", │
│ 1==1, "不明" │
│ ) │
│ │
│ 【1==1 の意味】 │
│ 「常に true」= else(その他すべて)の役割 │
│ どの条件にも当てはまらない場合のデフォルト値を設定 │
└─────────────────────────────────────────────────────────────────┘
```
### ステータスコードの設定
```spl
| eval status_code = case(
status=="正常", 0,
status=="警告", 1,
status=="異常", 2,
status=="停止", 3
)
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【役割】ステータス文字列を数値コードに変換 │
│ │
│ 【なぜ数値コードが必要?】 │
│ 1. JavaScriptのswitch文で使いやすい │
│ 2. 文字列より数値の方が比較が高速 │
│ 3. ソートや計算がしやすい │
│ │
│ 【対応表】 │
│ ┌─────────┬─────────────┬────────────────┐ │
│ │ status │ status_code │ 後で使う色 │ │
│ ├─────────┼─────────────┼────────────────┤ │
│ │ 正常 │ 0 │ 緑 (#28a745) │ │
│ │ 警告 │ 1 │ 黄 (#ffc107) │ │
│ │ 異常 │ 2 │ 赤 (#dc3545) │ │
│ │ 停止 │ 3 │ 灰 (#6c757d) │ │
│ └─────────┴─────────────┴────────────────┘ │
│ │
│ 【実務での修正例:新しいステータスを追加】 │
│ status=="情報", 4, │
│ status=="メンテナンス", 5 │
│ ↑ JavaScriptのswitch文にも対応するcaseを追加すること │
└─────────────────────────────────────────────────────────────────┘
```
### 図形タイプの設定
```spl
| eval shape_type = case(
id<=2, "circle",
id<=4, "rectangle",
id<=6, "hexagon",
id<=8, "diamond"
)
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【役割】各デバイスに表示する図形の種類を指定 │
│ │
│ 【図形の種類と見た目】 │
│ "circle" → ● 丸 │
│ "rectangle" → ■ 四角形 │
│ "hexagon" → ⬡ 六角形 │
│ "diamond" → ◆ ひし形 │
│ │
│ 【カテゴリと図形の対応】 │
│ Web層(id 1〜2) → 丸 │
│ AP層(id 3〜4) → 四角形 │
│ DB層(id 5〜6) → 六角形 │
│ NW層(id 7〜8) → ひし形 │
│ │
│ 【実務での修正例】 │
│ ・カテゴリごとに図形を変える(現在の方式) │
│ ・ステータスごとに図形を変える: │
│ | eval shape_type = case( │
│ status=="正常", "circle", │
│ status=="警告", "triangle", │
│ status=="異常", "rectangle", │
│ 1==1, "diamond" │
│ ) │
│ │
│ 【新しい図形を追加する場合】 │
│ 1. SPLに図形タイプを追加 │
│ 2. CSSに .newShape { } を追加 │
│ 3. JavaScriptの if-else に分岐を追加 │
└─────────────────────────────────────────────────────────────────┘
```
### サイズクラスの設定
```spl
| eval size_class = case(
status=="異常", "shape-large",
status=="停止", "shape-large",
1==1, ""
)
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【役割】ステータスに応じて図形のサイズを変える │
│ │
│ 【設定の意味】 │
│ 異常 → "shape-large" → 大きく表示(目立たせる) │
│ 停止 → "shape-large" → 大きく表示(目立たせる) │
│ その他 → "" → 通常サイズ │
│ │
│ 【1==1 の意味】 │
│ 「1は1に等しい」= 常にtrue │
│ = else(上のどの条件にも当てはまらない場合) │
│ = デフォルト値を設定するイディオム(慣用句) │
│ │
│ 【"" (空文字)の意味】 │
│ CSSクラスを追加しない = 通常のスタイルのまま │
│ │
│ 【実務での修正例】 │
│ ・警告も少し大きくしたい場合: │
│ status=="警告", "shape-medium", │
│ │
│ ・正常は小さくしたい場合: │
│ status=="正常", "shape-small", │
└─────────────────────────────────────────────────────────────────┘
```
### 出力フィールドの指定
```spl
| table category, device_name, status, status_code, shape_type, size_class
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【コマンド】table │
│ 【役割】最終的に出力するフィールドを指定し、表形式で表示 │
│ │
│ 【指定したフィールド】 │
│ category → グループ名(Web層、AP層など) │
│ device_name → デバイス名 │
│ status → ステータス文字列(正常、警告など) │
│ status_code → ステータス数値(0, 1, 2, 3) │
│ shape_type → 図形の種類(circle, rectangleなど) │
│ size_class → サイズクラス(shape-large または空) │
│ │
│ 【最終出力データ】 │
│ ┌──────────┬───────────────┬────────┬─────┬───────────┬────────┐│
│ │ category │ device_name │ status │code │shape_type │size ││
│ ├──────────┼───────────────┼────────┼─────┼───────────┼────────┤│
│ │ Web層 │ Webサーバー1 │ 正常 │ 0 │ circle │ ││
│ │ Web層 │ Webサーバー2 │ 正常 │ 0 │ circle │ ││
│ │ AP層 │ APサーバー1 │ 警告 │ 1 │ rectangle │ ││
│ │ AP層 │ APサーバー2 │ 正常 │ 0 │ rectangle │ ││
│ │ DB層 │ DBサーバー1 │ 異常 │ 2 │ hexagon │ large ││
│ │ DB層 │ DBサーバー2 │ 正常 │ 0 │ hexagon │ ││
│ │ NW層 │ FW1 │ 停止 │ 3 │ diamond │ large ││
│ │ NW層 │ FW2 │ 正常 │ 0 │ diamond │ ││
│ └──────────┴───────────────┴────────┴─────┴───────────┴────────┘│
│ │
│ 【実務ポイント】 │
│ ・tableで指定しないフィールドはJavaScriptに渡されない │
│ ・必要なフィールドは必ずtableに含める │
└─────────────────────────────────────────────────────────────────┘
```
---
# 🔷 Part 2: CSS(スタイル)完全解説
## 2-1. メインコンテナ
```css
/* ========== メインコンテナ ========== */
.main-container {
padding: 20px;
background-color: #f8f9fa ;
}
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【セレクタ】.main-container │
│ 【役割】ダッシュボード全体を包む最外側の箱 │
│ │
│ 【プロパティ解説】 │
│ │
│ padding: 20px; │
│ ├─ 意味: 内側の余白を20ピクセルに設定 │
│ ├─ 効果: コンテンツが枠にくっつかず、見やすくなる │
│ └─ 図解: │
│ ┌─────────────────────────┐ │
│ │ ←20px→ コンテンツ ←20px→│ │
│ │ ↑ │ │
│ │ 20px │ │
│ │ ↓ │ │
│ └─────────────────────────┘ │
│ │
│ background-color: #f8f9fa ; │
│ ├─ 意味: 背景色を薄いグレーに設定 │
│ ├─ #f8f9fa = Bootstrapのlight色(目に優しい薄グレー) │
│ └─ 効果: 白背景よりコンテンツが引き立つ │
│ │
│ 【実務での修正例】 │
│ padding: 30px; → もっと余白を広げる │
│ background-color: #fff ; → 白背景に変更 │
│ background-color: #343a40 ; → ダークモードに │
└─────────────────────────────────────────────────────────────────┘
```
## 2-2. グループスタイル
```css
/* ========== グループスタイル ========== */
.category-group {
border: 2px solid #dee2e6 ;
border-radius: 12px;
padding: 20px;
margin-bottom: 25px;
background-color: white;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【セレクタ】.category-group │
│ 【役割】各カテゴリ(Web層、AP層など)を囲む箱 │
│ │
│ 【プロパティ解説】 │
│ │
│ border: 2px solid #dee2e6 ; │
│ ├─ 2px = 枠線の太さ(2ピクセル) │
│ ├─ solid = 実線(他: dashed=破線, dotted=点線) │
│ └─ #dee2e6 = 枠線の色(薄いグレー) │
│ │
│ border-radius: 12px; │
│ ├─ 意味: 角を12ピクセル丸くする │
│ └─ 図解: │
│ 0px: 12px: │
│ ┌────┐ ╭────╮ ← 角が丸い │
│ │ │ │ │ │
│ └────┘ ╰────╯ │
│ │
│ padding: 20px; │
│ └─ 意味: 箱の内側の余白(コンテンツと枠線の間) │
│ │
│ margin-bottom: 25px; │
│ ├─ 意味: 箱の下側の外余白 │
│ └─ 効果: 次のグループとの間隔を確保 │
│ ┌────────────┐ │
│ │ Web層 │ │
│ └────────────┘ │
│ ↓ 25px ← この間隔 │
│ ┌────────────┐ │
│ │ AP層 │ │
│ └────────────┘ │
│ │
│ background-color: white; │
│ └─ 意味: 背景を白に(メインコンテナの薄グレーとのコントラスト) │
│ │
│ box-shadow: 0 2px 8px rgba(0,0,0,0.1); │
│ ├─ 0 = 横方向のずれ(なし) │
│ ├─ 2px = 縦方向のずれ(下に2px) │
│ ├─ 8px = ぼかしの範囲 │
│ ├─ rgba(0,0,0,0.1) = 影の色(黒10%透明度) │
│ └─ 効果: カードが浮いているように見える │
│ │
│ 【実務での修正例】 │
│ border: 3px solid #007bff ; → 青い太枠に │
│ border-radius: 0px; → 角張ったデザインに │
│ box-shadow: none; → 影をなくす │
└─────────────────────────────────────────────────────────────────┘
```
```css
.category-title {
font-size: 18px;
font-weight: bold;
color: #495057 ;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 3px solid #007bff ;
}
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【セレクタ】.category-title │
│ 【役割】カテゴリ名(「Web層」など)の見出しスタイル │
│ │
│ 【プロパティ解説】 │
│ │
│ font-size: 18px; │
│ └─ 意味: 文字サイズを18ピクセルに(デフォルト16pxより大きめ) │
│ │
│ font-weight: bold; │
│ └─ 意味: 太字にする │
│ │
│ color: #495057 ; │
│ └─ 意味: 文字色(濃いめのグレー) │
│ │
│ margin-bottom: 15px; │
│ └─ 意味: タイトルの下の外余白(図形との間隔) │
│ │
│ padding-bottom: 10px; │
│ └─ 意味: タイトルの下の内余白(線との間隔) │
│ │
│ border-bottom: 3px solid #007bff ; │
│ ├─ 意味: 下線を引く │
│ ├─ 3px = 線の太さ │
│ ├─ solid = 実線 │
│ ├─ #007bff = 青色 │
│ └─ 図解: │
│ Web層 │
│ ━━━━━━━━━━ ← 青い下線 │
│ │
│ 【実務での修正例】 │
│ border-bottom: 3px solid #28a745 ; → 緑の下線に │
│ font-size: 24px; → より大きな見出しに │
└─────────────────────────────────────────────────────────────────┘
```
```css
.category-shapes {
display: flex;
gap: 25px;
flex-wrap: wrap;
justify-content: flex-start;
}
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【セレクタ】.category-shapes │
│ 【役割】グループ内の図形を並べるコンテナ │
│ │
│ 【プロパティ解説】 │
│ │
│ display: flex; │
│ ├─ 意味: Flexboxレイアウトを有効にする │
│ └─ 効果: 子要素が横並びになる │
│ │
│ gap: 25px; │
│ ├─ 意味: 子要素間の余白を25ピクセルに │
│ └─ 図解: │
│ ● ←25px→ ● ←25px→ ● │
│ │
│ flex-wrap: wrap; │
│ ├─ 意味: 子要素がはみ出したら折り返す │
│ └─ 図解: │
│ nowrap: wrap: │
│ ● ● ● ● ● →はみ出す ● ● ● ● │
│ ● ● ← 折り返す │
│ │
│ justify-content: flex-start; │
│ ├─ 意味: 子要素を左寄せにする │
│ └─ 他の値: │
│ flex-start : 左寄せ │ ● ● ● │ │
│ center : 中央寄せ │ ● ● ● │ │
│ flex-end : 右寄せ │ ● ● ● │ │
│ space-between: 両端揃え │ ● ● ● │ │
│ space-around : 均等配置 │ ● ● ● │ │
│ │
│ 【実務での修正例】 │
│ justify-content: center; → 中央揃えに │
│ gap: 40px; → もっと間隔を広げる │
│ flex-direction: column; → 縦並びに変更 │
└─────────────────────────────────────────────────────────────────┘
```
## 2-3. 図形アイテム共通スタイル
```css
/* ========== 図形アイテム共通 ========== */
.shape-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 10px;
border-radius: 8px;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【セレクタ】.shape-item │
│ 【役割】1つの図形+ラベルをまとめるコンテナ │
│ │
│ 【構造イメージ】 │
│ ┌─────────────────┐ │
│ │ .shape-item │ │
│ │ ┌───────────┐ │ │
│ │ │ ● 図形 │ │ │
│ │ └───────────┘ │ │
│ │ デバイス名 │ │
│ └─────────────────┘ │
│ │
│ 【プロパティ解説】 │
│ │
│ display: flex; │
│ └─ Flexboxを有効化 │
│ │
│ flex-direction: column; │
│ ├─ 意味: 子要素を縦に並べる │
│ └─ 図解: │
│ row(横): column(縦): │
│ ● ラベル ● │
│ ラベル │
│ │
│ align-items: center; │
│ ├─ 意味: 子要素を中央揃え(横方向) │
│ └─ 図解: │
│ flex-start: center: flex-end: │
│ │● │ ● │ ●│ │
│ │ラベル │ ラベル │ ラベル│ │
│ │
│ gap: 8px; │
│ └─ 図形とラベルの間の余白 │
│ │
│ padding: 10px; │
│ └─ 内側の余白(ホバー効果用の余白確保) │
│ │
│ border-radius: 8px; │
│ └─ 角を丸くする(ホバー時の背景用) │
│ │
│ transition: transform 0.2s ease, box-shadow 0.2s ease; │
│ ├─ 意味: プロパティ変化時にアニメーションする │
│ ├─ transform 0.2s = 変形を0.2秒かけて行う │
│ ├─ box-shadow 0.2s = 影の変化を0.2秒かけて行う │
│ ├─ ease = 最初と最後がゆっくり、中間が速い │
│ └─ 効果: ホバー時に滑らかに変化する │
└─────────────────────────────────────────────────────────────────┘
```
```css
.shape-item:hover {
transform: translateY(-5px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【セレクタ】.shape-item:hover │
│ 【役割】マウスを乗せた時のスタイル │
│ │
│ 【:hover とは】 │
│ 疑似クラス(pseudo-class)の一つ │
│ マウスカーソルが要素の上にある時だけ適用される │
│ │
│ 【プロパティ解説】 │
│ │
│ transform: translateY(-5px); │
│ ├─ 意味: Y軸方向(縦)に-5px移動 = 5px上に移動 │
│ └─ 図解: │
│ 通常: ホバー時: │
│ ──── ──── │
│ │●│ │●│ ← 5px上に浮く │
│ ──── ──── │
│ ↑ │
│ 5px │
│ │
│ box-shadow: 0 4px 12px rgba(0,0,0,0.15); │
│ ├─ 0 = 横方向のずれ(なし) │
│ ├─ 4px = 縦方向のずれ(下に4px) │
│ ├─ 12px = ぼかしの範囲(より広く) │
│ ├─ rgba(0,0,0,0.15) = 影の色(黒15%透明度) │
│ └─ 効果: より大きな影 = より浮いて見える │
│ │
│ 【実務での修正例】 │
│ transform: scale(1.1); → 1.1倍に拡大 │
│ transform: rotate(5deg); → 5度傾ける │
│ background-color: #f0f0f0 ; → 背景色を変える │
└─────────────────────────────────────────────────────────────────┘
```
## 2-4. 四角形スタイル
```css
/* ========== 四角形 ========== */
.rectangle {
width: 90px;
height: 55px;
background-color: #808080 ;
border-radius: 6px;
border: 2px solid #333 ;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-weight: bold;
font-size: 13px;
transition: all 0.3s ease;
}
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【セレクタ】.rectangle │
│ 【役割】四角形図形のスタイル │
│ │
│ 【プロパティ解説】 │
│ │
│ width: 90px; │
│ height: 55px; │
│ ├─ 意味: 横90px、縦55pxの四角形 │
│ └─ 図解: │
│ ┌───────────────────────────┐ │
│ │ │ ← 90px │
│ │ 正常 │ ← 55px │
│ │ │ │
│ └───────────────────────────┘ │
│ │
│ background-color: #808080 ; │
│ └─ 意味: 初期背景色(グレー)。後でステータス色に上書きされる │
│ │
│ border-radius: 6px; │
│ └─ 意味: 角を6px丸める(少しだけ丸い四角形) │
│ │
│ border: 2px solid #333 ; │
│ └─ 意味: 2pxの黒い枠線 │
│ │
│ display: flex; │
│ justify-content: center; │
│ align-items: center; │
│ └─ 意味: 中のテキストを上下左右中央に配置 │
│ │
│ color: white; │
│ └─ 意味: テキストの色を白に │
│ │
│ font-weight: bold; │
│ └─ 意味: テキストを太字に │
│ │
│ font-size: 13px; │
│ └─ 意味: テキストサイズを13pxに │
│ │
│ transition: all 0.3s ease; │
│ ├─ all = すべてのプロパティ変化にアニメーション適用 │
│ ├─ 0.3s = 0.3秒かけて変化 │
│ └─ 効果: 色が変わる時に滑らかに変化 │
│ │
│ 【実務での修正例】 │
│ width: 120px; height: 70px; → より大きな四角形 │
│ border-radius: 0px; → 角張った四角形 │
│ border: none; → 枠線なし │
└─────────────────────────────────────────────────────────────────┘
```
## 2-5. 丸スタイル
```css
/* ========== 丸 ========== */
.circle {
width: 65px;
height: 65px;
background-color: #808080 ;
border-radius: 50%;
border: 2px solid #333 ;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-weight: bold;
font-size: 12px;
transition: all 0.3s ease;
}
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【セレクタ】.circle │
│ 【役割】丸(円)図形のスタイル │
│ │
│ 【丸を作るポイント】★重要★ │
│ │
│ width: 65px; │
│ height: 65px; │
│ border-radius: 50%; │
│ │
│ ┌─ 円を作る条件 ─────────────────────────────────────────────┐ │
│ │ 1. width と height が同じ値 → 正方形になる │ │
│ │ 2. border-radius: 50% → 正方形の角が最大限丸まる → 円に │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ 【図解】 │
│ border-radius: 0% border-radius: 25% border-radius: 50% │
│ ┌────┐ ╭────╮ (●) │
│ │ │ │ │ 完全な円 │
│ │ │ │ │ │
│ └────┘ ╰────╯ │
│ │
│ 【実務での修正例】 │
│ width: 100px; height: 100px; → より大きな円 │
│ border: 4px solid #007bff ; → 青い太枠の円 │
└─────────────────────────────────────────────────────────────────┘
```
## 2-6. 六角形スタイル
```css
/* ========== 六角形 ========== */
.hexagon {
width: 70px;
height: 60px;
background-color: #808080 ;
clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
display: flex;
justify-content: center;
align-items: center;
color: white;
font-weight: bold;
font-size: 11px;
transition: all 0.3s ease;
}
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【セレクタ】.hexagon │
│ 【役割】六角形図形のスタイル │
│ │
│ 【clip-pathとは】★新しい技術★ │
│ 要素を指定した形状で切り抜く(クリッピング)CSSプロパティ │
│ │
│ clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
│ │
│ 【polygon の座標システム】 │
│ ・左上が (0%, 0%) │
│ ・右下が (100%, 100%) │
│ ・時計回りに頂点を指定 │
│ │
│ 【六角形の頂点】 │
│ │
│ (25%,0%) (75%,0%) │
│ ●━━━━━━━● │
│ ╱ ╲ │
│ (0%,50%) ● ● (100%,50%) │
│ ╲ ╱ │
│ ●━━━━━━━● │
│ (25%,100%) (75%,100%) │
│ │
│ 【頂点の順番】 │
│ 1. (25%, 0%) = 左上 │
│ 2. (75%, 0%) = 右上 │
│ 3. (100%, 50%) = 右中央 │
│ 4. (75%, 100%) = 右下 │
│ 5. (25%, 100%) = 左下 │
│ 6. (0%, 50%) = 左中央 │
│ │
│ 【実務での修正例】 │
│ ・五角形にする: │
│ clip-path: polygon(50% 0%, 100% 38%, 82% 100%, 18% 100%, 0% 38%);
│ │
│ ・八角形にする: │
│ clip-path: polygon(30% 0%, 70% 0%, 100% 30%, 100% 70%, │
│ 70% 100%, 30% 100%, 0% 70%, 0% 30%); │
└─────────────────────────────────────────────────────────────────┘
```
## 2-7. ひし形スタイル
```css
/* ========== ひし形 ========== */
.diamond {
width: 50px;
height: 50px;
background-color: #808080 ;
transform: rotate(45deg);
display: flex;
justify-content: center;
align-items: center;
transition: all 0.3s ease;
}
.diamond span {
transform: rotate(-45deg);
color: white;
font-size: 10px;
font-weight: bold;
}
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【セレクタ】.diamond │
│ 【役割】ひし形図形のスタイル │
│ │
│ 【ひし形の作り方】★重要★ │
│ 正方形を45度回転させてひし形にする! │
│ │
│ transform: rotate(45deg); │
│ ├─ 意味: 要素を45度回転させる │
│ └─ 図解: │
│ 回転前: 回転後: │
│ ┌────┐ ◇ │
│ │ │ → ◇ ◇ │
│ └────┘ ◇ │
│ │
│ 【問題点と解決】 │
│ 問題: 外側を回転すると、中のテキストも回転してしまう │
│ │
│ そのままだと: 修正後: │
│ ◇ ◇ │
│ ◇正◇ → ◇正常◇ ← テキストは水平 │
│ ◇常 ◇ │
│ ↑ │
│ 読めない! │
│ │
│ 【解決方法】.diamond span │
│ │
│ .diamond span { │
│ transform: rotate(-45deg); │
│ } │
│ ├─ 意味: テキストを-45度回転(元に戻す) │
│ └─ 45deg + (-45deg) = 0deg → 水平になる! │
│ │
│ 【HTML構造】 │
│ <div class="diamond"> │
│ <span>正常</span> ← spanで囲む必要がある │
│ </div> │
│ │
│ 【実務での修正例】 │
│ transform: rotate(30deg); → 30度傾けたひし形 │
│ width: 70px; height: 70px; → より大きなひし形 │
└─────────────────────────────────────────────────────────────────┘
```
## 2-8. ラベルスタイル
```css
/* ========== ラベル ========== */
.device-label {
font-size: 12px;
color: #333 ;
font-weight: bold;
text-align: center;
}
.status-label {
font-size: 11px;
color: #666 ;
}
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【セレクタ】.device-label / .status-label │
│ 【役割】図形の下に表示するテキストラベルのスタイル │
│ │
│ 【表示イメージ】 │
│ ● 正常 ← 図形 │
│ Webサーバー1 ← .device-label(デバイス名) │
│ 正常 ← .status-label(ステータス)※今回は未使用│
│ │
│ 【.device-label】 │
│ font-size: 12px; → 少し小さめの文字 │
│ color: #333 ; → 濃いグレー(読みやすい) │
│ font-weight: bold; → 太字 │
│ text-align: center; → 中央揃え │
│ │
│ 【.status-label】 │
│ font-size: 11px; → さらに小さい(サブ情報) │
│ color: #666 ; → 中間グレー(控えめ) │
│ │
│ 【実務での修正例】 │
│ font-size: 14px; → もっと大きな文字に │
│ color: #007bff ; → 青色の文字に │
│ max-width: 100px; → 最大幅を指定して折り返し制御 │
│ overflow: hidden; → はみ出した部分を隠す │
│ text-overflow: ellipsis; → はみ出し部分を「...」で表示 │
└─────────────────────────────────────────────────────────────────┘
```
## 2-9. ステータス色
```css
/* ========== ステータス色 ========== */
.status-normal { background-color: #28a745 !important; }
.status-warning { background-color: #ffc107 !important; color: #333 !important; }
.status-error { background-color: #dc3545 !important; }
.status-stop { background-color: #6c757d !important; }
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【役割】ステータスに応じた背景色を定義 │
│ │
│ 【カラーコード一覧】 │
│ ┌───────────────┬───────────┬────────┬─────────────────────┐ │
│ │ クラス名 │ カラー │ 色 │ 用途 │ │
│ ├───────────────┼───────────┼────────┼─────────────────────┤ │
│ │ status-normal │ #28a745 │ 緑 │ 正常(問題なし) │ │
│ │ status-warning│ #ffc107 │ 黄 │ 警告(注意が必要) │ │
│ │ status-error │ #dc3545 │ 赤 │ 異常(問題発生) │ │
│ │ status-stop │ #6c757d │ 灰 │ 停止(稼働していない)│ │
│ └───────────────┴───────────┴────────┴─────────────────────┘ │
│ │
│ 【!important とは】 │
│ ├─ 意味: このスタイルを最優先で適用する │
│ ├─ 理由: .rectangle や .circle の background-color より │
│ │ 優先して適用したいため │
│ └─ 注意: 乱用は避ける(デバッグが難しくなる) │
│ │
│ 【status-warning の color: #333 】 │
│ └─ 理由: 黄色背景に白文字は読みにくいため、黒文字にする │
│ │
│ 【実務での修正例:新しいステータスを追加】 │
│ .status-info { background-color: #17a2b8 !important; } │
│ .status-success { background-color: #20c997 !important; } │
│ .status-danger { background-color: #e83e8c !important; } │
│ │
│ 【実務での修正例:色を変更】 │
│ .status-normal { background-color: #00ff00 !important; } │
│ ↑ より明るい緑に変更 │
└─────────────────────────────────────────────────────────────────┘
```
## 2-10. サイズ変更
```css
/* ========== サイズ変更 ========== */
.shape-large { transform: scale(1.3); }
.shape-small { transform: scale(0.8); }
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【役割】図形のサイズを変更するクラス │
│ │
│ 【transform: scale() とは】 │
│ ├─ 意味: 要素を拡大・縮小する │
│ ├─ scale(1) = 100% = 元のサイズ │
│ ├─ scale(1.3) = 130% = 1.3倍に拡大 │
│ └─ scale(0.8) = 80% = 0.8倍に縮小 │
│ │
│ 【図解】 │
│ scale(0.8): scale(1): scale(1.3): │
│ ● ● ● │
│ (小さい) (通常) (大きい) │
│ │
│ 【scaleのメリット】 │
│ ・width/heightを個別に変えなくていい │
│ ・縦横比を維持したまま拡大縮小できる │
│ ・アニメーションしやすい │
│ │
│ 【実務での使い方】 │
│ SPLで size_class フィールドに "shape-large" を設定 │
│ → JavaScriptでクラスに追加 │
│ → 自動的に図形が大きくなる │
│ │
│ 【実務での修正例】 │
│ .shape-xlarge { transform: scale(1.5); } → 1.5倍 │
│ .shape-medium { transform: scale(1.1); } → 1.1倍 │
└─────────────────────────────────────────────────────────────────┘
```
## 2-11. 点滅アニメーション
```css
/* ========== 点滅(異常・停止時) ========== */
@keyframes pulse {
0% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.05); opacity: 0.8; }
100% { transform: scale(1); opacity: 1; }
}
.status-error, .status-stop {
animation: pulse 1.5s ease-in-out infinite;
}
.shape-large.status-error,
.shape-large.status-stop {
animation: pulse 1s ease-in-out infinite;
}
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【@keyframes とは】 │
│ CSSアニメーションの動きを定義するルール │
│ │
│ @keyframes pulse { │
│ 0% { transform: scale(1); opacity: 1; } │
│ 50% { transform: scale(1.05); opacity: 0.8; } │
│ 100% { transform: scale(1); opacity: 1; } │
│ } │
│ │
│ 【各キーフレームの意味】 │
│ 0%(開始): │
│ scale(1) → 元のサイズ │
│ opacity: 1 → 完全に不透明 │
│ │
│ 50%(中間): │
│ scale(1.05) → 1.05倍に少し拡大 │
│ opacity: 0.8 → 少し透明に(80%不透明) │
│ │
│ 100%(終了): │
│ scale(1) → 元のサイズに戻る │
│ opacity: 1 → 完全に不透明に戻る │
│ │
│ 【図解:アニメーションの流れ】 │
│ 0% 50% 100% 0%(繰り返し) │
│ ● → ◯ → ● → ◯ → ... │
│ 通常 少し大きく 通常 少し大きく │
│ 少し薄く 少し薄く │
│ │
│ 【animation プロパティ】 │
│ animation: pulse 1.5s ease-in-out infinite; │
│ ↑ ↑ ↑ ↑ │
│ │ │ │ └─ 無限に繰り返す │
│ │ │ └─ 速度曲線(滑らかに) │
│ │ └─ 1回のアニメーション時間(1.5秒) │
│ └─ アニメーション名(@keyframesで定義した名前) │
│ │
│ 【セレクタの意味】 │
│ .status-error, .status-stop │
│ └─ 異常と停止の両方にアニメーションを適用(カンマで複数指定) │
│ │
│ .shape-large.status-error │
│ └─ 複合セレクタ: shape-large AND status-error の両方を持つ要素 │
│ └─ 大きい図形で異常 → 1秒間隔(より速く) │
│ │
│ 【実務での修正例】 │
│ ・もっと速く点滅: │
│ animation: pulse 0.5s ease-in-out infinite; │
│ │
│ ・点滅ではなく明滅(消える): │
│ @keyframes blink { │
│ 0% { opacity: 1; } │
│ 50% { opacity: 0; } ← 完全に透明 │
│ 100% { opacity: 1; } │
│ } │
└─────────────────────────────────────────────────────────────────┘
```
---
# 🔷 Part 3: HTML構造の解説
```html
<div class="main-container" id="mainContainer">
<!-- JavaScriptで生成 -->
</div>
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【役割】JavaScriptが図形を追加する先のコンテナ │
│ │
│ <div │
│ class="main-container" ← CSSスタイルを適用 │
│ id="mainContainer" ← JavaScriptから参照するためのID │
│ > │
│ │
│ 【classとidの違い】 │
│ class: │
│ ・複数の要素に同じクラスを付けられる │
│ ・CSSでスタイルを適用するために使う │
│ ・セレクタは .クラス名 │
│ │
│ id: │
│ ・ページ内で一意(1つだけ) │
│ ・JavaScriptから getElementById() で取得するために使う │
│ ・セレクタは #id名 │
│ │
│ 【JavaScript実行後の構造】 │
│ <div class="main-container" id="mainContainer"> │
│ <div class="category-group"> │
│ <div class="category-title">Web層</div> │
│ <div class="category-shapes"> │
│ <div class="shape-item"> │
│ <div class="circle status-normal">正常</div> │
│ <div class="device-label">Webサーバー1</div> │
│ </div> │
│ <div class="shape-item">...</div> │
│ </div> │
│ </div> │
│ <div class="category-group">...</div> │
│ </div> │
└─────────────────────────────────────────────────────────────────┘
```
---
# 🔷 Part 4: JavaScript完全解説
## 4-1. モジュール読み込み
```javascript
require([
'splunkjs/mvc',
'splunkjs/mvc/simplexml/ready!'
], function(mvc) {
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【役割】Splunk用のモジュールを読み込んで使えるようにする │
│ │
│ require([モジュール1, モジュール2, ...], function(引数) { │
│ // モジュールが読み込まれたら実行されるコード │
│ }); │
│ │
│ 【各モジュールの役割】 │
│ │
│ 'splunkjs/mvc' │
│ ├─ SplunkのMVCフレームワーク │
│ └─ ダッシュボードのコンポーネントを操作するために必要 │
│ │
│ 'splunkjs/mvc/simplexml/ready!' │
│ ├─ ダッシュボードの準備完了を待つ │
│ ├─ 末尾の「!」が「待つ」という意味 │
│ └─ これがないと、まだ読み込み中のコンポーネントにアクセスして │
│ エラーになる可能性がある │
│ │
│ function(mvc) │
│ ├─ 読み込んだモジュールを「mvc」という名前で受け取る │
│ └─ この中でSplunkの機能を使える │
│ │
│ 【実務ポイント】 │
│ このrequire文は定型文として覚えておく │
│ 基本的にこの形で書けばOK │
└─────────────────────────────────────────────────────────────────┘
```
## 4-2. サーチマネージャーと結果の取得
```javascript
var searchManager = mvc.Components.get("mainSearch");
var results = searchManager.data("results");
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【1行目】サーチマネージャーの取得 │
│ │
│ var searchManager = mvc.Components.get("mainSearch"); │
│ ↑ ↑ ↑ │
│ │ │ └─ XMLのsearch id │
│ │ └─ Splunkコンポーネントを名前で取得 │
│ └─ 変数に保存 │
│ │
│ 【対応関係】 │
│ XML: <search id="mainSearch"> │
│ ↑ │
│ JS: mvc.Components.get("mainSearch") │
│ ↑ │
│ ★この名前が一致していないとエラーになる★ │
│ │
│ 【2行目】結果データの取得 │
│ │
│ var results = searchManager.data("results"); │
│ ↑ ↑ │
│ │ └─ 完了した結果を取得 │
│ └─ 変数に保存 │
│ │
│ 【data()の引数】 │
│ "results" → 完了した最終結果(通常はこれを使う) │
│ "preview" → 途中経過の結果(リアルタイム監視用) │
└─────────────────────────────────────────────────────────────────┘
```
## 4-3. イベントリスナーの設定
```javascript
results.on("data", function() {
if (results.hasData()) {
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【results.on("data", function() {...})】 │
│ │
│ 【役割】データが更新されたら自動的に実行される関数を登録 │
│ │
│ results.on("data", function() { │
│ ↑ ↑ │
│ │ └─ データ更新時に実行する関数 │
│ └─ イベント名("data" = データ更新) │
│ │
│ 【いつ実行される?】 │
│ 1. ページ読み込み時(最初のサーチ完了時) │
│ 2. 自動更新時(<refresh>タグがある場合) │
│ 3. 手動でサーチを再実行した時 │
│ │
│ 【results.hasData()】 │
│ │
│ if (results.hasData()) { │
│ ↑ │
│ └─ 結果データがあるかチェック(true/false) │
│ │
│ 【なぜチェックが必要?】 │
│ ・サーチがエラーの場合、データがない │
│ ・結果が0件の場合、データがない │
│ ・データがない状態で処理するとエラーになる │
└─────────────────────────────────────────────────────────────────┘
```
## 4-4. データ取得とコンテナ初期化
```javascript
var rows = results.data().rows;
var fields = results.data().fields;
var container = document.getElementById('mainContainer');
container.innerHTML = '';
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【results.data()】 │
│ サーチ結果のデータオブジェクトを取得 │
│ │
│ 返り値の構造: │
│ { │
│ rows: [ │
│ ["Web層", "Webサーバー1", "正常", "0", "circle", ""], │
│ ["Web層", "Webサーバー2", "正常", "0", "circle", ""], │
│ ... │
│ ], │
│ fields: ["category", "device_name", "status", "status_code", │
│ "shape_type", "size_class"] │
│ } │
│ │
│ 【rows と fields の関係】 │
│ │
│ fields[0]="category" fields[1]="device_name" ... │
│ ↓ ↓ │
│ rows[0][0]="Web層" rows[0][1]="Webサーバー1" ... │
│ rows[1][0]="Web層" rows[1][1]="Webサーバー2" ... │
│ │
│ 【document.getElementById()】 │
│ │
│ var container = document.getElementById('mainContainer'); │
│ ↑ ↑ │
│ │ └─ HTML要素のid │
│ └─ HTMLからID指定で要素を取得 │
│ │
│ 【container.innerHTML = ''】 │
│ │
│ container.innerHTML = ''; │
│ ↑ ↑ │
│ │ └─ 空文字(中身を空にする) │
│ └─ 要素の中のHTMLを取得/設定するプロパティ │
│ │
│ 【なぜ空にする?】 │
│ データ更新のたびに図形を追加すると、前の図形が残ったまま │
│ 新しい図形が追加されてしまう。毎回リセットして再描画する。 │
└─────────────────────────────────────────────────────────────────┘
```
## 4-5. グループ化処理
```javascript
// カテゴリごとにグループ化
var groups = {};
rows.forEach(function(row) {
var catIndex = fields.indexOf('category');
var category = row[catIndex];
if (!groups[category]) {
groups[category] = [];
}
groups[category].push(row);
});
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【目的】データをカテゴリ(Web層、AP層など)ごとにまとめる │
│ │
│ 【var groups = {}】 │
│ 空のオブジェクト(連想配列)を作成 │
│ ここにカテゴリごとのデータを格納していく │
│ │
│ 【rows.forEach(function(row) {...})】 │
│ 配列の各要素に対して関数を実行(ループ処理) │
│ │
│ rows = [ │
│ ["Web層", "Webサーバー1", ...], ← 1回目: row = この行 │
│ ["Web層", "Webサーバー2", ...], ← 2回目: row = この行 │
│ ["AP層", "APサーバー1", ...], ← 3回目: row = この行 │
│ ... │
│ ] │
│ │
│ 【fields.indexOf('category')】 │
│ fieldsの中から'category'の位置を探す │
│ │
│ fields = ["category", "device_name", "status", ...] │
│ ↑ │
│ インデックス0 │
│ │
│ 【row[catIndex]】 │
│ row = ["Web層", "Webサーバー1", ...] │
│ ↑ │
│ row[0] = "Web層" │
│ │
│ 【if (!groups[category])】 │
│ そのカテゴリがまだgroupsに存在しないなら │
│ │
│ !groups["Web層"] │
│ ├─ groups["Web層"] が undefined(存在しない)の場合 │
│ │ → !undefined → true → if文の中に入る │
│ └─ groups["Web層"] がすでに存在する場合 │
│ → ![] → false → if文をスキップ │
│ │
│ 【groups[category] = []】 │
│ そのカテゴリ用の空配列を作成 │
│ │
│ 【groups[category].push(row)】 │
│ そのカテゴリの配列にデータ行を追加 │
│ │
│ 【処理後のgroupsの中身】 │
│ groups = { │
│ "Web層": [ │
│ ["Web層", "Webサーバー1", ...], │
│ ["Web層", "Webサーバー2", ...] │
│ ], │
│ "AP層": [ │
│ ["AP層", "APサーバー1", ...], │
│ ["AP層", "APサーバー2", ...] │
│ ], │
│ ... │
│ } │
└─────────────────────────────────────────────────────────────────┘
```
## 4-6. グループごとの描画処理
```javascript
// グループごとに描画
Object.keys(groups).forEach(function(category) {
var groupHtml = '<div class="category-group">';
groupHtml += '<div class="category-title">' + category + '</div>';
groupHtml += '<div class="category-shapes">';
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【Object.keys(groups)】 │
│ オブジェクトのキー(カテゴリ名)を配列として取得 │
│ │
│ groups = { "Web層": [...], "AP層": [...], "DB層": [...], ... } │
│ ↓ │
│ Object.keys(groups) = ["Web層", "AP層", "DB層", "NW層"] │
│ │
│ 【.forEach(function(category) {...})】 │
│ 各カテゴリ名に対してループ処理 │
│ │
│ 1回目: category = "Web層" │
│ 2回目: category = "AP層" │
│ 3回目: category = "DB層" │
│ 4回目: category = "NW層" │
│ │
│ 【HTML文字列の組み立て】 │
│ │
│ var groupHtml = '<div class="category-group">'; │
│ ├─ グループ全体を囲むdivの開始タグ │
│ │
│ groupHtml += '<div class="category-title">' + category + '</div>';
│ ├─ += → 既存の文字列に追加 │
│ ├─ category = "Web層" の場合 │
│ └─ 結果: '<div class="category-title">Web層</div>' │
│ │
│ groupHtml += '<div class="category-shapes">'; │
│ └─ 図形を並べるコンテナの開始タグ │
│ │
│ 【この時点のgroupHtml】 │
│ '<div class="category-group">' + │
│ '<div class="category-title">Web層</div>' + │
│ '<div class="category-shapes">' │
└─────────────────────────────────────────────────────────────────┘
```
## 4-7. 各図形のデータ取得
```javascript
groups[category].forEach(function(row) {
var deviceIndex = fields.indexOf('device_name');
var statusIndex = fields.indexOf('status');
var codeIndex = fields.indexOf('status_code');
var shapeIndex = fields.indexOf('shape_type');
var sizeIndex = fields.indexOf('size_class');
var deviceName = row[deviceIndex];
var status = row[statusIndex];
var statusCode = parseInt(row[codeIndex]);
var shapeType = row[shapeIndex];
var sizeClass = row[sizeIndex] || '';
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【groups[category].forEach(function(row) {...})】 │
│ そのカテゴリに属する各デバイスデータをループ処理 │
│ │
│ category = "Web層" の場合: │
│ groups["Web層"] = [ │
│ ["Web層", "Webサーバー1", "正常", "0", "circle", ""], │
│ ["Web層", "Webサーバー2", "正常", "0", "circle", ""] │
│ ] │
│ │
│ 1回目: row = ["Web層", "Webサーバー1", "正常", "0", "circle", ""]
│ 2回目: row = ["Web層", "Webサーバー2", "正常", "0", "circle", ""]
│ │
│ 【fields.indexOf() で各フィールドの位置を取得】 │
│ │
│ fields = ["category", "device_name", "status", "status_code", │
│ "shape_type", "size_class"] │
│ 0 1 2 3 │
│ 4 5 │
│ │
│ deviceIndex = 1 (device_nameの位置) │
│ statusIndex = 2 (statusの位置) │
│ codeIndex = 3 (status_codeの位置) │
│ shapeIndex = 4 (shape_typeの位置) │
│ sizeIndex = 5 (size_classの位置) │
│ │
│ 【値の取得】 │
│ row = ["Web層", "Webサーバー1", "正常", "0", "circle", ""] │
│ │
│ deviceName = row[1] = "Webサーバー1" │
│ status = row[2] = "正常" │
│ statusCode = parseInt(row[3]) = 0 ← 文字列"0"を数値0に変換 │
│ shapeType = row[4] = "circle" │
│ sizeClass = row[5] || '' = "" ← 空の場合は空文字 │
│ │
│ 【parseInt() が必要な理由】 │
│ Splunkのサーチ結果は全て文字列として返される │
│ switch文で数値と比較するために変換が必要 │
│ │
│ "0" === 0 → false(型が違う) │
│ 0 === 0 → true(正しく比較できる) │
│ │
│ 【|| '' の意味】 │
│ row[sizeIndex] が null や undefined の場合、空文字''を使う │
│ これを「デフォルト値」という │
└─────────────────────────────────────────────────────────────────┘
```
## 4-8. ステータスクラスの決定
```javascript
var statusClass = '';
switch(statusCode) {
case 0: statusClass = 'status-normal'; break;
case 1: statusClass = 'status-warning'; break;
case 2: statusClass = 'status-error'; break;
case 3: statusClass = 'status-stop'; break;
}
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【役割】ステータスコードに応じたCSSクラス名を決定 │
│ │
│ 【switch文の構造】 │
│ │
│ switch(statusCode) { ← 評価する値 │
│ case 0: ← statusCodeが0の場合 │
│ statusClass = 'status-normal'; │
│ break; ← switch文を抜ける │
│ case 1: ← statusCodeが1の場合 │
│ statusClass = 'status-warning'; │
│ break; │
│ ... │
│ } │
│ │
│ 【対応表】 │
│ statusCode │ statusClass │ CSSの色 │
│ ───────────┼─────────────────┼─────────────────────────────── │
│ 0 │ status-normal │ 緑 (#28a745) │
│ 1 │ status-warning │ 黄 (#ffc107) │
│ 2 │ status-error │ 赤 (#dc3545) │
│ 3 │ status-stop │ 灰 (#6c757d) │
│ │
│ 【break の重要性】★超重要★ │
│ │
│ breakがない場合(フォールスルー): │
│ case 0: statusClass = 'status-normal'; ← 実行 │
│ case 1: statusClass = 'status-warning'; ← これも実行される! │
│ case 2: statusClass = 'status-error'; ← これも実行される! │
│ → 最終的にstatus-errorになってしまう │
│ │
│ breakがある場合(正しい動作): │
│ case 0: statusClass = 'status-normal'; break; ← ここで終了 │
│ │
│ 【実務での修正例:新しいステータスを追加】 │
│ case 4: statusClass = 'status-info'; break; │
│ case 5: statusClass = 'status-success'; break; │
│ │
│ 【default の追加(推奨)】 │
│ default: statusClass = 'status-stop'; break; │
│ ↑ どのcaseにも当てはまらない場合のデフォルト値 │
└─────────────────────────────────────────────────────────────────┘
```
## 4-9. 図形HTMLの生成
```javascript
var shapeHtml = '';
var allClasses = statusClass + ' ' + sizeClass;
if (shapeType === 'rectangle') {
shapeHtml = '<div class="rectangle ' + allClasses + '">' + status + '</div>';
} else if (shapeType === 'circle') {
shapeHtml = '<div class="circle ' + allClasses + '">' + status + '</div>';
} else if (shapeType === 'hexagon') {
shapeHtml = '<div class="hexagon ' + allClasses + '">' + status + '</div>';
} else if (shapeType === 'diamond') {
shapeHtml = '<div class="diamond ' + allClasses + '"><span>' + status + '</span></div>';
}
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【目的】図形タイプに応じたHTML文字列を生成 │
│ │
│ 【var allClasses = statusClass + ' ' + sizeClass】 │
│ │
│ 例: statusClass = "status-normal" │
│ sizeClass = "" (空) │
│ → allClasses = "status-normal " │
│ │
│ 例: statusClass = "status-error" │
│ sizeClass = "shape-large" │
│ → allClasses = "status-error shape-large" │
│ │
│ 【if-else による図形タイプ分岐】 │
│ │
│ shapeType = 'rectangle' の場合: │
│ shapeHtml = '<div class="rectangle status-normal">正常</div>' │
│ │
│ shapeType = 'circle' の場合: │
│ shapeHtml = '<div class="circle status-normal">正常</div>' │
│ │
│ shapeType = 'hexagon' の場合: │
│ shapeHtml = '<div class="hexagon status-normal">正常</div>' │
│ │
│ shapeType = 'diamond' の場合: │
│ shapeHtml = '<div class="diamond status-normal"><span>正常</span></div>'
│ ↑ ひし形は<span>で囲む(テキスト回転のため) │
│ │
│ 【複数クラスの適用】 │
│ class="rectangle status-normal shape-large" │
│ ↑ ↑ ↑ │
│ │ │ └─ サイズクラス(大きく) │
│ │ └─ 色クラス(緑) │
│ └─ 図形クラス(四角形) │
│ │
│ → 3つのCSSルールが全て適用される │
│ │
│ 【実務での修正例:新しい図形を追加】 │
│ else if (shapeType === 'star') { │
│ shapeHtml = '<div class="star ' + allClasses + '"></div>'; │
│ } │
│ else if (shapeType === 'triangle') { │
│ shapeHtml = '<div class="triangle ' + allClasses + '"></div>';│
│ } │
└─────────────────────────────────────────────────────────────────┘
```
## 4-10. 図形アイテムの組み立てとコンテナへの追加
```javascript
groupHtml += '<div class="shape-item">';
groupHtml += shapeHtml;
groupHtml += '<div class="device-label">' + deviceName + '</div>';
groupHtml += '</div>';
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【目的】図形とラベルを組み合わせて1つのアイテムを作成 │
│ │
│ 【生成されるHTML構造】 │
│ │
│ <div class="shape-item"> ← アイテム開始 │
│ <div class="circle status-normal">正常</div> ← 図形 │
│ <div class="device-label">Webサーバー1</div> ← ラベル │
│ </div> ← アイテム終了 │
│ │
│ 【各行の意味】 │
│ │
│ groupHtml += '<div class="shape-item">'; │
│ └─ shape-itemコンテナの開始タグを追加 │
│ │
│ groupHtml += shapeHtml; │
│ └─ 前のステップで生成した図形HTMLを追加 │
│ │
│ groupHtml += '<div class="device-label">' + deviceName + '</div>';
│ └─ デバイス名ラベルを追加 │
│ deviceName = "Webサーバー1" の場合 │
│ → '<div class="device-label">Webサーバー1</div>' │
│ │
│ groupHtml += '</div>'; │
│ └─ shape-itemコンテナの終了タグを追加 │
│ │
│ 【表示イメージ】 │
│ ┌─────────────────┐ │
│ │ ● 正常 │ ← 図形 │
│ │ Webサーバー1 │ ← ラベル │
│ └─────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
## 4-11. グループ完成とコンテナへの追加
```javascript
}); // groups[category].forEach 終了
groupHtml += '</div></div>';
container.innerHTML += groupHtml;
}); // Object.keys(groups).forEach 終了
```
```
┌─────────────────────────────────────────────────────────────────┐
│ 【groupHtml += '</div></div>'】 │
│ │
│ 1つ目の </div> → category-shapes の終了タグ │
│ 2つ目の </div> → category-group の終了タグ │
│ │
│ 【完成したgroupHtmlの構造】 │
│ │
│ <div class="category-group"> ← グループ開始 │
│ <div class="category-title">Web層</div> ← タイトル │
│ <div class="category-shapes"> ← 図形コンテナ開始│
│ <div class="shape-item"> ← アイテム1 │
│ <div class="circle status-normal">正常</div> │
│ <div class="device-label">Webサーバー1</div> │
│ </div> │
│ <div class="shape-item"> ← アイテム2 │
│ <div class="circle status-normal">正常</div> │
│ <div class="device-label">Webサーバー2</div> │
│ </div> │
│ </div> ← 図形コンテナ終了│
│ </div> ← グループ終了 │
│ │
│ 【container.innerHTML += groupHtml】 │
│ │
│ += → 既存の内容に追加(上書きではない) │
│ │
│ 【処理の流れ】 │
│ 1. Web層のgroupHtmlを追加 │
│ 2. AP層のgroupHtmlを追加 │
│ 3. DB層のgroupHtmlを追加 │
│ 4. NW層のgroupHtmlを追加 │
│ │
│ 【最終的なcontainer.innerHTML】 │
│ <div class="category-group">...Web層...</div> │
│ <div class="category-group">...AP層...</div> │
│ <div class="category-group">...DB層...</div> │
│ <div class="category-group">...NW層...</div> │
└─────────────────────────────────────────────────────────────────┘
```
---
# 🎯 Part 5: 実務での修正チートシート
## 5-1. よくある修正パターン早見表
| やりたいこと | 修正箇所 | 修正内容 |
|-------------|---------|---------|
| デバイスを増やす | SPL | `count=N`増やす + `case`文追加 |
| カテゴリを増やす | SPL | `category`の`case`文に追加 |
| 新しいステータスを追加 | SPL + CSS + JS | 全部に追加が必要 |
| 色を変える | CSS | `.status-xxx { background-color: }` |
| サイズを変える | CSS | `width` / `height` または `scale()` |
| 配置を変える | CSS | `justify-content` / `gap` |
| 新しい図形を追加 | SPL + CSS + JS | 全部に追加が必要 |
| 点滅を変える | CSS | `@keyframes` / `animation` |
## 5-2. 修正箇所の対応表
```
【新しいステータス「メンテナンス」を追加する場合】
┌─ SPL ──────────────────────────────────────────────────────────┐
│ | eval status_code = case( │
│ status=="正常", 0, │
│ status=="警告", 1, │
│ status=="異常", 2, │
│ status=="停止", 3, │
│ status=="メンテナンス", 4 ← 追加 │
│ ) │
└────────────────────────────────────────────────────────────────┘
┌─ CSS ──────────────────────────────────────────────────────────┐
│ .status-maintenance { │
│ background-color: #17a2b8 !important; ← 水色 │
│ } │
└────────────────────────────────────────────────────────────────┘
┌─ JavaScript ───────────────────────────────────────────────────┐
│ switch(statusCode) { │
│ case 0: statusClass = 'status-normal'; break; │
│ case 1: statusClass = 'status-warning'; break; │
│ case 2: statusClass = 'status-error'; break; │
│ case 3: statusClass = 'status-stop'; break; │
│ case 4: statusClass = 'status-maintenance'; break; ← 追加 │
│ } │
└────────────────────────────────────────────────────────────────┘
```
---
**お疲れ様でした!🎊**
これで全コードの意味を完全に理解し、実務で修正・追加ができるようになりました!