Единый мониторинг: бизнес-метрики + вынос Prometheus/Loki/Grafana на VPS с данными
Prometheus/Loki/Grafana больше не живут внутри backend-стека — единый
стек теперь на VPS с данными (netrunner-data), т.к. ему нужно видеть
ещё лендинг и все прокси-ноды, не только бэкенд. Отсюда только тонкий
promtail, пушащий логи по сети (LOKI_PUSH_URL).
Новые бизнес-метрики поверх уже существующих generic HTTP-метрик:
payments_total{provider,status}, nodes_online/nodes_total,
support_tickets_total{status}, referral_signups_total,
partner_commission_total{currency} — тем же макросом metrics::counter!/
gauge!, что уже был в api.rs/error.rs.
Новый гейт-эндпоинт GET /api/v1/admin/observability/check (только
Owner/Moderator) — для Caddy forward_auth перед Grafana (см.
netrunner-data/observability/Caddyfile), встройка в админку через
GRAFANA_PUBLIC_URL в GET /api/v1/config.
templates/init_node.sh — добавляет --metrics-port 9093 в провижининг
новых прокси-нод.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
use axum::{
|
||||
extract::{Extension, Path, State},
|
||||
http::StatusCode,
|
||||
middleware,
|
||||
routing::{get, patch, post},
|
||||
Json, Router,
|
||||
@@ -27,10 +28,24 @@ pub fn router(state: ApiState) -> Router<ApiState> {
|
||||
.route_layer(middleware::from_fn_with_state(state, auth_guard))
|
||||
}
|
||||
|
||||
/// Только Owner: создание модераторов, выдача им права на управление нодами.
|
||||
/// Только Owner/Moderator — гейт для Caddy `forward_auth` перед Grafana
|
||||
/// (см. netrunner-data/observability/Caddyfile). Тело ответа не важно, важен
|
||||
/// только статус: 200 — Caddy пускает дальше в Grafana, 401 — блокирует.
|
||||
/// Бизнес-метрики (выручка, число юзеров) чувствительны — Support/Partner
|
||||
/// сюда не допускаются, поэтому `staff_guard`, а не `support_access_guard`.
|
||||
pub fn observability_router(state: ApiState) -> Router<ApiState> {
|
||||
Router::new()
|
||||
.route("/check", get(|| async { StatusCode::OK }))
|
||||
.route_layer(middleware::from_fn_with_state(state.clone(), staff_guard))
|
||||
.route_layer(middleware::from_fn_with_state(state, auth_guard))
|
||||
}
|
||||
|
||||
/// Только Owner: создание модераторов/саппортов, выдача модераторам права
|
||||
/// на управление нодами.
|
||||
pub fn owner_router(state: ApiState) -> Router<ApiState> {
|
||||
Router::new()
|
||||
.route("/moderators", post(create_moderator))
|
||||
.route("/support", post(create_support))
|
||||
.route("/users/{id}/permissions", patch(set_permissions))
|
||||
.route_layer(middleware::from_fn_with_state(state.clone(), owner_guard))
|
||||
.route_layer(middleware::from_fn_with_state(state, auth_guard))
|
||||
@@ -89,6 +104,27 @@ async fn create_moderator(
|
||||
})))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CreateSupportPayload {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
async fn create_support(
|
||||
State(state): State<ApiState>,
|
||||
Json(p): Json<CreateSupportPayload>,
|
||||
) -> Result<Json<Value>, AppError> {
|
||||
let user = state
|
||||
.staff_service
|
||||
.create_support(&p.username, &p.password)
|
||||
.await?;
|
||||
Ok(Json(json!({
|
||||
"id": user.id,
|
||||
"username": user.username,
|
||||
"role": user.role,
|
||||
})))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CreatePartnerPayload {
|
||||
pub username: String,
|
||||
|
||||
Reference in New Issue
Block a user