-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
332 lines (240 loc) · 9.83 KB
/
main.py
File metadata and controls
332 lines (240 loc) · 9.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
from connector_ops.api import router as connector_ops_router
from contextlib import asynccontextmanager
from fastapi.middleware.cors import CORSMiddleware
from fastapi import FastAPI, UploadFile, File
from fastapi.middleware.cors import CORSMiddleware
from incident_ops.api import router as incident_ops_router
from analytics_reporting import rebuild_reporting_tables, fetch_table
from analytics_quality import validate_data_quality
from analytics_stats import compare_recent_windows
from analytics_exports import export_powerbi_bundle
from analysis.network_signatures import infer_network_family
from analysis.runbooks import get_runbook
from analysis.correlation import correlate_incident
from analysis.fleet_health import fleet_summary
from analysis.decision_engine import automated_decision, blast_radius_estimate
from fastapi.responses import PlainTextResponse
from prometheus_client import Counter, generate_latest
from classifiers.config_loader import load_rules_config
from genai_summarizer import summarize_log
from classifiers.simulation import simulate_rule_update, build_rule_diff
from storage.history import get_all_analyses, get_audit_event_by_id
from ml_predictor import predict_log_issue, analyze_log_text
from reports.renderer import render_markdown_report, write_report_files
from storage.audit import init_audit_db, get_recent_audit_events
from storage.history import (
init_db,
record_analysis,
get_signature_stats,
get_top_recurring_signatures,
get_recent_analyses,
get_report_summary,
get_analysis_by_id,
)
@asynccontextmanager
async def lifespan(app: FastAPI):
init_db()
init_audit_db()
yield
app = FastAPI(title="AutoOps Insight", lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
allow_origins=["http://127.0.0.1:5173", "http://localhost:5173", "*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
def _prefer_network_family(log_text: str, predicted_family: str) -> str:
inferred = infer_network_family(log_text)
return inferred or predicted_family
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173"],
allow_origin_regex=r"^https:\/\/.*\.azurewebsites\.net$|^https:\/\/.*\.azurecontainerapps\.io$",
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
logs_processed = Counter("logs_processed_total", "Number of logs processed")
predict_requests = Counter("predict_requests_total", "Number of prediction requests")
analyze_requests = Counter("analyze_requests_total", "Number of structured analysis requests")
summarize_requests = Counter("summarize_requests_total", "Number of summary requests")
report_requests = Counter("report_requests_total", "Number of report generation requests")
@app.get("/")
def root():
return {"message": "AutoOps Insight is running!"}
@app.get("/rules")
def rules():
return {"items": load_rules_config()}
@app.get("/audit/recent")
def audit_recent(limit: int = 20):
return {"items": get_recent_audit_events(limit=limit)}
@app.post("/predict")
async def predict_log(file: UploadFile = File(...)):
logs_processed.inc()
predict_requests.inc()
content = await file.read()
text = content.decode("utf-8")
return predict_log_issue(text)
@app.post("/analyze")
async def analyze_log(file: UploadFile = File(...)):
logs_processed.inc()
analyze_requests.inc()
content = await file.read()
text = content.decode("utf-8")
result = analyze_log_text(text)
record_analysis(result, filename=file.filename)
result["recurrence"] = get_signature_stats(result["signature"])
return result
@app.post("/summarize")
async def summarize_log_endpoint(file: UploadFile = File(...)):
summarize_requests.inc()
content = await file.read()
text = content.decode("utf-8")
summary = summarize_log(text)
return {"summary": summary}
@app.get("/history/recent")
def history_recent(limit: int = 20):
return {"items": get_recent_analyses(limit=limit)}
@app.get("/history/recurring")
def history_recurring(limit: int = 10):
return {"items": get_top_recurring_signatures(limit=limit)}
@app.get("/history/signature/{signature}")
def history_signature(signature: str):
return get_signature_stats(signature)
@app.get("/history/analysis/{analysis_id}")
def history_analysis(analysis_id: int):
result = get_analysis_by_id(analysis_id)
if result is None:
return {"error": "analysis not found"}
result["recurrence"] = get_signature_stats(result["signature"])
return result
@app.get("/reports/summary")
def reports_summary():
report_requests.inc()
return get_report_summary()
@app.get("/reports/markdown", response_class=PlainTextResponse)
def reports_markdown():
report_requests.inc()
summary = get_report_summary()
return render_markdown_report(summary)
@app.post("/reports/generate")
def reports_generate():
report_requests.inc()
summary = get_report_summary()
return write_report_files(summary)
@app.get("/audit/{audit_id}")
def get_audit_event_endpoint(audit_id: int):
event = get_audit_event_by_id(audit_id)
if event is None:
return {"error": "audit event not found", "audit_id": audit_id}
if event.get("before") and event.get("after"):
event["diff"] = build_rule_diff(event["before"], event["after"])
else:
event["diff"] = {}
return event
@app.get("/audit/{audit_id}/rollback-preview")
def get_rollback_preview_endpoint(audit_id: int):
event = get_audit_event_by_id(audit_id)
if event is None:
return {"error": "audit event not found", "audit_id": audit_id}
if event.get("event_type") != "rule_update":
return {"error": "rollback preview only supports rule_update events", "audit_id": audit_id}
before = event.get("before") or {}
after = event.get("after") or {}
rollback_updates = {}
for key, value in before.items():
if after.get(key) != value:
rollback_updates[key] = value
incidents = get_all_analyses()
impact_preview = simulate_rule_update(event["rule_id"], rollback_updates, incidents)
return {
"audit_event_id": audit_id,
"rule_id": event["rule_id"],
"rollback_updates": rollback_updates,
"impact_preview": impact_preview,
}
@app.get("/audit/{audit_id}")
def get_audit_event_endpoint(audit_id: int):
event = get_audit_event_by_id(audit_id)
if event is None:
return {"error": "audit event not found", "audit_id": audit_id}
if event.get("before") and event.get("after"):
event["diff"] = build_rule_diff(event["before"], event["after"])
else:
event["diff"] = {}
return event
@app.get("/audit/{audit_id}/rollback-preview")
def get_rollback_preview_endpoint(audit_id: int):
event = get_audit_event_by_id(audit_id)
if event is None:
return {"error": "audit event not found", "audit_id": audit_id}
if event.get("event_type") != "rule_update":
return {"error": "rollback preview only supports rule_update events", "audit_id": audit_id}
before = event.get("before") or {}
after = event.get("after") or {}
rollback_updates = {}
for key, value in before.items():
if after.get(key) != value:
rollback_updates[key] = value
incidents = get_all_analyses()
impact_preview = simulate_rule_update(event["rule_id"], rollback_updates, incidents)
return {
"audit_event_id": audit_id,
"rule_id": event["rule_id"],
"rollback_updates": rollback_updates,
"impact_preview": impact_preview,
}
@app.get("/metrics", response_class=PlainTextResponse)
def metrics():
return generate_latest()
@app.get("/healthz")
@app.get("/health")
def health():
return {"status": "ok"}
def healthz():
return {"status": "ok"}
@app.post("/reporting/rebuild")
def reporting_rebuild():
return rebuild_reporting_tables()
@app.get("/reporting/daily")
def reporting_daily(limit: int = 100):
return {"items": fetch_table("reporting_daily_summary", limit=limit)}
@app.get("/reporting/weekly")
def reporting_weekly(limit: int = 100):
return {"items": fetch_table("reporting_weekly_summary", limit=limit)}
@app.get("/reporting/pipeline-trends")
def reporting_pipeline_trends(limit: int = 100):
return {"items": fetch_table("reporting_pipeline_trends", limit=limit)}
@app.get("/reporting/root-causes")
def reporting_root_causes(limit: int = 100):
return {"items": fetch_table("reporting_root_cause_counts", limit=limit)}
@app.get("/reporting/deployment-regressions")
def reporting_deployment_regressions(limit: int = 100):
return {"items": fetch_table("reporting_deployment_regressions", limit=limit)}
@app.get("/reporting/data-quality")
def reporting_data_quality():
return validate_data_quality()
@app.get("/reporting/compare")
def reporting_compare(before_limit: int = 10, after_limit: int = 10):
return compare_recent_windows(before_limit=before_limit, after_limit=after_limit)
@app.post("/reporting/export-powerbi")
def reporting_export_powerbi():
return export_powerbi_bundle()
@app.get("/incident/runbook/{failure_family}")
def incident_runbook(failure_family: str):
return get_runbook(failure_family)
@app.get("/incident/correlate")
def incident_correlate(incident_id: int | None = None, signature: str | None = None, window_minutes: int = 30):
return correlate_incident(incident_id=incident_id, signature=signature, window_minutes=window_minutes)
@app.get("/fleet/health")
def fleet_health_view():
return fleet_summary()
app.include_router(incident_ops_router)
@app.get("/incident/decision/{incident_id}")
def incident_decision(incident_id: int):
return automated_decision(incident_id)
@app.get("/incident/blast-radius/{incident_id}")
def incident_blast_radius(incident_id: int, window_minutes: int = 60):
return blast_radius_estimate(incident_id, window_minutes=window_minutes)
app.include_router(connector_ops_router)