Implement Monlet MVP stack and UI updates
This commit is contained in:
24
deploy/showcase/agents/dead.toml
Normal file
24
deploy/showcase/agents/dead.toml
Normal 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"
|
||||
46
deploy/showcase/agents/mixed.toml
Normal file
46
deploy/showcase/agents/mixed.toml
Normal 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"
|
||||
25
deploy/showcase/agents/prometheus_owned.toml
Normal file
25
deploy/showcase/agents/prometheus_owned.toml
Normal 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
|
||||
25
deploy/showcase/agents/resolve.toml
Normal file
25
deploy/showcase/agents/resolve.toml
Normal 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"
|
||||
24
deploy/showcase/agents/spool_replay.toml
Normal file
24
deploy/showcase/agents/spool_replay.toml
Normal 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"
|
||||
24
deploy/showcase/agents/stale.toml
Normal file
24
deploy/showcase/agents/stale.toml
Normal 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"
|
||||
3
deploy/showcase/checks/exit_0.sh
Executable file
3
deploy/showcase/checks/exit_0.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
echo "ok: $(date -u +%FT%TZ)"
|
||||
exit 0
|
||||
3
deploy/showcase/checks/exit_1.sh
Executable file
3
deploy/showcase/checks/exit_1.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
echo "warning: degraded"
|
||||
exit 1
|
||||
3
deploy/showcase/checks/exit_2.sh
Executable file
3
deploy/showcase/checks/exit_2.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
echo "critical: probe failed"
|
||||
exit 2
|
||||
3
deploy/showcase/checks/exit_3.sh
Executable file
3
deploy/showcase/checks/exit_3.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
echo "unknown: probe returned unexpected exit code"
|
||||
exit 3
|
||||
8
deploy/showcase/checks/flapping.sh
Executable file
8
deploy/showcase/checks/flapping.sh
Executable 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
|
||||
3
deploy/showcase/checks/replay_check.sh
Executable file
3
deploy/showcase/checks/replay_check.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
echo "ok: replay tick"
|
||||
exit 0
|
||||
69
deploy/showcase/mock_webhook.py
Executable file
69
deploy/showcase/mock_webhook.py
Executable 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())
|
||||
16
deploy/showcase/seed_discarded.sql
Normal file
16
deploy/showcase/seed_discarded.sql
Normal 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;
|
||||
Reference in New Issue
Block a user