0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SPL学習備忘録 基本構造マスターしよう編

0
Posted at

SPL学習備忘録 基本構造マスターしよう!まとめ編 Splunk(※クラシック)でオリジナルチャートを作るスキルを一緒に追加しよう 

SPL(Search Processing Language)基礎解説:新人エンジニア向け

🎯 SPLとは?

Search Processing Language = Splunkでデータを検索・加工・集計するための専用言語

イメージ:

  • SQL = データベース用の言語
  • SPL = Splunk用の言語

📌 SPLの基本構造

uploading...0
SP

パイプライン方式

データ取得 | 絞り込み | 加工 | 集計 | 表示

特徴: |(パイプ)で処理を繋げていく


🔍 今回使った SPL を完全解説

完全版コード

index=20260115tra
| where like(location,"%湾岸%")
| sort -_time
| head 1
| eval is_ng=if(result="NG",1,0)

1️⃣ index=20260115tra

📖 意味

データソース(index)を指定

🔧 構文

index=<インデックス名>

💡 新人向け解説

  • Splunkに取り込んだデータの「置き場所」
  • CSVをアップロードすると自動で index が作られる
  • 複数のindexがある場合、ここで絞り込む

📊 例

index=web_logs           # Webサーバーのログ
index=app_errors         # アプリケーションエラー
index=20260115tra        # 今回の道路データ

2️⃣ | where like(location,"%湾岸%")

📖 意味

location列に「湾岸」という文字を含むデータだけ抽出

🔧 構文

| where like(<フィールド名>, "<パターン>")

💡 新人向け解説

  • % はワイルドカード(何でもマッチ)
  • SQL の LIKE と同じ
  • %湾岸% = 「前後に何があっても湾岸を含めばOK」

📊 マッチ例

location マッチするか?
首都高 湾岸線 東京 ✅ マッチ
湾岸道路 ✅ マッチ
東京湾岸 ✅ マッチ
東名高速 ❌ マッチしない

🎯 応用パターン

# 前方一致
| where like(location, "首都高%")

# 後方一致
| where like(location, "%高速")

# 完全一致
| where location="首都高 湾岸線"

# 複数条件(OR)
| where like(location, "%湾岸%") OR like(location, "%東名%")

# 複数条件(AND)
| where like(location, "%首都高%") AND like(location, "%湾岸%")

3️⃣ | sort -_time

📖 意味

_time(時刻)フィールドで降順ソート(新しい順)

🔧 構文

| sort <-フィールド名>    # 降順(大→小)
| sort <フィールド名>     # 昇順(小→大)

💡 新人向け解説

  • - がつくと降順
  • - がないと昇順
  • _time は Splunk の特殊フィールド(全データに自動付与)

📊 ソート結果イメージ

元データ:

_time: 2024-01-15 18:30  result: OK
_time: 2024-01-15 18:40  result: NG
_time: 2024-01-15 18:35  result: OK

sort -_time 後:

_time: 2024-01-15 18:40  result: NG  ← 最新
_time: 2024-01-15 18:35  result: OK
_time: 2024-01-15 18:30  result: OK

🎯 応用パターン

# 昇順(古い順)
| sort _time

# 複数フィールドでソート
| sort -_time, location

# 数値フィールドでソート
| sort -count

# ソート後の件数制限
| sort -_time | head 10

4️⃣ | head 1

📖 意味

上から1件だけ取得(最新1件)

🔧 構文

| head <件数>

💡 新人向け解説

  • 最重要コマンド!
  • sort -_time と組み合わせて「最新1件」を取得
  • これがないと過去の全データが対象になる

📊 動作イメージ

head 1 なし:

2024-01-15 18:40  NG  ← 最新
2024-01-15 18:35  OK
2024-01-15 18:30  NG
2024-01-15 18:25  OK
↓
全部が処理対象(NGが2件ある)

head 1 あり:

2024-01-15 18:40  NG  ← これだけ
↓
最新の1件だけ処理(NGが1件)

🎯 応用パターン

# 最新5件
| head 5

# 最古の1件(昇順ソート + head)
| sort _time | head 1

# 最新10件の次の10件(offset)
| sort -_time | head 20 | tail 10

5️⃣ | eval is_ng=if(result="NG",1,0)

📖 意味

新しいフィールド is_ng を作成し、条件分岐で値を設定

🔧 構文

| eval <新フィールド名>=if(<条件>, <真の値>, <偽の値>)

💡 新人向け解説

  • eval = 計算・加工用のコマンド
  • if() = 3項演算子(プログラミングの ? : と同じ)
  • 結果を数値化することでトークン判定が簡単になる

📊 動作イメージ

入力データ:

result: NG

処理:

if(result="NG", 1, 0)
↓
result が "NG" か? → YES
↓
1 を返す

出力データ:

result: NG
is_ng: 1

🎯 応用パターン

複数条件(case)

| eval status=case(
    result="NG", "赤",
    result="WARN", "黄",
    result="OK", "緑",
    1=1, "不明"
  )

計算

| eval total=price * quantity
| eval discount_price=price * 0.9

文字列操作

| eval full_name=first_name." ".last_name
| eval upper_location=upper(location)

日時操作

| eval hour=strftime(_time, "%H")
| eval date=strftime(_time, "%Y-%m-%d")

NULL チェック

| eval is_empty=if(isnull(field), "空", "有")

🎓 SPL の処理順序(重要)

今回のクエリを分解

index=20260115tra                      # ① データ取得
| where like(location,"%湾岸%")        # ② 絞り込み
| sort -_time                          # ③ ソート
| head 1                               # ④ 最新1件
| eval is_ng=if(result="NG",1,0)      # ⑤ 加工

📊 各段階でのデータ変化

① index=20260115tra
┌─────────────────────────────┐
│ 100万件のデータ              │
└─────────────────────────────┘

② | where like(location,"%湾岸%")
┌─────────────────────────────┐
│ 1,000件(湾岸関連のみ)      │
└─────────────────────────────┘

③ | sort -_time
┌─────────────────────────────┐
│ 1,000件(新しい順に並び替え)│
└─────────────────────────────┘

④ | head 1
┌─────────────────────────────┐
│ 1件(最新のみ)              │
└─────────────────────────────┘

⑤ | eval is_ng=if(result="NG",1,0)
┌─────────────────────────────┐
│ 1件(is_ng フィールド追加)  │
│ location: 首都高湾岸線       │
│ result: NG                  │
│ is_ng: 1                    │
└─────────────────────────────┘

🔧 よく使う SPL コマンド一覧

データ取得系

コマンド 意味
index= データソース指定 index=web_logs
source= ファイル指定 source="/var/log/app.log"
sourcetype= データ形式指定 sourcetype=access_combined

絞り込み系

コマンド 意味
where 条件絞り込み where status=200
search キーワード検索 search error
like パターンマッチ like(field, "%keyword%")
in リスト照合 where status in(200,201,204)

並び替え・抽出系

コマンド 意味
sort ソート sort -_time
head 上からN件 head 10
tail 下からN件 tail 10
dedup 重複削除 dedup user_id

加工系

コマンド 意味
eval フィールド作成・計算 eval total=price*qty
rex 正規表現で抽出 rex field=_raw "(?<ip>\d+\.\d+\.\d+\.\d+)"
replace 文字列置換 eval status=replace(status,"OK","正常")

集計系

コマンド 意味
stats 集計 stats count by status
count 件数 stats count as total
sum 合計 stats sum(amount) by user
avg 平均 stats avg(response_time)
max / min 最大/最小 stats max(price)

時系列系

コマンド 意味
timechart 時系列グラフ timechart count by status
bin 時間バケット bin _time span=1h
earliest / latest 時刻範囲 earliest=-24h

🎯 実践パターン集

パターン1:最新のステータスを取得

index=mydata
| where user_id=12345
| sort -_time
| head 1
| table user_id, status, _time

パターン2:エラー件数を集計

index=app_logs
| where level="ERROR"
| stats count by error_type
| sort -count

パターン3:時間帯別の平均応答時間

index=web_logs
| eval hour=strftime(_time, "%H")
| stats avg(response_time) by hour
| sort hour

パターン4:上位10ユーザー

index=access_logs
| stats count by user_id
| sort -count
| head 10

パターン5:条件付き集計

index=sales
| stats 
    count as total,
    count(eval(status="completed")) as success,
    count(eval(status="failed")) as failed
  by store_id

⚠️ よくあるミス

ミス1:head の位置

❌ NG
index=mydata
| head 1
| sort -_time
↓
ランダムな1件をソートしても意味ない

✅ OK
index=mydata
| sort -_time
| head 1
↓
ソートしてから最新1件

ミス2:大文字小文字

❌ NG
| where result="ng"
↓
データが "NG" なのでマッチしない

✅ OK
| where result="NG"
または
| where lower(result)="ng"

ミス3:wheresearch の混同

# where:フィールド名を指定
| where status=200

# search:全文検索
| search "status=200"

🚀 デバッグテクニック

テクニック1:途中結果を確認

index=20260115tra
| where like(location,"%湾岸%")
| table location, _time, result  # ← 途中で確認

テクニック2:件数確認

index=20260115tra
| where like(location,"%湾岸%")
| stats count  # ← 何件あるか確認

テクニック3:フィールド一覧

index=20260115tra
| head 1
| table *  # ← 全フィールドを表示

📚 学習ロードマップ

レベル1(初級)

  • index, where, sort, head
  • eval の基本(if, 四則演算)
  • stats count

レベル2(中級)

  • 🔶 stats の高度な使い方(sum, avg, max, min)
  • 🔶 timechart
  • 🔶 rex(正規表現)

レベル3(上級)

  • 🔥 join(結合)
  • 🔥 eventstats, streamstats
  • 🔥 lookup(外部データ参照)

📝 まとめ

コマンド 役割 重要度
index= データ取得 ⭐⭐⭐
where 絞り込み ⭐⭐⭐
sort 並び替え ⭐⭐⭐
head 件数制限 ⭐⭐⭐
eval 加工 ⭐⭐⭐
stats 集計 ⭐⭐
table 表示 ⭐⭐

SPL は書いて覚える! まずは今回のクエリを理解して、1行ずつ変えて実験してみよう 🚀


Splunk SPL 文法作成のコツ:実践的テクニック集

🎯 この記事で学べること

  • SPL文法の 組み立て方の思考プロセス
  • 新人が間違えやすいポイントと回避法
  • 実務で使える デバッグ・検証手順

📌 SPL文法作成の基本思考フロー

✅ 5ステップ思考法

1️⃣ ゴールを明確化する
   ↓
2️⃣ データの形を確認する
   ↓
3️⃣ 処理順序を決める
   ↓
4️⃣ 1行ずつ組み立てる
   ↓
5️⃣ 検証・デバッグする

1️⃣ ゴールを明確化する

🎯 最初に決めるべき3つのこと

項目 質問
何を取得? どのデータが欲しい? 首都高速 湾岸線の最新ステータス
どう判定? 何を基準に判断? result列が"NG"なら赤
どう表示? 出力形式は? 緑/赤のボックス表示

📝 ゴール設定の例

悪い例(曖昧):

「道路の状態を見たい」

良い例(明確):

「首都高速 湾岸線の最新(_time最大)の1件について、
 result列が"NG"なら赤、それ以外なら緑にする」

2️⃣ データの形を確認する

🔍 必ず最初にやること

index=20260115tra
| head 10
| table *

確認ポイント:

  • ✅ フィールド名は何?(location? road? status?)
  • ✅ 値のフォーマットは?("NG" or "ng"? 全角/半角?)
  • ✅ 時刻フィールドは?(_time? timestamp?)

📊 実際のデータ例

location              _time                result
首都高速 湾岸線 東京  2024-01-18 09:00:00  正常
国道16号 相模原市     2024-01-18 08:50:00  NG
東名高速 横浜        2024-01-18 08:45:00  正常

ここから分かること:

  • location列に「首都高速 湾岸線」が含まれる
  • result列は「NG」「正常」(日本語)
  • _time が時刻フィールド

3️⃣ 処理順序を決める

🧠 SPLの黄金パターン

取得 → 絞り込み → 並び替え → 件数制限 → 加工 → 集計

📋 今回のケースで考える

やりたいこと:
「首都高速 湾岸線の最新1件のresultがNGなら赤」

処理順序:

1. index=20260115tra           # 全データ取得
2. where like(location,...)     # 湾岸線だけ絞る
3. sort -_time                  # 新しい順に並べる
4. head 1                       # 最新1件だけ残す
5. eval is_ng=if(...)          # NG判定用フィールド追加

⚠️ よくある失敗:順序ミス

❌ 間違った順序
index=20260115tra
| head 1                    # ← 先に1件取ると
| where like(location,...)  # ← 湾岸線じゃない可能性

✅ 正しい順序
index=20260115tra
| where like(location,...)  # ← 先に絞り込んでから
| head 1                    # ← 1件取る

4️⃣ 1行ずつ組み立てる

🔨 段階的構築法

Step 1: データ取得だけ

index=20260115tra

実行して確認: 件数が多すぎないか?


Step 2: 絞り込み追加

index=20260115tra
| where like(location,"%首都高速 湾岸線%")

実行して確認: 湾岸線だけになったか?


Step 3: ソート追加

index=20260115tra
| where like(location,"%首都高速 湾岸線%")
| sort -_time
| table location, _time, result  # ← 確認用

実行して確認: 最新が一番上に来たか?


Step 4: 最新1件に絞る

index=20260115tra
| where like(location,"%首都高速 湾岸線%")
| sort -_time
| head 1
| table location, _time, result

実行して確認: 1件だけになったか?


Step 5: 判定フィールド追加

index=20260115tra
| where like(location,"%首都高速 湾岸線%")
| sort -_time
| head 1
| eval is_ng=if(result="NG",1,0)
| table location, _time, result, is_ng  # ← is_ngも表示

実行して確認: is_ngが正しく1/0になったか?


💡 この方法の利点

方法 メリット
一気に書く ❌ エラー箇所が分からない
1行ずつ ✅ どこで間違えたか即分かる

5️⃣ 検証・デバッグする

🔍 デバッグの3大テクニック


テクニック1:table で中身を見る

# 任意の場所に追加
| table *  # 全フィールド表示
| table location, _time, result  # 特定フィールドのみ

使い方:

index=20260115tra
| where like(location,"%湾岸%")
| table *  # ← ここで一旦確認
| sort -_time
| head 1

テクニック2:stats count で件数確認

index=20260115tra
| where like(location,"%湾岸%")
| stats count  # ← 何件ヒットしたか?

活用例:

# パターン1: 条件を変えて比較
| where like(location,"%湾岸%")     | stats count  # 100件
| where like(location,"%首都高%")   | stats count  # 50件
| where location="首都高速 湾岸線"  | stats count  # 10件

テクニック3:eval の結果を確認

index=20260115tra
| where like(location,"%湾岸%")
| sort -_time
| head 1
| eval is_ng=if(result="NG",1,0)
| eval debug=result  # ← 元の値も残す
| table location, result, debug, is_ng

🎯 実践的な文法作成パターン集


パターン1:条件分岐(if文)

基本形

| eval 新フィールド=if(条件, 真の値, 偽の値)

実例

# 単純な条件
| eval status=if(result="NG", "赤", "緑")

# 数値比較
| eval level=if(score>=80, "合格", "不合格")

# フィールドの存在確認
| eval has_data=if(isnull(field), 0, 1)

パターン2:複数条件分岐(case文)

基本形

| eval 新フィールド=case(
    条件1, 値1,
    条件2, 値2,
    条件3, 値3,
    1=1, デフォルト値
  )

実例

# 3段階の色分け
| eval color=case(
    result="NG", "red",
    result="WARN", "yellow",
    result="OK", "green",
    1=1, "gray"
  )

# スコアでランク分け
| eval rank=case(
    score>=90, "S",
    score>=80, "A",
    score>=70, "B",
    score>=60, "C",
    1=1, "D"
  )

パターン3:文字列操作

部分一致検索

# like 関数
| where like(location, "%湾岸%")

# 正規表現
| where match(location, "湾岸|東名|中央")

文字列結合

# ドット演算子
| eval full_name=first_name." ".last_name

# 複数フィールド
| eval message=location." は ".result." です"

大文字小文字変換

| eval lower_result=lower(result)  # 小文字化
| eval upper_result=upper(result)  # 大文字化

パターン4:日時操作

時刻フォーマット変換

# 時間だけ抽出
| eval hour=strftime(_time, "%H")

# 日付だけ抽出
| eval date=strftime(_time, "%Y-%m-%d")

# 曜日抽出
| eval weekday=strftime(_time, "%A")

時間計算

# 1時間前
| eval one_hour_ago=_time - 3600

# 現在時刻との差
| eval elapsed=now() - _time

パターン5:集計系

グループ集計

# 道路ごとの件数
| stats count by location

# 道路ごとのNG件数
| stats count(eval(result="NG")) as ng_count by location

# 複数集計
| stats 
    count as total,
    count(eval(result="NG")) as ng,
    count(eval(result="OK")) as ok
  by location

最大・最小・平均

# 最新の_time
| stats latest(_time) as latest_time by location

# 最大値
| stats max(score) as max_score by user

# 平均値
| stats avg(response_time) as avg_time by server

🚨 よくあるミスと対策


ミス1:大文字小文字の不一致

❌ 間違い
| where result="ng"  # データは "NG"

✅ 対策1: 正確に書く
| where result="NG"

✅ 対策2: 小文字化して比較
| where lower(result)="ng"

ミス2:全角・半角の混在

❌ 間違い
| where like(location, "%湾岸%")  # データは全角スペース

✅ 対策: 実データを確認
| table location  # ← まず確認
| where like(location, "%首都高速 湾岸線%")  # 全角スペース

ミス3:フィールド名の間違い

❌ 間違い
| where status="NG"  # フィールド名は result

✅ 対策: フィールド一覧を確認
| head 1 | table *  # ← 全フィールド名を表示

ミス4:条件の優先順位

❌ 間違い
| where result="NG" OR result="WARN" AND location="湾岸"
# → (NG) OR (WARN かつ 湾岸) になる

✅ 正しい
| where (result="NG" OR result="WARN") AND location="湾岸"
# → (NG または WARN) かつ 湾岸

ミス5:ソート後の head 忘れ

❌ 間違い
| sort -_time
# → 全件がソートされただけ

✅ 正しい
| sort -_time
| head 1
# → 最新1件だけ取得

🧪 デバッグの実践例

ケース:「赤にならない」

Step 1: データ確認

index=20260115tra
| where like(location,"%湾岸%")
| table location, _time, result

確認すること:

  • ✅ location に「湾岸」が含まれているか?
  • ✅ result の値は?("NG"? "ng"? "エラー"?)

Step 2: 最新1件確認

index=20260115tra
| where like(location,"%湾岸%")
| sort -_time
| head 1
| table location, _time, result

確認すること:

  • ✅ 最新の _time は合っているか?
  • ✅ その時の result は何か?

Step 3: 判定ロジック確認

index=20260115tra
| where like(location,"%湾岸%")
| sort -_time
| head 1
| eval is_ng=if(result="NG",1,0)
| table location, result, is_ng

確認すること:

  • ✅ is_ng が 1 になるべきなのに 0 になっていないか?
  • ✅ result の値が "NG" と完全一致しているか?

Step 4: 大文字小文字を疑う

| eval is_ng=if(lower(result)="ng",1,0)

🎓 SPL文法作成のコツ まとめ

✅ 黄金ルール

ルール 内容
1. 小さく始める いきなり完成形を書かない
2. 1行ずつ確認 tablestats count で検証
3. データを見る 想像で書かず実データを確認
4. 順序を守る 取得→絞込→ソート→制限→加工
5. コメントを書く # コメント で意図を残す

📋 チェックリスト

□ index名は合っているか?
□ フィールド名は正確か?
□ 大文字小文字は合っているか?
□ 全角半角は合っているか?
□ ソートしてから head しているか?
□ 条件の優先順位は正しいか?
□ 中間結果を確認したか?

🚀 次のステップ

初級 → 中級への道

# 初級: 単純な絞り込み
| where result="NG"

# 中級: 複数条件の組み合わせ
| where result="NG" AND priority="高"

# 上級: 正規表現と集計
| rex field=message "(?<error_code>\d{3})"
| stats count by error_code

実務でよく使うパターン

  1. エラー率の計算
| stats 
    count as total,
    count(eval(result="NG")) as errors
| eval error_rate=round(errors/total*100, 2)
  1. 時系列グラフ
| timechart span=1h count by result
  1. トップN抽出
| stats count by location
| sort -count
| head 10

最後に: SPLは「書いて→実行→確認」の繰り返しで上達します。失敗を恐れず、どんどん試してください 💪



🎫 Splunkトークン完全ガイド|動的ダッシュボードの心臓部

トークンを徹底解説します!ダッシュボードを動的にする魔法の仕組みです!


📋 目次

Step1: トークンとは?(うさうさラーメン店で理解)
Step2: トークンの種類と基本文法
Step3: トークンの設定方法(set/unset/condition)
Step4: 実践例1: フォーム入力による絞り込み
Step5: 実践例2: ドリルダウンで詳細表示
Step6: 実践例3: 複数トークンの連携
Step7: デバッグ方法とトラブルシューティング

🍜 Step1: トークンとは?(うさうさラーメン店で理解)

トークンの概念

┌─────────────────────────────────────────┐
│ うさうさラーメン店で考えよう!          │
│                                         │
│ お客さん「味噌ラーメン大盛りで!」      │
│    ↓                                    │
│ 注文票に記録「味=$味噌$, 量=$大盛り$」  │
│    ↓                                    │
│ キッチン「$味$で$量$を作ります」        │
│    ↓                                    │
│ 結果「味噌ラーメン大盛りができた!」     │
│                                         │
│ トークン = 注文票に書かれた変数         │
└─────────────────────────────────────────┘

Splunkでのトークン

┌─────────────────────────────────────────┐
│ ユーザー「Server-Aのデータを見たい!」   │
│    ↓                                    │
│ トークンに保存「server_name=$Server-A$」│
│    ↓                                    │
│ サーチクエリ                            │
│ index=monitoring device=$server_name$  │
│    ↓                                    │
│ 実行されるクエリ                        │
│ index=monitoring device=Server-A       │
└─────────────────────────────────────────┘

トークンの役割

役割 説明
動的フィルタリング ユーザー入力で検索条件を変更 デバイス名選択
値の受け渡し パネル間でデータを連携 クリックで詳細表示
条件分岐 状況に応じて表示を切り替え エラー時に警告表示
再利用性 同じ値を複数箇所で使用 時間範囲の統一

🏷️ Step2: トークンの種類と基本文法

トークンの種類

┌─────────────────────────────────────────────┐
│ 1. デフォルトトークン(Splunk標準)         │
│    ├─ $earliest$  (検索開始時刻)            │
│    ├─ $latest$    (検索終了時刻)            │
│    └─ $env:page$  (現在のページ名)          │
│                                             │
│ 2. カスタムトークン(ユーザー定義)         │
│    ├─ $my_token$  (自由に命名)              │
│    ├─ $server$    (サーバー名)              │
│    └─ $status$    (ステータス)              │
│                                             │
│ 3. フォームトークン(入力フォーム)         │
│    ├─ $form.field1$  (入力1)                │
│    └─ $form.field2$  (入力2)                │
└─────────────────────────────────────────────┘

トークンの基本文法

<!-- トークンの参照 -->
$token_name$
<!-- ↑ ドル記号で囲んで使用 -->

<!-- フォームトークンの参照 -->
$form.input_name$
<!-- ↑ form.プレフィックスをつける -->

<!-- デフォルト値付きトークン -->
$token_name|default_value$
<!-- ↑ トークンが未定義の場合はdefault_valueを使用 -->

⚙️ Step3: トークンの設定方法

方法1: <set> タグで設定

<!-- 基本的なset構文 -->
<set token="token_name"></set>
<!-- ↑ トークンに固定値を設定 -->

<!-- サーチ結果からsetする -->
<search>
  <query>
    index=monitoring
    | stats count by device_name
    | head 1
    | table device_name
  </query>
  <!-- サーチ完了時にトークンを設定 -->
  <done>
    <set token="first_device">$result.device_name$</set>
    <!-- ↑ 検索結果の最初のdevice_nameをトークンに格納 -->
  </done>
</search>

方法2: <unset> タグで削除

<!-- トークンを削除 -->
<unset token="token_name"></unset>
<!-- ↑ トークンを未定義状態にする -->

<!-- 条件付きで削除 -->
<change>
  <condition value="reset">
    <!-- "reset"が選択されたらトークンを削除 -->
    <unset token="server_filter"></unset>
  </condition>
</change>

方法3: <condition> タグで条件分岐

<!-- 条件分岐でトークンを設定 -->
<change>
  <!-- フォームの値が変更されたとき -->
  <condition label="OK">
    <!-- "OK"が選択された場合 -->
    <set token="status_filter">status="OK"</set>
    <!-- ↑ ステータスフィルタを設定 -->
  </condition>
  <condition label="NG">
    <!-- "NG"が選択された場合 -->
    <set token="status_filter">status="NG"</set>
  </condition>
  <condition label="全て">
    <!-- "全て"が選択された場合 -->
    <unset token="status_filter"></unset>
    <!-- ↑ フィルタを解除 -->
  </condition>
</change>

📝 Step4: 実践例1 - フォーム入力による絞り込み

完全なダッシュボード例

<form>
  <!-- formタグを使用(ダッシュボードをフォーム型に) -->
  <label>🔍 サーバー監視ダッシュボード</label>
  
  <!-- フィールドセット(入力フォームのコンテナ) -->
  <fieldset submitButton="true" autoRun="false">
    <!-- submitButton: 検索ボタンを表示 -->
    <!-- autoRun: 自動実行しない(ボタンクリックで実行) -->
    
    <!-- 入力1: ドロップダウンでサーバー選択 -->
    <input type="dropdown" token="selected_server">
      <!-- type: 入力タイプを指定 -->
      <!-- token: このフォームの値を格納するトークン名 -->
      
      <label>サーバー選択</label>
      <!-- フォームのラベル表示 -->
      
      <choice value="*">全サーバー</choice>
      <!-- デフォルト選択肢(ワイルドカード) -->
      
      <choice value="Server-A">Server-A</choice>
      <!-- 選択肢1 -->
      
      <choice value="Server-B">Server-B</choice>
      <!-- 選択肢2 -->
      
      <choice value="Server-C">Server-C</choice>
      <!-- 選択肢3 -->
      
      <default>*</default>
      <!-- 初期選択値 -->
      
      <initialValue>*</initialValue>
      <!-- ページロード時の初期値 -->
    </input>
    
    <!-- 入力2: ラジオボタンでステータス選択 -->
    <input type="radio" token="selected_status">
      <!-- ラジオボタン形式の入力 -->
      
      <label>ステータス</label>
      
      <choice value="*">全て</choice>
      <!-- ワイルドカードで全て表示 -->
      
      <choice value="OK">OK</choice>
      
      <choice value="WARNING">WARNING</choice>
      
      <choice value="NG">NG</choice>
      
      <default>*</default>
    </input>
    
    <!-- 入力3: テキストボックスで温度閾値 -->
    <input type="text" token="temp_threshold">
      <!-- テキスト入力フォーム -->
      
      <label>温度閾値(℃)</label>
      
      <default>70</default>
      <!-- デフォルト値を70に設定 -->
    </input>
    
    <!-- 入力4: 時間範囲ピッカー -->
    <input type="time" token="time_range">
      <!-- 時間範囲選択フォーム -->
      
      <label>時間範囲</label>
      
      <default>
        <earliest>-24h@h</earliest>
        <!-- 24時間前から -->
        <latest>now</latest>
        <!-- 現在まで -->
      </default>
    </input>
  </fieldset>
  
  <!-- 検索結果を表示するパネル -->
  <row>
    <panel>
      <title>📊 フィルタリング結果</title>
      
      <table>
        <search>
          <query>
index=monitoring sourcetype=csv
<!-- インデックスからデータを検索 -->

| search device_name=$selected_server$
<!-- トークンを使ってサーバーでフィルタ -->
<!-- $selected_server$ が "*" なら全て -->
<!-- "Server-A" なら Server-A のみ -->

| search status=$selected_status$
<!-- トークンを使ってステータスでフィルタ -->

| where temperature > $temp_threshold$
<!-- 温度が閾値を超えるものだけ表示 -->

| table _time device_name status temperature response_time
<!-- 表示するフィールドを選択 -->

| sort -_time
<!-- 時刻の降順でソート -->
          </query>
          
          <!-- トークンから時間範囲を設定 -->
          <earliest>$time_range.earliest$</earliest>
          <latest>$time_range.latest$</latest>
        </search>
        
        <!-- テーブルのオプション -->
        <option name="drilldown">none</option>
        <option name="count">20</option>
      </table>
    </panel>
  </row>
  
  <!-- トークンの値を表示(デバッグ用) -->
  <row>
    <panel>
      <title>🐛 現在のトークン値(デバッグ用)</title>
      <html>
        <div style="padding: 15px; background: #f5f5f5; border-radius: 8px; font-family: monospace;">
          <!-- トークンの値を表示 -->
          <p><strong>selected_server:</strong> $selected_server$</p>
          <p><strong>selected_status:</strong> $selected_status$</p>
          <p><strong>temp_threshold:</strong> $temp_threshold$</p>
          <p><strong>earliest:</strong> $time_range.earliest$</p>
          <p><strong>latest:</strong> $time_range.latest$</p>
        </div>
      </html>
    </panel>
  </row>
</form>

フォーム入力の動作フロー

┌─────────────────────────────────────┐
│ ユーザーがフォーム入力              │
│  ├─ サーバー: Server-A              │
│  ├─ ステータス: NG                  │
│  └─ 温度閾値: 70                    │
└─────────────────────────────────────┘
           ↓
┌─────────────────────────────────────┐
│ トークンに値が設定される            │
│  ├─ $selected_server$ = "Server-A" │
│  ├─ $selected_status$ = "NG"       │
│  └─ $temp_threshold$ = "70"        │
└─────────────────────────────────────┘
           ↓
┌─────────────────────────────────────┐
│ サーチクエリが展開される            │
│  index=monitoring                  │
│  | search device_name=Server-A    │
│  | search status=NG               │
│  | where temperature > 70         │
└─────────────────────────────────────┘
           ↓
┌─────────────────────────────────────┐
│ 結果が表示される                    │
│  Server-AでNGのデータのみ表示       │
└─────────────────────────────────────┘

🎯 Step5: 実践例2 - ドリルダウンで詳細表示

ドリルダウンとは?

┌─────────────────────────────────────┐
│ 一覧表示(クリック可能)            │
│  ├─ Server-A (OK)    ← クリック!  │
│  ├─ Server-B (NG)                   │
│  └─ Server-C (OK)                   │
└─────────────────────────────────────┘
           ↓
┌─────────────────────────────────────┐
│ 詳細パネルが表示される              │
│  Server-Aの詳細情報:                │
│  ├─ CPU使用率: 45%                  │
│  ├─ メモリ: 8GB/16GB                │
│  └─ ディスク: 120GB/500GB           │
└─────────────────────────────────────┘

完全実装例

<dashboard>
  <label>🔍 ドリルダウンダッシュボード</label>
  
  <!-- 初期状態では詳細パネルを非表示 -->
  <init>
    <!-- ダッシュボード初期化時の処理 -->
    <set token="show_detail">false</set>
    <!-- ↑ 詳細表示フラグを false に設定 -->
  </init>
  
  <!-- サーバー一覧パネル -->
  <row>
    <panel>
      <title>📋 サーバー一覧(クリックで詳細表示)</title>
      
      <table>
        <search>
          <query>
index=monitoring sourcetype=csv
| stats latest(status) as status, 
        latest(temperature) as temperature,
        latest(response_time) as response_time
  by device_name
<!-- デバイスごとに最新のステータス、温度、応答時間を集計 -->

| sort device_name
<!-- デバイス名でソート -->
          </query>
          <earliest>-24h@h</earliest>
          <latest>now</latest>
        </search>
        
        <!-- ドリルダウンを有効化 -->
        <option name="drilldown">cell</option>
        <!-- ↑ セルクリックでドリルダウン -->
        
        <!-- ドリルダウン時の動作定義 -->
        <drilldown>
          <!-- クリックされた行のdevice_nameを取得 -->
          <set token="selected_device">$row.device_name$</set>
          <!-- ↑ クリックした行のデバイス名をトークンに格納 -->
          
          <!-- クリックされた行のstatusを取得 -->
          <set token="selected_device_status">$row.status$</set>
          
          <!-- 詳細パネルを表示 -->
          <set token="show_detail">true</set>
          <!-- ↑ フラグをtrueにして詳細パネルを表示 -->
        </drilldown>
      </table>
    </panel>
  </row>
  
  <!-- 詳細情報パネル(条件付き表示) -->
  <row depends="$show_detail$">
    <!-- depends属性: トークンが定義されている場合のみ表示 -->
    
    <panel>
      <title>📊 $selected_device$ の詳細情報</title>
      <!-- トークンをタイトルに埋め込み -->
      
      <html>
        <div style="padding: 10px; background: #e3f2fd; border-radius: 8px; margin-bottom: 15px;">
          <!-- 現在選択されているデバイス情報を表示 -->
          <h3 style="margin: 0; color: #1976d2;">
            選択中: $selected_device$ (ステータス: $selected_device_status$)
          </h3>
          <!-- 閉じるボタン -->
          <button style="margin-top: 10px; padding: 5px 15px; background: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer;"
                  onclick="
                    <!-- JavaScriptでトークンを削除 -->
                    require(['splunkjs/mvc/simplexml/ready!'], function(){
                      var tokens = require('splunkjs/mvc').Components.getInstance('default');
                      tokens.unset('show_detail');
                    });
                  ">
            ✕ 閉じる
          </button>
        </div>
      </html>
      
      <!-- 時系列グラフ -->
      <chart>
        <title>温度推移</title>
        <search>
          <query>
index=monitoring sourcetype=csv device_name=$selected_device$
<!-- 選択されたデバイスのみに絞り込み -->

| timechart span=1h avg(temperature) as "平均温度"
<!-- 1時間ごとの平均温度を時系列で表示 -->
          </query>
          <earliest>-7d@d</earliest>
          <latest>now</latest>
        </search>
        <option name="charting.chart">line</option>
        <!-- 折れ線グラフで表示 -->
        <option name="charting.axisTitleX.text">時刻</option>
        <option name="charting.axisTitleY.text">温度(℃)</option>
      </chart>
    </panel>
    
    <!-- 詳細テーブル -->
    <panel>
      <title>📋 詳細ログ</title>
      <table>
        <search>
          <query>
index=monitoring sourcetype=csv device_name=$selected_device$
<!-- 選択されたデバイスのログを表示 -->

| table _time status temperature response_time location
<!-- 必要なフィールドを選択 -->

| sort -_time
<!-- 時刻の降順でソート -->

| head 50
<!-- 最新50件を表示 -->
          </query>
          <earliest>-24h@h</earliest>
          <latest>now</latest>
        </search>
        <option name="count">10</option>
      </table>
    </panel>
  </row>
</dashboard>

ドリルダウンの動作フロー

┌──────────────────────────────────────┐
│ 1. ユーザーがServer-Aをクリック      │
└──────────────────────────────────────┘
           ↓
┌──────────────────────────────────────┐
│ 2. <drilldown> タグが発火            │
│    <set token="selected_device">     │
│         $row.device_name$            │
│    </set>                            │
└──────────────────────────────────────┘
           ↓
┌──────────────────────────────────────┐
│ 3. トークンに値が設定される          │
│    $selected_device$ = "Server-A"    │
│    $show_detail$ = "true"            │
└──────────────────────────────────────┘
           ↓
┌──────────────────────────────────────┐
│ 4. depends属性で詳細パネルが表示     │
│    <row depends="$show_detail$">     │
└──────────────────────────────────────┘
           ↓
┌──────────────────────────────────────┐
│ 5. Server-Aの詳細データが検索される  │
│    device_name=Server-A で絞り込み   │
└──────────────────────────────────────┘

🔗 Step6: 実践例3 - 複数トークンの連携

親子関係のあるトークン

<form>
  <label>🔗 連鎖ドロップダウン</label>
  
  <fieldset>
    <!-- 親トークン: 場所を選択 -->
    <input type="dropdown" token="selected_location" searchWhenChanged="true">
      <!-- searchWhenChanged: 値変更時に自動検索 -->
      
      <label>場所</label>
      
      <choice value="*">全ての場所</choice>
      <choice value="Tokyo">東京</choice>
      <choice value="Osaka">大阪</choice>
      <choice value="Nagoya">名古屋</choice>
      
      <default>*</default>
      
      <!-- 値が変更されたとき -->
      <change>
        <!-- 子トークンをリセット -->
        <unset token="selected_server"></unset>
        <!-- ↑ 場所を変更したらサーバー選択をリセット -->
      </change>
    </input>
    
    <!-- 子トークン: サーバーを選択(場所に応じて変化) -->
    <input type="dropdown" token="selected_server" depends="$selected_location$">
      <!-- depends: 親トークンが設定されている場合のみ表示 -->
      
      <label>サーバー</label>
      
      <!-- 動的にサーバーリストを取得 -->
      <search>
        <query>
index=monitoring location=$selected_location$
<!-- 選択された場所のサーバーのみ検索 -->

| stats count by device_name
<!-- デバイス名でグループ化 -->

| table device_name
<!-- デバイス名のみ抽出 -->

| sort device_name
<!-- ソート -->
        </query>
        <earliest>-24h@h</earliest>
        <latest>now</latest>
      </search>
      
      <!-- 検索結果からフィールドを取得 -->
      <fieldForLabel>device_name</fieldForLabel>
      <!-- ↑ 表示ラベルとして使用するフィールド -->
      
      <fieldForValue>device_name</fieldForValue>
      <!-- ↑ トークンに格納する値として使用するフィールド -->
      
      <default>*</default>
    </input>
    
    <!-- 孫トークン: ステータスを選択 -->
    <input type="radio" token="selected_status" depends="$selected_server$">
      <!-- サーバーが選択されている場合のみ表示 -->
      
      <label>ステータス</label>
      
      <choice value="*">全て</choice>
      <choice value="OK">OK</choice>
      <choice value="WARNING">WARNING</choice>
      <choice value="NG">NG</choice>
      
      <default>*</default>
    </input>
  </fieldset>
  
  <!-- 結果表示パネル -->
  <row>
    <panel>
      <title>🎯 フィルタリング結果</title>
      
      <table>
        <search>
          <query>
index=monitoring
| search location=$selected_location$
<!-- 場所でフィルタ -->

| search device_name=$selected_server$
<!-- サーバーでフィルタ -->

| search status=$selected_status$
<!-- ステータスでフィルタ -->

| table _time location device_name status temperature response_time
| sort -_time
          </query>
          <earliest>-24h@h</earliest>
          <latest>now</latest>
        </search>
      </table>
    </panel>
  </row>
  
  <!-- トークン連鎖の状態表示 -->
  <row>
    <panel>
      <title>🔗 トークン連鎖の状態</title>
      <html>
        <div style="padding: 20px; background: #f5f5f5; border-radius: 8px; font-family: monospace;">
          <div style="margin-bottom: 10px;">
            <strong>1. 場所:</strong> 
            <span style="color: #1976d2; font-weight: bold;">$selected_location$</span>
            <!-- ↑ 親トークン -->
          </div>
          <div style="margin-bottom: 10px; margin-left: 20px;">
            <strong>└→ 2. サーバー:</strong> 
            <span style="color: #388e3c; font-weight: bold;">$selected_server$</span>
            <!-- ↑ 子トークン(親に依存) -->
          </div>
          <div style="margin-left: 40px;">
            <strong>└→ 3. ステータス:</strong> 
            <span style="color: #f57c00; font-weight: bold;">$selected_status$</span>
            <!-- ↑ 孫トークン(子に依存) -->
          </div>
        </div>
      </html>
    </panel>
  </row>
</form>

連鎖の動作フロー

┌────────────────────────────────────────┐
│ ユーザーが「東京」を選択               │
└────────────────────────────────────────┘
           ↓
┌────────────────────────────────────────┐
│ 1. $selected_location$ = "Tokyo"       │
│ 2. <change>タグが発火                  │
│    └→ $selected_server$ をリセット     │
└────────────────────────────────────────┘
           ↓
┌────────────────────────────────────────┐
│ サーバードロップダウンが更新される     │
│ location=Tokyo のサーバーのみ表示      │
│  ├─ Server-A                           │
│  └─ Server-C                           │
└────────────────────────────────────────┘
           ↓
┌────────────────────────────────────────┐
│ ユーザーが「Server-A」を選択           │
│ $selected_server$ = "Server-A"         │
└────────────────────────────────────────┘
           ↓
┌────────────────────────────────────────┐
│ ステータス選択が表示される             │
│ (depends属性により)                    │
└────────────────────────────────────────┘

🐛 Step7: デバッグ方法とトラブルシューティング

デバッグパネルの実装

<dashboard>
  <label>🐛 トークンデバッグダッシュボード</label>
  
  <fieldset>
    <input type="text" token="test_token">
      <label>テストトークン</label>
      <default>test_value</default>
    </input>
  </fieldset>
  
  <!-- デバッグ専用パネル -->
  <row>
    <panel>
      <title>🔍 トークンデバッグ情報</title>
      <html>
        <style>
          /* デバッグ表示のスタイル */
          .debug-container {
            font-family: 'Courier New', monospace;
            /* 等幅フォントを使用 */
            background: #263238;
            /* ダークな背景色 */
            color: #aed581;
            /* 緑色のテキスト */
            padding: 20px;
            /* 内側の余白 */
            border-radius: 8px;
            /* 角を丸める */
            overflow-x: auto;
            /* 横スクロール可能に */
          }
          .debug-row {
            margin: 8px 0;
            /* 各行の余白 */
            padding: 5px;
            /* 内側の余白 */
            border-left: 3px solid #4caf50;
            /* 左側に緑のボーダー */
            padding-left: 10px;
            /* 左側の余白 */
          }
          .token-name {
            color: #81d4fa;
            /* 水色でトークン名を表示 */
            font-weight: bold;
            /* 太字 */
          }
          .token-value {
            color: #ffeb3b;
            /* 黄色でトークン値を表示 */
          }
          .undefined {
            color: #ef5350;
            /* 赤色で未定義を表示 */
            font-style: italic;
            /* イタリック体 */
          }
        </style>
        
        <div class="debug-container">
          <h3 style="color: #fff; margin-top: 0;">📋 現在のトークン一覧</h3>
          
          <!-- カスタムトークン -->
          <div class="debug-row">
            <span class="token-name">test_token:</span>
            <span class="token-value">"$test_token$"</span>
            <!-- トークンの値を表示 -->
          </div>
          
          <!-- デフォルトトークン -->
          <div class="debug-row">
            <span class="token-name">earliest:</span>
            <span class="token-value">"$earliest$"</span>
          </div>
          
          <div class="debug-row">
            <span class="token-name">latest:</span>
            <span class="token-value">"$latest$"</span>
          </div>
          
          <!-- 未定義トークンのテスト -->
          <div class="debug-row">
            <span class="token-name">undefined_token:</span>
            <span class="undefined">"$undefined_token$"</span>
            <!-- 未定義の場合はそのまま表示される -->
          </div>
          
          <!-- デフォルト値付きトークン -->
          <div class="debug-row">
            <span class="token-name">with_default:</span>
            <span class="token-value">"$undefined_token|デフォルト値$"</span>
            <!-- 未定義の場合はデフォルト値が表示される -->
          </div>
        </div>
      </html>
    </panel>
  </row>
  
  <!-- JavaScriptでトークンを取得 -->
  <row>
    <panel>
      <title>🔧 JavaScriptデバッグコンソール</title>
      <html>
        <div id="js-debug" style="background: #f5f5f5; padding: 15px; border-radius: 8px; font-family: monospace;"></div>
        
        <script>
          require([
            'splunkjs/mvc',
            // Splunk MVCフレームワークを読み込み
            'splunkjs/mvc/simplexml/ready!'
            // Simple XMLの準備完了を待つ
          ], function(mvc) {
            // トークンモデルを取得
            var tokens = mvc.Components.get('default');
            // ↑ デフォルトトークンセットを取得
            
            // トークンモデルを取得(サブミット済み)
            var submittedTokens = mvc.Components.get('submitted');
            // ↑ サブミット済みトークンセットを取得
            
            // デバッグ情報を表示する関数
            function updateDebug() {
              var debugDiv = document.getElementById('js-debug');
              // 表示用のdiv要素を取得
              
              var html = '<h4>トークン詳細情報:</h4>';
              // 見出しを追加
              
              // 全トークンを取得
              var allTokens = tokens.toJSON();
              // ↑ トークンをJSONオブジェクトに変換
              
              // トークンをループで表示
              for (var key in allTokens) {
                html += '<div><strong>' + key + ':</strong> ' + allTokens[key] + '</div>';
                // 各トークンの名前と値を表示
              }
              
              // HTML更新
              debugDiv.innerHTML = html;
              // ↑ デバッグ情報を画面に表示
            }
            
            // 初期表示
            updateDebug();
            
            // トークン変更時に更新
            tokens.on('change', function() {
              updateDebug();
              // ↑ トークンが変更されたら再表示
            });
          });
        </script>
      </html>
    </panel>
  </row>
</dashboard>

⚠️ よくあるトラブルと解決策

❌ 問題1: トークンが展開されない

表示結果: $token_name$
期待結果: actual_value

診断SPL:

| makeresults
| eval test_value="$token_name$"
<!-- トークンが展開されているか確認 -->
| table test_value

原因と解決策:

<!-- ❌ 間違い: ダブルクォートで囲んでいない -->
<set token="my_token">value</set>
<query>
  index=main field=$my_token$
  <!-- SPL内で裸のトークンは展開されない -->
</query>

<!-- ✅ 正しい: ダブルクォートで囲む -->
<set token="my_token">value</set>
<query>
  index=main field="$my_token$"
  <!-- ダブルクォートで囲むと正しく展開される -->
</query>

❌ 問題2: フォームトークンが更新されない

原因: searchWhenChanged が設定されていない

解決策:

<!-- ❌ 間違い -->
<input type="dropdown" token="my_token">
  <label>選択</label>
  <choice value="a">A</choice>
  <!-- 値を変更しても検索が実行されない -->
</input>

<!-- ✅ 正しい -->
<input type="dropdown" token="my_token" searchWhenChanged="true">
  <!-- searchWhenChanged属性を追加 -->
  <label>選択</label>
  <choice value="a">A</choice>
  <!-- 値を変更すると自動的に検索が実行される -->
</input>

❌ 問題3: depends属性が動作しない

診断方法:

<!-- デバッグ用パネル -->
<row>
  <panel>
    <html>
      <div>トークンの値: "$my_token$"</div>
      <!-- トークンが設定されているか確認 -->
      <div>長さ: $my_token|length$</div>
      <!-- トークンの長さを確認 -->
    </html>
  </panel>
</row>

<!-- depends付きパネル -->
<row depends="$my_token$">
  <!-- トークンが空文字列の場合も表示されない -->
  <panel>
    <title>詳細情報</title>
  </panel>
</row>

解決策:

<!-- トークンが空文字列でも表示したい場合 -->
<set token="show_panel">true</set>
<!-- フラグ用のトークンを別途用意 -->

<row depends="$show_panel$">
  <!-- フラグトークンで制御 -->
  <panel>
    <title>詳細情報</title>
  </panel>
</row>

❌ 問題4: ドリルダウンが動作しない

チェックリスト:

<table>
  <!-- 1. drilldownオプションを確認 -->
  <option name="drilldown">cell</option>
  <!-- ↑ "none"になっていないか確認 -->
  
  <drilldown>
    <!-- 2. トークン設定を確認 -->
    <set token="clicked_value">$row.fieldname$</set>
    <!-- ↑ フィールド名が正しいか確認 -->
    
    <!-- 3. デバッグ出力を追加 -->
    <eval token="debug_click">"Clicked: " + $row.fieldname$</eval>
    <!-- ↑ クリックイベントが発火しているか確認 -->
  </drilldown>
</table>

<!-- デバッグ表示 -->
<html>
  <div>デバッグ: $debug_click$</div>
</html>

📊 トークンの使い分けチャート

┌─────────────────────────────────────────────────┐
│ トークンの使い分け判断フロー                    │
└─────────────────────────────────────────────────┘
           ↓
    [ユーザー入力が必要?]
           ↓ YES              ↓ NO
   ┌─────────────┐    ┌─────────────┐
   │フォームトークン│    │カスタムトークン│
   │$form.xxx$    │    │$xxx$         │
   └─────────────┘    └─────────────┘
           ↓                  ↓
   [入力タイプ選択]    [設定方法選択]
    ├─ dropdown        ├─ <set>
    ├─ radio           ├─ <eval>
    ├─ text            └─ サーチ結果
    ├─ multiselect
    └─ time

🎓 まとめ

✅ 学んだこと

  • トークンの基本: 動的な値を保持する変数
  • 3つの種類: デフォルト、カスタム、フォーム
  • 設定方法: <set>, <unset>, <condition>
  • 実践パターン: フォーム、ドリルダウン、連鎖
  • デバッグ技術: 値の確認、トラブルシューティング

📌 重要ポイント

項目 ポイント
命名規則 わかりやすい名前をつける
スコープ form と通常トークンを使い分け
depends 条件付き表示で動的UI
デバッグ 常に値を確認できるパネルを用意
初期化 <init> で初期値を設定

📚 参考リンク


🎉 お疲れ様でした!トークンを使いこなして動的なダッシュボードを作りましょう!


さらに一緒に学びたい方はこちらもどうぞ!

🎨 SplunkクラシックでJS不要!HTML/CSS/SPLだけで作るオリジナルチャート

JavaScriptなしHTML+CSS+SPL**だけで図形チャートを作ります!


📋 実装の全体像

┌─────────────────────────────────────────┐
│ Step1: インデックス作成                 │
│  ├─ ルールと制約                        │
│  └─ ベストプラクティス                  │
└─────────────────────────────────────────┘
           ↓
┌─────────────────────────────────────────┐
│ Step2: 外部データ準備                   │
│  └─ CSV形式でデータ作成                 │
└─────────────────────────────────────────┘
           ↓
┌─────────────────────────────────────────┐
│ Step3: SPLでHTMLタグ生成                │
│  └─ eval + mvappend で動的HTML          │
└─────────────────────────────────────────┘
           ↓
┌─────────────────────────────────────────┐
│ Step4: ダッシュボードXML                │
│  └─ <html>パネルに埋め込み              │
└─────────────────────────────────────────┘
           ↓
┌─────────────────────────────────────────┐
│ Step5: CSSスタイリング                  │
│  └─ インラインスタイル適用              │
└─────────────────────────────────────────┘

🗂️ Step1: インデックス作成のルールと制約

インデックスとは?

┌────────────────────────────────────────┐
│ インデックス = データの保管庫          │
│                                        │
│  生データ → インデックス → 検索可能   │
│                                        │
│  [ログファイル] → [main] → SPL検索    │
└────────────────────────────────────────┘

インデックス作成の制約

項目 制約内容 理由
名前 小文字、数字、アンダースコアのみ Splunk内部処理の仕様
予約語 main, _internal, _audit等は使用不可 システムインデックスとの競合
長さ 最大255文字 ファイルシステムの制限
作成後 名前の変更不可 データ整合性のため
削除 慎重に実施(データ消失) 復旧不可

インデックス作成手順

方法A: Web UIから作成

┌─────────────────────────────────┐
│ 1. Settings クリック            │
│    └→ Indexes クリック          │
│                                 │
│ 2. New Index ボタン             │
│                                 │
│ 3. 設定項目入力                 │
│    ├─ Index Name: monitoring   │
│    ├─ Max Size: 500MB          │
│    └─ Retention: 7 days        │
│                                 │
│ 4. Save ボタン                  │
└─────────────────────────────────┘

方法B: CLIから作成

# Splunkディレクトリに移動
cd $SPLUNK_HOME/bin

# インデックスを作成
./splunk add index monitoring -auth admin:password
# ↑ 'monitoring'という名前のインデックスを作成

# インデックスの最大サイズを設定
./splunk set index-maxsize 500 monitoring -auth admin:password
# ↑ 最大サイズを500MBに制限

# 設定を確認
./splunk list index monitoring -auth admin:password
# ↑ 作成したインデックスの設定を表示

方法C: indexes.confで作成

$SPLUNK_HOME/etc/apps/custom_monitoring/local/indexes.conf

# カスタム監視用インデックス定義
[monitoring]
# インデックス名(必須)
homePath = $SPLUNK_DB/monitoring/db
# データファイルの保存場所
coldPath = $SPLUNK_DB/monitoring/colddb
# 古いデータの保存場所
thawedPath = $SPLUNK_DB/monitoring/thaweddb
# 削除予定データの一時保存場所
maxTotalDataSizeMB = 500
# インデックスの最大サイズ(MB)
frozenTimePeriodInSecs = 604800
# データ保持期間(秒)7日 = 604800秒
enableDataIntegrityControl = 1
# データ整合性チェックを有効化
enableTsidxReduction = 1
# インデックスサイズ最適化を有効化

設定後の再起動

# Splunkを再起動して設定を反映
$SPLUNK_HOME/bin/splunk restart

📥 Step2: 外部データの準備

monitoring_data.csv

_time,device_name,status,temperature,response_time,location
2026-01-17 10:00:00,Server-A,OK,45,120,Tokyo
2026-01-17 10:00:00,Server-B,NG,78,350,Osaka
2026-01-17 10:00:00,Server-C,OK,52,95,Nagoya
2026-01-17 10:00:00,Server-D,WARNING,68,280,Fukuoka

データアップロード手順

┌──────────────────────────────────────┐
│ Settings → Add Data → Upload        │
│                                      │
│ 1. ファイル選択                      │
│    └→ monitoring_data.csv           │
│                                      │
│ 2. Set Source Type                  │
│    └→ csv を選択                    │
│                                      │
│ 3. Input Settings                   │
│    ├─ Host: localhost               │
│    ├─ Index: monitoring (新規作成) │
│    └─ Source type: csv              │
│                                      │
│ 4. Review → Submit                  │
└──────────────────────────────────────┘

ルックアップテーブルとして登録

transforms.conf

# ルックアップテーブル定義
[monitoring_lookup]
# ルックアップ名(SPLで使用する名前)
filename = monitoring_data.csv
# CSVファイル名を指定
max_matches = 1000
# 最大マッチ数を設定
min_matches = 1
# 最小マッチ数を設定
case_sensitive_match = false
# 大文字小文字を区別しない

props.conf

# ソースタイプ定義
[csv]
# CSVファイル用の設定
LOOKUP-monitoring = monitoring_lookup
# ルックアップテーブルを関連付け
INDEXED_EXTRACTIONS = csv
# CSVフィールドを自動抽出
SHOULD_LINEMERGE = false
# 複数行を結合しない
TIME_FORMAT = %Y-%m-%d %H:%M:%S
# 時刻フォーマットを指定
TIME_PREFIX = ^
# 時刻の開始位置を指定

🔍 Step3: SPLでHTMLタグ生成

基本SPL

index=monitoring sourcetype=csv
| eval status_code=case(
    status=="OK", 0,
    status=="WARNING", 1,
    status=="NG", 2,
    1==1, 3
)
| eval color=case(
    status_code==0, "#4CAF50",
    status_code==1, "#FFC107",
    status_code==2, "#F44336",
    1==1, "#9E9E9E"
)
| eval shape_class=case(
    device_name=="Server-A", "square",
    device_name=="Server-B", "circle",
    device_name=="Server-C", "square",
    device_name=="Server-D", "bubble",
    1==1, "square"
)
| table device_name status color shape_class temperature response_time location

HTML生成SPL(完全版)

index=monitoring sourcetype=csv
`-- インデックスからCSVデータを検索`

| eval status_code=case(
    status=="OK", 0,
    `-- OKの場合は0`
    status=="WARNING", 1,
    `-- WARNINGの場合は1`
    status=="NG", 2,
    `-- NGの場合は2`
    1==1, 3
    `-- それ以外は3`
)
`-- ステータスを数値コードに変換`

| eval color=case(
    status_code==0, "#4CAF50",
    `-- OK時は緑色`
    status_code==1, "#FFC107",
    `-- WARNING時は黄色`
    status_code==2, "#F44336",
    `-- NG時は赤色`
    1==1, "#9E9E9E"
    `-- 不明時はグレー`
)
`-- ステータスに応じた色コードを設定`

| eval shape_class=case(
    device_name=="Server-A", "square",
    `-- Server-Aは四角`
    device_name=="Server-B", "circle",
    `-- Server-Bは丸`
    device_name=="Server-C", "square",
    `-- Server-Cは四角`
    device_name=="Server-D", "bubble",
    `-- Server-Dは吹き出し`
    1==1, "square"
    `-- デフォルトは四角`
)
`-- デバイスごとに図形クラスを決定`

| eval bubble_arrow=if(shape_class=="bubble", 
    "<div class=\"bubble-arrow\" style=\"border-top-color: " + color + ";\"></div>", 
    ""
)
`-- 吹き出しの場合は三角形タグを追加`

| eval html_shape="<div class=\"shape-item\">" +
    "<div class=\"shape shape-" + shape_class + "\" style=\"background-color: " + color + ";\">" +
    bubble_arrow +
    "</div>" +
    "<div class=\"shape-label\">" +
    "<div class=\"device-name\">" + device_name + "</div>" +
    "<div class=\"status\">" + status + "</div>" +
    "<div class=\"info\">温度: " + temperature + "℃</div>" +
    "<div class=\"info\">応答: " + response_time + "ms</div>" +
    "<div class=\"info\">場所: " + location + "</div>" +
    "</div>" +
    "</div>"
`-- HTMLタグ文字列を1つのフィールドに結合`

| stats values(html_shape) as html_shapes
`-- 全てのHTML要素を配列として収集`

| eval html_output="<div class=\"shape-grid\">" + mvjoin(html_shapes, "") + "</div>"
`-- グリッドコンテナで全体をラップ`

| table html_output
`-- HTML文字列のみを出力`

📊 Step4: ダッシュボードXML

dashboard.xml(完全版)

<!-- Splunk Simple XMLダッシュボード -->
<dashboard>
  <!-- ダッシュボードのタイトル -->
  <label>🎨 監視状態ダッシュボード(JS不要版)</label>
  
  <!-- ダッシュボードの説明 -->
  <description>HTML + CSS + SPLのみで実装</description>
  
  <!-- CSSスタイルを定義 -->
  <row>
    <panel>
      <html>
        <style>
          /* ビジュアライゼーション全体のコンテナ */
          .custom-shapes-viz {
            width: 100%;
            /* 幅を100%に設定 */
            padding: 20px;
            /* 内側の余白を20px */
            background-color: #f5f5f5;
            /* 背景色を薄いグレーに */
            box-sizing: border-box;
            /* パディングを含めたサイズ計算 */
          }
          
          /* 図形を配置するグリッドコンテナ */
          .shape-grid {
            display: grid;
            /* CSS Gridレイアウトを使用 */
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            /* 自動でカラム数を調整 */
            gap: 30px;
            /* グリッドアイテム間の隙間 */
            padding: 20px;
            /* 内側の余白 */
          }
          
          /* 各図形アイテムのコンテナ */
          .shape-item {
            display: flex;
            /* Flexboxレイアウト */
            flex-direction: column;
            /* 縦方向に配置 */
            align-items: center;
            /* 水平方向に中央揃え */
            gap: 15px;
            /* 子要素間の隙間 */
            padding: 15px;
            /* 内側の余白 */
            background: white;
            /* 背景色を白に */
            border-radius: 12px;
            /* 角を12pxで丸める */
            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
            /* 影を追加 */
            transition: transform 0.3s ease;
            /* アニメーション設定 */
          }
          
          /* ホバー時のエフェクト */
          .shape-item:hover {
            transform: translateY(-5px);
            /* 上に5px移動 */
            box-shadow: 0 4px 16px rgba(0,0,0,0.15);
            /* 影を濃く */
          }
          
          /* 図形の基本スタイル */
          .shape {
            width: 100px;
            /* 幅を100px */
            height: 100px;
            /* 高さを100px */
            display: flex;
            /* Flexboxレイアウト */
            align-items: center;
            /* 垂直方向に中央揃え */
            justify-content: center;
            /* 水平方向に中央揃え */
            position: relative;
            /* 相対位置指定 */
            transition: all 0.3s ease;
            /* アニメーション */
          }
          
          /* 四角形のスタイル */
          .shape-square {
            border-radius: 8px;
            /* 角を8pxで丸める */
          }
          
          /* 円形のスタイル */
          .shape-circle {
            border-radius: 50%;
            /* 角を50%で丸めて円形に */
          }
          
          /* 吹き出しのスタイル */
          .shape-bubble {
            border-radius: 12px;
            /* 角を12pxで丸める */
          }
          
          /* 吹き出しの三角形部分 */
          .bubble-arrow {
            position: absolute;
            /* 絶対位置指定 */
            bottom: -15px;
            /* 下に15px移動 */
            left: 50%;
            /* 左から50%の位置 */
            transform: translateX(-50%);
            /* 中央に配置 */
            width: 0;
            /* 幅を0に */
            height: 0;
            /* 高さを0に */
            border-left: 15px solid transparent;
            /* 左のボーダー */
            border-right: 15px solid transparent;
            /* 右のボーダー */
            border-top: 15px solid #4CAF50;
            /* 上のボーダーで三角形作成 */
          }
          
          /* ラベル全体のスタイル */
          .shape-label {
            text-align: center;
            /* テキストを中央揃え */
            width: 100%;
            /* 幅を100% */
          }
          
          /* デバイス名のスタイル */
          .device-name {
            font-weight: bold;
            /* 太字 */
            font-size: 16px;
            /* フォントサイズ */
            color: #333;
            /* テキスト色 */
            margin-bottom: 5px;
            /* 下に余白 */
          }
          
          /* ステータステキストのスタイル */
          .status {
            font-size: 14px;
            /* フォントサイズ */
            font-weight: 600;
            /* セミボールド */
            color: #666;
            /* テキスト色 */
            margin-bottom: 8px;
            /* 下に余白 */
            padding: 4px 12px;
            /* パディング */
            background-color: #f0f0f0;
            /* 背景色 */
            border-radius: 12px;
            /* 角を丸める */
            display: inline-block;
            /* インラインブロック */
          }
          
          /* 詳細情報のスタイル */
          .info {
            font-size: 12px;
            /* フォントサイズ */
            color: #888;
            /* テキスト色 */
            margin: 2px 0;
            /* 余白 */
          }
          
          /* レスポンシブ: タブレット */
          @media (max-width: 768px) {
            .shape-grid {
              grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
              /* カラム幅を縮小 */
              gap: 20px;
              /* 隙間を縮小 */
            }
            .shape {
              width: 80px;
              /* 図形を縮小 */
              height: 80px;
              /* 図形を縮小 */
            }
          }
          
          /* レスポンシブ: スマホ */
          @media (max-width: 480px) {
            .shape-grid {
              grid-template-columns: 1fr;
              /* 1カラム表示 */
              gap: 15px;
              /* 隙間を縮小 */
            }
          }
        </style>
      </html>
    </panel>
  </row>
  
  <!-- メインビジュアライゼーション -->
  <row>
    <panel>
      <title>🎨 デバイス状態ビジュアライゼーション</title>
      <html>
        <div class="custom-shapes-viz">
          <!-- SPLで生成されたHTMLがここに表示される -->
          $html_content$
        </div>
      </html>
    </panel>
  </row>
  
  <!-- HTML生成用のサーチ -->
  <search id="shape_search">
    <query>
index=monitoring sourcetype=csv
| eval status_code=case(status=="OK", 0, status=="WARNING", 1, status=="NG", 2, 1==1, 3)
| eval color=case(status_code==0, "#4CAF50", status_code==1, "#FFC107", status_code==2, "#F44336", 1==1, "#9E9E9E")
| eval shape_class=case(device_name=="Server-A", "square", device_name=="Server-B", "circle", device_name=="Server-C", "square", device_name=="Server-D", "bubble", 1==1, "square")
| eval bubble_arrow=if(shape_class=="bubble", "<div class=\"bubble-arrow\" style=\"border-top-color: " + color + ";\"></div>", "")
| eval html_shape="<div class=\"shape-item\"><div class=\"shape shape-" + shape_class + "\" style=\"background-color: " + color + ";\">" + bubble_arrow + "</div><div class=\"shape-label\"><div class=\"device-name\">" + device_name + "</div><div class=\"status\">" + status + "</div><div class=\"info\">温度: " + temperature + "℃</div><div class=\"info\">応答: " + response_time + "ms</div><div class=\"info\">場所: " + location + "</div></div></div>"
| stats values(html_shape) as html_shapes
| eval html_content="<div class=\"shape-grid\">" + mvjoin(html_shapes, "") + "</div>"
| table html_content
    </query>
    <!-- サーチの時間範囲 -->
    <earliest>-24h@h</earliest>
    <latest>now</latest>
    <!-- トークンに結果を設定 -->
    <done>
      <set token="html_content">$result.html_content$</set>
    </done>
  </search>
  
  <!-- 詳細テーブル表示 -->
  <row>
    <panel>
      <title>📋 詳細データテーブル</title>
      <table>
        <search>
          <query>
index=monitoring sourcetype=csv
| eval status_code=case(status=="OK", 0, status=="WARNING", 1, status=="NG", 2, 1==1, 3)
| table device_name status temperature response_time location _time
| sort device_name
          </query>
          <earliest>-24h@h</earliest>
          <latest>now</latest>
        </search>
        <!-- テーブルのオプション -->
        <option name="drilldown">none</option>
        <option name="wrap">true</option>
        <option name="rowNumbers">true</option>
        <option name="dataOverlayMode">none</option>
        <option name="count">10</option>
      </table>
    </panel>
  </row>
</dashboard>

🚨 Step5: 困ったときの対応

❌ 問題1: インデックスが作成できない

┌────────────────────────────────────┐
│ エラー: Index name contains       │
│        invalid characters         │
└────────────────────────────────────┘

原因チェックリスト:

| makeresults
| eval index_name="monitoring-data"
`-- ハイフンはNG(アンダースコアを使用)`
| eval correct_name="monitoring_data"
`-- 正しい命名規則`
| eval check=if(match(index_name, "^[a-z0-9_]+$"), "OK", "NG")
`-- 正規表現でチェック`
| table index_name correct_name check

解決策:

# 正しいインデックス名で再作成
cd $SPLUNK_HOME/bin
./splunk add index monitoring_data -auth admin:password
# ↑ アンダースコアを使用

# 確認
./splunk list index | grep monitoring
# ↑ 作成されたインデックスを確認

❌ 問題2: データがインデックスに入らない

┌────────────────────────────────────┐
│ 検索結果: 0 events                │
└────────────────────────────────────┘

診断SPL:

| rest /services/data/indexes
`-- 全インデックスの情報を取得`
| search title="monitoring"
`-- 対象インデックスに絞り込み`
| table title totalEventCount currentDBSizeMB
`-- イベント数とサイズを確認`

| rest /services/data/inputs/monitor
`-- 監視中の入力ソースを確認`
| table title disabled index
`-- 有効/無効状態を確認`

解決策A: inputs.confを確認

# 設定ファイルを確認
cat $SPLUNK_HOME/etc/apps/custom_monitoring/local/inputs.conf

# 正しい設定例
[monitor:///opt/monitoring/data/*.csv]
disabled = false
# ↑ disabledがfalseになっているか確認
sourcetype = csv
index = monitoring
# ↑ インデックス名が正しいか確認

解決策B: 手動でデータを再投入

# Splunk CLIでデータを追加
cd $SPLUNK_HOME/bin
./splunk add oneshot /path/to/monitoring_data.csv -sourcetype csv -index monitoring -auth admin:password
# ↑ ファイルパスを確認してコマンド実行

❌ 問題3: HTMLが表示されない

┌────────────────────────────────────┐
│ パネルが空白                       │
└────────────────────────────────────┘

診断SPL:

index=monitoring sourcetype=csv
| eval html_test="<div>テスト</div>"
`-- シンプルなHTMLタグで確認`
| table html_test
`-- 出力を確認`

index=monitoring sourcetype=csv
| stats count
`-- データ件数を確認`

解決策: トークンの確認

<!-- dashboard.xmlを修正 -->
<search id="shape_search">
  <query>
    index=monitoring sourcetype=csv
    | head 1
    | eval html_content="<div style='color: red;'>テスト表示</div>"
    | table html_content
  </query>
  <done>
    <!-- トークン名が一致しているか確認 -->
    <set token="html_content">$result.html_content$</set>
  </done>
</search>

<html>
  <div class="custom-shapes-viz">
    <!-- トークンが正しく参照されているか確認 -->
    $html_content$
  </div>
</html>

❌ 問題4: 色が反映されない

┌────────────────────────────────────┐
│ 全て同じ色で表示される             │
└────────────────────────────────────┘

診断SPL:

index=monitoring sourcetype=csv
| eval status_debug=status
`-- ステータス値を確認`
| eval color_debug=case(
    status=="OK", "#4CAF50",
    status=="WARNING", "#FFC107",
    status=="NG", "#F44336",
    1==1, "#9E9E9E"
)
`-- 色の割り当てをデバッグ`
| table device_name status status_debug color_debug
`-- 全フィールドを表示して確認`

解決策: ステータス値の正規化

index=monitoring sourcetype=csv
| eval status=upper(trim(status))
`-- 大文字に統一&空白削除`
| eval status=case(
    status=="OK" OR status=="NORMAL", "OK",
    status=="WARN" OR status=="WARNING", "WARNING",
    status=="ERROR" OR status=="NG", "NG",
    1==1, "UNKNOWN"
)
`-- ステータス値を標準化`
| eval color=case(
    status=="OK", "#4CAF50",
    status=="WARNING", "#FFC107",
    status=="NG", "#F44336",
    1==1, "#9E9E9E"
)
| table device_name status color

❌ 問題5: パーミッションエラー

┌────────────────────────────────────┐
│ Error: Insufficient permissions   │
└────────────────────────────────────┘

原因:

  • ユーザーのロールにインデックスへのアクセス権限がない

解決策:

# ロールの確認
cd $SPLUNK_HOME/bin
./splunk list role
# ↑ 利用可能なロールを表示

# ユーザーにロールを追加
./splunk edit user username -role power -auth admin:password
# ↑ usernameをpowerロールに追加

# インデックスの権限を確認
./splunk list index monitoring -auth admin:password
# ↑ アクセス権限を確認

authorize.confで権限設定:

# $SPLUNK_HOME/etc/apps/custom_monitoring/local/authorize.conf

# powerロールに権限を付与
[role_power]
# インデックスの読み取り権限
srchIndexesAllowed = monitoring;main;_*
# インデックスの書き込み権限
srchIndexesDefault = monitoring;main

📈 動作確認フロー図

┌─────────────────────────────────────────┐
│ 1. データ確認                           │
│    index=monitoring | stats count      │
│    └→ 件数が0なら: データ投入を確認   │
└─────────────────────────────────────────┘
           ↓ OK
┌─────────────────────────────────────────┐
│ 2. フィールド確認                       │
│    index=monitoring | table *          │
│    └→ 必要なフィールドが無いなら:      │
│       props.confを確認                 │
└─────────────────────────────────────────┘
           ↓ OK
┌─────────────────────────────────────────┐
│ 3. ステータス確認                       │
│    | eval color=case(...)              │
│    └→ 色が正しく割り当てられているか   │
└─────────────────────────────────────────┘
           ↓ OK
┌─────────────────────────────────────────┐
│ 4. HTML生成確認                         │
│    | eval html_shape="<div>..."        │
│    └→ HTMLタグが正しく生成されているか │
└─────────────────────────────────────────┘
           ↓ OK
┌─────────────────────────────────────────┐
│ 5. ダッシュボード表示確認               │
│    dashboard.xml の $token$ が正しいか │
└─────────────────────────────────────────┘

🎯 まとめ

✅ できるようになったこと

  • インデックスの作成(ルールと制約の理解)
  • 外部CSVデータのインデックス投入
  • SPLでのHTML動的生成
  • CSS+HTMLのみでの図形描画
  • JavaScriptなしのカスタムビジュアライゼーション
  • トラブルシューティングの基本フロー

📌 重要ポイント

項目 ポイント
インデックス名 小文字+数字+アンダースコアのみ
SPL evalでHTML文字列を動的生成
トークン $token$でXMLに値を埋め込み
CSS インラインスタイルで色を制御
デバッグ 段階的に確認して原因を特定

📚 参考コマンド集

# インデックス一覧表示
$SPLUNK_HOME/bin/splunk list index

# インデックス作成
$SPLUNK_HOME/bin/splunk add index <index_name>

# インデックス削除(注意!)
$SPLUNK_HOME/bin/splunk remove index <index_name>

# データ投入
$SPLUNK_HOME/bin/splunk add oneshot <file_path> -index <index_name>

# Splunk再起動
$SPLUNK_HOME/bin/splunk restart

# ログ確認
tail -f $SPLUNK_HOME/var/log/splunk/splunkd.log

🎉 お疲れ様でした!JavaScriptなしでもSplunkで動的なビジュアライゼーションが作れます!

もっと学びたい方はこちらをどうぞ!

SPL(Search Processing Language)完全解説:新人エンジニア向け

🎯 SPLとは?

Search Processing Language = Splunkでデータを検索・加工・集計するための専用言語

イメージ:

  • SQL = データベース用の言語
  • SPL = Splunk用の言語

📌 SPLの基本構造

パイプライン方式

データ取得 | 絞り込み | 加工 | 集計 | 表示

特徴: |(パイプ)で処理を繋げていく


🔍 今回使った SPL を完全解説

完全版コード

index=20260115tra
| where like(location,"%湾岸%")
| sort -_time
| head 1
| eval is_ng=if(result="NG",1,0)

1️⃣ index=20260115tra

📖 意味

データソース(index)を指定

🔧 構文

index=<インデックス名>

💡 新人向け解説

  • Splunkに取り込んだデータの「置き場所」
  • CSVをアップロードすると自動で index が作られる
  • 複数のindexがある場合、ここで絞り込む

📊 例

index=web_logs           # Webサーバーのログ
index=app_errors         # アプリケーションエラー
index=20260115tra        # 今回の道路データ

2️⃣ | where like(location,"%湾岸%")

📖 意味

location列に「湾岸」という文字を含むデータだけ抽出

🔧 構文

| where like(<フィールド名>, "<パターン>")

💡 新人向け解説

  • % はワイルドカード(何でもマッチ)
  • SQL の LIKE と同じ
  • %湾岸% = 「前後に何があっても湾岸を含めばOK」

📊 マッチ例

location マッチするか?
首都高 湾岸線 東京 ✅ マッチ
湾岸道路 ✅ マッチ
東京湾岸 ✅ マッチ
東名高速 ❌ マッチしない

🎯 応用パターン

# 前方一致
| where like(location, "首都高%")

# 後方一致
| where like(location, "%高速")

# 完全一致
| where location="首都高 湾岸線"

# 複数条件(OR)
| where like(location, "%湾岸%") OR like(location, "%東名%")

# 複数条件(AND)
| where like(location, "%首都高%") AND like(location, "%湾岸%")

3️⃣ | sort -_time

📖 意味

_time(時刻)フィールドで降順ソート(新しい順)

🔧 構文

| sort <-フィールド名>    # 降順(大→小)
| sort <フィールド名>     # 昇順(小→大)

💡 新人向け解説

  • - がつくと降順
  • - がないと昇順
  • _time は Splunk の特殊フィールド(全データに自動付与)

📊 ソート結果イメージ

元データ:

_time: 2024-01-15 18:30  result: OK
_time: 2024-01-15 18:40  result: NG
_time: 2024-01-15 18:35  result: OK

sort -_time 後:

_time: 2024-01-15 18:40  result: NG  ← 最新
_time: 2024-01-15 18:35  result: OK
_time: 2024-01-15 18:30  result: OK

🎯 応用パターン

# 昇順(古い順)
| sort _time

# 複数フィールドでソート
| sort -_time, location

# 数値フィールドでソート
| sort -count

# ソート後の件数制限
| sort -_time | head 10

4️⃣ | head 1

📖 意味

上から1件だけ取得(最新1件)

🔧 構文

| head <件数>

💡 新人向け解説

  • 最重要コマンド!
  • sort -_time と組み合わせて「最新1件」を取得
  • これがないと過去の全データが対象になる

📊 動作イメージ

head 1 なし:

2024-01-15 18:40  NG  ← 最新
2024-01-15 18:35  OK
2024-01-15 18:30  NG
2024-01-15 18:25  OK
↓
全部が処理対象(NGが2件ある)

head 1 あり:

2024-01-15 18:40  NG  ← これだけ
↓
最新の1件だけ処理(NGが1件)

🎯 応用パターン

# 最新5件
| head 5

# 最古の1件(昇順ソート + head)
| sort _time | head 1

# 最新10件の次の10件(offset)
| sort -_time | head 20 | tail 10

5️⃣ | eval is_ng=if(result="NG",1,0)

📖 意味

新しいフィールド is_ng を作成し、条件分岐で値を設定

🔧 構文

| eval <新フィールド名>=if(<条件>, <真の値>, <偽の値>)

💡 新人向け解説

  • eval = 計算・加工用のコマンド
  • if() = 3項演算子(プログラミングの ? : と同じ)
  • 結果を数値化することでトークン判定が簡単になる

📊 動作イメージ

入力データ:

result: NG

処理:

if(result="NG", 1, 0)
↓
result が "NG" か? → YES
↓
1 を返す

出力データ:

result: NG
is_ng: 1

🎯 応用パターン

複数条件(case)

| eval status=case(
    result="NG", "赤",
    result="WARN", "黄",
    result="OK", "緑",
    1=1, "不明"
  )

計算

| eval total=price * quantity
| eval discount_price=price * 0.9

文字列操作

| eval full_name=first_name." ".last_name
| eval upper_location=upper(location)

日時操作

| eval hour=strftime(_time, "%H")
| eval date=strftime(_time, "%Y-%m-%d")

NULL チェック

| eval is_empty=if(isnull(field), "空", "有")

🎓 SPL の処理順序(重要)

今回のクエリを分解

index=20260115tra                      # ① データ取得
| where like(location,"%湾岸%")        # ② 絞り込み
| sort -_time                          # ③ ソート
| head 1                               # ④ 最新1件
| eval is_ng=if(result="NG",1,0)      # ⑤ 加工

📊 各段階でのデータ変化

① index=20260115tra
┌─────────────────────────────┐
│ 100万件のデータ              │
└─────────────────────────────┘

② | where like(location,"%湾岸%")
┌─────────────────────────────┐
│ 1,000件(湾岸関連のみ)      │
└─────────────────────────────┘

③ | sort -_time
┌─────────────────────────────┐
│ 1,000件(新しい順に並び替え)│
└─────────────────────────────┘

④ | head 1
┌─────────────────────────────┐
│ 1件(最新のみ)              │
└─────────────────────────────┘

⑤ | eval is_ng=if(result="NG",1,0)
┌─────────────────────────────┐
│ 1件(is_ng フィールド追加)  │
│ location: 首都高湾岸線       │
│ result: NG                  │
│ is_ng: 1                    │
└─────────────────────────────┘

🔧 よく使う SPL コマンド一覧

データ取得系

コマンド 意味
index= データソース指定 index=web_logs
source= ファイル指定 source="/var/log/app.log"
sourcetype= データ形式指定 sourcetype=access_combined

絞り込み系

コマンド 意味
where 条件絞り込み where status=200
search キーワード検索 search error
like パターンマッチ like(field, "%keyword%")
in リスト照合 where status in(200,201,204)

並び替え・抽出系

コマンド 意味
sort ソート sort -_time
head 上からN件 head 10
tail 下からN件 tail 10
dedup 重複削除 dedup user_id

加工系

コマンド 意味
eval フィールド作成・計算 eval total=price*qty
rex 正規表現で抽出 rex field=_raw "(?<ip>\d+\.\d+\.\d+\.\d+)"
replace 文字列置換 eval status=replace(status,"OK","正常")

集計系

コマンド 意味
stats 集計 stats count by status
count 件数 stats count as total
sum 合計 stats sum(amount) by user
avg 平均 stats avg(response_time)
max / min 最大/最小 stats max(price)

時系列系

コマンド 意味
timechart 時系列グラフ timechart count by status
bin 時間バケット bin _time span=1h
earliest / latest 時刻範囲 earliest=-24h

🎯 実践パターン集

パターン1:最新のステータスを取得

index=mydata
| where user_id=12345
| sort -_time
| head 1
| table user_id, status, _time

パターン2:エラー件数を集計

index=app_logs
| where level="ERROR"
| stats count by error_type
| sort -count

パターン3:時間帯別の平均応答時間

index=web_logs
| eval hour=strftime(_time, "%H")
| stats avg(response_time) by hour
| sort hour

パターン4:上位10ユーザー

index=access_logs
| stats count by user_id
| sort -count
| head 10

パターン5:条件付き集計

index=sales
| stats 
    count as total,
    count(eval(status="completed")) as success,
    count(eval(status="failed")) as failed
  by store_id

⚠️ よくあるミス

ミス1:head の位置

❌ NG
index=mydata
| head 1
| sort -_time
↓
ランダムな1件をソートしても意味ない

✅ OK
index=mydata
| sort -_time
| head 1
↓
ソートしてから最新1件

ミス2:大文字小文字

❌ NG
| where result="ng"
↓
データが "NG" なのでマッチしない

✅ OK
| where result="NG"
または
| where lower(result)="ng"

ミス3:wheresearch の混同

# where:フィールド名を指定
| where status=200

# search:全文検索
| search "status=200"

🚀 デバッグテクニック

テクニック1:途中結果を確認

index=20260115tra
| where like(location,"%湾岸%")
| table location, _time, result  # ← 途中で確認

テクニック2:件数確認

index=20260115tra
| where like(location,"%湾岸%")
| stats count  # ← 何件あるか確認

テクニック3:フィールド一覧

index=20260115tra
| head 1
| table *  # ← 全フィールドを表示

📚 学習ロードマップ

レベル1(初級)

  • index, where, sort, head
  • eval の基本(if, 四則演算)
  • stats count

レベル2(中級)

  • 🔶 stats の高度な使い方(sum, avg, max, min)
  • 🔶 timechart
  • 🔶 rex(正規表現)

レベル3(上級)

  • 🔥 join(結合)
  • 🔥 eventstats, streamstats
  • 🔥 lookup(外部データ参照)

📝 まとめ

コマンド 役割 重要度
index= データ取得 ⭐⭐⭐
where 絞り込み ⭐⭐⭐
sort 並び替え ⭐⭐⭐
head 件数制限 ⭐⭐⭐
eval 加工 ⭐⭐⭐
stats 集計 ⭐⭐
table 表示 ⭐⭐

SPL は書いて覚える! まずは今回のクエリを理解して、1行ずつ変えて実験してみよう 🚀

Splunk Dashboard で「4つのボックスを線でつなぐ」フロー型UI実装ガイド

🎯 この記事で作るもの

道路ステータスを 横並び + 線でつなぐ フロー型ダッシュボード

🟢 首都高湾岸線 ━━ 🔴 国道16号 ━━ 🟢 東名高速 ━━ 🟢 中央高速
   (OK)              (NG)           (OK)           (OK)

📌 実装の全体像

3つの要素で構成

要素 役割 技術
① SPL検索 データ取得 + NG判定 Splunk Search Processing Language
② トークン 検索結果をHTML側に渡す Dashboard Token機能
③ HTML+CSS ボックスと線の描画 Flexbox レイアウト

🔍 実装解説:3ステップ


ステップ1️⃣:SPL検索で「最新1件のNG判定」

📝 コード全文

<search id="box1_search">
  <query>
    index=20260115tra
    | where like(location,"%湾岸%")
    | sort -_time
    | head 1
    | eval is_ng=if(result="NG",1,0)
  </query>
  <done>
    <condition match="$result.is_ng$=1">
      <set token="box1_color">red</set>
      <set token="box1_state">NG</set>
    </condition>
    <condition>
      <set token="box1_color">green</set>
      <set token="box1_state">OK</set>
    </condition>
  </done>
</search>

🔑 SPL 1行ずつ解説

index=20260115tra

意味: データソースを指定(CSVから取り込んだindex)
新人向け: Splunkにアップロードしたデータの「置き場所」を指定している


| where like(location,"%湾岸%")

意味: location列に「湾岸」を含むデータだけ抽出
新人向け: % は SQL の LIKE と同じワイルドカード(前後に何があってもOK)


| sort -_time

意味: 時刻の降順でソート(新しい順)
新人向け: - がつくと降順、つかないと昇順


| head 1

意味: 最新の1件だけ取得
新人向け: ここが最重要! 過去の全データではなく「最新の状態」だけを見る


| eval is_ng=if(result="NG",1,0)

意味: result列が"NG"なら1、それ以外なら0
新人向け: 条件分岐して数値化(後でトークン判定に使う)


🎯 なぜ head 1 が必須なのか?

パターン 結果 理由
head 1 なし 過去のNG全部が影響 最新がOKでも昔のNGで赤になる
head 1 あり 最新の状態だけ反映 現在の状態 を正確に表示

ステップ2️⃣:トークンで検索結果をHTML側に渡す

📝 トークン設定部分

<done>
  <condition match="$result.is_ng$=1">
    <set token="box1_color">red</set>
    <set token="box1_state">NG</set>
  </condition>
  <condition>
    <set token="box1_color">green</set>
    <set token="box1_state">OK</set>
  </condition>
</done>

🔑 仕組み解説

要素 意味
<done> 検索が完了したら実行
$result.is_ng$ SPLで作った is_ng の値を参照
match="...=1" 条件に一致するか判定
<set token="box1_color">red</set> トークン変数に値を格納

🎨 トークンの使い方

設定側(XML)

<set token="box1_color">red</set>

利用側(HTML)

<div class="box $box1_color$">

結果(レンダリング後)

<div class="box red">

ステップ3️⃣:HTML + CSS でボックスと線を描画

📝 HTML構造

<div class="flow">
  
  <div class="box $box1_color$">
    <div class="name">首都高 湾岸線</div>
    <div class="state">$box1_state$</div>
  </div>

  <div class="line"></div>

  <div class="box $box2_color$">
    <div class="name">国道16号</div>
    <div class="state">$box2_state$</div>
  </div>

  <div class="line"></div>

  <!-- 以下、BOX3, BOX4 も同様 -->

</div>

🎨 CSS 設計のポイント

① 横並びレイアウト(Flexbox)

.flow{
  display: flex;        /* 横並び有効化 */
  align-items: center;  /* 縦方向の中央揃え */
  padding: 30px;
}

新人向け: display: flex をつけると、子要素が自動で横並びになる


② ボックスのデザイン

.box{
  width: 200px;
  height: 110px;
  border-radius: 14px;        /* 角丸 */
  display: flex;
  flex-direction: column;     /* 中身を縦並び */
  align-items: center;        /* 横方向の中央揃え */
  justify-content: center;    /* 縦方向の中央揃え */
  font-weight: bold;
  color: white;
}

新人向け: ボックス自体も Flexbox にして、内部のテキストを中央配置


③ 色の切り替え(クラス)

.green { background: #4CAF50; }  /* 緑 */
.red   { background: #E53935; }  /* 赤 */

HTML側でクラス名が切り替わる仕組み:

<!-- トークンが "green" の場合 -->
<div class="box green">

<!-- トークンが "red" の場合 -->
<div class="box red">

④ 線のデザイン

.line{
  width: 60px;
  height: 6px;
  background: #999;
  margin: 0 10px;  /* 左右に余白 */
}

新人向け: 実体は「細長いグレーの四角」だが、ボックスと並べると線に見える


🏗️ 全体の組み立て順序

設計フロー

1. SPL検索で最新1件を取得
   ↓
2. is_ng=1 か 0 かを判定
   ↓
3. トークンに color と state を格納
   ↓
4. HTML側でトークンを展開
   ↓
5. CSS でスタイル適用
   ↓
6. ブラウザに表示

🎯 各ボックスに ID をつける理由

❌ 全部同じIDだとダメな理由

<!-- 全部 box_search だと -->
<search id="box_search">...</search>
<search id="box_search">...</search>  <!-- 上書きされる -->

結果: 最後の検索結果だけが全ボックスに反映される


✅ 正しい実装(個別ID)

<search id="box1_search">  <!-- 湾岸線専用 -->
<search id="box2_search">  <!-- 国道16号専用 -->
<search id="box3_search">  <!-- 東名専用 -->
<search id="box4_search">  <!-- 中央高速専用 -->

ポイント: 1ボックス = 1検索 = 1セットのトークン


📊 データフロー図

┌─────────────────┐
│ index=20260115tra│
│ location: 湾岸   │
│ _time: 18:40    │
│ result: NG      │
└────────┬────────┘
         │ where like(location,"%湾岸%")
         ↓
┌─────────────────┐
│ 湾岸線のデータ   │
│ (全期間)        │
└────────┬────────┘
         │ sort -_time | head 1
         ↓
┌─────────────────┐
│ 最新1件         │
│ result: NG      │
└────────┬────────┘
         │ eval is_ng=if(result="NG",1,0)
         ↓
┌─────────────────┐
│ is_ng: 1        │
└────────┬────────┘
         │ <condition match="$result.is_ng$=1">
         ↓
┌─────────────────┐
│ box1_color: red │
│ box1_state: NG  │
└────────┬────────┘
         │ $box1_color$ → HTML展開
         ↓
┌─────────────────┐
│ <div class="box red">
│   NG
│ </div>
└─────────────────┘

🔧 トラブルシューティング

Q1: ボックスが縦並びになる

原因: .flowdisplay: flex がない

解決:

.flow {
  display: flex;  /* これを追加 */
}

Q2: 全部緑のままになる

原因: トークンが設定されていない

デバッグ方法:

<!-- 検索結果を確認 -->
<search id="box1_search">
  <query>
    index=20260115tra
    | where like(location,"%湾岸%")
    | sort -_time
    | head 1
    | eval is_ng=if(result="NG",1,0)
    | table location, _time, result, is_ng  <!-- 追加 -->
  </query>
</search>

確認ポイント:

  • is_ng が 1 になっているか?
  • result が "NG"(大文字)になっているか?

Q3: 線が表示されない

原因: <div class="line"/> の書き方

修正:

<!-- ❌ NG(Classic Dashboardでは動かない場合がある) -->
<div class="line"/>

<!-- ✅ OK -->
<div class="line"></div>

🚀 発展課題

レベル1:WARN(黄色)を追加

| eval is_ng=case(
    result="NG", 2,
    result="WARN", 1,
    1=1, 0
  )
<condition match="$result.is_ng$=2">
  <set token="box1_color">red</set>
</condition>
<condition match="$result.is_ng$=1">
  <set token="box1_color">yellow</set>
</condition>
<condition>
  <set token="box1_color">green</set>
</condition>

レベル2:NG件数を表示

| stats 
    latest(_time) as latest_time,
    latest(result) as latest_result,
    count(eval(result="NG")) as ng_count
  by location
| eval is_ng=if(latest_result="NG",1,0)
<div class="state">$box1_state$ ($box1_ng_count$件)</div>

レベル3:検索を1本に統合(上級)

index=20260115tra
| eventstats latest(_time) as latest_time by location
| where _time=latest_time
| eval is_ng=if(result="NG",1,0)
| eval road=case(
    like(location,"%湾岸%"), "box1",
    like(location,"%国道16号%"), "box2",
    like(location,"%東名%"), "box3",
    like(location,"%中央高速%"), "box4"
  )
| stats values(is_ng) as is_ng by road

📝 まとめ

技術 役割 重要ポイント
SPL データ取得 sort -_time | head 1 で最新1件
トークン データ受け渡し 1ボックス=1セット
Flexbox レイアウト display: flex で横並び
CSS クラス 色切り替え .red .green

🎓 新人エンジニアへのアドバイス

  1. まず動かす:コピペして動作確認
  2. 1つずつ変える:色・文言・検索条件を変えて理解
  3. デバッグ癖をつける| table でデータ確認
  4. コメントを書く:自分の言葉で説明を残す


Splunk Dashboard で動的な色変更とアニメーション実装【完全解説】

🎯 この記事で作るもの

国道16号のステータスに応じて ボックスと線の色が自動で変わり、NG時は赤く点滅 するダッシュボード

🟢 首都高湾岸線 ━━ 🔴 国道16号 ━━ 🟢 東名高速 ━━ 🟢 中央高速
   (OK)      赤点滅  (NG)       赤点滅  (OK)           (OK)

📌 実装の3つの特徴

特徴 説明
① JSなし JavaScriptを使わずトークンのみで実装
② 動的色変更 SPL検索結果でリアルタイムに色が変わる
③ CSSアニメ NG時に赤く脈打つ視覚効果

🏗️ アーキテクチャ全体像

┌─────────────────────────────────────┐
│  SPL検索(NG判定)                   │
│  index=20260115tra                  │
│  → 国道16号のNG件数をカウント         │
└──────────┬──────────────────────────┘
           │
           ↓ 検索完了時
┌─────────────────────────────────────┐
│  <done> トークン設定                 │
│  ng > 0 → red / それ以外 → green    │
└──────────┬──────────────────────────┘
           │
           ↓ トークン展開
┌─────────────────────────────────────┐
│  HTML + CSS                         │
│  class="box $box16_color$"          │
│  → class="box red" に変換される      │
└─────────────────────────────────────┘

📝 実装解説:3つのパート


Part 1️⃣:SPL検索でNG判定

完全版コード

<search id="ng_search">
  <query>
    index=20260115tra
    | eval route=if(like(location,"%国道16号%"),"国道16号",null())
    | where isnotnull(route)
    | stats sum(eval(result="NG")) as ng
  </query>
  <earliest>0</earliest>
  <latest>now</latest>
  <done>
    <condition match="$result.ng$ &gt; 0">
      <set token="box16_color">red</set>
      <set token="box16_state">NG</set>
      <set token="line_color">red</set>
    </condition>
    <condition>
      <set token="box16_color">green</set>
      <set token="box16_state">OK</set>
      <set token="line_color">gray</set>
    </condition>
  </done>
</search>

🔍 SPL 1行ずつ解説

① データ取得

index=20260115tra

意味: CSVから取り込んだデータソースを指定


② 道路フィールド作成

| eval route=if(like(location,"%国道16号%"),"国道16号",null())

意味: location列に「国道16号」が含まれる場合のみ route フィールドを作成

詳細:

  • like(location,"%国道16号%") → 部分一致検索
  • "国道16号" → マッチした場合の値
  • null() → マッチしなかった場合は null

データ変化:

location route
国道16号 相模原市 国道16号
首都高 湾岸線 null
東名高速 null

③ 国道16号のみ抽出

| where isnotnull(route)

意味: route が null でないデータだけ残す

結果: 国道16号のデータだけになる


④ NG件数をカウント

| stats sum(eval(result="NG")) as ng

意味: result列が"NG"の件数を合計

仕組み:

  • eval(result="NG") → NGなら1、それ以外は0
  • sum(...) → 1を全部足す
  • as ng → 結果を ng という名前にする

例:

result eval(result="NG")
NG 1
OK 0
NG 1
合計 2

🎯 なぜ sum(eval(...)) を使うのか?

比較:他の方法との違い

方法 結果 問題点
count 全件数 NGもOKもカウントされる
count(eval(...)) エラーになる Splunkの仕様
sum(eval(...)) NG件数のみ ✅ 正しい方法

🔑 トークン設定の仕組み

<done>
  <condition match="$result.ng$ &gt; 0">
    <set token="box16_color">red</set>
    <set token="box16_state">NG</set>
    <set token="line_color">red</set>
  </condition>
  <condition>
    <set token="box16_color">green</set>
    <set token="box16_state">OK</set>
    <set token="line_color">gray</set>
  </condition>
</done>

構文解説

要素 意味
<done> 検索完了時に実行
$result.ng$ SPLで作った ng の値を参照
&gt; XML内で > を表す(Greater Than)
<set token="..."> トークン変数に値を格納

動作フロー

検索結果: ng = 2
↓
$result.ng$ = 2
↓
2 > 0 ? → YES
↓
box16_color = "red"
box16_state = "NG"
line_color = "red"

Part 2️⃣:HTML でトークンを展開

📝 HTML構造

<div class="flow">
  
  <!-- 固定の緑ボックス -->
  <div class="box green">
    <div>首都高 湾岸線</div>
    <div>OK</div>
  </div>

  <!-- 動的な線(色が変わる) -->
  <div class="line $line_color$"></div>

  <!-- 動的なボックス(色と文字が変わる) -->
  <div class="box $box16_color$">
    <div>国道16号</div>
    <div>$box16_state$</div>
  </div>

  <div class="line $line_color$"></div>

  <!-- 固定の緑ボックス -->
  <div class="box green">
    <div>東名高速</div>
    <div>OK</div>
  </div>

  <div class="line gray"></div>

  <div class="box green">
    <div>中央高速</div>
    <div>OK</div>
  </div>

</div>

🔄 トークン展開の仕組み

レンダリング前(ソースコード)

<div class="box $box16_color$">
  <div>国道16号</div>
  <div>$box16_state$</div>
</div>

レンダリング後(NG時)

<div class="box red">
  <div>国道16号</div>
  <div>NG</div>
</div>

レンダリング後(OK時)

<div class="box green">
  <div>国道16号</div>
  <div>OK</div>
</div>

💡 固定と動的の使い分け

ボックス 実装方法 理由
首都高湾岸線 class="box green" 固定 今回は判定対象外
国道16号 class="box $box16_color$" 動的 検索結果で色が変わる
東名高速 class="box green" 固定 今回は判定対象外
中央高速 class="box green" 固定 今回は判定対象外

Part 3️⃣:CSS でスタイルとアニメーション

🎨 CSS全体構造

/* レイアウト */
.flow { ... }           /* 横並び配置 */
.box { ... }            /* ボックス基本形 */

/* 色定義 */
.green { ... }          /* 緑ボックス */
.red { ... }            /* 赤ボックス + アニメ */

/* 線 */
.line { ... }           /* 線の基本形 */
.line.gray { ... }      /* グレーの線 */
.line.red { ... }       /* 赤い線 + アニメ */

/* アニメーション */
@keyframes pulse { ... }  /* ボックスの脈打ち */
@keyframes blink { ... }  /* 線の点滅 */

🔧 レイアウト設計

① 横並び配置(Flexbox)

.flow{
  display: flex;          /* Flexbox有効化 */
  align-items: center;    /* 縦方向の中央揃え */
  padding: 30px;
}

視覚イメージ:

┌─────────────────────────────────────────┐
│  padding: 30px                          │
│  ┌────┐ ━━ ┌────┐ ━━ ┌────┐ ━━ ┌────┐ │
│  │BOX1│    │BOX2│    │BOX3│    │BOX4│ │
│  └────┘    └────┘    └────┘    └────┘ │
│  ↑ align-items: center(縦中央揃え)    │
└─────────────────────────────────────────┘

② ボックスデザイン

.box{
  width: 200px;
  height: 110px;
  border-radius: 14px;        /* 角丸 */
  display: flex;
  flex-direction: column;     /* 内部を縦並び */
  align-items: center;        /* 横方向の中央揃え */
  justify-content: center;    /* 縦方向の中央揃え */
  font-weight: bold;
  color: white;
}

視覚イメージ:

┌──────────────────────┐
│                      │
│    ┌─────────┐       │
│    │ 国道16号 │       │ ← align-items: center
│    └─────────┘       │
│    ┌─────┐           │
│    │  NG │           │ ← justify-content: center
│    └─────┘           │
│                      │
└──────────────────────┘
   ↑ 200px ↑

🎭 色とアニメーションの実装

① 緑ボックス(通常状態)

.green {
  background: #4CAF50;  /* Material Design の緑 */
}

② 赤ボックス(NG状態 + 脈打ちアニメ)

.red {
  background: #E53935;          /* Material Design の赤 */
  animation: pulse 1.5s infinite; /* アニメーション適用 */
}

パラメータ解説:

意味
pulse アニメーション名(後で定義)
1.5s 1周期の長さ(1.5秒)
infinite 無限ループ

③ 脈打ちアニメーション定義

@keyframes pulse {
  0% {
    box-shadow: 0 0 0 0 rgba(229,57,53,0.7);
  }
  70% {
    box-shadow: 0 0 0 15px rgba(229,57,53,0);
  }
  100% {
    box-shadow: 0 0 0 0 rgba(229,57,53,0);
  }
}

動作解説:

タイミング box-shadow 視覚効果
0% 広がり 0px / 不透明度 70% ボックスに密着
70% 広がり 15px / 不透明度 0% 外側に広がって消える
100% 広がり 0px / 不透明度 0% 初期状態に戻る

視覚イメージ:

時間経過 →

0%      30%     60%     100%
┌───┐   ┌───┐   ┌───┐   ┌───┐
│BOX│ → │BOX│ → │BOX│ → │BOX│
└───┘   └───┘   └───┘   └───┘
  ■      ▓       ░        (透明)
  ↑ 影が広がって薄くなる

④ 線のスタイル

.line {
  width: 60px;
  height: 6px;
  margin: 0 10px;  /* 左右に余白 */
  background: #999;
}

.line.gray {
  background: #999;  /* グレー(固定) */
}

.line.red {
  background: #E53935;             /* 赤 */
  animation: blink 1s infinite;    /* 点滅アニメ */
}

⑤ 点滅アニメーション定義

@keyframes blink {
  0%, 100% {
    opacity: 1;    /* 完全に見える */
  }
  50% {
    opacity: 0.3;  /* 薄くなる */
  }
}

動作:

時間経過 →

0%    0.5s   1s    1.5s   2s
━━━━  ····  ━━━━  ····  ━━━━
100%   30%  100%   30%  100%
↑ opacity(不透明度)

🎯 クラスの動的切り替えの仕組み

トークンによるクラス名変更

① ボックスのクラス切り替え

ソースコード:

<div class="box $box16_color$">

NG時(box16_color = "red"):

<div class="box red">  <!-- .red のスタイルが適用される -->

OK時(box16_color = "green"):

<div class="box green">  <!-- .green のスタイルが適用される -->

② 線のクラス切り替え

ソースコード:

<div class="line $line_color$"></div>

NG時(line_color = "red"):

<div class="line red">  <!-- 点滅する赤い線 -->

OK時(line_color = "gray"):

<div class="line gray">  <!-- 固定のグレー線 -->

📊 クラスとスタイルの対応表

トークン値 HTML結果 適用CSS 視覚効果
box16_color="green" class="box green" .green{background:#4CAF50;} 緑色・静止
box16_color="red" class="box red" .red{background:#E53935; animation:pulse...} 赤色・脈打ち
line_color="gray" class="line gray" .line.gray{background:#999;} グレー・静止
line_color="red" class="line red" .line.red{background:#E53935; animation:blink...} 赤色・点滅

🔍 デバッグ方法

問題1:色が変わらない

デバッグ手順

Step 1: 検索結果を確認

<search id="ng_search">
  <query>
    index=20260115tra
    | eval route=if(like(location,"%国道16号%"),"国道16号",null())
    | where isnotnull(route)
    | stats sum(eval(result="NG")) as ng
    | table ng  <!-- ← 追加 -->
  </query>
</search>

確認ポイント:

  • ng の値は何か?(0? 1? 2?)

Step 2: トークンを確認

Dashboard の設定画面で「Tokens」タブを見る

box16_color = "red"   ← 設定されているか?
box16_state = "NG"
line_color = "red"

Step 3: HTML を確認

ブラウザの開発者ツール(F12)で要素を検証

<!-- 期待値 -->
<div class="box red">

<!-- 実際の値がこうなっていたらトークン未設定 -->
<div class="box $box16_color$">

問題2:アニメーションが動かない

チェックリスト

 @keyframes が定義されているか
 animation プロパティが正しいか
 クラス名が正しく適用されているか

修正例

/* ❌ 間違い */
.red {
  animation: pulse 1.5s;  /* infinite がない */
}

/* ✅ 正しい */
.red {
  animation: pulse 1.5s infinite;
}

🚀 発展課題

レベル1:4路線すべてを動的にする

<!-- 各道路ごとに search を作成 -->
<search id="wangan_search">...</search>
<search id="route16_search">...</search>
<search id="tomei_search">...</search>
<search id="chuo_search">...</search>

レベル2:WARN(黄色)を追加

SPL側

| eval status=case(
    result="NG", 2,
    result="WARN", 1,
    1=1, 0
  )
| stats max(status) as level

トークン側

<condition match="$result.level$=2">
  <set token="box_color">red</set>
</condition>
<condition match="$result.level$=1">
  <set token="box_color">yellow</set>
</condition>
<condition>
  <set token="box_color">green</set>
</condition>

CSS側

.yellow {
  background: #FFA726;
  animation: pulse-yellow 1.5s infinite;
}

@keyframes pulse-yellow {
  0% { box-shadow: 0 0 0 0 rgba(255,167,38,0.7); }
  70% { box-shadow: 0 0 0 15px rgba(255,167,38,0); }
  100% { box-shadow: 0 0 0 0 rgba(255,167,38,0); }
}

レベル3:NG件数を表示

| stats 
    sum(eval(result="NG")) as ng_count,
    count as total
| eval ng_rate=round(ng_count/total*100, 1)
<div class="state">
  $box16_state$ ($ng_count$件 / $ng_rate$%)
</div>

📝 まとめ

実装の3本柱

技術 役割 ポイント
SPL データ判定 sum(eval(...)) でNG件数カウント
トークン 動的制御 <done> で検索後に色を切り替え
CSS 視覚表現 animation で脈打ち・点滅

重要なポイント

  1. JSなしで実装可能 → Splunk Classic Dashboard の標準機能のみ
  2. トークンが核心 → 検索結果を HTML に橋渡し
  3. CSS アニメで視認性向上 → 異常を一目で認識可能

完成形コードは記事冒頭を参照! コピペで即動作します 🚀

  • 再掲

Splunk Classic Dashboard:絶対位置レイアウト完全パターン集【新人エンジニア向け】

📚 目次

  1. 基礎:最初の四角を作る
  2. パターン1:ネットワーク構成図
  3. パターン2:山手線風(環状配置)

🎯 基礎:最初の四角を作る {#基礎最初の四角を作る}

Step 1:左上に1つの四角

<dashboard version="1.1">
  <label>【基礎】左上に四角を1つ作る</label>
  
  <row>
    <panel>
      <html>
        
        <!-- ========================================== -->
        <!-- CSS(スタイル定義) -->
        <!-- ========================================== -->
        <style>
          /* 親コンテナ(基準となる箱) */
          .container {
            /* 相対位置(これで基準点になる) */
            position: relative;
            
            /* コンテナのサイズ */
            width: 800px;   /* 幅800ピクセル */
            height: 600px;  /* 高さ600ピクセル */
            
            /* 背景色(薄いグレー) */
            background: #f5f5f5;
            
            /* 余白 */
            margin: 30px;
            
            /* 枠線(確認用) */
            border: 2px solid #ddd;
          }
          
          /* 四角ボックス */
          .box {
            /* 絶対位置(自由に配置できる) */
            position: absolute;
            
            /* サイズ */
            width: 150px;   /* 幅150ピクセル */
            height: 100px;  /* 高さ100ピクセル */
            
            /* 背景色(青) */
            background: #42A5F5;
            
            /* 角を丸くする */
            border-radius: 12px;
            
            /* 影をつける */
            box-shadow: 0 4px 10px rgba(0,0,0,0.2);
            
            /* 文字色(白) */
            color: white;
            
            /* 文字を太字に */
            font-weight: bold;
            
            /* 文字を中央揃え */
            display: flex;              /* Flexboxを使う */
            justify-content: center;    /* 横方向の中央 */
            align-items: center;        /* 縦方向の中央 */
          }
          
          /* 左上の位置 */
          .box1 {
            /* 上から20ピクセル */
            top: 20px;
            
            /* 左から20ピクセル */
            left: 20px;
          }
        </style>
        
        <!-- ========================================== -->
        <!-- HTML(表示する内容) -->
        <!-- ========================================== -->
        <div class="container">
          <!-- 四角ボックス -->
          <div class="box box1">
            左上の四角
          </div>
        </div>
        
      </html>
    </panel>
  </row>
</dashboard>

コードの説明

container (親)
┌─────────────────────────────────┐
│ (0, 0) ← 左上が原点              │
│                                 │
│   top: 20px                     │
│   ↓                             │
│   ┌──────────┐                  │
│ → │  BOX1    │                  │
│   └──────────┘                  │
│   ↑                             │
│   left: 20px                    │
│                                 │
└─────────────────────────────────┘

Step 2:四角を増やす(4隅に配置)

<dashboard version="1.1">
  <label>【基礎】四隅に四角を配置</label>
  
  <row>
    <panel>
      <html>
        
        <style>
          /* 親コンテナ */
          .container {
            position: relative;
            width: 800px;
            height: 600px;
            background: #f5f5f5;
            margin: 30px;
            border: 2px solid #ddd;
          }
          
          /* 四角ボックスの共通スタイル */
          .box {
            position: absolute;      /* 絶対位置 */
            width: 120px;            /* 幅 */
            height: 80px;            /* 高さ */
            border-radius: 10px;     /* 角丸 */
            box-shadow: 0 4px 10px rgba(0,0,0,0.2);
            color: white;
            font-weight: bold;
            display: flex;
            justify-content: center;
            align-items: center;
          }
          
          /* ========================================== */
          /* 各四角の位置と色 */
          /* ========================================== */
          
          /* 左上 */
          .box1 {
            top: 20px;       /* 上から20px */
            left: 20px;      /* 左から20px */
            background: #42A5F5;  /* 青 */
          }
          
          /* 右上 */
          .box2 {
            top: 20px;       /* 上から20px */
            right: 20px;     /* 右から20px(rightを使う!) */
            background: #4CAF50;  /* 緑 */
          }
          
          /* 左下 */
          .box3 {
            bottom: 20px;    /* 下から20px(bottomを使う!) */
            left: 20px;      /* 左から20px */
            background: #FFA726;  /* 黄色 */
          }
          
          /* 右下 */
          .box4 {
            bottom: 20px;    /* 下から20px */
            right: 20px;     /* 右から20px */
            background: #E53935;  /* 赤 */
          }
        </style>
        
        <div class="container">
          <!-- 左上 -->
          <div class="box box1">左上</div>
          
          <!-- 右上 -->
          <div class="box box2">右上</div>
          
          <!-- 左下 -->
          <div class="box box3">左下</div>
          
          <!-- 右下 -->
          <div class="box box4">右下</div>
        </div>
        
      </html>
    </panel>
  </row>
</dashboard>

🌐 パターン1:ネットワーク構成図 {#パターン1ネットワーク構成図}

完全版:サーバー・スイッチ・クライアント構成

<dashboard version="1.1">
  <label>【パターン1】ネットワーク構成図</label>
  
  <row>
    <panel>
      <html>
        
        <style>
          /* ========================================== */
          /* コンテナ */
          /* ========================================== */
          .container {
            position: relative;
            width: 1000px;
            height: 700px;
            background: #fafafa;
            margin: 30px auto;
            border: 2px solid #ddd;
          }
          
          /* ========================================== */
          /* サーバーボックス(大きめ・四角) */
          /* ========================================== */
          .server {
            position: absolute;
            width: 180px;
            height: 140px;
            background: #1976D2;  /* 濃い青 */
            border-radius: 8px;
            box-shadow: 0 6px 15px rgba(0,0,0,0.3);
            color: white;
            font-weight: bold;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
          }
          
          /* ========================================== */
          /* スイッチボックス(中くらい・横長) */
          /* ========================================== */
          .switch {
            position: absolute;
            width: 200px;
            height: 60px;
            background: #388E3C;  /* 緑 */
            border-radius: 6px;
            box-shadow: 0 4px 10px rgba(0,0,0,0.2);
            color: white;
            font-weight: bold;
            display: flex;
            justify-content: center;
            align-items: center;
          }
          
          /* ========================================== */
          /* クライアントボックス(小さめ・円形っぽく) */
          /* ========================================== */
          .client {
            position: absolute;
            width: 100px;
            height: 100px;
            background: #757575;  /* グレー */
            border-radius: 50%;   /* 円形 */
            box-shadow: 0 4px 10px rgba(0,0,0,0.2);
            color: white;
            font-weight: bold;
            font-size: 12px;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
          }
          
          /* ========================================== */
          /* 接続線(縦・横) */
          /* ========================================== */
          .line {
            position: absolute;
            background: #999;
          }
          
          /* 横線 */
          .line-h {
            height: 3px;
          }
          
          /* 縦線 */
          .line-v {
            width: 3px;
          }
          
          /* ========================================== */
          /* 各要素の配置 */
          /* ========================================== */
          
          /* サーバー1(左上) */
          .server1 {
            top: 50px;
            left: 80px;
          }
          
          /* サーバー2(右上) */
          .server2 {
            top: 50px;
            right: 80px;
          }
          
          /* スイッチ(中央) */
          .switch1 {
            top: 280px;
            left: 400px;  /* 中央に配置 */
          }
          
          /* クライアント1(左下) */
          .client1 {
            bottom: 80px;
            left: 100px;
          }
          
          /* クライアント2(中央下) */
          .client2 {
            bottom: 80px;
            left: 350px;
          }
          
          /* クライアント3(右下) */
          .client3 {
            bottom: 80px;
            right: 100px;
          }
          
          /* ========================================== */
          /* 接続線の配置 */
          /* ========================================== */
          
          /* サーバー1 → スイッチ(縦線) */
          .line1 {
            top: 200px;     /* サーバー1の下端 */
            left: 168px;    /* サーバー1の中央 */
            height: 80px;   /* スイッチまでの距離 */
          }
          
          /* サーバー2 → スイッチ(縦線) */
          .line2 {
            top: 200px;
            right: 168px;   /* サーバー2の中央 */
            height: 80px;
          }
          
          /* スイッチ → サーバー1(横線) */
          .line3 {
            top: 308px;     /* スイッチの中央 */
            left: 268px;    /* サーバー1の右端 */
            width: 132px;   /* スイッチまで */
          }
          
          /* スイッチ → サーバー2(横線) */
          .line4 {
            top: 308px;
            left: 600px;    /* スイッチの右端 */
            width: 152px;   /* サーバー2まで */
          }
          
          /* スイッチ → クライアント(縦線) */
          .line5 {
            top: 350px;     /* スイッチの下端 */
            left: 498px;    /* スイッチの中央 */
            height: 100px;  /* クライアントまで */
          }
          
          /* スイッチ → クライアント1(横線) */
          .line6 {
            top: 450px;
            left: 150px;
            width: 348px;
          }
          
          /* スイッチ → クライアント2(横線) */
          .line7 {
            top: 450px;
            left: 400px;
            width: 98px;
          }
          
          /* スイッチ → クライアント3(横線) */
          .line8 {
            top: 450px;
            left: 500px;
            width: 300px;
          }
          
          /* テキストスタイル */
          .label {
            font-size: 14px;
            margin-top: 8px;
          }
          
          .sublabel {
            font-size: 11px;
            margin-top: 4px;
            opacity: 0.9;
          }
        </style>
        
        <!-- ========================================== -->
        <!-- HTML構造 -->
        <!-- ========================================== -->
        <div class="container">
          
          <!-- サーバー1 -->
          <div class="server server1">
            <div>サーバー1</div>
            <div class="sublabel">Web Server</div>
          </div>
          
          <!-- サーバー2 -->
          <div class="server server2">
            <div>サーバー2</div>
            <div class="sublabel">DB Server</div>
          </div>
          
          <!-- スイッチ -->
          <div class="switch switch1">
            Core Switch
          </div>
          
          <!-- クライアント1 -->
          <div class="client client1">
            <div>PC-1</div>
            <div class="sublabel">192.168.1.10</div>
          </div>
          
          <!-- クライアント2 -->
          <div class="client client2">
            <div>PC-2</div>
            <div class="sublabel">192.168.1.11</div>
          </div>
          
          <!-- クライアント3 -->
          <div class="client client3">
            <div>PC-3</div>
            <div class="sublabel">192.168.1.12</div>
          </div>
          
          <!-- 接続線 -->
          <div class="line line-v line1"></div>
          <div class="line line-v line2"></div>
          <div class="line line-h line3"></div>
          <div class="line line-h line4"></div>
          <div class="line line-v line5"></div>
          <div class="line line-h line6"></div>
          <div class="line line-h line7"></div>
          <div class="line line-h line8"></div>
          
        </div>
        
      </html>
    </panel>
  </row>
</dashboard>

🚆 パターン2:山手線風(環状配置) {#パターン2山手線風環状配置}

完全版:駅を円形に配置

<dashboard version="1.1">
  <label>【パターン2】山手線風・環状配置</label>
  
  <row>
    <panel>
      <html>
        
        <style>
          /* ========================================== */
          /* コンテナ */
          /* ========================================== */
          .container {
            position: relative;
            width: 800px;
            height: 800px;
            background: #fafafa;
            margin: 30px auto;
            border: 2px solid #ddd;
          }
          
          /* ========================================== */
          /* 路線(円形の線) */
          /* ========================================== */
          .rail-circle {
            position: absolute;
            /* 中央に配置 */
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            
            /* 円のサイズ */
            width: 600px;
            height: 600px;
            
            /* 円形の枠線(線路に見立てる) */
            border: 8px solid #4CAF50;  /* 緑色(山手線) */
            border-radius: 50%;         /* 円形 */
            
            /* 影 */
            box-shadow: 0 0 20px rgba(76, 175, 80, 0.3);
          }
          
          /* ========================================== */
          /* 駅ボックス */
          /* ========================================== */
          .station {
            position: absolute;
            width: 80px;
            height: 80px;
            background: white;
            border: 4px solid #4CAF50;
            border-radius: 50%;  /* 円形 */
            box-shadow: 0 4px 10px rgba(0,0,0,0.2);
            
            /* 文字スタイル */
            color: #333;
            font-weight: bold;
            font-size: 11px;
            
            /* 中央揃え */
            display: flex;
            justify-content: center;
            align-items: center;
            text-align: center;
          }
          
          /* ========================================== */
          /* 各駅の配置(12時方向から時計回り) */
          /* ========================================== */
          
          /* 東京駅(12時) */
          .station1 {
            top: 50px;
            left: 50%;
            transform: translateX(-50%);
          }
          
          /* 上野駅(2時) */
          .station2 {
            top: 130px;
            right: 130px;
          }
          
          /* 秋葉原駅(3時) */
          .station3 {
            top: 50%;
            right: 50px;
            transform: translateY(-50%);
          }
          
          /* 品川駅(5時) */
          .station4 {
            bottom: 130px;
            right: 130px;
          }
          
          /* 渋谷駅(6時) */
          .station5 {
            bottom: 50px;
            left: 50%;
            transform: translateX(-50%);
          }
          
          /* 新宿駅(7時) */
          .station6 {
            bottom: 130px;
            left: 130px;
          }
          
          /* 池袋駅(9時) */
          .station7 {
            top: 50%;
            left: 50px;
            transform: translateY(-50%);
          }
          
          /* 上野駅(11時) */
          .station8 {
            top: 130px;
            left: 130px;
          }
          
          /* ========================================== */
          /* 中央のラベル */
          /* ========================================== */
          .center-label {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            
            font-size: 24px;
            font-weight: bold;
            color: #4CAF50;
            text-align: center;
          }
          
          /* ========================================== */
          /* ステータス色(Splunkトークンで動的に変更可能) */
          /* ========================================== */
          .status-ok {
            background: #E8F5E9;  /* 薄い緑 */
            border-color: #4CAF50;
          }
          
          .status-ng {
            background: #FFEBEE;  /* 薄い赤 */
            border-color: #E53935;
          }
        </style>
        
        <!-- ========================================== -->
        <!-- HTML構造 -->
        <!-- ========================================== -->
        <div class="container">
          
          <!-- 路線(円形) -->
          <div class="rail-circle"></div>
          
          <!-- 中央ラベル -->
          <div class="center-label">
            山手線<br/>
            <span style="font-size:16px;">JY Line</span>
          </div>
          
          <!-- 各駅 -->
          <div class="station station1 status-ok">
            東京
          </div>
          
          <div class="station station2 status-ok">
            上野
          </div>
          
          <div class="station station3 status-ng">
            秋葉原
          </div>
          
          <div class="station station4 status-ok">
            品川
          </div>
          
          <div class="station station5 status-ok">
            渋谷
          </div>
          
          <div class="station station6 status-ok">
            新宿
          </div>
          
          <div class="station station7 status-ok">
            池袋
          </div>
          
          <div class="station station8 status-ok">
            日暮里
          </div>
          
        </div>
        
      </html>
    </panel>
  </row>
</dashboard>

Splunk Classic Dashboard:HTML四角ボックス作成 完全ガイド

📚 目次

  1. 基本の1つの四角
  2. 四角に色をつける
  3. 四角の中に文字を入れる
  4. Splunkトークンで動的に色を変える
  5. 完全版:コピペ用テンプレート
  6. 横に四角を4つ並べる

🎯 基本の1つの四角 {#基本の1つの四角}

Step 1:最小構成のダッシュボード

<dashboard version="1.1">
  <!-- ダッシュボードのタイトル -->
  <label>四角ボックス:基本編</label>
  
  <!-- 行(row)を作成 -->
  <row>
    <!-- パネル(panel)を作成 -->
    <panel>
      <!-- HTMLを書く場所 -->
      <html>
        
        <!-- ここにHTMLを書く -->
        <div>これは四角です</div>
        
      </html>
    </panel>
  </row>
</dashboard>

解説(1行ずつ)

コード 意味
1 <dashboard version="1.1"> Splunk Classic Dashboard の宣言
2 <label>四角ボックス:基本編</label> 画面上部に表示されるタイトル
4 <row> 横一列のエリアを作る(複数のパネルを横に並べられる)
5 <panel> 個別の表示エリア(グラフや表を入れる箱)
6 <html> HTMLを書くための開始タグ
8 <div>これは四角です</div> HTML要素(divタグ)で文字を表示
10 </html> HTMLの終了タグ
11 </panel> パネルの終了タグ
12 </row> 行の終了タグ
13 </dashboard> ダッシュボードの終了タグ

Step 2:四角に見た目をつける(CSS)

<dashboard version="1.1">
  <label>四角ボックス:スタイル付き</label>
  
  <row>
    <panel>
      <html>
        
        <!-- スタイル(CSS)を定義 -->
        <style>
          /* .box というクラス名のスタイル */
          .box {
            /* 幅を200ピクセルに */
            width: 200px;
            
            /* 高さを100ピクセルに */
            height: 100px;
            
            /* 背景色を青に */
            background: #42A5F5;
            
            /* 角を丸くする(14ピクセル) */
            border-radius: 14px;
            
            /* 余白を30ピクセル */
            padding: 30px;
          }
        </style>
        
        <!-- 四角を表示(class="box" でスタイルを適用) -->
        <div class="box">
          これは四角です
        </div>
        
      </html>
    </panel>
  </row>
</dashboard>

解説(CSS部分)

コード 意味
9 <style> CSSを書くための開始タグ
10 /* コメント */ CSSのコメント(説明文)
11 .box { 「box」というクラス名のスタイル定義開始
13 width: 200px; 横幅を200ピクセルに設定
16 height: 100px; 縦幅を100ピクセルに設定
19 background: #42A5F5; 背景色を青(#42A5F5)に設定
22 border-radius: 14px; 四隅を丸くする(14ピクセルの半径)
25 padding: 30px; 内側の余白を30ピクセル
26 } スタイル定義の終了
27 </style> CSSの終了タグ
30 <div class="box"> divタグに「box」クラスを適用

🎨 四角に色をつける {#四角に色をつける}

Step 3:色を変えられるようにする

<dashboard version="1.1">
  <label>四角ボックス:色違い3つ</label>
  
  <row>
    <panel>
      <html>
        
        <style>
          /* 四角の基本スタイル */
          .box {
            /* 幅 */
            width: 200px;
            /* 高さ */
            height: 100px;
            /* 角丸 */
            border-radius: 14px;
            /* 余白 */
            padding: 20px;
            /* 文字色を白に */
            color: white;
            /* 文字を太字に */
            font-weight: bold;
            /* 文字サイズ */
            font-size: 18px;
          }
          
          /* 緑色のクラス */
          .green {
            /* 背景を緑に */
            background: #4CAF50;
          }
          
          /* 赤色のクラス */
          .red {
            /* 背景を赤に */
            background: #E53935;
          }
          
          /* 黄色のクラス */
          .yellow {
            /* 背景を黄色に */
            background: #FFA726;
          }
        </style>
        
        <!-- 緑の四角 -->
        <div class="box green">
          緑の四角
        </div>
        
        <!-- 赤の四角 -->
        <div class="box red">
          赤の四角
        </div>
        
        <!-- 黄色の四角 -->
        <div class="box yellow">
          黄色の四角
        </div>
        
      </html>
    </panel>
  </row>
</dashboard>

解説(色の仕組み)

<!-- 2つのクラスを同時に指定 -->
<div class="box green">

意味:

  • box クラス → 幅・高さ・角丸などの基本スタイル
  • green クラス → 背景色だけ上書き

結果: 緑色の四角ができる


📝 四角の中に文字を入れる {#四角の中に文字を入れる}

Step 4:文字を中央揃えにする

<dashboard version="1.1">
  <label>四角ボックス:文字中央揃え</label>
  
  <row>
    <panel>
      <html>
        
        <style>
          .box {
            width: 200px;
            height: 100px;
            background: #42A5F5;
            border-radius: 14px;
            color: white;
            font-weight: bold;
            
            /* Flexboxで中央揃え */
            /* Flexboxを有効化 */
            display: flex;
            
            /* 横方向の中央揃え */
            justify-content: center;
            
            /* 縦方向の中央揃え */
            align-items: center;
          }
        </style>
        
        <!-- 文字が中央に配置される -->
        <div class="box">
          中央揃え
        </div>
        
      </html>
    </panel>
  </row>
</dashboard>

Flexbox 解説

プロパティ 意味
display: flex Flexboxレイアウトを有効化 -
justify-content 横方向の配置 center(中央)、flex-start(左)、flex-end(右)
align-items 縦方向の配置 center(中央)、flex-start(上)、flex-end(下)

Step 5:複数行の文字を入れる

<dashboard version="1.1">
  <label>四角ボックス:複数行テキスト</label>
  
  <row>
    <panel>
      <html>
        
        <style>
          .box {
            width: 200px;
            height: 120px;
            background: #4CAF50;
            border-radius: 14px;
            color: white;
            
            /* Flexboxで縦並び */
            display: flex;
            /* 子要素を縦に並べる */
            flex-direction: column;
            /* 横方向の中央揃え */
            justify-content: center;
            /* 縦方向の中央揃え */
            align-items: center;
          }
          
          /* タイトル部分のスタイル */
          .title {
            /* フォントサイズ */
            font-size: 16px;
            /* 下に余白 */
            margin-bottom: 8px;
          }
          
          /* ステータス部分のスタイル */
          .status {
            /* フォントサイズを大きく */
            font-size: 24px;
            /* 太字 */
            font-weight: bold;
          }
        </style>
        
        <div class="box">
          <!-- タイトル -->
          <div class="title">国道16号</div>
          <!-- ステータス -->
          <div class="status">OK</div>
        </div>
        
      </html>
    </panel>
  </row>
</dashboard>

HTML構造の解説

<div class="box">              ← 外側の四角
  <div class="title">国道16号</div>   ← 小さい文字(タイトル)
  <div class="status">OK</div>        ← 大きい文字(ステータス)
</div>

ポイント:

  • 外側の boxflex-direction: column を指定
  • 内側の titlestatus が縦に並ぶ

🔄 Splunkトークンで動的に色を変える {#splunkトークンで動的に色を変える}

Step 6:トークンで色を切り替え

<dashboard version="1.1">
  <label>四角ボックス:動的に色変更</label>
  
  <!-- ダミー検索(実際のデータの代わり) -->
  <search id="dummy_search">
    <query>
      <!-- ダミーデータを作成 -->
      | makeresults
      <!-- NG判定(1=NG, 0=OK) -->
      | eval is_ng=1
    </query>
    
    <!-- 検索完了時にトークンを設定 -->
    <done>
      <!-- is_ng が 1 の場合 -->
      <condition match="$result.is_ng$=1">
        <!-- box_color トークンに "red" を設定 -->
        <set token="box_color">red</set>
        <!-- box_state トークンに "NG" を設定 -->
        <set token="box_state">NG</set>
      </condition>
      
      <!-- それ以外の場合 -->
      <condition>
        <!-- box_color トークンに "green" を設定 -->
        <set token="box_color">green</set>
        <!-- box_state トークンに "OK" を設定 -->
        <set token="box_state">OK</set>
      </condition>
    </done>
  </search>
  
  <row>
    <panel>
      <html>
        
        <style>
          .box {
            width: 200px;
            height: 120px;
            border-radius: 14px;
            color: white;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
          }
          
          /* 緑色 */
          .green {
            background: #4CAF50;
          }
          
          /* 赤色 */
          .red {
            background: #E53935;
          }
          
          .title {
            font-size: 16px;
            margin-bottom: 8px;
          }
          
          .status {
            font-size: 24px;
            font-weight: bold;
          }
        </style>
        
        <!-- $box_color$ がトークンの値に置き換わる -->
        <div class="box $box_color$">
          <div class="title">国道16号</div>
          <!-- $box_state$ がトークンの値に置き換わる -->
          <div class="status">$box_state$</div>
        </div>
        
      </html>
    </panel>
  </row>
</dashboard>

トークンの仕組み解説

① 検索でトークンを設定:

<set token="box_color">red</set>

box_color という変数に red を保存

② HTMLでトークンを使用:

<div class="box $box_color$">

$box_color$red に置き換わる

③ 最終的なHTML(レンダリング後):

<div class="box red">

📦 完全版:コピペ用テンプレート {#完全版コピペ用テンプレート}

テンプレート:1つの動的四角

<dashboard version="1.1">
  <label>【テンプレート】動的四角ボックス</label>
  
  <!-- ========================================== -->
  <!-- 検索部分(ここをカスタマイズ) -->
  <!-- ========================================== -->
  <search id="box1_search">
    <query>
      <!-- ★ここに実際の検索を書く -->
      index=mydata
      | where box_id="BOX1"
      | sort -_time
      | head 1
      | eval is_ng=if(status="NG",1,0)
    </query>
    
    <!-- 検索完了時の処理 -->
    <done>
      <!-- NGの場合 -->
      <condition match="$result.is_ng$=1">
        <set token="box1_color">red</set>
        <set token="box1_state">NG</set>
      </condition>
      
      <!-- OKの場合 -->
      <condition>
        <set token="box1_color">green</set>
        <set token="box1_state">OK</set>
      </condition>
    </done>
  </search>
  
  <!-- ========================================== -->
  <!-- 表示部分(基本的に変更不要) -->
  <!-- ========================================== -->
  <row>
    <panel>
      <html>
        
        <!-- スタイル定義 -->
        <style>
          /* 四角の基本スタイル */
          .box {
            /* サイズ */
            width: 200px;
            height: 120px;
            
            /* 見た目 */
            border-radius: 14px;
            box-shadow: 0 4px 10px rgba(0,0,0,0.15);
            
            /* 文字 */
            color: white;
            font-weight: bold;
            
            /* 中央揃え */
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
          }
          
          /* 色定義 */
          .green { background: #4CAF50; }
          .red { background: #E53935; }
          .yellow { background: #FFA726; }
          .blue { background: #42A5F5; }
          .gray { background: #757575; }
          
          /* テキストスタイル */
          .box-title {
            font-size: 16px;
            margin-bottom: 8px;
          }
          
          .box-status {
            font-size: 24px;
            font-weight: bold;
          }
        </style>
        
        <!-- 四角の表示 -->
        <div class="box $box1_color$">
          <!-- ★ここにタイトルを書く -->
          <div class="box-title">国道16号</div>
          <!-- ステータス(トークン) -->
          <div class="box-status">$box1_state$</div>
        </div>
        
      </html>
    </panel>
  </row>
</dashboard>

カスタマイズポイント

① 検索部分を変更

<query>
  <!-- 自分のインデックスとフィールドに変更 -->
  index=20260115tra
  | where like(location,"%湾岸%")
  | sort -_time
  | head 1
  | eval is_ng=if(result="NG",1,0)
</query>

② タイトルを変更

<div class="box-title">首都高 湾岸線</div>

③ トークン名を変更(複数ボックスの場合)

<!-- BOX1用 -->
<set token="box1_color">red</set>
<set token="box1_state">NG</set>

<!-- BOX2用 -->
<set token="box2_color">green</set>
<set token="box2_state">OK</set>

④ 色を追加

/* 紫色を追加 */
.purple { background: #9C27B0; }
<div class="box purple">

⑤ サイズを変更

.box {
  width: 250px;   /* 幅を変更 */
  height: 150px;  /* 高さを変更 */
}

🎯 横に四角を4つ並べる {#横に四角を4つ並べる}

完全版:4ボックス横並び

<dashboard version="1.1">
  <label>4つの四角ボックス(横並び)</label>
  
  <!-- ========================================== -->
  <!-- 検索部分(4つ分) -->
  <!-- ========================================== -->
  
  <!-- BOX1 検索 -->
  <search id="box1_search">
    <query>
      index=20260115tra
      | where like(location,"%湾岸%")
      | sort -_time
      | head 1
      | eval is_ng=if(result="NG",1,0)
    </query>
    <done>
      <condition match="$result.is_ng$=1">
        <set token="box1_color">red</set>
        <set token="box1_state">NG</set>
      </condition>
      <condition>
        <set token="box1_color">green</set>
        <set token="box1_state">OK</set>
      </condition>
    </done>
  </search>
  
  <!-- BOX2 検索 -->
  <search id="box2_search">
    <query>
      index=20260115tra
      | where like(location,"%国道16号%")
      | sort -_time
      | head 1
      | eval is_ng=if(result="NG",1,0)
    </query>
    <done>
      <condition match="$result.is_ng$=1">
        <set token="box2_color">red</set>
        <set token="box2_state">NG</set>
      </condition>
      <condition>
        <set token="box2_color">green</set>
        <set token="box2_state">OK</set>
      </condition>
    </done>
  </search>
  
  <!-- BOX3 検索 -->
  <search id="box3_search">
    <query>
      index=20260115tra
      | where like(location,"%東名%")
      | sort -_time
      | head 1
      | eval is_ng=if(result="NG",1,0)
    </query>
    <done>
      <condition match="$result.is_ng$=1">
        <set token="box3_color">red</set>
        <set token="box3_state">NG</set>
      </condition>
      <condition>
        <set token="box3_color">green</set>
        <set token="box3_state">OK</set>
      </condition>
    </done>
  </search>
  
  <!-- BOX4 検索 -->
  <search id="box4_search">
    <query>
      index=20260115tra
      | where like(location,"%中央高速%")
      | sort -_time
      | head 1
      | eval is_ng=if(result="NG",1,0)
    </query>
    <done>
      <condition match="$result.is_ng$=1">
        <set token="box4_color">red</set>
        <set token="box4_state">NG</set>
      </condition>
      <condition>
        <set token="box4_color">green</set>
        <set token="box4_state">OK</set>
      </condition>
    </done>
  </search>
  
  <!-- ========================================== -->
  <!-- 表示部分 -->
  <!-- ========================================== -->
  <row>
    <panel>
      <html>
        
        <style>
          /* ========================================== */
          /* 横並びコンテナ */
          /* ========================================== */
          .container {
            /* Flexboxで横並び */
            display: flex;
            
            /* 子要素を横方向に配置 */
            flex-direction: row;
            
            /* 縦方向の中央揃え */
            align-items: center;
            
            /* 要素間の余白 */
            gap: 30px;
            
            /* 外側の余白 */
            padding: 30px;
          }
          
          /* ========================================== */
          /* 四角ボックス */
          /* ========================================== */
          .box {
            /* サイズ */
            width: 200px;
            height: 120px;
            
            /* 見た目 */
            border-radius: 14px;
            box-shadow: 0 4px 10px rgba(0,0,0,0.15);
            
            /* 文字 */
            color: white;
            font-weight: bold;
            
            /* 中央揃え */
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
          }
          
          /* ========================================== */
          /* 色定義 */
          /* ========================================== */
          .green {
            /* 緑色 */
            background: #4CAF50;
          }
          
          .red {
            /* 赤色 */
            background: #E53935;
          }
          
          .yellow {
            /* 黄色 */
            background: #FFA726;
          }
          
          .blue {
            /* 青色 */
            background: #42A5F5;
          }
          
          .gray {
            /* グレー */
            background: #757575;
          }
          
          /* ========================================== */
          /* テキストスタイル */
          /* ========================================== */
          .box-title {
            /* タイトルのサイズ */
            font-size: 15px;
            
            /* 下に余白 */
            margin-bottom: 8px;
          }
          
          .box-status {
            /* ステータスのサイズ */
            font-size: 22px;
            
            /* 太字 */
            font-weight: bold;
          }
        </style>
        
        <!-- ========================================== -->
        <!-- 横並びコンテナ -->
        <!-- ========================================== -->
        <div class="container">
          
          <!-- BOX1:首都高 湾岸線 -->
          <div class="box $box1_color$">
            <div class="box-title">首都高 湾岸線</div>
            <div class="box-status">$box1_state$</div>
          </div>
          
          <!-- BOX2:国道16号 -->
          <div class="box $box2_color$">
            <div class="box-title">国道16号</div>
            <div class="box-status">$box2_state$</div>
          </div>
          
          <!-- BOX3:東名高速 -->
          <div class="box $box3_color$">
            <div class="box-title">東名高速</div>
            <div class="box-status">$box3_state$</div>
          </div>
          
          <!-- BOX4:中央高速 -->
          <div class="box $box4_color$">
            <div class="box-title">中央高速</div>
            <div class="box-status">$box4_state$</div>
          </div>
          
        </div>
        
      </html>
    </panel>
  </row>
</dashboard>

横並びの仕組み解説

① コンテナで囲む

<div class="container">
  <!-- この中に四角を入れる -->
  <div class="box">BOX1</div>
  <div class="box">BOX2</div>
  <div class="box">BOX3</div>
  <div class="box">BOX4</div>
</div>

② Flexboxで横並び

.container {
  /* Flexboxを有効化 */
  display: flex;
  
  /* 横方向に配置(これが重要!) */
  flex-direction: row;
  
  /* 要素間の余白 */
  gap: 30px;
}

ポイント:

  • display: flex → Flexbox有効化
  • flex-direction: row → 横並び(デフォルト値だが明示的に書く)
  • gap: 30px → ボックス間の余白

応用パターン

パターン1:等間隔に配置

.container {
  display: flex;
  /* 両端を揃えて、間を均等に */
  justify-content: space-between;
}

パターン2:中央揃え

.container {
  display: flex;
  /* 中央に寄せる */
  justify-content: center;
  gap: 20px;
}

パターン3:右寄せ

.container {
  display: flex;
  /* 右に寄せる */
  justify-content: flex-end;
  gap: 20px;
}

パターン4:折り返し(5個以上の場合)

.container {
  display: flex;
  /* 折り返しを許可 */
  flex-wrap: wrap;
  gap: 20px;
}

カスタマイズ例

① ボックス間に線を入れる

<div class="container">
  <div class="box $box1_color$">...</div>
  
  <!-- 線 -->
  <div style="width:2px; height:80px; background:#999;"></div>
  
  <div class="box $box2_color$">...</div>
  
  <div style="width:2px; height:80px; background:#999;"></div>
  
  <div class="box $box3_color$">...</div>
</div>

② ボックスのサイズを個別に変える

/* BOX1だけ大きくする */
.box.large {
  width: 250px;
  height: 150px;
}
<div class="box large $box1_color$">...</div>

③ ホバー効果を追加

.box:hover {
  /* マウスを乗せたら少し大きくする */
  transform: scale(1.05);
  
  /* スムーズに変化 */
  transition: transform 0.3s;
}

📝 まとめ

基本構造

Dashboard
 └ Row(行)
    └ Panel(パネル)
       └ HTML
          ├ <style> (CSS)
          └ <div>   (HTML要素)

重要なポイント

項目 説明
display: flex 横並び・中央揃えの基本
class="box $token$" トークンで動的にクラスを変更
gap: 30px ボックス間の余白
border-radius 角を丸くする

このテンプレートをコピペして、自分のダッシュボードを作ろう! 🚀


Splunk Dashboard:絶対位置で複数の四角を自由配置する完全ガイド

📚 目次

  1. 絶対位置の基本
  2. 座標系の理解
  3. 基本パターン:2つの四角
  4. 応用パターン:複数配置
  5. 実践例:フロー図
  6. 完全版テンプレート

🎯 絶対位置の基本 {#絶対位置の基本}

Step 1:position の種類

position 意味 用途
static デフォルト(通常の配置) 特に指定しない場合
relative 相対位置(元の位置を基準) 親要素として使う
absolute 絶対位置(親要素を基準) 自由配置に使う
fixed 固定位置(画面を基準) スクロールしても動かない

Step 2:最小構成のコード

<dashboard version="1.1">
  <label>絶対位置:基本編</label>
  
  <row>
    <panel>
      <html>
        
        <style>
          /* ========================================== */
          /* 親コンテナ(基準となる箱) */
          /* ========================================== */
          .container {
            /* 相対位置(これが重要!) */
            position: relative;
            
            /* コンテナのサイズを指定 */
            width: 800px;
            height: 600px;
            
            /* 背景色(確認用) */
            background: #f5f5f5;
            
            /* 余白 */
            margin: 30px;
          }
          
          /* ========================================== */
          /* 四角(絶対位置で配置) */
          /* ========================================== */
          .box {
            /* 絶対位置(これで自由配置可能) */
            position: absolute;
            
            /* サイズ */
            width: 150px;
            height: 100px;
            
            /* 見た目 */
            background: #42A5F5;
            border-radius: 12px;
            color: white;
            
            /* 文字を中央に */
            display: flex;
            justify-content: center;
            align-items: center;
            font-weight: bold;
          }
          
          /* ========================================== */
          /* BOX1 の位置 */
          /* ========================================== */
          .box1 {
            /* 上から50ピクセル */
            top: 50px;
            
            /* 左から50ピクセル */
            left: 50px;
          }
          
          /* ========================================== */
          /* BOX2 の位置 */
          /* ========================================== */
          .box2 {
            /* 上から50ピクセル */
            top: 50px;
            
            /* 左から300ピクセル */
            left: 300px;
            
            /* 色を変える */
            background: #4CAF50;
          }
        </style>
        
        <!-- ========================================== -->
        <!-- HTML構造 -->
        <!-- ========================================== -->
        
        <!-- 親コンテナ -->
        <div class="container">
          
          <!-- BOX1 -->
          <div class="box box1">
            BOX1
          </div>
          
          <!-- BOX2 -->
          <div class="box box2">
            BOX2
          </div>
          
        </div>
        
      </html>
    </panel>
  </row>
</dashboard>

動作の仕組み

① 親要素を position: relative にする

.container {
  position: relative;  /* これで基準点になる */
  width: 800px;
  height: 600px;
}

意味: この箱の左上が座標 (0, 0) になる


② 子要素を position: absolute にする

.box {
  position: absolute;  /* 絶対位置で配置 */
}

意味: 親要素を基準に自由に配置できる


③ top と left で位置を指定

.box1 {
  top: 50px;   /* 上から50ピクセル */
  left: 50px;  /* 左から50ピクセル */
}

📐 座標系の理解 {#座標系の理解}

座標の指定方法

┌─────────────────────────────────┐
│ container (800px × 600px)      │
│ (0, 0) ← 左上が原点             │
│                                 │
│   top: 50px                     │
│   ↓                             │
│   ┌────────┐                    │
│ → │  BOX1  │                    │
│   └────────┘                    │
│   ↑                             │
│   left: 50px                    │
│                                 │
└─────────────────────────────────┘

4つの位置指定プロパティ

プロパティ 意味
top 上からの距離 top: 50px
bottom 下からの距離 bottom: 50px
left 左からの距離 left: 50px
right 右からの距離 right: 50px

位置指定のパターン

パターン1:左上

.box {
  top: 0;
  left: 0;
}

パターン2:右上

.box {
  top: 0;
  right: 0;
}

パターン3:左下

.box {
  bottom: 0;
  left: 0;
}

パターン4:右下

.box {
  bottom: 0;
  right: 0;
}

パターン5:中央

.box {
  /* 左から50%の位置 */
  left: 50%;
  /* 上から50%の位置 */
  top: 50%;
  
  /* 自分のサイズの半分だけ戻す */
  transform: translate(-50%, -50%);
}

🎨 基本パターン:2つの四角 {#基本パターン2つの四角}

パターン1:横並び(絶対位置版)

<dashboard version="1.1">
  <label>絶対位置:横並び</label>
  
  <row>
    <panel>
      <html>
        
        <style>
          .container {
            position: relative;
            width: 600px;
            height: 200px;
            background: #f5f5f5;
            margin: 30px;
          }
          
          .box {
            position: absolute;
            width: 150px;
            height: 100px;
            background: #42A5F5;
            border-radius: 12px;
            color: white;
            display: flex;
            justify-content: center;
            align-items: center;
            font-weight: bold;
          }
          
          /* BOX1:左側 */
          .box1 {
            top: 50px;      /* 上から50px */
            left: 50px;     /* 左から50px */
          }
          
          /* BOX2:右側 */
          .box2 {
            top: 50px;      /* 上から50px */
            left: 300px;    /* 左から300px */
            background: #4CAF50;
          }
        </style>
        
        <div class="container">
          <div class="box box1">BOX1</div>
          <div class="box box2">BOX2</div>
        </div>
        
      </html>
    </panel>
  </row>
</dashboard>

パターン2:縦並び

<dashboard version="1.1">
  <label>絶対位置:縦並び</label>
  
  <row>
    <panel>
      <html>
        
        <style>
          .container {
            position: relative;
            width: 300px;
            height: 400px;
            background: #f5f5f5;
            margin: 30px;
          }
          
          .box {
            position: absolute;
            width: 200px;
            height: 80px;
            background: #42A5F5;
            border-radius: 12px;
            color: white;
            display: flex;
            justify-content: center;
            align-items: center;
            font-weight: bold;
          }
          
          /* BOX1:上 */
          .box1 {
            top: 50px;      /* 上から50px */
            left: 50px;     /* 左から50px */
          }
          
          /* BOX2:下 */
          .box2 {
            top: 200px;     /* 上から200px */
            left: 50px;     /* 左から50px */
            background: #4CAF50;
          }
        </style>
        
        <div class="container">
          <div class="box box1">BOX1</div>
          <div class="box box2">BOX2</div>
        </div>
        
      </html>
    </panel>
  </row>
</dashboard>

パターン3:斜め配置

<dashboard version="1.1">
  <label>絶対位置:斜め配置</label>
  
  <row>
    <panel>
      <html>
        
        <style>
          .container {
            position: relative;
            width: 600px;
            height: 400px;
            background: #f5f5f5;
            margin: 30px;
          }
          
          .box {
            position: absolute;
            width: 150px;
            height: 100px;
            background: #42A5F5;
            border-radius: 12px;
            color: white;
            display: flex;
            justify-content: center;
            align-items: center;
            font-weight: bold;
          }
          
          /* BOX1:左上 */
          .box1 {
            top: 50px;
            left: 50px;
          }
          
          /* BOX2:右下 */
          .box2 {
            top: 250px;
            left: 400px;
            background: #4CAF50;
          }
        </style>
        
        <div class="container">
          <div class="box box1">BOX1</div>
          <div class="box box2">BOX2</div>
        </div>
        
      </html>
    </panel>
  </row>
</dashboard>

🔀 応用パターン:複数配置 {#応用パターン複数配置}

パターン4:4つの四角(四隅)

<dashboard version="1.1">
  <label>絶対位置:四隅配置</label>
  
  <row>
    <panel>
      <html>
        
        <style>
          .container {
            position: relative;
            width: 600px;
            height: 400px;
            background: #f5f5f5;
            margin: 30px;
          }
          
          .box {
            position: absolute;
            width: 120px;
            height: 80px;
            border-radius: 10px;
            color: white;
            display: flex;
            justify-content: center;
            align-items: center;
            font-weight: bold;
          }
          
          /* ========================================== */
          /* 位置指定 */
          /* ========================================== */
          
          /* 左上 */
          .box1 {
            top: 20px;
            left: 20px;
            background: #42A5F5;
          }
          
          /* 右上 */
          .box2 {
            top: 20px;
            right: 20px;
            background: #4CAF50;
          }
          
          /* 左下 */
          .box3 {
            bottom: 20px;
            left: 20px;
            background: #FFA726;
          }
          
          /* 右下 */
          .box4 {
            bottom: 20px;
            right: 20px;
            background: #E53935;
          }
        </style>
        
        <div class="container">
          <div class="box box1">BOX1</div>
          <div class="box box2">BOX2</div>
          <div class="box box3">BOX3</div>
          <div class="box box4">BOX4</div>
        </div>
        
      </html>
    </panel>
  </row>
</dashboard>

パターン5:グリッド状配置(3x3)

<dashboard version="1.1">
  <label>絶対位置:グリッド配置</label>
  
  <row>
    <panel>
      <html>
        
        <style>
          .container {
            position: relative;
            width: 700px;
            height: 500px;
            background: #f5f5f5;
            margin: 30px;
          }
          
          .box {
            position: absolute;
            width: 100px;
            height: 80px;
            border-radius: 10px;
            color: white;
            background: #42A5F5;
            display: flex;
            justify-content: center;
            align-items: center;
            font-weight: bold;
          }
          
          /* ========================================== */
          /* 3×3 グリッド配置 */
          /* ========================================== */
          
          /* 1行目 */
          .box1 { top: 50px; left: 50px; }
          .box2 { top: 50px; left: 250px; }
          .box3 { top: 50px; left: 450px; }
          
          /* 2行目 */
          .box4 { top: 200px; left: 50px; background: #4CAF50; }
          .box5 { top: 200px; left: 250px; background: #4CAF50; }
          .box6 { top: 200px; left: 450px; background: #4CAF50; }
          
          /* 3行目 */
          .box7 { top: 350px; left: 50px; background: #FFA726; }
          .box8 { top: 350px; left: 250px; background: #FFA726; }
          .box9 { top: 350px; left: 450px; background: #FFA726; }
        </style>
        
        <div class="container">
          <div class="box box1">1</div>
          <div class="box box2">2</div>
          <div class="box box3">3</div>
          <div class="box box4">4</div>
          <div class="box box5">5</div>
          <div class="box box6">6</div>
          <div class="box box7">7</div>
          <div class="box box8">8</div>
          <div class="box box9">9</div>
        </div>
        
      </html>
    </panel>
  </row>
</dashboard>

パターン6:円形配置

<dashboard version="1.1">
  <label>絶対位置:円形配置</label>
  
  <row>
    <panel>
      <html>
        
        <style>
          .container {
            position: relative;
            width: 600px;
            height: 600px;
            background: #f5f5f5;
            margin: 30px;
          }
          
          .box {
            position: absolute;
            width: 80px;
            height: 80px;
            border-radius: 50%;  /* 円形に */
            color: white;
            background: #42A5F5;
            display: flex;
            justify-content: center;
            align-items: center;
            font-weight: bold;
          }
          
          /* 中央 */
          .center {
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 100px;
            height: 100px;
            background: #E53935;
          }
          
          /* ========================================== */
          /* 円形配置(12時、3時、6時、9時) */
          /* ========================================== */
          
          /* 12時(上) */
          .box1 {
            top: 50px;
            left: 50%;
            transform: translateX(-50%);
          }
          
          /* 3時(右) */
          .box2 {
            top: 50%;
            right: 50px;
            transform: translateY(-50%);
            background: #4CAF50;
          }
          
          /* 6時(下) */
          .box3 {
            bottom: 50px;
            left: 50%;
            transform: translateX(-50%);
            background: #FFA726;
          }
          
          /* 9時(左) */
          .box4 {
            top: 50%;
            left: 50px;
            transform: translateY(-50%);
            background: #9C27B0;
          }
        </style>
        
        <div class="container">
          <!-- 中央 -->
          <div class="box center">中央</div>
          
          <!-- 周辺4つ -->
          <div class="box box1">12時</div>
          <div class="box box2">3時</div>
          <div class="box box3">6時</div>
          <div class="box box4">9時</div>
        </div>
        
      </html>
    </panel>
  </row>
</dashboard>

🎯 実践例:フロー図 {#実践例フロー図}

完全版:プロセスフロー

<dashboard version="1.1">
  <label>絶対位置:プロセスフロー図</label>
  
  <!-- ========================================== -->
  <!-- 検索部分(トークン設定) -->
  <!-- ========================================== -->
  <search id="box1_search">
    <query>| makeresults | eval is_ng=0</query>
    <done>
      <condition match="$result.is_ng$=1">
        <set token="box1_color">red</set>
      </condition>
      <condition>
        <set token="box1_color">green</set>
      </condition>
    </done>
  </search>
  
  <search id="box2_search">
    <query>| makeresults | eval is_ng=1</query>
    <done>
      <condition match="$result.is_ng$=1">
        <set token="box2_color">red</set>
      </condition>
      <condition>
        <set token="box2_color">green</set>
      </condition>
    </done>
  </search>
  
  <search id="box3_search">
    <query>| makeresults | eval is_ng=0</query>
    <done>
      <condition match="$result.is_ng$=1">
        <set token="box3_color">red</set>
      </condition>
      <condition>
        <set token="box3_color">green</set>
      </condition>
    </done>
  </search>
  
  <search id="box4_search">
    <query>| makeresults | eval is_ng=0</query>
    <done>
      <condition match="$result.is_ng$=1">
        <set token="box4_color">red</set>
      </condition>
      <condition>
        <set token="box4_color">green</set>
      </condition>
    </done>
  </search>
  
  <!-- ========================================== -->
  <!-- 表示部分 -->
  <!-- ========================================== -->
  <row>
    <panel>
      <html>
        
        <style>
          /* ========================================== */
          /* コンテナ */
          /* ========================================== */
          .container {
            position: relative;
            width: 900px;
            height: 500px;
            background: #fafafa;
            margin: 30px auto;
            border: 2px solid #ddd;
          }
          
          /* ========================================== */
          /* ボックス基本スタイル */
          /* ========================================== */
          .box {
            position: absolute;
            width: 150px;
            height: 100px;
            border-radius: 14px;
            color: white;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            font-weight: bold;
            box-shadow: 0 4px 10px rgba(0,0,0,0.2);
          }
          
          /* 色定義 */
          .green { background: #4CAF50; }
          .red { background: #E53935; }
          
          /* テキスト */
          .box-title {
            font-size: 14px;
            margin-bottom: 6px;
          }
          
          .box-status {
            font-size: 20px;
          }
          
          /* ========================================== */
          /* 線(矢印) */
          /* ========================================== */
          .line {
            position: absolute;
            background: #999;
          }
          
          /* 横線 */
          .line-h {
            height: 4px;
          }
          
          /* 縦線 */
          .line-v {
            width: 4px;
          }
          
          /* 矢印 */
          .arrow {
            position: absolute;
            width: 0;
            height: 0;
            border-style: solid;
          }
          
          /* 右向き矢印 */
          .arrow-right {
            border-width: 8px 0 8px 12px;
            border-color: transparent transparent transparent #999;
          }
          
          /* 下向き矢印 */
          .arrow-down {
            border-width: 12px 8px 0 8px;
            border-color: #999 transparent transparent transparent;
          }
          
          /* ========================================== */
          /* 各要素の位置 */
          /* ========================================== */
          
          /* BOX1:左上 */
          .box1 {
            top: 50px;
            left: 50px;
          }
          
          /* 横線1 */
          .line1 {
            top: 97px;     /* BOX1の中央(50 + 100/2 - 4/2) */
            left: 215px;   /* BOX1の右端(50 + 150 + 15) */
            width: 100px;
          }
          
          /* 矢印1 */
          .arrow1 {
            top: 89px;     /* 線の中央(97 - 8) */
            left: 315px;   /* 線の右端 */
          }
          
          /* BOX2:右上 */
          .box2 {
            top: 50px;
            left: 350px;
          }
          
          /* 縦線1 */
          .line2 {
            top: 165px;    /* BOX2の下端(50 + 100 + 15) */
            left: 422px;   /* BOX2の中央(350 + 150/2 - 4/2) */
            height: 80px;
          }
          
          /* 矢印2 */
          .arrow2 {
            top: 245px;    /* 線の下端 */
            left: 414px;   /* 線の中央(422 - 8) */
          }
          
          /* BOX3:右下 */
          .box3 {
            top: 280px;
            left: 350px;
          }
          
          /* 横線2 */
          .line3 {
            top: 327px;    /* BOX3の中央 */
            left: 235px;   /* BOX3の左端 - 100 */
            width: 100px;
          }
          
          /* 矢印3(左向き) */
          .arrow3 {
            top: 319px;
            left: 223px;
            border-width: 8px 12px 8px 0;
            border-color: transparent #999 transparent transparent;
          }
          
          /* BOX4:左下 */
          .box4 {
            top: 280px;
            left: 50px;
          }
        </style>
        
        <!-- ========================================== -->
        <!-- HTML構造 -->
        <!-- ========================================== -->
        <div class="container">
          
          <!-- BOX1:スタート -->
          <div class="box box1 $box1_color$">
            <div class="box-title">スタート</div>
            <div class="box-status">OK</div>
          </div>
          
          <!-- 横線1(BOX1→BOX2) -->
          <div class="line line-h line1"></div>
          <div class="arrow arrow-right arrow1"></div>
          
          <!-- BOX2:処理A -->
          <div class="box box2 $box2_color$">
            <div class="box-title">処理A</div>
            <div class="box-status">NG</div>
          </div>
          
          <!-- 縦線1(BOX2→BOX3) -->
          <div class="line line-v line2"></div>
          <div class="arrow arrow-down arrow2"></div>
          
          <!-- BOX3:処理B -->
          <div class="box box3 $box3_color$">
            <div class="box-title">処理B</div>
            <div class="box-status">OK</div>
          </div>
          
          <!-- 横線2(BOX3→BOX4) -->
          <div class="line line-h line3"></div>
          <div class="arrow arrow3"></div>
          
          <!-- BOX4:終了 -->
          <div class="box box4 $box4_color$">
            <div class="box-title">終了</div>
            <div class="box-status">OK</div>
          </div>
          
        </div>
        
      </html>
    </panel>
  </row>
</dashboard>

📦 完全版テンプレート {#完全版テンプレート}

コピペ用:自由配置テンプレート

<dashboard version="1.1">
  <label>【テンプレート】絶対位置・自由配置</label>
  
  <row>
    <panel>
      <html>
        
        <style>
          /* ========================================== */
          /* コンテナ(サイズを調整) */
          /* ========================================== */
          .container {
            /* 相対位置(必須) */
            position: relative;
            
            /* ★サイズを調整 */
            width: 1000px;
            height: 600px;
            
            /* 背景色 */
            background: #fafafa;
            
            /* 余白 */
            margin: 30px auto;
            
            /* 枠線(確認用) */
            border: 1px dashed #ccc;
          }
          
          /* ========================================== */
          /* ボックス共通スタイル */
          /* ========================================== */
          .box {
            /* 絶対位置(必須) */
            position: absolute;
            
            /* デフォルトサイズ */
            width: 150px;
            height: 100px;
            
            /* 見た目 */
            border-radius: 12px;
            box-shadow: 0 4px 10px rgba(0,0,0,0.15);
            
            /* 文字 */
            color: white;
            font-weight: bold;
            
            /* 中央揃え */
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
          }
          
          /* ========================================== */
          /* 色定義 */
          /* ========================================== */
          .green { background: #4CAF50; }
          .red { background: #E53935; }
          .yellow { background: #FFA726; }
          .blue { background: #42A5F5; }
          .purple { background: #9C27B0; }
          .gray { background: #757575; }
          
          /* ========================================== */
          /* 各ボックスの位置(★ここを調整) */
          /* ========================================== */
          
          .box1 {
            top: 50px;
            left: 50px;
          }
          
          .box2 {
            top: 50px;
            left: 300px;
          }
          
          .box3 {
            top: 50px;
            left: 550px;
          }
          
          .box4 {
            top: 250px;
            left: 50px;
          }
          
          .box5 {
            top: 250px;
            left: 300px;
          }
          
          .box6 {
            top: 250px;
            left: 550px;
          }
          
          /* ========================================== */
          /* 線(オプション) */
          /* ========================================== */
          .line {
            position: absolute;
            background: #999;
          }
          
          /* 横線のサンプル */
          .line-h-1 {
            top: 97px;      /* ★調整 */
            left: 215px;    /* ★調整 */
            width: 70px;    /* ★調整 */
            height:4px;
          }
          
          /* 縦線のサンプル */
          .line-v-1 {
            top: 165px;     /* ★調整 */
            left: 122px;    /* ★調整 */
            width: 4px;
            height: 70px;   /* ★調整 */
          }
        </style>
        
        <!-- ========================================== -->
        <!-- HTML(★ボックスを追加・削除) -->
        <!-- ========================================== -->
        <div class="container">
          
          <!-- BOX1 -->
          <div class="box box1 green">
            <div>BOX1</div>
          </div>
          
          <!-- BOX2 -->
          <div class="box box2 blue">
            <div>BOX2</div>
          </div>
          
          <!-- BOX3 -->
          <div class="box box3 red">
            <div>BOX3</div>
          </div>
          
          <!-- BOX4 -->
          <div class="box box4 yellow">
            <div>BOX4</div>
          </div>
          
          <!-- BOX5 -->
          <div class="box box5 purple">
            <div>BOX5</div>
          </div>
          
          <!-- BOX6 -->
          <div class="box box6 gray">
            <div>BOX6</div>
          </div>
          
          <!-- 線(オプション) -->
          <!-- <div class="line line-h-1"></div> -->
          <!-- <div class="line line-v-1"></div> -->
          
        </div>
        
      </html>
    </panel>
  </row>
</dashboard>

🎓 位置計算のコツ

計算式

横線の中央位置

top = ボックスのtop + (ボックスの高さ / 2) - (線の高さ / 2)

例:

.box1 { top: 50px; height: 100px; }
.line { height: 4px; }

/* 計算 */
top = 50 + (100 / 2) - (4 / 2)
    = 50 + 50 - 2
    = 98px

縦線の中央位置

left = ボックスのleft + (ボックスの幅 / 2) - (線の幅 / 2)

例:

.box1 { left: 50px; width: 150px; }
.line { width: 4px; }

/* 計算 */
left = 50 + (150 / 2) - (4 / 2)
     = 50 + 75 - 2
     = 123px

デバッグのコツ

① グリッド背景を追加

.container {
  background-image: 
    linear-gradient(#ddd 1px, transparent 1px),
    linear-gradient(90deg, #ddd 1px, transparent 1px);
  background-size: 50px 50px;
}

効果: 50pxごとにグリッド線が表示される


② 座標を表示

<div class="box box1 green">
  <div>BOX1</div>
  <div style="font-size:10px; margin-top:5px;">
    (50, 50)
  </div>
</div>

📝 まとめ

絶対位置の3ステップ

Step 内容 コード
1 親を相対位置に .container { position: relative; }
2 子を絶対位置に .box { position: absolute; }
3 座標を指定 top: 50px; left: 100px;

位置指定のパターン

目的 プロパティ
左上からの距離 top, left
右下からの距離 bottom, right
中央配置 left: 50%; transform: translateX(-50%);

このテンプレートで自由なレイアウトを作ろう! 🎨

お疲れ様でした!

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?