複数サイトのメンテナンスを順番に流していると、一覧画面のどこに「いま処理中のサイト」がいて、「終わったサイト」がどれなのかが、文字情報だけだとパッと掴めません。クライアントから「メンテナンス中と完了サイトが一覧で視覚的にわかると良い」という素直な要望が来ました。
ぱっと見でわかる色付きの枠を付ければよさそうですが、決めることがいくつかあります。色は何にするか、状態はどこから取るか、終わったマークはいつ消すか、そして ── バックエンドを改修しないで実装できるか。今回はこの 4 つの判断と、最小限のフロント実装で済ませた話をまとめておきます。
色の選び方 — 「赤の点滅」は最初に却下した
実行中サイトをどう目立たせるか。直感的には「赤い点滅」が思い浮かびますが、これは早めに却下しました。多サイトメンテナンスは長時間の作業で、その間ずっと画面のどこかで赤が点滅していると、利用者の疲労源になります。
採用したのは「青の穏やかな脈動 + 緑の実線枠」の組合せです。
- 実行中: 青
#2563ebの border + box-shadow による緩やかな脈動(2.2s ease-in-out) - 完了(24h 以内): 緑
#10b981の実線 border + 薄い inset シャドウ
@keyframes site-running-pulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(37, 99, 235, 0.4); }
50% { box-shadow: 0 0 0 6px rgba(37, 99, 235, 0); }
}
.site-running {
border-color: #2563eb !important;
animation: site-running-pulse 2.2s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
.site-running { animation: none; } /* アクセシビリティ配慮 */
}
.site-completed {
border-color: #10b981 !important;
box-shadow: inset 0 0 0 1px rgba(16, 185, 129, 0.25);
}
prefers-reduced-motion: reduce の OS 設定(モーション軽減を有効にしているユーザー)では脈動が止まる ── これは前庭機能に過敏な利用者への配慮です。動きで気を引く設計を入れるなら、ほぼ自動で必須の対応になります。
バックエンド改修ゼロ — 既存のログストリーミングを再利用する
「いま処理中のサイトはどれか」を一覧 UI に伝えるためには、サーバー側から状態を流す必要があります。素直にやるなら /api/maintenance/status のような新規エンドポイントを立てる ── けれど、メンテナンスログはもう既にストリーミング配信されていました。[サイト名] メッセージ 形式の行が次々と流れてくるあのログです。
このログをフロント側で正規表現パースすれば、新規 API なしで「いま処理中のサイト」が取れることに気づきました。バックエンドに手を入れずに済む経路です。
function _detectRunningSiteFromLog(logText) {
// ログは "2026-05-28 09:40:13 - [INFO] - [サイト名] メッセージ" 形式
const lines = logText.split('\n');
for (let i = lines.length - 1; i >= 0; i--) { // 末尾から走査
const matches = [...lines[i].matchAll(/\[([^\]]+)\]/g)];
for (const m of matches) {
const candidate = m[1];
// log level の bracket は除外
if (['INFO', 'WARNING', 'ERROR', 'DEBUG', 'TRACE'].includes(candidate)) continue;
// allSites の site_name と照合
const site = allSites.find(s => s.site_name === candidate);
if (site) return site._id;
}
}
return null;
}
末尾から走査するのは「最新のサイト名」を取りたいから。[INFO] のような log level の bracket も [...] でマッチするので、明示的に除外します。1 行に [INFO] と [サイト名] が両方あるケースに対応するため、行内のすべての bracket を順に試す形にしています。
サイト切替で前を完了マーク
_runningSiteId が変わったら、前のサイトを完了扱いにしてやれば、自然に「実行中 → 完了」の流れが UI に現れます。
function _setRunningSiteId(siteId) {
if (_runningSiteId && _runningSiteId !== siteId) {
_markSiteCompleted(_runningSiteId); // 前のサイトは完了
}
_runningSiteId = siteId;
filterSites(); // 一覧再描画で枠色切替
}
この実装はこの記事の時点で「素朴」です。実際にはこの後、ログの非単調な出方(複数サイト名が前後に混ざる・初期化ループで全サイト名が先行出力される等)で3 つの落とし穴を踏むことになります。その顛末は ストリーミングログから処理中サイトを当てる難しさ にまとめました ── 結論だけ言うと、「サイト切替で前を完了」というルールはログの並びに対して脆く、後でマーカー方式(特定の 1 行だけを検出対象にする)に書き直すことになります。
最初の素朴な実装で気づけなかったのは、ログの並びを「常に単調に進む」と暗黙に仮定していたことでした。可視化機能を組み込むときは、ログという非同期データソースの揺れに対する耐性を最初から考えておくべき、という後付けの学びになっています。
完了マークを 24h で自動消失させる
緑枠を「永久に残す」と、利用者が「これ、いつメンテしたんだっけ」を読み取れなくなります(特に複数日にまたがる運用で混乱する)。逆に「ページリロードで消える」だと、それはそれで頼りない。
折衷案として 24 時間で自動消失 を入れました。完了タイムスタンプを localStorage に保存し、読み込み時に 24h を超えたエントリをフィルタ除外します。
function _loadCompletedSites() {
try {
const raw = localStorage.getItem(KEY_COMPLETED_SITES);
if (!raw) return {};
const parsed = JSON.parse(raw);
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
return {}; // 型不正は捨てる
}
const cutoff = Date.now() - 24 * 60 * 60 * 1000;
const filtered = {};
for (const [siteId, ts] of Object.entries(parsed)) {
if (typeof ts === 'number' && ts > cutoff) { // 24h 以内のみ
filtered[siteId] = ts;
}
}
return filtered;
} catch (e) {
return {}; // JSON 不正・localStorage 無効
}
}
3 段階のデータ防御を入れています。JSON パース失敗・型不正・古すぎるの 3 ケースで空辞書にフォールバック。localStorage が無効な環境(プライベートモード等)でも try/catch で落ちないようにします。「あれば嬉しい」種類のキャッシュは、壊れていても起動を妨げない方が原則です。
ちなみに、この 24h で消える完了マークの挙動は後で「キャッシュ寿命の設計」を巡るやり直しに発展して、ダッシュボードキャッシュの寿命を巡る 3 つのやり直し で TTL を 30 日に伸ばしたり、メンテ実行で部分消去にしたりと、何度も触ることになります。最初の 24h は「ひとまず合理的に見える初期値」でしかなく、運用に出てから「もっと長く残してほしい」「いや、メンテで全消ししないでほしい」と各方面から指摘が来ました。
まとめ — 「最小限で組んで、運用で揺らす」
このラウンドから取り出せる原則は次の 3 つでした。
- 新規 API を立てる前に既存ストリームを再利用できないか確認する — ログストリーミングが既に動いていれば、新規エンドポイントを生やすよりフロントでパースする方が、影響範囲も小さく素早く組める。バックエンド改修ゼロで状態可視化を実装できた
- アクセシビリティはアニメーション導入の自動条件 —
prefers-reduced-motion: reduceの対応は、動きで気を引く設計を入れるなら必須。「赤点滅」を最初から却下したのも同じ系列の判断 - 「素朴な第一実装」は運用に出してから揺らす前提で組む — サイト切替で前を完了マークするルールは、ログの並びに対して脆い前提を含んでいた。後にマーカー方式に書き直すことになる。「最小限で動かして、運用で見えた揺らぎに対応する」進め方は、過剰に設計するより何が問題かを早く明らかにできる
可視化機能は「あると嬉しい」程度に思われがちですが、実際には運用者の負荷を地味に下げる価値があります。複数サイト × 長時間メンテ × 視覚情報ほぼゼロの組合せは、シンプルに疲労源だったので。色 1 つでも、思っているより効きます。