File size: 16,242 Bytes
c4f5819
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ROGLAG — Self-host Docs</title>
<style>
  :root{--bg:#0b0f17;--panel:#121826;--panel2:#0f1420;--line:#1f2a3d;--txt:#e6edf6;
    --muted:#90a0b8;--accent:#4da3ff;--accent2:#7c5cff;--ok:#34d399;--warn:#fbbf24;--crit:#f87171;--code:#0a0e16;}
  *{box-sizing:border-box}html{scroll-behavior:smooth}
  body{margin:0;background:radial-gradient(1200px 600px at 80% -10%,#15203a 0%,var(--bg) 55%);
    color:var(--txt);font-family:-apple-system,"Segoe UI",Roboto,"Noto Sans Thai",sans-serif;line-height:1.65;font-size:15px}
  .wrap{max-width:1040px;margin:0 auto;padding:32px 22px 96px}
  header.hero{border:1px solid var(--line);border-radius:18px;padding:26px 30px;
    background:linear-gradient(135deg,#13203a,#0f1420);margin-bottom:14px}
  .hero h1{margin:0 0 6px;font-size:28px}.hero p{margin:0;color:var(--muted)}
  .chips{display:flex;gap:8px;flex-wrap:wrap;margin-top:14px}
  .chip{font-size:12px;padding:5px 11px;border:1px solid var(--line);border-radius:999px;background:#16203250;color:var(--accent)}
  nav.toc{position:sticky;top:0;z-index:5;backdrop-filter:blur(8px);background:#0b0f17cc;
    border:1px solid var(--line);border-radius:14px;padding:12px 16px;margin:18px 0 26px;display:flex;gap:14px;flex-wrap:wrap}
  nav.toc a{color:var(--muted);text-decoration:none;font-size:13px}nav.toc a:hover{color:var(--accent)}
  section{border:1px solid var(--line);border-radius:16px;padding:24px 26px;background:var(--panel);margin-bottom:22px}
  h2{font-size:21px;margin:0 0 10px;display:flex;align-items:center;gap:10px}
  h2 .num{font-size:13px;color:var(--accent2);border:1px solid var(--line);border-radius:8px;padding:2px 8px;background:var(--panel2)}
  h3{font-size:16px;margin:20px 0 8px;color:var(--accent)}
  .sub{color:var(--muted);margin:2px 0 16px;font-size:13.5px}
  code{background:var(--code);border:1px solid var(--line);border-radius:6px;padding:1px 6px;font-size:13px;color:#cfe3ff;
    font-family:"SF Mono",Menlo,Consolas,monospace}
  pre{background:var(--code);border:1px solid var(--line);border-radius:12px;padding:16px 18px;overflow:auto;font-size:12.8px;line-height:1.55}
  pre code{background:none;border:none;padding:0;color:#d6e2f2}
  table{width:100%;border-collapse:collapse;margin:12px 0;font-size:13.5px}
  th,td{border:1px solid var(--line);padding:9px 12px;text-align:left;vertical-align:top}
  th{background:var(--panel2);color:var(--accent)}
  .m-get{color:var(--ok);font-weight:700}.m-post{color:var(--warn);font-weight:700}.m-ws{color:var(--accent2);font-weight:700}
  .flow{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:14px 0;font-size:13px}
  .node{border:1px solid var(--line);background:var(--panel2);border-radius:10px;padding:8px 12px}
  .arrow{color:var(--accent2);font-weight:700}
  .callout{border-left:3px solid var(--accent);background:#0e1a2e;border-radius:0 10px 10px 0;padding:12px 16px;margin:14px 0;font-size:13.5px}
  .callout.warn{border-left-color:var(--warn);background:#241d0e}
  .callout.crit{border-left-color:var(--crit);background:#2a1414}
  ul,ol{margin:8px 0 8px 4px;padding-left:20px}li{margin:4px 0}
  .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px;margin:14px 0}
  .card{border:1px solid var(--line);border-radius:12px;padding:16px;background:var(--panel2)}
  .card h4{margin:0 0 6px;font-size:14px}.card p{margin:0;color:var(--muted);font-size:13px}
  footer{color:var(--muted);text-align:center;font-size:12px;margin-top:30px}
</style>
</head>
<body>
<div class="wrap">
  <header class="hero">
    <h1>🛡️ ROGLAG — Self-host Realtime Docs</h1>
    <p>ระบบตรวจจับความเสี่ยงจาก security log แบบ realtime + RAG ที่ self-host ได้ทั้งหมด รันได้แม้ออฟไลน์</p>
    <div class="chips">
      <span class="chip">FastAPI</span><span class="chip">WebSocket Realtime</span>
      <span class="chip">Rule Classifier</span><span class="chip">RAG</span>
      <span class="chip">PostgreSQL / SQLite</span><span class="chip">Docker</span>
    </div>
  </header>

  <nav class="toc">
    <a href="#about">ภาพรวม</a><a href="#run">วิธีรัน</a><a href="#features">ฟีเจอร์</a>
    <a href="#flow">Data Flow</a><a href="#api">API</a><a href="#csv">รูปแบบ CSV</a>
    <a href="#n8n">ต่อ n8n</a><a href="#prod">Production</a>
  </nav>

  <section id="about">
    <h2><span class="num">1</span> ภาพรวม</h2>
    <p class="sub">ทำอะไร และต่างจาก demo เดิมอย่างไร</p>
    <p>ROGLAG รับ log (อัปโหลด CSV หรือ stream เข้ามา) → <b>classify ด้วย rule</b> ทันที (brute force, port scan, web attack, priv esc, data exfil, C2 beacon) → เก็บลง database → <b>push เข้าหน้าเว็บแบบ realtime</b> ผ่าน WebSocket → ถามตอบด้วย RAG ภาษาไทยได้</p>
    <ul>
      <li><b>รันได้ออฟไลน์ 100%</b> — การตรวจจับใช้ rule-based ไม่ต้องมี API key / model</li>
      <li><b>Optional LLM</b> — ใส่ <code>OPENROUTER_API_KEY</code> เพื่อให้ตอบ RAG สวยขึ้น (มี fallback เสมอ)</li>
      <li><b>Portable DB</b> — default เป็น SQLite (zero-config), สลับเป็น PostgreSQL ได้ด้วย env เดียว</li>
      <li><b>Realtime จริง</b> — WebSocket fan-out ไปทุก browser ที่เปิดอยู่</li>
    </ul>
  </section>

  <section id="run">
    <h2><span class="num">2</span> วิธีรัน</h2>
    <p class="sub">เลือกได้ 3 แบบ — แนะนำ Docker สำหรับคนอื่นที่อยากรันต่อ</p>

    <h3>A) Docker Compose — full stack (PostgreSQL) ✅ แนะนำ</h3>
    <pre><code>docker compose up --build
# เปิด http://localhost:8000</code></pre>
    <p class="sub">คำสั่งเดียวได้ทั้ง API + PostgreSQL พร้อม volume เก็บข้อมูลถาวร</p>

    <h3>B) Docker Compose — SQLite ล้วน (ไม่ต้องมี postgres)</h3>
    <pre><code>docker compose up api-sqlite --build
# เปิด http://localhost:8000</code></pre>

    <h3>C) รันตรงด้วย Python (dev)</h3>
    <pre><code>python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
uvicorn app.main:app --host 0.0.0.0 --port 8000
# เปิด http://localhost:8000  (ใช้ SQLite ที่ ./data/lograg.db อัตโนมัติ)</code></pre>

    <div class="callout warn">
      รันด้วย <b>1 worker</b> เท่านั้น (default) เพราะ WebSocket manager เก็บ connection ใน process เดียว — ถ้าจะ scale หลาย worker ต้องใส่ Redis pub/sub (ดูหัวข้อ Production)
    </div>
    <div class="callout">
      เปิด LLM: ตั้งค่า <code>OPENROUTER_API_KEY</code> ใน <code>.env</code> (ดู <code>.env.example</code>) แล้วติ๊ก "ใช้ LLM" ในแท็บ Ask
    </div>
  </section>

  <section id="features">
    <h2><span class="num">3</span> ฟีเจอร์ในหน้า Console</h2>
    <div class="grid">
      <div class="card"><h4>⚡ ส่ง Mock Log</h4><p>ปุ่ม burst สร้าง log จำลองหลากหลาย <b>ไม่ซ้ำทุกครั้ง</b> ยิงเข้า pipeline จริง เห็นใน feed ทันที</p></div>
      <div class="card"><h4>▶ Live Stream</h4><p>สตรีม mock log ต่อเนื่องตาม rate/interval ที่ตั้ง เพื่อจำลองทราฟฟิกจริงสำหรับทดสอบ</p></div>
      <div class="card"><h4>⬇ Generate CSV</h4><p>สร้างไฟล์ CSV ใหม่ที่สุ่มไม่ซ้ำ (unseeded) ดาวน์โหลดไปทดสอบ upload ได้</p></div>
      <div class="card"><h4>🔍 Upload CSV</h4><p>อัปโหลด CSV → classify ทุกแถว realtime → เทียบความแม่นยำกับ label เดิม (ถ้ามี)</p></div>
      <div class="card"><h4>📡 Live Feed</h4><p>แสดง event เข้ามาสดๆ สีตาม severity + เหตุผลการตรวจจับ + MITRE</p></div>
      <div class="card"><h4>🚨 Alerts</h4><p>รวม critical event เป็น alert (group ตาม IP/user) พร้อม runbook แนะนำ</p></div>
      <div class="card"><h4>💬 Ask (RAG)</h4><p>ถามภาษาไทยเกี่ยวกับ log ที่เก็บไว้ — keyword retrieval + สรุป (local หรือ LLM)</p></div>
      <div class="card"><h4>📊 Stats</h4><p>สัดส่วน category / severity / risk rate แบบ realtime</p></div>
    </div>
  </section>

  <section id="flow">
    <h2><span class="num">4</span> Data Flow</h2>
    <div class="flow">
      <span class="node">CSV upload / Mock / Stream</span><span class="arrow"></span>
      <span class="node">parse</span><span class="arrow"></span>
      <span class="node">detect_rule()<br><small>classify</small></span><span class="arrow"></span>
      <span class="node">DB<br><small>log_events</small></span><span class="arrow"></span>
      <span class="node">WebSocket<br><small>broadcast</small></span><span class="arrow"></span>
      <span class="node">Browser feed</span>
    </div>
    <p>โค้ดหลัก: <code>app/ingest.py</code> (parse+classify+store) · <code>app/realtime.py</code> (WebSocket manager + stream) · <code>app/rag.py</code> (retrieval+answer) · <code>app/main.py</code> (routes)</p>
  </section>

  <section id="api">
    <h2><span class="num">5</span> API Reference</h2>
    <table>
      <tr><th>Method</th><th>Path</th><th>หน้าที่</th></tr>
      <tr><td class="m-get">GET</td><td><code>/health</code></td><td>สถานะระบบ (storage, llm_ready, ws_clients, stream_running)</td></tr>
      <tr><td class="m-get">GET</td><td><code>/api/stats</code></td><td>สถิติ category/severity/source/top_ips</td></tr>
      <tr><td class="m-get">GET</td><td><code>/api/logs?limit&offset&category&severity&source&q</code></td><td>query log ที่เก็บไว้</td></tr>
      <tr><td class="m-get">GET</td><td><code>/api/alerts?limit</code></td><td>critical alerts ที่ group แล้ว</td></tr>
      <tr><td class="m-post">POST</td><td><code>/api/ask</code></td><td>RAG Q&A — body: <code>{question, use_llm, model, limit}</code></td></tr>
      <tr><td class="m-post">POST</td><td><code>/api/ingest/csv</code></td><td>อัปโหลดไฟล์ CSV (multipart <code>file</code>) → ตรวจจับ + broadcast</td></tr>
      <tr><td class="m-get">GET</td><td><code>/api/generate/csv?count&profile</code></td><td>ดาวน์โหลด CSV สุ่มใหม่ (profile: mixed/attack/realistic)</td></tr>
      <tr><td class="m-post">POST</td><td><code>/api/mock/emit?count&profile</code></td><td>ยิง mock burst เข้าระบบ realtime</td></tr>
      <tr><td class="m-post">POST</td><td><code>/api/mock/stream/start?rate&interval&profile</code></td><td>เริ่มสตรีม mock ต่อเนื่อง</td></tr>
      <tr><td class="m-post">POST</td><td><code>/api/mock/stream/stop</code></td><td>หยุดสตรีม</td></tr>
      <tr><td class="m-ws">WS</td><td><code>/ws</code></td><td>realtime feed — รับ <code>{type:event|summary|hello}</code></td></tr>
    </table>
    <h3>ตัวอย่าง (curl)</h3>
    <pre><code># ยิง mock 50 event แบบ attack-heavy
curl -X POST "http://localhost:8000/api/mock/emit?count=50&profile=attack"

# ถาม RAG
curl -X POST http://localhost:8000/api/ask \
  -H "Content-Type: application/json" \
  -d '{"question":"มี brute force จาก ip ไหนบ้าง"}'

# อัปโหลด CSV
curl -X POST http://localhost:8000/api/ingest/csv -F "file=@roglag_logs.csv"</code></pre>
  </section>

  <section id="csv">
    <h2><span class="num">6</span> รูปแบบ CSV</h2>
    <p class="sub">คอลัมน์ที่ระบบอ่าน — ต้องมีอย่างน้อย <code>message</code> (ใช้ตรวจจับ) หรือ <code>source</code></p>
    <pre><code>timestamp,source,category,severity,message,src_ip,dst_ip,user,dst_port,path,action</code></pre>
    <ul>
      <li><code>message</code> = ข้อความ log ดิบ → ใช้ <b>ตรวจจับ category</b></li>
      <li><code>category</code> (ถ้ามี) = label เดิม → ระบบเอาไป<b>วัดความแม่นยำ</b>การตรวจจับ ไม่ใช้ในการตัดสิน</li>
      <li>field อื่นเป็น metadata เสริม</li>
    </ul>
    <div class="callout">ใช้ปุ่ม <b>Generate CSV</b> สร้างไฟล์ตัวอย่างที่ถูกฟอร์แมตพอดี แล้วเอามา Upload ทดสอบได้ทันที</div>
  </section>

  <section id="n8n">
    <h2><span class="num">7</span> ต่อ n8n (automation)</h2>
    <p class="sub">ใช้ ROGLAG เป็น detection engine แล้วให้ n8n ทำ action ต่อ</p>
    <ol>
      <li>n8n <b>Schedule</b> node เรียก <code>GET /api/alerts?limit=20</code> ทุก X นาที (หรือเก็บ <code>last_seen</code> ไว้กรองของใหม่)</li>
      <li><b>Filter</b> เฉพาะ alert ใหม่ → <b>HTTP Request</b> ไป enrich (AbuseIPDB/GeoIP ด้วย <code>src_ip</code>)</li>
      <li>เรียก <code>POST /api/ask</code> ให้สรุปภาษาไทย แนบเข้า ticket</li>
      <li><b>Action</b>: สร้าง ticket (Jira/GitHub), แจ้ง Slack/LINE, (ออปชัน) block IP</li>
    </ol>
    <div class="callout warn">การ block IP อัตโนมัติเป็น action ผลกระทบสูง — ควรมี allowlist + approval node + TTL</div>
  </section>

  <section id="prod">
    <h2><span class="num">8</span> หมายเหตุ Production / Security</h2>
    <div class="callout crit">
      <b>ยังไม่มี authentication.</b> CORS เปิด <code>*</code> และทุก endpoint เปิด public — เหมาะกับ dev/self-host ในเครือข่ายปิดเท่านั้น ก่อนขึ้นจริงต้องวาง reverse proxy (Traefik/Caddy) + auth (API key/OAuth) หน้า API
    </div>
    <ul>
      <li><b>Scale realtime:</b> WebSocket manager อยู่ใน process เดียว → ถ้าใช้หลาย worker/instance ต้องใช้ Redis pub/sub broadcast ข้าม process</li>
      <li><b>Storage:</b> SQLite เหมาะ single-node; ปริมาณสูงให้ใช้ PostgreSQL (ตั้ง <code>DATABASE_URL</code>) และทำ retention/partition</li>
      <li><b>Vector RAG:</b> เวอร์ชันนี้ใช้ keyword retrieval (ออฟไลน์); อัปเกรดเป็น pgvector/Qdrant ได้ตามแผนใน <a href="/plan">plan</a></li>
      <li><b>Secrets:</b> อย่า commit <code>.env</code> — ใช้ secret manager ใน production</li>
    </ul>
  </section>

  <footer>ROGLAG self-host · ดูแผนยกระดับ production ที่ <a href="/plan">/plan</a></footer>
</div>
</body>
</html>