Compare commits
19
Commits
6e9c5f20d4
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
727fd1195a | ||
|
|
2df40d5dbe | ||
|
|
0d716b2549 | ||
|
|
0784e8fcea | ||
|
|
f0262dde3a | ||
|
|
12a51a5ebe | ||
|
|
78785e54e1 | ||
|
|
a7722af29e | ||
|
|
2d9d179b0a | ||
|
|
b0697da8e4 | ||
|
|
3a082923cf | ||
|
|
64f9f20500 | ||
|
|
2917f4de44 | ||
|
|
28801586d1 | ||
|
|
40b8afb150 | ||
|
|
af6a439452 | ||
|
|
dac3b4ce40 | ||
|
|
fd949ddaed | ||
|
|
c713a4f142 |
@@ -11,11 +11,11 @@
|
|||||||
// {
|
// {
|
||||||
// "node_id": "uc06",
|
// "node_id": "uc06",
|
||||||
// "type": "asterisk",
|
// "type": "asterisk",
|
||||||
// "nats_url": "nats://10.101.60.81:4222",
|
// "nats_url": "nats://balancer.lan:4222",
|
||||||
// "sip_gateway": "sip:10.101.60.115:5060",
|
// "sip_gateway": "sip:uc-pbx.lan:5060",
|
||||||
// "interval_sec": 5,
|
// "interval_sec": 5,
|
||||||
// "max_calls": 150,
|
// "max_calls": 150,
|
||||||
// "ami": { "host": "127.0.0.1", "port": 6154, "username": "ctt", "password": "cttpass" }
|
// "ami": { "host": "127.0.0.1", "port": 6154, "username": "admin", "password": "changeme" }
|
||||||
// }
|
// }
|
||||||
package agent
|
package agent
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,11 @@
|
|||||||
// {
|
// {
|
||||||
// "node_id": "uc06",
|
// "node_id": "uc06",
|
||||||
// "type": "asterisk",
|
// "type": "asterisk",
|
||||||
// "nats_url": "nats://10.101.60.81:4222",
|
// "nats_url": "nats://balancer.lan:4222",
|
||||||
// "sip_gateway": "sip:10.101.60.115:5060",
|
// "sip_gateway": "sip:uc-pbx.lan:5060",
|
||||||
// "interval_sec": 5,
|
// "interval_sec": 5,
|
||||||
// "max_calls": 150,
|
// "max_calls": 150,
|
||||||
// "ami": { "host": "127.0.0.1", "port": 6154, "username": "ctt", "password": "cttpass" }
|
// "ami": { "host": "127.0.0.1", "port": 6154, "username": "admin", "password": "changeme" }
|
||||||
// }
|
// }
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"node_id": "ses-sip",
|
"node_id": "ses-pbx",
|
||||||
"type": "freeswitch",
|
"type": "freeswitch",
|
||||||
"nats_url": "nats://10.101.60.81:4222",
|
"nats_url": "nats://balancer.lan:4222",
|
||||||
"interval_sec": 5,
|
"interval_sec": 5,
|
||||||
"max_calls": 250,
|
"max_calls": 250,
|
||||||
"sip_gateway": "sip:10.3.0.44:5060",
|
"sip_gateway": "sip:ses-pbx.lan:5060",
|
||||||
"sip_gateway_auto": false,
|
"sip_gateway_auto": false,
|
||||||
"failure_window": 1000,
|
"failure_window": 1000,
|
||||||
"esl": {
|
"esl": {
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"node_id": "uc06",
|
"node_id": "uc-pbx",
|
||||||
"type": "asterisk",
|
"type": "asterisk",
|
||||||
"nats_url": "nats://10.101.60.81:4222",
|
"nats_url": "nats://balancer.lan:4222",
|
||||||
"interval_sec": 5,
|
"interval_sec": 5,
|
||||||
"max_calls": 150,
|
"max_calls": 150,
|
||||||
"sip_gateway": "sip:10.101.60.115:5060",
|
"sip_gateway": "sip:uc-pbx.lan:5060",
|
||||||
"sip_gateway_auto": false,
|
"sip_gateway_auto": false,
|
||||||
"failure_window": 1000,
|
"failure_window": 1000,
|
||||||
"ami": {
|
"ami": {
|
||||||
"host": "127.0.0.1",
|
"host": "127.0.0.1",
|
||||||
"port": 6154,
|
"port": 6154,
|
||||||
"username": "ctt",
|
"username": "admin",
|
||||||
"password": "cttpass"
|
"password": "changeme"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-9
@@ -48,24 +48,24 @@ make build-agent
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"node_id": "uc06",
|
"node_id": "pbx-01",
|
||||||
"type": "asterisk",
|
"type": "asterisk",
|
||||||
"nats_url": "nats://balancer.host:4222",
|
"nats_url": "nats://balancer.host:4222",
|
||||||
"nats_user": "",
|
"nats_user": "",
|
||||||
"nats_password": "",
|
"nats_password": "",
|
||||||
"interval_sec": 5,
|
"interval_sec": 5,
|
||||||
"max_calls": 150,
|
"max_calls": 150,
|
||||||
"sip_gateway": "sip:10.101.60.115:5060",
|
"sip_gateway": "sip:pbx-01.lan:5060",
|
||||||
"sip_gateway_auto": false,
|
"sip_gateway_auto": false,
|
||||||
"failure_window": 1000,
|
"failure_window": 1000,
|
||||||
"esl": { "host": "127.0.0.1", "port": 8021, "password": "ClueCon" },
|
"esl": { "host": "127.0.0.1", "port": 8021, "password": "ClueCon" },
|
||||||
"ami": { "host": "127.0.0.1", "port": 6154, "username": "ctt", "password": "cttpass" }
|
"ami": { "host": "127.0.0.1", "port": 6154, "username": "admin", "password": "changeme" }
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
| Field | Type | Default | Description |
|
| Field | Type | Default | Description |
|
||||||
|-------|------|---------|-------------|
|
|-------|------|---------|-------------|
|
||||||
| `node_id` | string | — | Unique node ID (uc06, ses-sip) |
|
| `node_id` | string | — | Unique node ID (pbx-01, pbx-02) |
|
||||||
| `type` | string | — | `freeswitch` or `asterisk` |
|
| `type` | string | — | `freeswitch` or `asterisk` |
|
||||||
| `nats_url` | string | — | NATS server URL |
|
| `nats_url` | string | — | NATS server URL |
|
||||||
| `nats_user` | string | `""` | NATS username |
|
| `nats_user` | string | `""` | NATS username |
|
||||||
@@ -113,8 +113,8 @@ New PBX type = new `collector_<type>.go` implementing 5 interface methods. No ex
|
|||||||
|
|
||||||
| File | Target | Type |
|
| File | Target | Type |
|
||||||
|------|--------|------|
|
|------|--------|------|
|
||||||
| `contrib/agent-uc.json` | Asterisk UC nodes (05, 06, 66-69) | asterisk |
|
| `contrib/agent-uc.json` | Asterisk UC nodes | asterisk |
|
||||||
| `contrib/agent-sessip.json` | FreeSWITCH ses-sip (10.3.0.44) | freeswitch |
|
| `contrib/agent-sessip.json` | FreeSWITCH ses-sip | freeswitch |
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
@@ -123,13 +123,13 @@ New PBX type = new `collector_<type>.go` implementing 5 interface methods. No ex
|
|||||||
make build-agent
|
make build-agent
|
||||||
|
|
||||||
# 2. Copy to node
|
# 2. Copy to node
|
||||||
scp bin/pulse-lets-go-agent devadmin@NODE_IP:/usr/local/bin/
|
scp bin/pulse-lets-go-agent admin@pbx-node:/usr/local/bin/
|
||||||
|
|
||||||
# 3. Create config
|
# 3. Create config
|
||||||
scp contrib/agent-uc.json devadmin@NODE_IP:/etc/pulse-lets-go-agent/agent.json
|
scp contrib/agent-uc.json admin@pbx-node:/etc/pulse-lets-go-agent/agent.json
|
||||||
|
|
||||||
# 4. Start
|
# 4. Start
|
||||||
ssh devadmin@NODE_IP "pulse-lets-go-agent -config /etc/pulse-lets-go-agent/agent.json"
|
ssh admin@pbx-node "pulse-lets-go-agent -config /etc/pulse-lets-go-agent/agent.json"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Systemd Unit
|
## Systemd Unit
|
||||||
|
|||||||
+188
-14
@@ -17,9 +17,36 @@ Roles: `admin` (full access), `viewer` (read-only nodes/metrics).
|
|||||||
|--------|------|------|-------------|
|
|--------|------|------|-------------|
|
||||||
| `GET` | `/api/route` | – | Route a call. Query: `caller_id`, `dest`, `ingress_trunk` |
|
| `GET` | `/api/route` | – | Route a call. Query: `caller_id`, `dest`, `ingress_trunk` |
|
||||||
| `PUT` | `/api/route/mode` | admin | Set balancing mode: `{"mode":"best\|weighted_random"}` — см. [Routing](routing.md#balancing-modes) |
|
| `PUT` | `/api/route/mode` | admin | Set balancing mode: `{"mode":"best\|weighted_random"}` — см. [Routing](routing.md#balancing-modes) |
|
||||||
| `GET` | `/api/health` | – | Stats: total nodes, healthy, requests, uptime |
|
| `GET` | `/api/health` | – | Full health status with connections and stats |
|
||||||
| `GET` | `/api/health/live` | – | Liveness probe (always 200) |
|
| `GET` | `/api/health/live` | – | Liveness probe — always 200 `{"status":"alive"}` |
|
||||||
| `GET` | `/api/health/ready` | – | Readiness probe (NATS + metrics) |
|
| `GET` | `/api/health/ready` | – | Readiness — 200 `{"status":"ready"}` или 503 `{"reason":"nats_disconnected"}` |
|
||||||
|
|
||||||
|
### Health Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"connections": {
|
||||||
|
"nats": "connected|disconnected",
|
||||||
|
"esl": {
|
||||||
|
"status": "disabled|connected|disconnected",
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"uptime_seconds": 3600,
|
||||||
|
"reconnects": 0,
|
||||||
|
"gateway_ops": 42,
|
||||||
|
"events_recv": 15000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stats": {
|
||||||
|
"total_nodes": 3,
|
||||||
|
"healthy_nodes": 2,
|
||||||
|
"route_requests": 5000,
|
||||||
|
"route_fallbacks": 10,
|
||||||
|
"uptime_seconds": 7200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Route Response (success)
|
### Route Response (success)
|
||||||
|
|
||||||
@@ -29,6 +56,13 @@ Roles: `admin` (full access), `viewer` (read-only nodes/metrics).
|
|||||||
|
|
||||||
`matched_rule` — имя сработавшего routing rule, либо `"none"` если применился глобальный scoring.
|
`matched_rule` — имя сработавшего routing rule, либо `"none"` если применился глобальный scoring.
|
||||||
|
|
||||||
|
### Route Mode Response
|
||||||
|
|
||||||
|
`PUT /api/route/mode` возвращает текущий режим:
|
||||||
|
```json
|
||||||
|
{ "mode": "best" }
|
||||||
|
```
|
||||||
|
|
||||||
### Route Response (fallback — all nodes unhealthy)
|
### Route Response (fallback — all nodes unhealthy)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -36,14 +70,19 @@ Roles: `admin` (full access), `viewer` (read-only nodes/metrics).
|
|||||||
"fallback": true,
|
"fallback": true,
|
||||||
"sip_gateway": "sip:operator.lan:5060",
|
"sip_gateway": "sip:operator.lan:5060",
|
||||||
"reason": "all_nodes_unhealthy",
|
"reason": "all_nodes_unhealthy",
|
||||||
"nodes": [...]
|
"matched_rule": "my-pool-rule",
|
||||||
|
"nodes": [
|
||||||
|
{ "id": "pbx-01", "score": -100, "reason": "disabled", "status": "disabled" },
|
||||||
|
{ "id": "pbx-02", "score": -1, "reason": "stale", "status": "stale" },
|
||||||
|
{ "id": "pbx-03", "score": 70, "reason": "", "status": "healthy" }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Route Response (no nodes registered)
|
### Route Response (no nodes registered)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{ "error": "no_nodes_registered" }
|
{ "error": "no_nodes_registered", "matched_rule": "none" }
|
||||||
```
|
```
|
||||||
|
|
||||||
## Nodes
|
## Nodes
|
||||||
@@ -52,7 +91,42 @@ Roles: `admin` (full access), `viewer` (read-only nodes/metrics).
|
|||||||
|--------|------|------|-------------|
|
|--------|------|------|-------------|
|
||||||
| `GET` | `/api/nodes` | admin/viewer | List all nodes with scores and state |
|
| `GET` | `/api/nodes` | admin/viewer | List all nodes with scores and state |
|
||||||
| `GET` | `/api/nodes/{id}/metrics` | admin/viewer | Metrics history (ring buffer, ~30 min) |
|
| `GET` | `/api/nodes/{id}/metrics` | admin/viewer | Metrics history (ring buffer, ~30 min) |
|
||||||
| `PUT` | `/api/nodes/{id}/toggle` | admin | `{"disabled":true, "reason":"..."}` |
|
| `PUT` | `/api/nodes/{id}/toggle` | admin | `{"disabled":true, "reason":"..."}` — returns updated `NodeInfo` |
|
||||||
|
|
||||||
|
### NodeInfo (GET /api/nodes)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"node_id": "pbx-01",
|
||||||
|
"ts": 1719000000,
|
||||||
|
"status": "ok",
|
||||||
|
"active_calls": 42,
|
||||||
|
"max_calls": 250,
|
||||||
|
"idle_cpu": 35.0,
|
||||||
|
"load_avg": 0.85,
|
||||||
|
"call_failure_rate": 0.5,
|
||||||
|
"disabled": false,
|
||||||
|
"disabled_reason": "",
|
||||||
|
"score": 88.0,
|
||||||
|
"lethal_reason": "",
|
||||||
|
"is_stale": false,
|
||||||
|
"seconds_ago": 3.0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Node Metrics (GET /api/nodes/{id}/metrics)
|
||||||
|
|
||||||
|
История метрик ноды из ring buffer (~30 мин, шаг 5 с):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"node_id": "pbx-01",
|
||||||
|
"snapshots": [
|
||||||
|
{ "active_calls": 42, "load_avg": 0.85, "score": 88.0, ... }
|
||||||
|
],
|
||||||
|
"count": 360
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Trunks
|
## Trunks
|
||||||
|
|
||||||
@@ -88,16 +162,32 @@ Trunk types:
|
|||||||
| `balance` | Destination trunk (bound to a Node) | 1..N |
|
| `balance` | Destination trunk (bound to a Node) | 1..N |
|
||||||
| `fallback` | Operator fallback (when all balance score < 0) | exactly 1 |
|
| `fallback` | Operator fallback (when all balance score < 0) | exactly 1 |
|
||||||
|
|
||||||
|
## Routing Rules
|
||||||
|
|
||||||
|
| Method | Path | Role | Description |
|
||||||
|
|--------|------|------|-------------|
|
||||||
|
| `GET` | `/api/routing/rules` | admin/viewer | Список правил с счётчиками hits |
|
||||||
|
| `POST` | `/api/routing/rules` | admin | Создать правило |
|
||||||
|
| `PUT` | `/api/routing/rules/{id}` | admin | Обновить правило |
|
||||||
|
| `DELETE` | `/api/routing/rules/{id}` | admin | Удалить правило |
|
||||||
|
| `PUT` | `/api/routing/rules/{id}/toggle` | admin | Вкл/выкл: `{"enabled":true}` |
|
||||||
|
| `PUT` | `/api/routing/rules/reorder` | admin | Новый порядок: `["rr-abc", "rr-def", ...]` |
|
||||||
|
|
||||||
|
Поля правила — см. [docs/routing.md](routing.md#routing-rules). Ответы содержат полную модель правила.
|
||||||
|
|
||||||
## Users (admin only)
|
## Users (admin only)
|
||||||
|
|
||||||
| Method | Path | Description |
|
| Method | Path | Description |
|
||||||
|--------|------|-------------|
|
|--------|------|-------------|
|
||||||
| `GET` | `/api/users` | List users |
|
| `GET` | `/api/users` | List users (без password_hash) |
|
||||||
| `POST` | `/api/users` | Create user |
|
| `POST` | `/api/users` | `{"username":"...","password":"...","role":"admin\|viewer"}` |
|
||||||
| `PUT` | `/api/users/{id}` | Update user |
|
| `PUT` | `/api/users/{id}` | Partial update: `{"username":"...","role":"viewer"}` |
|
||||||
| `DELETE` | `/api/users/{id}` | Delete user |
|
| `DELETE` | `/api/users/{id}` | Удалить (204 No Content) |
|
||||||
|
|
||||||
Note: `user-01` (primary admin) cannot be deleted or demoted to viewer.
|
Защиты:
|
||||||
|
- `user-01` нельзя удалить или понизить до viewer
|
||||||
|
- Нельзя удалить последнего администратора
|
||||||
|
- GET-ответ исключает поле `password_hash`
|
||||||
|
|
||||||
## Monitoring (API-key auth)
|
## Monitoring (API-key auth)
|
||||||
|
|
||||||
@@ -105,14 +195,67 @@ Use `X-API-Key` header (not JWT).
|
|||||||
|
|
||||||
| Method | Path | Description |
|
| Method | Path | Description |
|
||||||
|--------|------|-------------|
|
|--------|------|-------------|
|
||||||
| `GET` | `/api/monitoring/zabbix` | JSON for Zabbix LLD + items |
|
| `GET` | `/api/monitoring/zabbix` | JSON для Zabbix LLD |
|
||||||
| `GET` | `/api/monitoring/prometheus` | `text/plain` metrics for Prometheus |
|
| `GET` | `/api/monitoring/prometheus` | `text/plain` метрики для Prometheus |
|
||||||
|
|
||||||
|
### Zabbix Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"{#NODE_ID}": "pbx-01",
|
||||||
|
"{#STATUS}": "ok",
|
||||||
|
"{#ACTIVE_CALLS}": 42,
|
||||||
|
"{#MAX_CALLS}": 250,
|
||||||
|
"{#IDLE_CPU}": 35.0,
|
||||||
|
"{#LOAD_AVG}": 0.85,
|
||||||
|
"{#CALL_FAILURE_RATE}": 0.5,
|
||||||
|
"{#SCORE}": 88.0,
|
||||||
|
"{#DISABLED}": false,
|
||||||
|
"{#IS_STALE}": false,
|
||||||
|
"{#SECONDS_AGO}": 3.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"route_requests_total": 5000,
|
||||||
|
"route_fallbacks_total": 42,
|
||||||
|
"uptime_seconds": 7200
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Prometheus Metrics
|
||||||
|
|
||||||
|
| Метрика | Type | Labels |
|
||||||
|
|---------|------|--------|
|
||||||
|
| `pulse_route_requests_total` | counter | – |
|
||||||
|
| `pulse_route_fallbacks_total` | counter | – |
|
||||||
|
| `pulse_nodes_total` | gauge | – |
|
||||||
|
| `pulse_nodes_healthy` | gauge | – |
|
||||||
|
| `pulse_uptime_seconds` | gauge | – |
|
||||||
|
| `pulse_node_active_calls` | gauge | `node` |
|
||||||
|
| `pulse_node_max_calls` | gauge | `node` |
|
||||||
|
| `pulse_node_idle_cpu` | gauge | `node` |
|
||||||
|
| `pulse_node_load_avg` | gauge | `node` |
|
||||||
|
| `pulse_node_call_failure_rate` | gauge | `node` |
|
||||||
|
| `pulse_node_score` | gauge | `node` |
|
||||||
|
| `pulse_node_seconds_ago` | gauge | `node` |
|
||||||
|
| `pulse_node_disabled` | gauge | `node` (0/1) |
|
||||||
|
| `pulse_node_stale` | gauge | `node` (0/1) |
|
||||||
|
| `pulse_node_status_ok` | gauge | `node` (0/1) |
|
||||||
|
|
||||||
## WebSocket
|
## WebSocket
|
||||||
|
|
||||||
| Method | Path | Description |
|
| Method | Path | Description |
|
||||||
|--------|------|-------------|
|
|--------|------|-------------|
|
||||||
| `WS` | `/ws/metrics` | Real-time metrics stream for frontend (JWT in query `?token=`) |
|
| `WS` | `/ws/metrics` | Real-time metrics (JWT в query `?token=`) |
|
||||||
|
|
||||||
|
Типы сообщений (поле `type`):
|
||||||
|
|
||||||
|
| Тип | Payload | Описание |
|
||||||
|
|-----|---------|----------|
|
||||||
|
| `nodes_update` | `[]NodeInfo` | Обновление метрик всех нод (каждые 5 с) |
|
||||||
|
| `balancer_update` | `BalancerInfo` | Состояние балансировщика (uptime, mode, ESL, NATS) |
|
||||||
|
| `gateway_update` | `{"gateway","trunk_id","status"}` | Статус FS-gateway (up/down) |
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -135,6 +278,37 @@ curl -X PUT http://localhost:8080/api/nodes/pbx-01/toggle \
|
|||||||
-H 'Content-Type: application/json' \
|
-H 'Content-Type: application/json' \
|
||||||
-d '{"disabled":true,"reason":"maintenance"}'
|
-d '{"disabled":true,"reason":"maintenance"}'
|
||||||
|
|
||||||
|
# Route mode (admin)
|
||||||
|
curl -X PUT http://localhost:8080/api/route/mode \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d '{"mode":"weighted_random"}'
|
||||||
|
# → {"mode":"weighted_random"}
|
||||||
|
|
||||||
|
# Routing rules (admin)
|
||||||
|
curl -X POST http://localhost:8080/api/routing/rules \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d '{
|
||||||
|
"name":"VIP pool",
|
||||||
|
"priority":10,
|
||||||
|
"match_caller_id":"^7495",
|
||||||
|
"action_type":"pool",
|
||||||
|
"action_pool_ids":["pbx-01","pbx-02"]
|
||||||
|
}'
|
||||||
|
|
||||||
|
# Toggle rule (admin)
|
||||||
|
curl -X PUT http://localhost:8080/api/routing/rules/rr-abc123/toggle \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d '{"enabled":false}'
|
||||||
|
|
||||||
|
# Reorder rules (admin)
|
||||||
|
curl -X PUT http://localhost:8080/api/routing/rules/reorder \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d '["rr-def456", "rr-abc123"]'
|
||||||
|
|
||||||
# Prometheus metrics (API-key)
|
# Prometheus metrics (API-key)
|
||||||
curl -H "X-API-Key: $(jq -r '.monitoring_api_key' data/config.json)" \
|
curl -H "X-API-Key: $(jq -r '.monitoring_api_key' data/config.json)" \
|
||||||
http://localhost:8080/api/monitoring/prometheus
|
http://localhost:8080/api/monitoring/prometheus
|
||||||
|
|||||||
+7
-7
@@ -60,14 +60,14 @@ WantedBy=multi-user.target
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
make deploy
|
make deploy
|
||||||
scp -r /opt/pulse-lets-go devadmin@10.101.60.81:/opt/
|
scp -r /opt/pulse-lets-go admin@balancer.lan:/opt/
|
||||||
scp contrib/route.lua devadmin@10.101.60.81:/tmp/
|
scp contrib/route.lua admin@balancer.lan:/tmp/
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 2 — NATS Server (if not installed)
|
### Step 2 — NATS Server (if not installed)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ssh devadmin@10.101.60.81
|
ssh admin@balancer.lan
|
||||||
sudo mkdir -p /opt/nats
|
sudo mkdir -p /opt/nats
|
||||||
# Install nats-server from repository or copy binary
|
# Install nats-server from repository or copy binary
|
||||||
# Run: nats-server -p 4222 -D &
|
# Run: nats-server -p 4222 -D &
|
||||||
@@ -79,11 +79,11 @@ Repeat for each UC/Asterisk node:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Copy binary and config to each PBX:
|
# Copy binary and config to each PBX:
|
||||||
scp bin/pulse-lets-go-agent devadmin@PBX_IP:/usr/local/bin/
|
scp bin/pulse-lets-go-agent admin@pbx-node:/usr/local/bin/
|
||||||
scp contrib/agent-uc.json devadmin@PBX_IP:/etc/pulse-lets-go-agent/agent.json
|
scp contrib/agent-uc.json admin@pbx-node:/etc/pulse-lets-go-agent/agent.json
|
||||||
|
|
||||||
# Start:
|
# Start:
|
||||||
ssh devadmin@PBX_IP "systemctl start pulse-lets-go-agent"
|
ssh admin@pbx-node "systemctl start pulse-lets-go-agent"
|
||||||
|
|
||||||
# Check log:
|
# Check log:
|
||||||
# journalctl -u pulse-lets-go-agent -f
|
# journalctl -u pulse-lets-go-agent -f
|
||||||
@@ -121,7 +121,7 @@ Trunks are created automatically when an agent sends its first metric with `sip_
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST .../api/trunks \
|
curl -X POST .../api/trunks \
|
||||||
-d '{"name":"uc06","type":"balance","node_id":"uc06","gateway":"sip:10.101.60.115:5060","enabled":true}'
|
-d '{"name":"pbx-01","type":"balance","node_id":"pbx-01","gateway":"sip:pbx-01.lan:5060","enabled":true}'
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 7 — Start pulse-lets-go
|
### Step 7 — Start pulse-lets-go
|
||||||
|
|||||||
@@ -90,6 +90,21 @@ Authorization: Bearer <admin-jwt>
|
|||||||
|
|
||||||
Допустимые значения: `"best"`, `"weighted_random"`. API-эндпоинт сразу применяет режим и сохраняет в `config.json`.
|
Допустимые значения: `"best"`, `"weighted_random"`. API-эндпоинт сразу применяет режим и сохраняет в `config.json`.
|
||||||
|
|
||||||
|
## Routing Rules API
|
||||||
|
|
||||||
|
Правила маршрутизации CRUD — `/api/routing/rules*`. Все эндпоинты требуют JWT (admin).
|
||||||
|
|
||||||
|
| Метод | Путь | Описание |
|
||||||
|
|-------|------|----------|
|
||||||
|
| `GET` | `/api/routing/rules` | Список всех правил (с hits) |
|
||||||
|
| `POST` | `/api/routing/rules` | Создать новое правило |
|
||||||
|
| `PUT` | `/api/routing/rules/{id}` | Обновить правило (частично) |
|
||||||
|
| `DELETE` | `/api/routing/rules/{id}` | Удалить правило |
|
||||||
|
| `PUT` | `/api/routing/rules/{id}/toggle` | Вкл/выкл: `{"enabled":true}` |
|
||||||
|
| `PUT` | `/api/routing/rules/reorder` | Новый порядок: `["rr-abc", "rr-def"]` — переназначает Priority |
|
||||||
|
|
||||||
|
Правила хранятся в `data/routes.json` и применяются в порядке `Priority` (first-match-wins).
|
||||||
|
|
||||||
## Ответ /api/route
|
## Ответ /api/route
|
||||||
|
|
||||||
В ответе маршрутизации появилось поле `matched_rule` — имя сработавшего правила (или `"none"` если правило не применилось):
|
В ответе маршрутизации появилось поле `matched_rule` — имя сработавшего правила (или `"none"` если правило не применилось):
|
||||||
|
|||||||
@@ -29,7 +29,13 @@ func (a *API) handleZabbix(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
writeJSON(w, http.StatusOK, models.ZabbixResponse{Data: data})
|
stats := a.engine.GetHealthStats()
|
||||||
|
writeJSON(w, http.StatusOK, models.ZabbixResponse{
|
||||||
|
Data: data,
|
||||||
|
RouteRequests: stats.RouteRequests,
|
||||||
|
RouteFallbacks: stats.RouteFallbacks,
|
||||||
|
UptimeSeconds: stats.UptimeSeconds,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// handlePrometheus — GET /api/monitoring/prometheus — text/plain метрики для Prometheus.
|
// handlePrometheus — GET /api/monitoring/prometheus — text/plain метрики для Prometheus.
|
||||||
@@ -52,6 +58,10 @@ func (a *API) handlePrometheus(w http.ResponseWriter, r *http.Request) {
|
|||||||
fmt.Fprintf(&sb, "# TYPE pulse_nodes_healthy gauge\n")
|
fmt.Fprintf(&sb, "# TYPE pulse_nodes_healthy gauge\n")
|
||||||
fmt.Fprintf(&sb, "pulse_nodes_healthy %d\n", stats.HealthyNodes)
|
fmt.Fprintf(&sb, "pulse_nodes_healthy %d\n", stats.HealthyNodes)
|
||||||
|
|
||||||
|
fmt.Fprintf(&sb, "# HELP pulse_route_fallbacks_total Total number of fallback route requests.\n")
|
||||||
|
fmt.Fprintf(&sb, "# TYPE pulse_route_fallbacks_total counter\n")
|
||||||
|
fmt.Fprintf(&sb, "pulse_route_fallbacks_total %d\n", stats.RouteFallbacks)
|
||||||
|
|
||||||
fmt.Fprintf(&sb, "# HELP pulse_uptime_seconds Uptime in seconds.\n")
|
fmt.Fprintf(&sb, "# HELP pulse_uptime_seconds Uptime in seconds.\n")
|
||||||
fmt.Fprintf(&sb, "# TYPE pulse_uptime_seconds gauge\n")
|
fmt.Fprintf(&sb, "# TYPE pulse_uptime_seconds gauge\n")
|
||||||
fmt.Fprintf(&sb, "pulse_uptime_seconds %d\n", stats.UptimeSeconds)
|
fmt.Fprintf(&sb, "pulse_uptime_seconds %d\n", stats.UptimeSeconds)
|
||||||
|
|||||||
@@ -242,6 +242,9 @@ type ZabbixNode struct {
|
|||||||
// ZabbixResponse — ответ на запрос Zabbix LLD.
|
// ZabbixResponse — ответ на запрос Zabbix LLD.
|
||||||
type ZabbixResponse struct {
|
type ZabbixResponse struct {
|
||||||
Data []ZabbixNode `json:"data"`
|
Data []ZabbixNode `json:"data"`
|
||||||
|
RouteRequests int64 `json:"route_requests_total"`
|
||||||
|
RouteFallbacks int64 `json:"route_fallbacks_total"`
|
||||||
|
UptimeSeconds int64 `json:"uptime_seconds"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Balancer ---
|
// --- Balancer ---
|
||||||
|
|||||||
Reference in New Issue
Block a user