File size: 26,644 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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ROGLAG — Production Plan & Stack</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;
    --chip:#16203250;
  }
  *{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:1080px; margin:0 auto; padding:32px 22px 96px}
  header.hero{
    border:1px solid var(--line); border-radius:18px; padding:28px 30px;
    background:linear-gradient(135deg,#13203a,#0f1420); margin-bottom:14px;
  }
  .hero h1{margin:0 0 6px; font-size:30px; letter-spacing:.3px}
  .hero p{margin:0; color:var(--muted)}
  .tagline{display:flex; gap:8px; flex-wrap:wrap; margin-top:16px}
  .chip{font-size:12px; padding:5px 11px; border:1px solid var(--line);
    border-radius:999px; background:var(--chip); 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 4px; 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:22px 0 8px; color:var(--accent)}
  .sub{color:var(--muted); margin:2px 0 18px; font-size:13.5px}
  p{margin:8px 0}
  ul,ol{margin:8px 0 8px 4px; padding-left:20px}
  li{margin:4px 0}
  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); font-weight:600}
  td b{color:#fff}
  .grid{display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,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 8px; font-size:14.5px; color:var(--txt)}
  .card p{margin:0; color:var(--muted); font-size:13px}
  .pill{display:inline-block; font-size:11px; padding:2px 8px; border-radius:999px; margin-left:6px}
  .p-now{background:#0e2a1e; color:var(--ok); border:1px solid #1c4b36}
  .p-next{background:#2a230e; color:var(--warn); border:1px solid #4b3d1c}
  .p-later{background:#241a2e; color:#c4a3ff; border:1px solid #3d2c4b}
  .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; color:#c8d8ee; font-size:13.5px}
  .callout.warn{border-left-color:var(--warn); background:#241d0e}
  .callout.crit{border-left-color:var(--crit); background:#2a1414}
  footer{color:var(--muted); text-align:center; font-size:12px; margin-top:30px}
  .legend{display:flex; gap:14px; flex-wrap:wrap; font-size:12px; color:var(--muted); margin-top:8px}
</style>
</head>
<body>
<div class="wrap">

  <header class="hero">
    <h1>🛡️ ROGLAG — Production Plan & Tech Stack</h1>
    <p>แผนยกระดับจาก demo (stdlib HTTP + in-memory) ไปสู่ระบบ Security Log RAG + Automation ระดับ production</p>
    <div class="tagline">
      <span class="chip">RAG</span>
      <span class="chip">Security Log Analytics</span>
      <span class="chip">n8n Automation</span>
      <span class="chip">Vector DB</span>
      <span class="chip">Docker</span>
      <span class="chip">FastAPI</span>
      <span class="chip">PostgreSQL</span>
    </div>
    <div class="legend">
      <span><span class="pill p-now">NOW</span> เฟส 1 (MVP รันได้)</span>
      <span><span class="pill p-next">NEXT</span> เฟส 2 (production core)</span>
      <span><span class="pill p-later">LATER</span> เฟส 3 (scale & ops)</span>
    </div>
  </header>

  <nav class="toc">
    <a href="#run">▶ วิธีรัน</a>
    <a href="#assess">① ประเมินของเดิม</a>
    <a href="#arch">② Target Architecture</a>
    <a href="#stack">③ Stack รวบรวม</a>
    <a href="#pipeline">④ Pipeline จริง</a>
    <a href="#n8n">⑤ n8n Workflows</a>
    <a href="#db">⑥ Database</a>
    <a href="#docker">⑦ Docker</a>
    <a href="#roadmap">⑧ Roadmap</a>
  </nav>

  <!-- RUN -->
  <section id="run">
    <h2><span class="num">RUN</span> วิธีรันโปรเจกต์ตอนนี้</h2>
    <p class="sub">ต้องมี Python 3.10+ — local mode ใช้ stdlib ล้วน ไม่ต้องลง dependency</p>
    <pre><code># Local mode (keyword RAG + local fallback) — ใช้ได้ทันที
python3 server.py            # เปิด http://127.0.0.1:8765

# เปิด LLM summary ผ่าน OpenRouter
export OPENROUTER_API_KEY="your_key"
python3 server.py

# Vector RAG จริง (build index ก่อน)
python3 build_vector_index.py --limit 10000 --batch-size 32
python3 server.py

# รันกับชุด 50k
LOGRAG_DATA_FILE=Data/files/security_logs_extended_50k.jsonl python3 server.py</code></pre>
    <div class="callout">
      โหมด local ทำงานได้แม้ไม่มี API key — ระบบ fallback ทุกชั้น (vector → keyword → rule-based local answer)
    </div>
  </section>

  <!-- ASSESS -->
  <section id="assess">
    <h2><span class="num"></span> ประเมินของเดิม (ทำไมยังไม่ production-ready)</h2>
    <p class="sub">เข้าใจข้อจำกัดก่อน เพื่อรู้ว่าต้องเปลี่ยนอะไร</p>
    <table>
      <tr><th>ด้าน</th><th>ของเดิม (demo)</th><th>ปัญหาตอนขึ้นจริง</th></tr>
      <tr><td><b>Web server</b></td><td><code>http.server</code> + ThreadingHTTPServer</td><td>ไม่มี async/connection pool, ไม่เหมาะ traffic จริง, ไม่มี auth</td></tr>
      <tr><td><b>Data store</b></td><td>โหลด JSONL ทั้งไฟล์เข้า RAM ตอน start</td><td>ไม่ scale, restart ช้า, query ใหม่ไม่ได้, ไม่มี retention</td></tr>
      <tr><td><b>Index</b></td><td>สร้าง TF-IDF/vector ใน memory</td><td>หายเมื่อ restart, ไม่ incremental, single process</td></tr>
      <tr><td><b>Logic ซ้ำ</b></td><td><code>server.py</code> กับ <code>api/_lograg.py</code> ซ้ำกันเกือบหมด</td><td>แก้ rule ต้องแก้ 2 ที่ — แหล่ง bug</td></tr>
      <tr><td><b>Ingestion</b></td><td>ไม่มี — ใช้ไฟล์สังเคราะห์ static</td><td>ไม่มีทางรับ log จริงแบบ real-time</td></tr>
      <tr><td><b>Automation</b></td><td>หน้าเว็บ poll <code>/api/alerts</code> ทุก 15 วิ</td><td>ไม่มี action จริง (block IP, ticket, notify)</td></tr>
      <tr><td><b>Secrets/Auth</b></td><td>ไม่มี authentication เลย</td><td>เปิด public = ใครก็เรียก API ได้</td></tr>
    </table>
    <div class="callout crit">
      <b>สิ่งที่เก็บไว้ได้:</b> CATEGORY_META (rule + MITRE + runbook), logic <code>detect_rule</code>, query expansion ไทย/อังกฤษ, โครงสร้าง API — ทั้งหมดนี้ port ไป FastAPI ได้ตรงๆ
    </div>
  </section>

  <!-- ARCHITECTURE -->
  <section id="arch">
    <h2><span class="num"></span> Target Architecture (production)</h2>
    <p class="sub">แยกเป็นชั้นชัดเจน: ingest → process → store → RAG/API → automation → notify</p>
    <div class="flow">
      <span class="node">Log Sources<br><small>auth/nginx/fw/syslog</small></span>
      <span class="arrow"></span>
      <span class="node">Collector<br><small>Vector / Fluent Bit</small></span>
      <span class="arrow"></span>
      <span class="node">Queue<br><small>Redpanda / Redis Stream</small></span>
      <span class="arrow"></span>
      <span class="node">Processor<br><small>classify + embed</small></span>
      <span class="arrow"></span>
      <span class="node">Stores<br><small>PG + Qdrant + OpenSearch</small></span>
    </div>
    <div class="flow">
      <span class="node">RAG API<br><small>FastAPI</small></span>
      <span class="arrow"></span>
      <span class="node">n8n<br><small>orchestration</small></span>
      <span class="arrow"></span>
      <span class="node">Enrich<br><small>GeoIP / threat intel</small></span>
      <span class="arrow"></span>
      <span class="node">Action<br><small>ticket / block / notify</small></span>
    </div>
    <p>หลักการ: ทุก critical alert ที่ classifier จับได้ → ยิงเข้า n8n → enrich ด้วย threat intel → เรียก RAG API สรุปเป็นภาษาไทย → สร้าง ticket + แจ้งเตือน + (ออปชัน) block IP อัตโนมัติ</p>
  </section>

  <!-- STACK -->
  <section id="stack">
    <h2><span class="num"></span> Tech Stack รวบรวม</h2>
    <p class="sub">เลือกแบบ self-hostable ก่อน (ลง Docker ได้หมด) แล้วค่อยสลับไป managed service ทีหลัง</p>

    <h3>Core Pipeline</h3>
    <table>
      <tr><th>ชั้น</th><th>เลือกใช้</th><th>ทางเลือก</th><th>เฟส</th></tr>
      <tr><td><b>Log collector</b></td><td>Vector.dev</td><td>Fluent Bit, Filebeat</td><td><span class="pill p-next">NEXT</span></td></tr>
      <tr><td><b>Message queue</b></td><td>Redpanda (Kafka API)</td><td>Redis Streams, NATS, Kafka</td><td><span class="pill p-next">NEXT</span></td></tr>
      <tr><td><b>API / RAG service</b></td><td>FastAPI + Uvicorn</td><td>Litestar, Flask</td><td><span class="pill p-now">NOW</span></td></tr>
      <tr><td><b>Task worker</b></td><td>ARQ / Celery + Redis</td><td>Dramatiq, RQ</td><td><span class="pill p-next">NEXT</span></td></tr>
    </table>

    <h3>Data Stores</h3>
    <table>
      <tr><th>ชนิด</th><th>เลือกใช้</th><th>ใช้เก็บอะไร</th></tr>
      <tr><td><b>Relational</b></td><td>PostgreSQL 16</td><td>alerts, incidents, users, audit, runbook, rule config</td></tr>
      <tr><td><b>Vector DB</b></td><td>Qdrant <small>(โค้ดเดิมรองรับอยู่แล้ว)</small></td><td>embeddings ของ log message สำหรับ RAG</td></tr>
      <tr><td><b>Log search</b></td><td>OpenSearch <small>(หรือ ClickHouse)</small></td><td>full-text + time-series log จำนวนมหาศาล</td></tr>
      <tr><td><b>Cache / queue</b></td><td>Redis</td><td>rate-limit, task queue, dedup alert key</td></tr>
    </table>
    <div class="callout">
      <b>ทางลัดประหยัด:</b> ใช้ <code>pgvector</code> บน PostgreSQL แทน Qdrant ได้ในเฟสแรก เหลือ DB เดียว — แต่ Qdrant scale ดีกว่าเมื่อ vector เยอะ. ClickHouse เหมาะกว่า OpenSearch ถ้าเน้น analytics/ราคาถูก
    </div>

    <h3>AI / RAG</h3>
    <table>
      <tr><th>หน้าที่</th><th>เลือกใช้</th><th>ทางเลือก</th></tr>
      <tr><td><b>Embeddings</b></td><td>OpenRouter <small>(เดิม)</small></td><td>OpenAI, Ollama (local: nomic-embed-text)</td></tr>
      <tr><td><b>LLM สรุป</b></td><td>OpenRouter <small>(เดิม)</small></td><td>Ollama (llama3/qwen), vLLM self-host</td></tr>
      <tr><td><b>RAG framework</b></td><td>เขียนเอง <small>(เบา, คุมได้)</small></td><td>LlamaIndex, Haystack</td></tr>
    </table>

    <h3>Automation, Frontend & Ops</h3>
    <div class="grid">
      <div class="card"><h4>Automation</h4><p>n8n (self-host) — orchestrate alert → enrich → action</p></div>
      <div class="card"><h4>Frontend</h4><p>คงหน้า HTML เดิมได้ หรือย้ายไป Next.js + Tailwind เมื่อต้อง auth/role</p></div>
      <div class="card"><h4>Notify</h4><p>Slack / Discord / Email (SMTP) / PagerDuty / LINE Notify</p></div>
      <div class="card"><h4>Threat Intel</h4><p>AbuseIPDB, VirusTotal, GreyNoise, MaxMind GeoIP</p></div>
      <div class="card"><h4>Observability</h4><p>Prometheus + Grafana + Loki</p></div>
      <div class="card"><h4>Auth/Gateway</h4><p>Traefik / Caddy + OAuth2-proxy หรือ Keycloak</p></div>
      <div class="card"><h4>Secrets</h4><p>Doppler / Infisical / .env + SOPS</p></div>
      <div class="card"><h4>Container/CI</h4><p>Docker Compose → Kubernetes, GitHub Actions</p></div>
    </div>
  </section>

  <!-- PIPELINE -->
  <section id="pipeline">
    <h2><span class="num"></span> Pipeline จริง (data flow ละเอียด)</h2>
    <p class="sub">เส้นทางของ log ตั้งแต่เข้าระบบจนเกิด action</p>
    <ol>
      <li><b>Ingest</b> — Vector/Fluent Bit อ่าน log จาก source (syslog, file, http) → ส่งเข้า Redpanda topic <code>logs.raw</code></li>
      <li><b>Classify</b> — worker consume <code>logs.raw</code> → รัน <code>detect_rule()</code> (port มาจากของเดิม) → ใส่ category/severity/MITRE → เขียนลง PostgreSQL + OpenSearch</li>
      <li><b>Embed</b> — worker เดียวกัน (หรือแยก) เรียก embedding model → upsert vector เข้า Qdrant พร้อม payload (row + metadata)</li>
      <li><b>Detect critical</b> — ถ้า severity = critical → group ตาม <code>alert_key</code> (logic เดิม) → publish เข้า topic <code>alerts.critical</code> + insert ตาราง <code>alerts</code></li>
      <li><b>Orchestrate</b> — n8n trigger จาก webhook/queue → enrich → RAG → action (รายละเอียดข้อ ⑤)</li>
      <li><b>Query/RAG</b> — FastAPI <code>/api/ask</code>: retrieve จาก Qdrant (vector) + OpenSearch (keyword) → hybrid rank → ส่ง context ให้ LLM → ตอบภาษาไทย</li>
    </ol>
    <pre><code>logs.raw  ──&gt; [classify worker] ──&gt; PostgreSQL (structured + alerts)
                      │              └─&gt; OpenSearch (full-text search)
                      └─&gt; [embed worker] ──&gt; Qdrant (vector RAG)

severity=critical ──&gt; alerts.critical (queue) ──&gt; n8n webhook ──&gt; enrich+RAG+action

User/Frontend ──&gt; FastAPI /api/ask ──&gt; hybrid retrieve (Qdrant + OpenSearch)
                                          └─&gt; LLM (OpenRouter/Ollama) ──&gt; Thai answer</code></pre>
  </section>

  <!-- N8N -->
  <section id="n8n">
    <h2><span class="num"></span> n8n Workflows</h2>
    <p class="sub">หัวใจของ automation — แต่ละ workflow คือ DAG ของ node</p>

    <h3>WF-1: Critical Alert Responder</h3>
    <div class="flow">
      <span class="node">Webhook<br><small>/critical</small></span><span class="arrow"></span>
      <span class="node">GeoIP + AbuseIPDB<br><small>enrich src_ip</small></span><span class="arrow"></span>
      <span class="node">HTTP: RAG<br><small>POST /api/ask</small></span><span class="arrow"></span>
      <span class="node">Switch<br><small>by category</small></span><span class="arrow"></span>
      <span class="node">Create Ticket<br><small>Jira/GitHub</small></span><span class="arrow"></span>
      <span class="node">Notify<br><small>Slack/LINE</small></span>
    </div>
    <ul>
      <li><b>Trigger:</b> n8n Webhook node รับ payload จาก <code>alerts.critical</code> (หรือ Schedule poll <code>/api/alerts?since=</code>)</li>
      <li><b>Enrich:</b> HTTP Request → AbuseIPDB/VirusTotal เช็ค reputation ของ <code>src_ip</code>, MaxMind GeoIP หาประเทศ</li>
      <li><b>RAG summary:</b> POST <code>/api/ask</code> ด้วย <code>context_rows</code> ของ alert → ได้สรุปไทย + runbook</li>
      <li><b>Route:</b> Switch ตาม category → <code>priv_esc/data_exfil</code> = แจ้ง on-call ทันที (PagerDuty); อื่นๆ = ticket ปกติ</li>
      <li><b>Action:</b> สร้าง ticket, ส่ง Slack/LINE, (ออปชัน) เรียก firewall API block IP ที่ reputation แย่</li>
    </ul>

    <h3>WF-2: Daily Threat Digest</h3>
    <p>Schedule (เช้า 9 โมง) → query <code>/api/stats</code> + top IPs → RAG สรุปภาพรวม 24 ชม. → ส่งรายงานเข้า Slack channel</p>

    <h3>WF-3: Auto-block Feedback Loop</h3>
    <p>เมื่อ analyst กด "confirm threat" บน dashboard → webhook → n8n update firewall rule + บันทึก audit ลง PostgreSQL + (ออปชัน) ใส่ IP เข้า blocklist ที่ feed กลับ collector</p>

    <div class="callout warn">
      <b>ข้อควรระวัง auto-block:</b> การ block IP อัตโนมัติคือ action ที่ผลกระทบสูง — ควรมี allowlist (IP ภายใน/พาร์ทเนอร์), require approval node สำหรับ critical action, และ TTL ให้ block ชั่วคราวก่อน
    </div>
  </section>

  <!-- DB -->
  <section id="db">
    <h2><span class="num"></span> Database Schema (PostgreSQL)</h2>
    <p class="sub">โครงเริ่มต้น — เก็บ structured log, alert grouping, audit trail</p>
    <pre><code>-- log ที่ผ่าน classify แล้ว (hot 30-90 วัน, retention policy แยก)
CREATE TABLE log_events (
  id           BIGSERIAL PRIMARY KEY,
  ts           TIMESTAMPTZ NOT NULL,
  source       TEXT NOT NULL,              -- auth/nginx/firewall/system
  category     TEXT NOT NULL,              -- brute_force, c2_beacon, ...
  severity     TEXT NOT NULL,              -- info/medium/high/critical
  mitre        TEXT,
  risk_score   SMALLINT,
  src_ip       INET,
  dst_ip       INET,
  "user"       TEXT,
  message      TEXT NOT NULL,
  raw          JSONB NOT NULL,             -- เก็บ field ดิบทั้งหมด
  embedded     BOOLEAN DEFAULT FALSE       -- ส่งเข้า Qdrant แล้วหรือยัง
);
CREATE INDEX ON log_events (ts DESC);
CREATE INDEX ON log_events (category, severity);
CREATE INDEX ON log_events (src_ip);

-- alert ที่ group แล้ว (จาก alert_key เดิม)
CREATE TABLE alerts (
  id            TEXT PRIMARY KEY,          -- ALT-xxxxxx
  alert_key     TEXT NOT NULL,
  title         TEXT NOT NULL,
  category      TEXT NOT NULL,
  severity      TEXT NOT NULL DEFAULT 'critical',
  mitre         TEXT,
  status        TEXT NOT NULL DEFAULT 'new', -- new/ack/resolved/false_positive
  event_count   INT NOT NULL,
  first_seen    TIMESTAMPTZ NOT NULL,
  last_seen     TIMESTAMPTZ NOT NULL,
  src_ip        INET, dst_ip INET, "user" TEXT,
  rag_summary   TEXT,                      -- คำตอบจาก LLM
  enrichment    JSONB,                     -- ผล GeoIP/threat intel
  assigned_to   TEXT,
  created_at    TIMESTAMPTZ DEFAULT now(),
  updated_at    TIMESTAMPTZ DEFAULT now()
);

-- audit ทุก action (โดยเฉพาะ auto-block)
CREATE TABLE audit_log (
  id          BIGSERIAL PRIMARY KEY,
  ts          TIMESTAMPTZ DEFAULT now(),
  actor       TEXT NOT NULL,               -- user / n8n / system
  action      TEXT NOT NULL,               -- block_ip / create_ticket / ack
  target      TEXT,
  alert_id    TEXT REFERENCES alerts(id),
  detail      JSONB
);

-- rule config (ย้าย CATEGORY_META ออกจาก hardcode มาเป็น data)
CREATE TABLE rule_config (
  category    TEXT PRIMARY KEY,
  label       TEXT, mitre TEXT, risk SMALLINT, runbook TEXT,
  enabled     BOOLEAN DEFAULT TRUE
);</code></pre>
    <div class="callout">
      ย้าย <code>CATEGORY_META</code> จาก hardcode ใน Python มาเป็นตาราง <code>rule_config</code> → แก้ runbook/risk ได้โดยไม่ต้อง deploy ใหม่ และเลิกปัญหา logic ซ้ำ 2 ไฟล์
    </div>
  </section>

  <!-- DOCKER -->
  <section id="docker">
    <h2><span class="num"></span> Docker (โครงอ้างอิงสำหรับขึ้น proj จริง)</h2>
    <p class="sub">ไฟล์อ้างอิง — ปรับ version/secret ตอนทำจริง</p>

    <h3>Dockerfile (FastAPI service)</h3>
    <pre><code>FROM python:3.12-slim
WORKDIR /app
ENV PYTHONUNBUFFERED=1 PIP_NO_CACHE_DIR=1

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .
EXPOSE 8000
# ใช้ uvicorn worker จริง (อย่าใช้ http.server เดิม)
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]</code></pre>

    <h3>docker-compose.yml (full stack dev)</h3>
    <pre><code>services:
  api:
    build: .
    ports: ["8000:8000"]
    env_file: .env
    depends_on: [postgres, qdrant, redis]

  worker:                      # classify + embed consumer
    build: .
    command: python -m app.worker
    env_file: .env
    depends_on: [postgres, qdrant, redis, redpanda]

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: ${PG_PASSWORD}
      POSTGRES_DB: roglag
    volumes: ["pgdata:/var/lib/postgresql/data"]
    ports: ["5432:5432"]

  qdrant:
    image: qdrant/qdrant:latest
    volumes: ["qdrant:/qdrant/storage"]
    ports: ["6333:6333"]

  redis:
    image: redis:7-alpine
    ports: ["6379:6379"]

  redpanda:                    # Kafka-compatible, เบากว่า Kafka
    image: redpandadata/redpanda:latest
    command: redpanda start --smp 1 --overprovisioned --node-id 0
    ports: ["9092:9092"]

  n8n:
    image: n8nio/n8n:latest
    environment:
      N8N_HOST: localhost
      WEBHOOK_URL: http://n8n:5678/
    volumes: ["n8n:/home/node/.n8n"]
    ports: ["5678:5678"]

  vector:                      # log collector
    image: timberio/vector:latest-alpine
    volumes: ["./vector.toml:/etc/vector/vector.toml:ro"]
    depends_on: [redpanda]

volumes:
  pgdata: {}
  qdrant: {}
  n8n: {}</code></pre>
    <div class="callout warn">
      ทุก service เปิด port ใน compose นี้ไม่มี auth — สำหรับ <b>dev เท่านั้น</b>. ตอนขึ้น production ให้วาง Traefik/Caddy + OAuth ข้างหน้า, ปิด port ที่ไม่จำเป็น, ใส่ password ทุกตัวผ่าน secret manager
    </div>
  </section>

  <!-- ROADMAP -->
  <section id="roadmap">
    <h2><span class="num"></span> Migration Roadmap</h2>
    <p class="sub">ทำเป็นเฟส — แต่ละเฟสรันได้จริงก่อนไปต่อ</p>

    <h3><span class="pill p-now">เฟส 1</span> Refactor core (1-2 สัปดาห์)</h3>
    <ul>
      <li>รวม <code>server.py</code> + <code>api/_lograg.py</code> เป็น package เดียว ลบ logic ซ้ำ</li>
      <li>ย้ายไป FastAPI + Uvicorn, เพิ่ม Pydantic models สำหรับ request/response</li>
      <li>เก็บ vector ใน Qdrant (เลิกใช้ SQLite blob), keyword ใน OpenSearch หรือ pgvector</li>
      <li>เพิ่ม API key auth ขั้นต่ำ + rate limit</li>
    </ul>

    <h3><span class="pill p-next">เฟส 2</span> Real ingestion + automation (2-4 สัปดาห์)</h3>
    <ul>
      <li>ต่อ Vector/Fluent Bit → Redpanda → classify/embed worker</li>
      <li>PostgreSQL schema (ข้อ ⑥) + retention policy</li>
      <li>n8n WF-1 (Critical Responder) + WF-2 (Daily Digest)</li>
      <li>ต่อ threat intel (AbuseIPDB/GeoIP), Slack/LINE notify</li>
    </ul>

    <h3><span class="pill p-later">เฟส 3</span> Scale & Ops (ต่อเนื่อง)</h3>
    <ul>
      <li>Observability: Prometheus + Grafana + Loki</li>
      <li>ย้าย compose → Kubernetes (Helm), HPA สำหรับ worker</li>
      <li>Self-host LLM (Ollama/vLLM) ลดค่า API + ข้อมูลไม่ออกนอก</li>
      <li>Hybrid search tuning, eval RAG ด้วยชุด 50k, A/B model</li>
      <li>WF-3 auto-block + approval flow, SOAR-style playbook</li>
    </ul>
  </section>

  <footer>
    ROGLAG Production Plan · สร้างเป็น HTML · อัปเดตแผนได้ตามเฟสที่ทำจริง
  </footer>

</div>
</body>
</html>