2026-04-20 11:48:28 +00:00
<!DOCTYPE html>
2026-04-20 23:08:14 +00:00
< html lang = "ja" data-theme = "dark" >
2026-04-20 11:48:28 +00:00
< head >
< meta charset = "utf-8" / >
< meta name = "viewport" content = "width=device-width, initial-scale=1" / >
2026-04-20 23:08:14 +00:00
< title > PC Audit — Posimai< / title >
2026-04-21 05:39:48 +00:00
<!-- AUDIT_PRELOAD -->
2026-04-20 11:48:28 +00:00
< style >
2026-04-20 23:08:14 +00:00
:root {
--bg: #0D0D0D;
--surface: #1A1A1A;
--surface2: #252525;
--border: #2D2D2D;
--text: #F3F4F6;
--text2: #9CA3AF;
--text3: #6B7280;
--accent: #6EE7B7;
--warn-color: #F59E0B;
--warn-dim: rgba(245,158,11,0.08);
--warn-border: rgba(245,158,11,0.2);
--ok-color: #6EE7B7;
--ok-dim: rgba(110,231,183,0.06);
--ok-border: rgba(110,231,183,0.2);
--info-color: #60A5FA;
--info-dim: rgba(96,165,250,0.06);
--info-border: rgba(96,165,250,0.2);
--radius: 12px;
--radius-sm: 8px;
2026-04-21 06:59:07 +00:00
--header-h: 44px;
2026-04-20 23:08:14 +00:00
}
* { box-sizing: border-box; margin: 0; padding: 0; }
2026-04-21 06:59:07 +00:00
html, body {
height: 100%;
overflow: hidden;
2026-04-20 23:08:14 +00:00
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.6;
}
2026-04-21 06:59:07 +00:00
/* ── Page shell ── */
.page {
height: 100vh;
display: flex;
flex-direction: column;
padding: 0.9rem 1.5rem 0.75rem;
gap: 0.75rem;
}
2026-04-20 23:24:33 +00:00
2026-04-21 06:59:07 +00:00
/* ── Header bar ── */
.header {
flex-shrink: 0;
display: flex;
align-items: center;
gap: 1rem;
flex-wrap: wrap;
min-height: var(--header-h);
}
.header h1 {
font-size: 1rem; font-weight: 600; letter-spacing: 0.05em; color: var(--text2);
}
2026-04-20 23:08:14 +00:00
.header h1 span { color: var(--accent); }
2026-04-21 06:59:07 +00:00
.header-meta {
display: flex; align-items: center; gap: 0.75rem; flex: 1; flex-wrap: wrap;
}
2026-04-20 23:08:14 +00:00
.privacy-note {
2026-04-21 06:59:07 +00:00
font-size: 0.72rem; color: var(--text3);
display: flex; align-items: center; gap: 0.35rem;
}
.privacy-dot {
display: inline-block; width: 5px; height: 5px;
border-radius: 50%; background: var(--accent); flex-shrink: 0;
}
.alt-file-btn {
background: none; border: none; padding: 0; margin-left: auto;
font-size: 0.72rem; color: var(--text3); cursor: pointer;
text-decoration: underline; text-underline-offset: 2px;
}
.alt-file-btn:hover { color: var(--accent); }
/* ── Upload state (no data) ── */
.upload-wrap {
flex: 1;
2026-04-20 23:08:14 +00:00
display: flex;
align-items: center;
2026-04-21 06:59:07 +00:00
justify-content: center;
2026-04-20 23:08:14 +00:00
}
.upload-area {
2026-04-21 06:59:07 +00:00
background: var(--surface); border: 1px dashed var(--border);
border-radius: var(--radius); padding: 2rem 2.5rem;
width: 100%; max-width: 440px;
}
.upload-label {
font-size: 0.85rem; font-weight: 600; color: var(--text2);
margin-bottom: 0.5rem; display: block;
2026-04-20 23:08:14 +00:00
}
.upload-path {
2026-04-21 05:39:48 +00:00
font-size: 0.73rem; color: var(--text3); background: var(--surface2);
2026-04-21 06:59:07 +00:00
padding: 0.2rem 0.5rem; border-radius: var(--radius-sm);
font-family: monospace; display: inline-block; margin-bottom: 0.75rem;
2026-04-20 23:08:14 +00:00
}
2026-04-20 23:24:33 +00:00
input[type="file"] { font-size: 0.82rem; color: var(--text2); cursor: pointer; max-width: 100%; }
2026-04-20 23:08:14 +00:00
input[type="file"]::file-selector-button {
2026-04-20 23:24:33 +00:00
background: var(--surface2); color: var(--text); border: 1px solid var(--border);
border-radius: var(--radius-sm); padding: 0.3rem 0.75rem; font-size: 0.78rem;
2026-04-21 05:39:48 +00:00
cursor: pointer; margin-right: 0.5rem;
}
2026-04-21 06:59:07 +00:00
/* ── 3-column grid (data state) ── */
#output {
flex: 1;
min-height: 0;
2026-04-21 05:39:48 +00:00
display: none;
2026-04-21 06:59:07 +00:00
flex-direction: column;
2026-04-21 05:39:48 +00:00
}
.grid3 {
2026-04-21 06:59:07 +00:00
flex: 1;
min-height: 0;
2026-04-21 05:39:48 +00:00
display: grid;
2026-04-21 06:59:07 +00:00
grid-template-columns: 264px 1fr 1fr;
gap: 1rem;
2026-04-21 05:39:48 +00:00
}
@media (max-width: 900px) {
2026-04-21 06:59:07 +00:00
html, body { overflow: auto; height: auto; }
.page { height: auto; overflow: visible; padding: 1rem; }
#output { display: flex !important; flex-direction: column; }
2026-04-21 05:39:48 +00:00
.grid3 { grid-template-columns: 1fr; }
2026-04-21 06:59:07 +00:00
.grid-col { max-height: 70vh; }
}
/* ── Grid column (flex column, child scroll-pane fills it) ── */
.grid-col {
min-height: 0;
display: flex;
flex-direction: column;
overflow: hidden;
2026-04-20 23:08:14 +00:00
}
2026-04-21 05:39:48 +00:00
.col-label {
2026-04-21 06:59:07 +00:00
flex-shrink: 0;
font-size: 0.63rem; font-weight: 700; letter-spacing: 0.1em;
2026-04-21 05:39:48 +00:00
text-transform: uppercase; color: var(--text3);
2026-04-21 06:59:07 +00:00
margin-bottom: 0.6rem; padding-bottom: 0.45rem;
border-bottom: 1px solid var(--border);
}
.filter-row {
flex-shrink: 0;
display: flex; gap: 0.4rem; margin-bottom: 0.6rem; flex-wrap: wrap;
2026-04-21 08:52:58 +00:00
align-items: center;
2026-04-20 23:08:14 +00:00
}
2026-04-21 08:52:58 +00:00
/* ── AI prompt copy button ── */
.copy-prompt-btn {
margin-left: auto;
font-size: 0.68rem; font-weight: 600;
padding: 0.2rem 0.65rem;
border-radius: 99px;
border: 1px solid var(--border);
background: transparent; color: var(--text3);
cursor: pointer;
transition: border-color 0.12s, color 0.12s, background 0.12s;
white-space: nowrap;
}
.copy-prompt-btn:hover { border-color: var(--accent); color: var(--accent); }
.copy-prompt-btn.copied { border-color: var(--ok-color); color: var(--ok-color); }
2026-04-21 06:59:07 +00:00
/* ── Scrollable area inside each column ── */
2026-04-21 05:39:48 +00:00
.scroll-pane {
2026-04-21 06:59:07 +00:00
flex: 1;
min-height: 0;
2026-04-21 05:39:48 +00:00
overflow-y: auto;
padding-right: 3px;
scrollbar-width: thin;
scrollbar-color: var(--border) transparent;
}
.scroll-pane::-webkit-scrollbar { width: 4px; }
.scroll-pane::-webkit-scrollbar-track { background: transparent; }
.scroll-pane::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
2026-04-21 06:59:07 +00:00
/* ── Filter chips ── */
2026-04-21 05:39:48 +00:00
.chip {
font-size: 0.68rem; font-weight: 600; padding: 0.18rem 0.55rem;
border-radius: 99px; border: 1px solid var(--border);
cursor: pointer; color: var(--text3); background: transparent;
transition: border-color 0.12s, color 0.12s, background 0.12s;
user-select: none;
}
.chip:hover { border-color: var(--text2); color: var(--text2); }
.chip.active { background: var(--surface2); color: var(--text); border-color: var(--accent); }
2026-04-21 06:59:07 +00:00
.chip.warn-chip.active { border-color: var(--warn-color); color: var(--warn-color); }
.chip.info-chip.active { border-color: var(--info-color); color: var(--info-color); }
.chip.scope-all-chip.active { border-color: var(--warn-color); color: var(--warn-color); }
.chip.scope-local-chip.active { border-color: var(--ok-color); color: var(--ok-color); }
2026-04-21 05:39:48 +00:00
2026-04-21 06:59:07 +00:00
/* ── Summary metrics ── */
.summary-grid {
display: grid; grid-template-columns: 1fr 1fr;
gap: 0.45rem; margin-bottom: 0.65rem;
}
2026-04-20 23:08:14 +00:00
.metric {
2026-04-20 23:24:33 +00:00
background: var(--surface); border: 1px solid var(--border);
2026-04-21 06:59:07 +00:00
border-radius: var(--radius-sm); padding: 0.6rem 0.8rem;
2026-04-20 23:08:14 +00:00
}
2026-04-21 03:48:34 +00:00
.metric.full { grid-column: 1 / -1; }
2026-04-21 06:59:07 +00:00
.metric-label { font-size: 0.67rem; color: var(--text3); margin-bottom: 0.1rem; }
.metric-value { font-size: 0.88rem; font-weight: 600; color: var(--text); word-break: break-all; }
2026-04-20 23:08:14 +00:00
2026-04-21 06:59:07 +00:00
/* ── Diff card ── */
2026-04-21 05:39:48 +00:00
.diff-card {
background: var(--surface); border: 1px solid var(--border);
2026-04-21 06:59:07 +00:00
border-radius: var(--radius); padding: 0.8rem 0.9rem; margin-bottom: 0.65rem;
}
.diff-card-title { font-size: 0.77rem; font-weight: 600; color: var(--text2); margin-bottom: 0.55rem; }
.diff-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.45rem; }
.diff-alert { margin-top: 0.45rem; font-size: 0.77rem; color: var(--warn-color); }
/* ── Raw data (lives in col 1 scroll pane) ── */
.raw-details {
background: var(--surface); border: 1px solid var(--border);
border-radius: var(--radius); padding: 0.8rem 1rem;
margin-bottom: 0.65rem;
}
.raw-details summary {
cursor: pointer; font-size: 0.75rem; font-weight: 600;
color: var(--text3); user-select: none;
}
.raw-details pre {
margin-top: 0.5rem; font-size: 0.68rem; color: var(--text3);
overflow: auto; max-height: 14rem; line-height: 1.5;
}
.disclaimer {
font-size: 0.7rem; color: var(--text3);
border-top: 1px solid var(--border); padding-top: 0.75rem; margin-bottom: 0.5rem;
2026-04-21 05:39:48 +00:00
}
2026-04-20 23:08:14 +00:00
2026-04-21 06:59:07 +00:00
/* ── Guidance cards ── */
2026-04-20 23:08:14 +00:00
.card {
2026-04-20 23:24:33 +00:00
background: var(--surface); border: 1px solid var(--border);
2026-04-21 06:59:07 +00:00
border-radius: var(--radius); padding: 0.85rem 0.95rem; margin-bottom: 0.45rem;
2026-04-20 23:08:14 +00:00
}
.card.warn { background: var(--warn-dim); border-color: var(--warn-border); }
.card.ok { background: var(--ok-dim); border-color: var(--ok-border); }
.card.info { background: var(--info-dim); border-color: var(--info-border); }
2026-04-21 06:59:07 +00:00
.card-header { display: flex; align-items: flex-start; gap: 0.4rem; margin-bottom: 0.4rem; }
2026-04-20 23:08:14 +00:00
.badge {
2026-04-21 06:59:07 +00:00
font-size: 0.61rem; font-weight: 700; letter-spacing: 0.06em;
padding: 0.14rem 0.42rem; border-radius: 99px; white-space: nowrap;
flex-shrink: 0; margin-top: 0.22rem;
2026-04-20 23:08:14 +00:00
}
.badge-warn { background: rgba(245,158,11,0.15); color: var(--warn-color); }
.badge-ok { background: rgba(110,231,183,0.15); color: var(--ok-color); }
.badge-info { background: rgba(96,165,250,0.15); color: var(--info-color); }
2026-04-21 06:59:07 +00:00
.card-title { font-size: 0.85rem; font-weight: 600; color: var(--text); line-height: 1.4; }
.card-body { font-size: 0.79rem; color: var(--text2); line-height: 1.65; }
2026-04-20 23:08:14 +00:00
.action-box {
2026-04-21 06:59:07 +00:00
margin-top: 0.6rem; background: var(--surface2);
border-radius: var(--radius-sm); padding: 0.55rem 0.75rem;
font-size: 0.77rem; color: var(--text2);
2026-04-20 23:08:14 +00:00
}
.action-label {
2026-04-21 06:59:07 +00:00
font-size: 0.61rem; font-weight: 700; letter-spacing: 0.1em;
color: var(--accent); text-transform: uppercase; display: block; margin-bottom: 0.18rem;
2026-04-20 23:08:14 +00:00
}
2026-04-21 05:39:48 +00:00
2026-04-21 06:59:07 +00:00
/* ── Port list ── */
2026-04-21 05:39:48 +00:00
.port-legend {
2026-04-21 06:59:07 +00:00
flex-shrink: 0;
font-size: 0.72rem; color: var(--text2);
margin-bottom: 0.55rem; line-height: 1.7;
2026-04-21 05:39:48 +00:00
}
.port-summary-row {
2026-04-21 06:59:07 +00:00
display: flex; gap: 0.9rem; flex-wrap: wrap; margin-bottom: 0.55rem;
2026-04-21 05:39:48 +00:00
}
2026-04-21 06:59:07 +00:00
.port-summary-item { font-size: 0.72rem; color: var(--text3); }
2026-04-21 05:39:48 +00:00
.port-summary-item strong { color: var(--text); }
2026-04-21 06:59:07 +00:00
.table-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: 0.78rem; }
thead th {
position: sticky; top: 0; z-index: 1;
background: var(--bg);
text-align: left; padding: 0.42rem 0.55rem;
font-size: 0.62rem; font-weight: 700; letter-spacing: 0.08em;
text-transform: uppercase; color: var(--text3);
border-bottom: 1px solid var(--border);
2026-04-20 23:08:14 +00:00
}
2026-04-21 06:59:07 +00:00
tbody tr { border-bottom: 1px solid var(--border); }
tbody tr:last-child { border-bottom: none; }
tbody td { padding: 0.36rem 0.55rem; color: var(--text2); vertical-align: top; }
.addr-cell { font-family: monospace; font-size: 0.71rem; color: var(--text3); }
.port-cell { font-family: monospace; font-weight: 600; color: var(--text); }
.scope-all { color: var(--warn-color); font-size: 0.73rem; }
.scope-localhost { color: var(--ok-color); font-size: 0.73rem; }
.scope-specific { color: var(--info-color); font-size: 0.73rem; }
.hint-line { font-size: 0.66rem; color: var(--text3); margin-top: 0.08rem; }
.port-empty { font-size: 0.78rem; color: var(--text3); padding: 0.6rem 0; }
2026-04-20 11:48:28 +00:00
< / style >
< / head >
< body >
2026-04-20 23:08:14 +00:00
< div class = "page" >
2026-04-21 06:59:07 +00:00
<!-- ── Header ── -->
2026-04-20 23:08:14 +00:00
< div class = "header" >
< h1 > PC Audit < span > /< / span > Posimai< / h1 >
2026-04-21 06:59:07 +00:00
< div class = "header-meta" >
< p class = "privacy-note" > < span class = "privacy-dot" > < / span > ブラウザ内のみで動作。データは外部に送信されません。< / p >
< button id = "alt-file-btn" class = "alt-file-btn" style = "display:none" > 別のファイルを読む< / button >
< / div >
2026-04-20 23:08:14 +00:00
< / div >
2026-04-20 11:48:28 +00:00
2026-04-21 06:59:07 +00:00
<!-- ── Upload state ── -->
< div id = "upload-wrap" class = "upload-wrap" >
< div class = "upload-area" >
< label class = "upload-label" for = "f" > レポートファイルを選択< / label >
< div class = "upload-path" > tools\pc-audit\out\latest.json< / div > < br / >
< input id = "f" type = "file" accept = ".json,application/json" / >
< / div >
2026-04-21 05:39:48 +00:00
< / div >
2026-04-21 06:59:07 +00:00
<!-- ── Data state: 3 - column grid ── -->
< div id = "output" >
2026-04-20 23:24:33 +00:00
< div class = "grid3" >
2026-04-21 06:59:07 +00:00
<!-- Col 1: Summary + Diff + Raw -->
< div class = "grid-col" >
2026-04-21 05:39:48 +00:00
< div class = "col-label" > 概要< / div >
2026-04-21 06:59:07 +00:00
< div class = "scroll-pane" >
< div id = "summary" > < / div >
< div id = "diff" > < / div >
< details class = "raw-details" >
< summary > 生データ( JSON) < / summary >
< pre id = "raw" > < / pre >
< / details >
< p class = "disclaimer" > 補助的な情報です。セキュリティ診断・コンプライアンス判定の代わりにはなりません。< / p >
< / div >
2026-04-20 23:24:33 +00:00
< / div >
2026-04-21 06:59:07 +00:00
<!-- Col 2: Guidance -->
< div class = "grid-col" >
2026-04-21 09:06:18 +00:00
< div class = "col-label" style = "display:flex;align-items:center;justify-content:space-between" >
< span > チェック項目< / span >
2026-04-21 08:52:58 +00:00
< button id = "copy-prompt-btn" class = "copy-prompt-btn" > AI 相談用にコピー< / button >
< / div >
2026-04-21 09:06:18 +00:00
< div class = "filter-row" id = "guidance-filter-row" > < / div >
2026-04-21 05:39:48 +00:00
< div class = "scroll-pane" id = "guidance" > < / div >
2026-04-20 23:24:33 +00:00
< / div >
2026-04-21 06:59:07 +00:00
<!-- Col 3: Ports -->
< div class = "grid-col" >
2026-04-20 23:24:33 +00:00
< div class = "col-label" > 待ち受けポート一覧< / div >
2026-04-21 05:39:48 +00:00
< div class = "filter-row" id = "port-filter-row" > < / div >
2026-04-21 06:59:07 +00:00
< div id = "port-legend" class = "port-legend" > < / div >
2026-04-21 05:39:48 +00:00
< div class = "scroll-pane" >
< div class = "table-wrap" > < div id = "listeners" > < / div > < / div >
< / div >
2026-04-20 23:24:33 +00:00
< / div >
2026-04-21 06:59:07 +00:00
2026-04-20 23:24:33 +00:00
< / div >
2026-04-20 23:08:14 +00:00
< / div >
2026-04-21 06:59:07 +00:00
2026-04-20 23:08:14 +00:00
< / div >
2026-04-20 11:48:28 +00:00
< script >
2026-04-20 23:08:14 +00:00
(function () {
2026-04-21 05:39:48 +00:00
/* ------------------------------------------------------------------ */
/* State */
/* ------------------------------------------------------------------ */
var allGuidanceItems = [];
var allPortRows = [];
2026-04-21 06:59:07 +00:00
var guidanceFilter = "all";
var portFilter = "all";
2026-04-21 05:39:48 +00:00
/* ------------------------------------------------------------------ */
/* Helpers */
/* ------------------------------------------------------------------ */
2026-04-20 23:08:14 +00:00
function esc(s) {
var d = document.createElement("div");
d.textContent = s == null ? "" : String(s);
return d.innerHTML;
}
function len(a) { return Array.isArray(a) ? a.length : 0; }
2026-04-20 11:48:28 +00:00
2026-04-20 23:24:33 +00:00
/* ------------------------------------------------------------------ */
2026-04-21 05:39:48 +00:00
/* Port knowledge */
2026-04-20 23:24:33 +00:00
/* ------------------------------------------------------------------ */
2026-04-20 23:08:14 +00:00
var PORT_NOTES = {
2026-04-20 23:24:33 +00:00
"80": "HTTP( Web サーバー)",
"135": "Windows RPC — 正常",
"139": "NetBIOS ファイル共有 — LAN 内なら正常",
"443": "HTTPS( Web サーバー)",
"445": "SMB ファイル共有 — LAN 内なら正常",
2026-04-20 23:08:14 +00:00
"843": "Adobe Flash 関連またはローカルアプリ",
2026-04-20 23:24:33 +00:00
"902": "VMware 管理",
"912": "VMware 管理",
2026-04-21 05:39:48 +00:00
"1080": "SOCKS プロキシ",
2026-04-20 23:24:33 +00:00
"2015": "Caddy 開発サーバー",
"3000": "開発用ローカルサーバー( Node 等)— 正常",
"3306": "MySQL",
"3389": "リモートデスクトップ( RDP) ",
"5040": "Windows 標準サービス — 正常",
2026-04-20 23:08:14 +00:00
"5354": "mDNS( ローカルデバイス検索) — 正常",
"5357": "Windows ネットワーク検出 — 正常",
2026-04-20 23:24:33 +00:00
"5432": "PostgreSQL",
"5900": "VNC リモートデスクトップ",
"6379": "Redis",
2026-04-20 23:08:14 +00:00
"7680": "Windows Update 配信最適化 — 正常",
2026-04-20 23:24:33 +00:00
"8080": "開発用 HTTP サーバー — 正常",
"8443": "開発用 HTTPS サーバー",
2026-04-20 23:08:14 +00:00
"17500": "Dropbox — インストール済みなら正常",
"17600": "Dropbox — インストール済みなら正常",
2026-04-20 23:24:33 +00:00
"27015": "Steam — インストール済みなら正常",
"27017": "MongoDB",
"33060": "MySQL X Protocol"
2026-04-20 23:08:14 +00:00
};
2026-04-20 11:48:28 +00:00
2026-04-20 23:24:33 +00:00
function isEphemeral(port) {
2026-04-21 06:59:07 +00:00
return parseInt(port, 10) >= 49152;
2026-04-20 23:24:33 +00:00
}
function portNote(port, hint) {
if (PORT_NOTES[String(port)]) return PORT_NOTES[String(port)];
if (hint) return hint;
2026-04-21 05:39:48 +00:00
if (isEphemeral(port)) return "一時ポート( Windows 自動割当)— 正常";
2026-04-20 23:24:33 +00:00
return "";
}
2026-04-20 23:08:14 +00:00
function addressNote(addr) {
if (addr === "::" || addr === "0.0.0.0") return "全インターフェース( Windows 標準)";
2026-04-20 23:24:33 +00:00
if (addr === "::1" || addr === "127.0.0.1") return "localhost — PC 内だけ、安全";
2026-04-21 06:59:07 +00:00
if (/^100\./.test(addr)) return "Tailscale VPN — 正常";
2026-04-20 23:24:33 +00:00
if (/^192\.168\./.test(addr)) return "LAN — 正常";
2026-04-20 23:08:14 +00:00
if (/^fd7a:115c:/.test(addr)) return "Tailscale VPN( IPv6) — 正常";
return "";
}
2026-04-20 11:48:28 +00:00
2026-04-20 23:24:33 +00:00
/* ------------------------------------------------------------------ */
/* Render: Summary */
/* ------------------------------------------------------------------ */
2026-04-20 23:08:14 +00:00
function renderSummary(data) {
2026-04-20 23:24:33 +00:00
var uc = data.userContext || {};
var m = data.machine || {};
2026-04-20 23:08:14 +00:00
var uac = data.uac || {};
var net = data.network || {};
var listeners = Array.isArray(net.TcpListeners) ? net.TcpListeners.length : 0;
var envs = data.projectScan & & data.projectScan.EnvLikeRelPaths ? len(data.projectScan.EnvLikeRelPaths) : 0;
var hkcu = data.registryRun & & data.registryRun.HKCU_Run ? len(data.registryRun.HKCU_Run) : 0;
var hklm = data.registryRun & & data.registryRun.HKLM_Run ? len(data.registryRun.HKLM_Run) : 0;
var shareSafe = data.meta & & data.meta.shareSafe === true;
2026-04-20 11:48:28 +00:00
2026-04-20 23:08:14 +00:00
var items = [
{ label: "PC 名", value: m.Name || "—" },
{ label: "ユーザー", value: uc.UserName || "—" },
{ label: "管理者権限", value: uc.IsAdmin === true ? "あり" : "なし" },
{ label: "UAC EnableLUA", value: uac.EnableLUA != null ? String(uac.EnableLUA) : "—" },
{ label: "LISTEN ポート数", value: String(listeners) },
{ label: ".env 系ファイル", value: envs + " 件" },
{ label: "自動起動 HKCU / HKLM", value: hkcu + " / " + hklm }
];
if (shareSafe) items.unshift({ label: "モード", value: "ShareSafe" });
2026-04-20 11:48:28 +00:00
2026-04-20 23:24:33 +00:00
var html = '< div class = "summary-grid" > ';
2026-04-20 23:08:14 +00:00
items.forEach(function (it) {
2026-04-21 03:48:34 +00:00
var full = (it.label === "PC 名" || it.label === "モード") ? ' full' : '';
html += '< div class = "metric' + full + '" > < div class = "metric-label" > ' + esc(it.label) + '< / div > < div class = "metric-value" > ' + esc(it.value) + '< / div > < / div > ';
2026-04-20 23:08:14 +00:00
});
html += '< / div > ';
document.getElementById("summary").innerHTML = html;
}
2026-04-20 11:48:28 +00:00
2026-04-20 23:24:33 +00:00
/* ------------------------------------------------------------------ */
2026-04-21 06:59:07 +00:00
/* Render: Diff (2× 2 grid) */
2026-04-20 23:24:33 +00:00
/* ------------------------------------------------------------------ */
2026-04-20 23:08:14 +00:00
function renderDiff(data) {
var df = data.diffFromPrevious;
if (!df) return;
2026-04-21 05:39:48 +00:00
var html = '< div class = "diff-card" > ';
2026-04-20 23:08:14 +00:00
if (df.HasPrevious === false) {
2026-04-21 05:39:48 +00:00
html += '< div class = "diff-card-title" > 前回データなし< / div > ';
2026-04-21 06:59:07 +00:00
html += '< p style = "font-size:0.77rem;color:var(--text3)" > ' + esc(df.NoteJa || "初回実行のため差分はありません。") + '< / p > ';
2026-04-20 23:08:14 +00:00
} else {
2026-04-20 23:24:33 +00:00
html += '< div class = "diff-card-title" > 前回との差分< / div > ';
2026-04-20 23:08:14 +00:00
var tcp = df.TcpListeners || {};
var envd = df.EnvLikeRelPaths || {};
var hkcu = df.HKCU_Run || {};
var hklm = df.HKLM_Run || {};
2026-04-21 05:39:48 +00:00
html += '< div class = "diff-grid" > ';
2026-04-20 23:08:14 +00:00
[
2026-04-21 05:39:48 +00:00
{ label: "TCP 追加 / 削除", value: (tcp.AddedCount || 0) + " / " + (tcp.RemovedCount || 0) },
{ label: ".env 追加 / 削除", value: (envd.AddedCount || 0) + " / " + (envd.RemovedCount || 0) },
{ label: "Run HKCU +/− ", value: (hkcu.AddedCount || 0) + " / " + (hkcu.RemovedCount || 0) },
{ label: "Run HKLM +/− ", value: (hklm.AddedCount || 0) + " / " + (hklm.RemovedCount || 0) }
2026-04-20 23:08:14 +00:00
].forEach(function (r) {
html += '< div class = "metric" > < div class = "metric-label" > ' + esc(r.label) + '< / div > < div class = "metric-value" > ' + esc(r.value) + '< / div > < / div > ';
2026-04-20 11:48:28 +00:00
});
2026-04-20 23:08:14 +00:00
html += '< / div > ';
2026-04-21 05:39:48 +00:00
if (df.UserIsAdminChanged) html += '< p class = "diff-alert" > 管理者権限の状態が前回と変わりました。< / p > ';
if (df.UacEnableLuaChanged) html += '< p class = "diff-alert" > UAC 設定が前回と変わりました。< / p > ';
2026-04-20 11:48:28 +00:00
}
2026-04-20 23:08:14 +00:00
html += '< / div > ';
document.getElementById("diff").innerHTML = html;
}
2026-04-20 11:48:28 +00:00
2026-04-20 23:24:33 +00:00
/* ------------------------------------------------------------------ */
2026-04-21 06:59:07 +00:00
/* Render: Guidance */
2026-04-20 23:24:33 +00:00
/* ------------------------------------------------------------------ */
2026-04-20 23:08:14 +00:00
function buildGuidanceItems(data) {
var list = Array.isArray(data.guidanceJa) ? data.guidanceJa : [];
if (list.length) return list;
var uc = data.userContext || {};
var uac = data.uac || {};
var net = data.network || {};
var ssh = data.ssh || {};
var envs = data.projectScan & & data.projectScan.EnvLikeRelPaths ? len(data.projectScan.EnvLikeRelPaths) : 0;
var out = [];
if (uc.IsAdmin === true) {
out.push({ Level:"warn", Title:"管理者権限が有効です", Body:"この PC は管理者として動作しています。意図的な設定なら問題ありませんが、誤操作やウイルスが PC 全体に影響しやすくなります。", ActionJa:"社内 IT に「管理者権限が必要か確認したい」と相談する。急ぎでなければ次回 IT 棚卸しのタイミングで OK。" });
} else {
out.push({ Level:"ok", Title:"管理者権限は検出されませんでした", Body:"この監査実行時点では管理者権限は確認されていません。" });
2026-04-20 11:48:28 +00:00
}
2026-04-20 23:08:14 +00:00
if (uac.EnableLUA === 0) {
2026-04-20 23:24:33 +00:00
out.push({ Level:"warn", Title:"UAC( 確認ダイアログ) が無効です", Body:"アプリが PC 設定を変更しようとしても確認ダイアログが出ない状態です。マルウェアが気づかず実行されやすくなります。", ActionJa:"社内 IT に「UAC を有効にしてほしい」と伝える。" });
2026-04-20 23:08:14 +00:00
}
if (ssh.DirectoryExists & & len(ssh.Files) > 0) {
2026-04-21 06:59:07 +00:00
out.push({ Level:"info", Title:"SSH 鍵ファイルがあります", Body:"サーバーへの接続に使う鍵ファイルが ~/.ssh フォルダに存在します。鍵が漏えいするとサーバーに不正アクセスされる可能性があります。", ActionJa:"鍵ファイルのバックアップがあるか確認する。パスフレーズを設定済みか、エンジニアに確認する。" });
2026-04-20 23:08:14 +00:00
}
if (envs > 0) {
2026-04-20 23:24:33 +00:00
out.push({ Level:"info", Title:".env ファイルが " + envs + " 件見つかりました", Body:"API キーやパスワードが書かれることが多いファイル名です。中身はこのツールでは読んでいません。Git に含まれると外部に漏えいします。", ActionJa:"エンジニアに「.env が Git に含まれていないか確認して」と伝える。" });
2026-04-20 23:08:14 +00:00
}
var lc = Array.isArray(net.TcpListeners) ? net.TcpListeners.length : 0;
if (lc > 45) {
2026-04-20 23:24:33 +00:00
out.push({ Level:"info", Title:"待ち受けポートが多めです(" + lc + " 件)", Body:"多くのサービスが通信を受け付けています。使っていないアプリが起動したままの可能性があります。", ActionJa:"エンジニアに一覧を見てもらい、不要なアプリがないか確認してもらう。" });
2026-04-20 23:08:14 +00:00
}
return out;
}
2026-04-20 11:48:28 +00:00
2026-04-21 06:59:07 +00:00
function buildGuidanceFilterChips(items) {
2026-04-21 05:39:48 +00:00
var hasWarn = items.some(function(i) { return (i.Level||'').toLowerCase() === 'warn'; });
var hasInfo = items.some(function(i) { return (i.Level||'').toLowerCase() === 'info'; });
var row = document.getElementById("guidance-filter-row");
row.innerHTML = "";
2026-04-21 06:59:07 +00:00
function chip(label, value, extra) {
var b = document.createElement("button");
b.className = "chip" + (guidanceFilter === value ? " active" : "") + (extra ? " " + extra : "");
b.textContent = label;
b.addEventListener("click", function() {
2026-04-21 05:39:48 +00:00
guidanceFilter = value;
applyGuidanceFilter();
row.querySelectorAll(".chip").forEach(function(c) { c.classList.remove("active"); });
2026-04-21 06:59:07 +00:00
b.classList.add("active");
2026-04-21 05:39:48 +00:00
});
2026-04-21 06:59:07 +00:00
row.appendChild(b);
2026-04-21 05:39:48 +00:00
}
2026-04-21 06:59:07 +00:00
chip("全て", "all");
if (hasWarn) chip("注意のみ", "warn", "warn-chip");
if (hasInfo) chip("情報のみ", "info", "info-chip");
2026-04-21 05:39:48 +00:00
}
function applyGuidanceFilter() {
var items = guidanceFilter === "all"
? allGuidanceItems
: allGuidanceItems.filter(function(i) { return (i.Level||'info').toLowerCase() === guidanceFilter; });
2026-04-20 23:24:33 +00:00
var html = "";
2026-04-20 23:08:14 +00:00
items.forEach(function (item) {
var level = (item.Level || "info").toLowerCase();
2026-04-21 06:59:07 +00:00
var bc = level === "warn" ? "badge-warn" : (level === "ok" ? "badge-ok" : "badge-info");
var bl = level === "warn" ? "注意" : (level === "ok" ? "問題なし" : "情報");
2026-04-20 23:08:14 +00:00
html += '< div class = "card ' + level + '" > ';
2026-04-21 06:59:07 +00:00
html += '< div class = "card-header" > < span class = "badge ' + bc + '" > ' + bl + '< / span > < div class = "card-title" > ' + esc(item.Title || "") + '< / div > < / div > ';
2026-04-20 23:08:14 +00:00
html += '< div class = "card-body" > ' + esc(item.Body || "").replace(/\n/g, "< br / > ") + '< / div > ';
var action = item.ActionJa || item.actionJa;
2026-04-21 06:59:07 +00:00
if (action) html += '< div class = "action-box" > < span class = "action-label" > 対処方法< / span > ' + esc(action) + '< / div > ';
2026-04-20 23:08:14 +00:00
html += '< / div > ';
});
2026-04-21 06:59:07 +00:00
if (!html) html = '< p style = "font-size:0.78rem;color:var(--text3);padding:0.4rem 0" > 該当するチェック項目はありません。< / p > ';
2026-04-20 23:08:14 +00:00
document.getElementById("guidance").innerHTML = html;
}
2026-04-20 11:48:28 +00:00
2026-04-21 05:39:48 +00:00
function renderGuidance(data) {
allGuidanceItems = buildGuidanceItems(data);
2026-04-21 06:59:07 +00:00
buildGuidanceFilterChips(allGuidanceItems);
2026-04-21 05:39:48 +00:00
applyGuidanceFilter();
}
2026-04-20 23:24:33 +00:00
/* ------------------------------------------------------------------ */
2026-04-21 06:59:07 +00:00
/* Render: Port list */
2026-04-20 23:24:33 +00:00
/* ------------------------------------------------------------------ */
2026-04-21 06:59:07 +00:00
function buildPortFilterChips() {
2026-04-21 05:39:48 +00:00
var scopes = {};
allPortRows.forEach(function(r) { if (r.BindScope) scopes[r.BindScope] = true; });
var row = document.getElementById("port-filter-row");
row.innerHTML = "";
2026-04-21 06:59:07 +00:00
function chip(label, value, extra) {
var b = document.createElement("button");
b.className = "chip" + (portFilter === value ? " active" : "") + (extra ? " " + extra : "");
b.textContent = label;
b.addEventListener("click", function() {
2026-04-21 05:39:48 +00:00
portFilter = value;
applyPortFilter();
row.querySelectorAll(".chip").forEach(function(c) { c.classList.remove("active"); });
2026-04-21 06:59:07 +00:00
b.classList.add("active");
2026-04-21 05:39:48 +00:00
});
2026-04-21 06:59:07 +00:00
row.appendChild(b);
2026-04-20 23:08:14 +00:00
}
2026-04-21 06:59:07 +00:00
chip("全て", "all");
if (scopes["allInterfaces"]) chip("全IF のみ", "allInterfaces", "scope-all-chip");
if (scopes["localhost"]) chip("localhost のみ", "localhost", "scope-local-chip");
if (scopes["specific"]) chip("個別アドレス", "specific");
2026-04-21 05:39:48 +00:00
}
function applyPortFilter() {
var rows = portFilter === "all"
? allPortRows
: allPortRows.filter(function(r) { return r.BindScope === portFilter; });
var html = "";
var shown = Math.min(rows.length, 80);
2026-04-20 23:08:14 +00:00
for (var i = 0; i < shown ; i + + ) {
var r = rows[i];
2026-04-20 23:24:33 +00:00
var sc = r.BindScope === "allInterfaces" ? "scope-all" : (r.BindScope === "localhost" ? "scope-localhost" : "scope-specific");
var aN = addressNote(r.Address || "");
var pN = portNote(r.Port, r.WellKnownHint);
2026-04-20 23:08:14 +00:00
html += '< tr > ';
2026-04-20 23:24:33 +00:00
html += '< td class = "addr-cell" > ' + esc(r.Address || "") + (aN ? '< div class = "hint-line" > ' + esc(aN) + '< / div > ' : '') + '< / td > ';
2026-04-20 23:08:14 +00:00
html += '< td class = "port-cell" > ' + esc(r.Port || "") + '< / td > ';
2026-04-20 23:24:33 +00:00
html += '< td class = "' + sc + '" > ' + esc(r.BindScope || "") + '< / td > ';
2026-04-21 06:59:07 +00:00
html += '< td style = "font-size:0.73rem;color:var(--text3)" > ' + esc(pN) + '< / td > ';
2026-04-20 23:08:14 +00:00
html += '< / tr > ';
}
2026-04-21 05:39:48 +00:00
var tableHtml = rows.length
? '< table > < thead > < tr > < th > Address< / th > < th > Port< / th > < th > 種類< / th > < th > 用途< / th > < / tr > < / thead > < tbody > ' + html + '< / tbody > < / table > '
: '< p class = "port-empty" > 該当するポートはありません。< / p > ';
2026-04-21 06:59:07 +00:00
if (rows.length > 80) tableHtml += '< p style = "margin-top:0.4rem;font-size:0.68rem;color:var(--text3)" > 先頭 80 行表示。全データは生データを参照。< / p > ';
2026-04-21 05:39:48 +00:00
document.getElementById("listeners").innerHTML = tableHtml;
}
function renderListeners(data) {
var net = data.network || {};
var li = net.ListenerInsights;
allPortRows = (li & & li.RowsEnrichedSample) ? li.RowsEnrichedSample : (net.TcpListeners || []);
if (!allPortRows.length) return;
var s = (li & & li.Summary) || {};
2026-04-21 06:59:07 +00:00
var legend = '< span style = "color:var(--ok-color)" > localhost< / span > = PC 内のみ。'
+ ' < span style = "color:var(--warn-color)" > :: / 0.0.0.0< / span > = Windows 標準、通常は問題なし。';
2026-04-21 05:39:48 +00:00
if (s.AllInterfacesCount != null) {
2026-04-21 06:59:07 +00:00
legend += '< div class = "port-summary-row" style = "margin-top:0.4rem" > '
+ '< span class = "port-summary-item" > 全IF: < strong > ' + s.AllInterfacesCount + '< / strong > < / span > '
+ '< span class = "port-summary-item" > localhost: < strong > ' + s.LocalhostOnlyCount + '< / strong > < / span > '
+ '< span class = "port-summary-item" > 合計: < strong > ' + s.TotalListenRows + '< / strong > < / span > '
+ '< / div > ';
2026-04-21 05:39:48 +00:00
}
document.getElementById("port-legend").innerHTML = legend;
2026-04-21 06:59:07 +00:00
buildPortFilterChips();
2026-04-21 05:39:48 +00:00
applyPortFilter();
2026-04-20 23:08:14 +00:00
}
2026-04-20 11:48:28 +00:00
2026-04-20 23:24:33 +00:00
/* ------------------------------------------------------------------ */
2026-04-21 05:39:48 +00:00
/* Load data */
2026-04-20 23:24:33 +00:00
/* ------------------------------------------------------------------ */
2026-04-21 05:39:48 +00:00
function loadData(data) {
2026-04-21 08:52:58 +00:00
currentData = data;
2026-04-21 06:59:07 +00:00
document.getElementById("upload-wrap").style.display = "none";
document.getElementById("alt-file-btn").style.display = "";
document.getElementById("output").style.display = "flex";
2026-04-21 05:39:48 +00:00
renderSummary(data);
renderDiff(data);
renderGuidance(data);
renderListeners(data);
document.getElementById("raw").textContent = JSON.stringify(data, null, 2);
}
2026-04-21 08:52:58 +00:00
/* ------------------------------------------------------------------ */
/* AI prompt generation */
/* ------------------------------------------------------------------ */
var currentData = null;
function buildAiPrompt(data) {
var uc = data.userContext || {};
var m = data.machine || {};
var uac = data.uac || {};
var shareSafe = data.meta & & data.meta.shareSafe;
var items = allGuidanceItems.filter(function(i) {
return (i.Level||'').toLowerCase() !== 'ok';
});
var lines = [];
lines.push("以下は PC 監査ツール( Posimai PC Audit) の実行結果です。");
2026-04-21 09:11:02 +00:00
lines.push("各チェック項目への対応をお願いします。");
lines.push("");
lines.push("## 対応方針");
lines.push("- PowerShell コマンドや設定変更など、AI が自律的に実行・提案できる対応はそのまま進めてください。");
lines.push("- 以下に該当する場合は、実行前に必ず私に確認してください:");
lines.push(" - システム全体に影響する変更( UAC 設定変更・ユーザー権限変更など)");
lines.push(" - 元に戻しにくい操作(ファイル削除・レジストリ変更など)");
lines.push(" - 私の環境や業務の判断が必要なもの(「このソフトは必要か?」など)");
lines.push(" - リスクや副作用が不明確なもの");
2026-04-21 08:52:58 +00:00
lines.push("");
lines.push("## PC 情報");
lines.push("- PC 名: " + (m.Name || "不明"));
lines.push("- ユーザー: " + (uc.UserName || "不明"));
lines.push("- 管理者権限: " + (uc.IsAdmin === true ? "あり" : "なし"));
lines.push("- UAC (EnableLUA): " + (uac.EnableLUA != null ? String(uac.EnableLUA) : "不明"));
if (shareSafe) lines.push("- モード: ShareSafe( 機微情報は省略済み) ");
lines.push("");
if (items.length === 0) {
lines.push("## チェック結果");
lines.push("注意・情報レベルの指摘はありませんでした。");
} else {
lines.push("## チェック結果(" + items.length + " 件)");
items.forEach(function(item, idx) {
var level = (item.Level||'info').toLowerCase();
var tag = level === 'warn' ? '[注意]' : '[情報]';
lines.push("");
lines.push("### " + (idx + 1) + ". " + tag + " " + (item.Title || ""));
if (item.Body) lines.push((item.Body || "").replace(/\n/g, " "));
2026-04-21 09:11:02 +00:00
if (item.ActionJa) lines.push("推奨対処のヒント: " + item.ActionJa);
2026-04-21 08:52:58 +00:00
});
}
lines.push("");
lines.push("---");
2026-04-21 09:11:02 +00:00
lines.push("各項目について対応方針を示した上で、自律実行できるものはそのまま進め、判断が必要なものは確認を入れてください。");
2026-04-21 08:52:58 +00:00
return lines.join("\n");
}
document.addEventListener("DOMContentLoaded", function () {
var btn = document.getElementById("copy-prompt-btn");
btn.addEventListener("click", function () {
if (!currentData) return;
var text = buildAiPrompt(currentData);
navigator.clipboard.writeText(text).then(function () {
btn.textContent = "コピーしました";
btn.classList.add("copied");
setTimeout(function () {
btn.textContent = "AI 相談用にコピー";
btn.classList.remove("copied");
}, 2000);
}).catch(function () {
var ta = document.createElement("textarea");
ta.value = text;
ta.style.position = "fixed"; ta.style.opacity = "0";
document.body.appendChild(ta);
ta.select(); document.execCommand("copy");
document.body.removeChild(ta);
btn.textContent = "コピーしました";
btn.classList.add("copied");
setTimeout(function () {
btn.textContent = "AI 相談用にコピー";
btn.classList.remove("copied");
}, 2000);
});
});
});
2026-04-21 05:39:48 +00:00
/* ------------------------------------------------------------------ */
2026-04-21 06:59:07 +00:00
/* Auto-load */
2026-04-21 05:39:48 +00:00
/* ------------------------------------------------------------------ */
if (window.__AUDIT_PRELOAD__) {
document.addEventListener("DOMContentLoaded", function () {
loadData(window.__AUDIT_PRELOAD__);
});
}
/* ------------------------------------------------------------------ */
2026-04-21 06:59:07 +00:00
/* "別のファイルを読む" */
2026-04-21 05:39:48 +00:00
/* ------------------------------------------------------------------ */
document.addEventListener("DOMContentLoaded", function () {
document.getElementById("alt-file-btn").addEventListener("click", function () {
2026-04-21 06:59:07 +00:00
document.getElementById("upload-wrap").style.display = "flex";
document.getElementById("alt-file-btn").style.display = "none";
document.getElementById("output").style.display = "none";
2026-04-21 05:39:48 +00:00
});
});
/* ------------------------------------------------------------------ */
2026-04-21 06:59:07 +00:00
/* File input */
2026-04-21 05:39:48 +00:00
/* ------------------------------------------------------------------ */
document.addEventListener("DOMContentLoaded", function () {
document.getElementById("f").addEventListener("change", function (ev) {
var file = ev.target.files & & ev.target.files[0];
if (!file) return;
var reader = new FileReader();
reader.onload = function () {
var data;
try { data = JSON.parse(reader.result); }
catch (e) {
2026-04-21 06:59:07 +00:00
document.getElementById("output").style.display = "flex";
2026-04-21 05:39:48 +00:00
document.getElementById("guidance").innerHTML =
'< div class = "card warn" > < div class = "card-header" > < span class = "badge badge-warn" > エラー< / span > < div class = "card-title" > JSON の読み込みに失敗しました< / div > < / div > < div class = "card-body" > pc-audit が出力したファイルか確認してください。< / div > < / div > ';
return;
}
loadData(data);
};
reader.readAsText(file, "UTF-8");
});
2026-04-20 23:08:14 +00:00
});
})();
2026-04-20 11:48:28 +00:00
< / script >
< / body >
< / html >