Implement Monlet MVP stack and UI updates

This commit is contained in:
Stanislav Rossovskii
2026-05-27 10:01:59 +04:00
parent dcea096327
commit edc51e9c59
145 changed files with 15618 additions and 248 deletions

View File

@@ -0,0 +1,24 @@
hostname = "agent-dead"
state_dir = "/var/lib/monlet-agent"
[labels]
scenario = "dead"
role = "showcase"
[server]
enabled = true
url = "http://server:8000"
token = "monlet-dev-token"
heartbeat_interval = "5s"
batch_interval = "3s"
[metrics]
enabled = false
listen = "127.0.0.1:9465"
[[checks]]
id = "ok_check"
name = "Dead agent placeholder check"
command = "/opt/monlet/checks/exit_0.sh"
interval = "10s"
timeout = "3s"

View File

@@ -0,0 +1,46 @@
hostname = "agent-mixed"
state_dir = "/var/lib/monlet-agent"
[labels]
scenario = "mixed-status"
role = "showcase"
[server]
enabled = true
url = "http://server:8000"
token = "monlet-dev-token"
heartbeat_interval = "5s"
batch_interval = "3s"
[metrics]
enabled = true
listen = "0.0.0.0:9465"
[[checks]]
id = "ok_check"
name = "Showcase ok"
command = "/opt/monlet/checks/exit_0.sh"
interval = "5s"
timeout = "3s"
[[checks]]
id = "warning_check"
name = "Showcase warning"
command = "/opt/monlet/checks/exit_1.sh"
interval = "5s"
timeout = "3s"
[[checks]]
id = "critical_check"
name = "Showcase critical (permanent webhook fail)"
command = "/opt/monlet/checks/exit_2.sh"
interval = "5s"
timeout = "3s"
dedupe_key = "agent-mixed:critical:fail"
[[checks]]
id = "unknown_check"
name = "Showcase unknown"
command = "/opt/monlet/checks/exit_3.sh"
interval = "5s"
timeout = "3s"

View File

@@ -0,0 +1,25 @@
hostname = "agent-prometheus-owned"
state_dir = "/var/lib/monlet-agent"
[labels]
scenario = "prometheus-owned"
role = "showcase"
[server]
enabled = true
url = "http://server:8000"
token = "monlet-dev-token"
heartbeat_interval = "5s"
batch_interval = "3s"
[metrics]
enabled = false
listen = "127.0.0.1:9465"
[[checks]]
id = "promcheck"
name = "Prometheus-owned critical (no outbox expected)"
command = "/opt/monlet/checks/exit_2.sh"
interval = "5s"
timeout = "3s"
notifications_enabled = false

View File

@@ -0,0 +1,25 @@
hostname = "agent-resolve"
state_dir = "/var/lib/monlet-agent"
[labels]
scenario = "resolve"
role = "showcase"
[server]
enabled = true
url = "http://server:8000"
token = "monlet-dev-token"
heartbeat_interval = "5s"
batch_interval = "3s"
[metrics]
enabled = false
listen = "127.0.0.1:9465"
[[checks]]
id = "flapping"
name = "Flapping check (critical then ok)"
command = "/opt/monlet/checks/flapping.sh"
interval = "5s"
timeout = "3s"
dedupe_key = "agent-resolve:flapping:retry"

View File

@@ -0,0 +1,24 @@
hostname = "agent-spool-replay"
state_dir = "/var/lib/monlet-agent"
[labels]
scenario = "spool-replay"
role = "showcase"
[server]
enabled = true
url = "http://server:8000"
token = "monlet-dev-token"
heartbeat_interval = "5s"
batch_interval = "3s"
[metrics]
enabled = false
listen = "127.0.0.1:9465"
[[checks]]
id = "replay_check"
name = "Spool replay check"
command = "/opt/monlet/checks/replay_check.sh"
interval = "3s"
timeout = "2s"

View File

@@ -0,0 +1,24 @@
hostname = "agent-stale"
state_dir = "/var/lib/monlet-agent"
[labels]
scenario = "stale"
role = "showcase"
[server]
enabled = true
url = "http://server:8000"
token = "monlet-dev-token"
heartbeat_interval = "5s"
batch_interval = "3s"
[metrics]
enabled = false
listen = "127.0.0.1:9465"
[[checks]]
id = "ok_check"
name = "Stale agent placeholder check"
command = "/opt/monlet/checks/exit_0.sh"
interval = "10s"
timeout = "3s"

View File

@@ -0,0 +1,3 @@
#!/bin/sh
echo "ok: $(date -u +%FT%TZ)"
exit 0

View File

@@ -0,0 +1,3 @@
#!/bin/sh
echo "warning: degraded"
exit 1

View File

@@ -0,0 +1,3 @@
#!/bin/sh
echo "critical: probe failed"
exit 2

View File

@@ -0,0 +1,3 @@
#!/bin/sh
echo "unknown: probe returned unexpected exit code"
exit 3

View File

@@ -0,0 +1,8 @@
#!/bin/sh
# critical until /state/resolve.flag exists, then ok.
if [ -f /state/resolve.flag ]; then
echo "ok: resolve flag present"
exit 0
fi
echo "critical: waiting for resolve flag"
exit 2

View File

@@ -0,0 +1,3 @@
#!/bin/sh
echo "ok: replay tick"
exit 0

69
deploy/showcase/mock_webhook.py Executable file
View File

@@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""Mock webhook for showcase/e2e.
Decides response status by substring in incident.incident_key from request body:
- contains 'retry' -> 503 (retryable failure)
- contains 'fail' -> 400 (permanent failure)
- otherwise -> 200 (success)
"""
from __future__ import annotations
import json
import sys
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
def _classify(body: bytes) -> int:
try:
data = json.loads(body or b"{}")
except Exception:
return 200
incident = data.get("incident") or {}
key = str(incident.get("incident_key") or "")
if "retry" in key:
return 503
if "fail" in key:
return 400
return 200
class Handler(BaseHTTPRequestHandler):
def do_POST(self) -> None: # noqa: N802
length = int(self.headers.get("Content-Length") or 0)
body = self.rfile.read(length) if length else b""
code = _classify(body)
self.send_response(code)
self.send_header("Content-Type", "text/plain")
self.send_header("Content-Length", "0")
self.end_headers()
try:
data = json.loads(body or b"{}")
inc = (data.get("incident") or {})
print(
f"webhook {code} event_type={data.get('event_type')!r} "
f"incident_key={inc.get('incident_key')!r}",
flush=True,
)
except Exception:
print(f"webhook {code} (unparsable body, {length}B)", flush=True)
def log_message(self, *_args: object) -> None:
return
def main() -> int:
addr = ("0.0.0.0", 8080)
srv = ThreadingHTTPServer(addr, Handler)
print(f"mock_webhook listening on {addr[0]}:{addr[1]}", flush=True)
try:
srv.serve_forever()
except KeyboardInterrupt:
pass
finally:
srv.server_close()
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,16 @@
-- Seed one outbox row with an unknown notifier name so the worker marks it 'discarded'.
-- Attached to any existing incident; if none exists yet the script is a no-op.
INSERT INTO notification_outbox
(id, incident_id, notifier, event_type, state, attempts, next_attempt_at, payload)
SELECT
gen_random_uuid(),
i.id,
'showcase-unknown',
'firing',
'pending',
0,
now(),
'{"showcase":"discarded"}'::jsonb
FROM incidents i
ORDER BY i.opened_at DESC
LIMIT 1;