init project+ci/cd
Some checks failed
CI / lint-and-build (push) Failing after 1m30s

This commit is contained in:
2026-06-09 13:27:51 +08:00
parent b8d4ee0abb
commit 66879f7db8
16 changed files with 533 additions and 0 deletions

0
web/__init__.py Normal file
View File

64
web/app.py Normal file
View File

@@ -0,0 +1,64 @@
"""Flask web layer for the syscall monitor."""
import json
import threading
from pathlib import Path
from flask import Flask, jsonify, redirect, render_template, request, url_for
from collector.syscall_tracer import get_tracer
BASE_DIR = Path(__file__).resolve().parent.parent
CONFIG_PATH = BASE_DIR / "config" / "monitors.json"
_config_lock = threading.Lock()
def _read_config() -> dict:
if not CONFIG_PATH.exists():
return {"syscalls": []}
with CONFIG_PATH.open("r", encoding="utf-8") as f:
return json.load(f)
def _write_config(data: dict) -> None:
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
tmp = CONFIG_PATH.with_suffix(".json.tmp")
with tmp.open("w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
tmp.replace(CONFIG_PATH)
def create_app() -> Flask:
app = Flask(__name__, template_folder="templates", static_folder="static")
tracer = get_tracer(CONFIG_PATH)
@app.get("/")
def index():
cfg = _read_config()
return render_template("index.html", syscalls=cfg.get("syscalls", []))
@app.get("/api/counts")
def api_counts():
return jsonify(tracer.get_counts())
@app.route("/config", methods=["GET", "POST"])
def config_page():
with _config_lock:
cfg = _read_config()
syscalls = list(cfg.get("syscalls", []))
if request.method == "POST":
action = request.form.get("action", "")
name = (request.form.get("name") or "").strip()
if action == "add" and name and name not in syscalls:
syscalls.append(name)
elif action == "remove" and name in syscalls:
syscalls.remove(name)
cfg["syscalls"] = syscalls
_write_config(cfg)
return redirect(url_for("config_page"))
return render_template("config.html", syscalls=syscalls)
return app

19
web/static/style.css Normal file
View File

@@ -0,0 +1,19 @@
body { font-family: -apple-system, "Segoe UI", Helvetica, Arial, sans-serif; margin: 0; background: #f5f6fa; color: #1f2330; }
header { background: #1f2330; color: #fff; padding: 16px 24px; display: flex; align-items: center; justify-content: space-between; }
header h1 { margin: 0; font-size: 18px; font-weight: 600; }
nav a { color: #c9cbd1; margin-left: 16px; text-decoration: none; font-size: 14px; }
nav a:hover { color: #fff; }
main { max-width: 900px; margin: 24px auto; padding: 0 16px; }
.card { background: #fff; border-radius: 8px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.08); margin-bottom: 16px; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 10px 12px; text-align: left; border-bottom: 1px solid #eef0f4; font-size: 14px; }
th { background: #fafbfd; color: #5a6172; font-weight: 600; }
td.count { font-family: "SF Mono", Consolas, monospace; text-align: right; color: #2f6feb; }
form.inline { display: inline; }
input[type=text] { padding: 8px 10px; border: 1px solid #d6d9e0; border-radius: 6px; font-size: 14px; min-width: 220px; }
button { padding: 8px 14px; border: 0; border-radius: 6px; font-size: 14px; cursor: pointer; }
button.primary { background: #2f6feb; color: #fff; }
button.danger { background: #fff; color: #d33; border: 1px solid #f0c2c2; }
button:hover { opacity: 0.9; }
.muted { color: #8a90a0; font-size: 13px; }
.tag { display: inline-block; padding: 2px 8px; border-radius: 12px; background: #eef2ff; color: #2f6feb; font-size: 12px; }

53
web/templates/config.html Normal file
View File

@@ -0,0 +1,53 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>监控配置</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<header>
<h1>Syscall Monitor</h1>
<nav>
<a href="{{ url_for('index') }}">实时监控</a>
<a href="{{ url_for('config_page') }}">配置</a>
</nav>
</header>
<main>
<div class="card">
<h3 style="margin-top:0">添加监控项</h3>
<form method="post" action="{{ url_for('config_page') }}">
<input type="hidden" name="action" value="add">
<input type="text" name="name" placeholder="syscall 名称,例如 openat" required>
<button type="submit" class="primary">添加</button>
</form>
<p class="muted">名称需为内核 syscall 名(可参考 <code>man syscalls</code>)。新增后会立即出现在监控页面。</p>
</div>
<div class="card">
<h3 style="margin-top:0">当前监控项 ({{ syscalls|length }})</h3>
{% if syscalls %}
<table>
<thead><tr><th>名称</th><th style="text-align:right">操作</th></tr></thead>
<tbody>
{% for name in syscalls %}
<tr>
<td><span class="tag">{{ name }}</span></td>
<td style="text-align:right">
<form method="post" action="{{ url_for('config_page') }}" class="inline">
<input type="hidden" name="action" value="remove">
<input type="hidden" name="name" value="{{ name }}">
<button type="submit" class="danger">移除</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="muted">尚无监控项。</p>
{% endif %}
</div>
</main>
</body>
</html>

48
web/templates/index.html Normal file
View File

@@ -0,0 +1,48 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>Syscall Monitor</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<header>
<h1>Syscall Monitor</h1>
<nav>
<a href="{{ url_for('index') }}">实时监控</a>
<a href="{{ url_for('config_page') }}">配置</a>
</nav>
</header>
<main>
<div class="card">
<p class="muted">当前监控 {{ syscalls|length }} 项,每秒刷新。计数自 Agent 启动起累计。</p>
{% if syscalls %}
<table>
<thead><tr><th>System Call</th><th style="text-align:right">调用次数</th></tr></thead>
<tbody id="rows">
{% for name in syscalls %}
<tr><td><span class="tag">{{ name }}</span></td><td class="count" data-name="{{ name }}"></td></tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>尚未配置监控项,前往 <a href="{{ url_for('config_page') }}">配置页面</a> 添加。</p>
{% endif %}
</div>
</main>
<script>
async function refresh() {
try {
const r = await fetch('/api/counts');
const data = await r.json();
document.querySelectorAll('td.count').forEach(td => {
const n = td.dataset.name;
td.textContent = (data[n] ?? 0).toLocaleString();
});
} catch (e) { /* keep last value on error */ }
}
refresh();
setInterval(refresh, 1000);
</script>
</body>
</html>