Update app.py
Browse files
app.py
CHANGED
|
@@ -1041,7 +1041,7 @@ class AuditTrailManager:
|
|
| 1041 |
|
| 1042 |
def get_execution_dataframe(self) -> pd.DataFrame:
|
| 1043 |
"""
|
| 1044 |
-
FIXED:
|
| 1045 |
"""
|
| 1046 |
try:
|
| 1047 |
if not self.executions:
|
|
@@ -1051,50 +1051,136 @@ class AuditTrailManager:
|
|
| 1051 |
"Start Time", "End Time", "Duration", "Boundary"
|
| 1052 |
])
|
| 1053 |
|
| 1054 |
-
# Build DataFrame from executions
|
| 1055 |
data = []
|
| 1056 |
for i, execution in enumerate(self.executions):
|
| 1057 |
-
|
| 1058 |
-
|
| 1059 |
-
|
| 1060 |
-
|
| 1061 |
-
|
| 1062 |
-
|
| 1063 |
-
|
| 1064 |
-
|
| 1065 |
-
|
| 1066 |
-
|
| 1067 |
-
|
| 1068 |
-
|
| 1069 |
-
|
| 1070 |
-
|
| 1071 |
-
|
| 1072 |
-
|
| 1073 |
-
|
| 1074 |
-
|
| 1075 |
-
|
| 1076 |
-
|
| 1077 |
-
|
| 1078 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1079 |
|
|
|
|
| 1080 |
df = pd.DataFrame(data)
|
| 1081 |
|
| 1082 |
-
#
|
| 1083 |
if not df.empty and "Start Time" in df.columns:
|
| 1084 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1085 |
|
|
|
|
| 1086 |
return df
|
| 1087 |
|
| 1088 |
except Exception as e:
|
| 1089 |
-
logger.error(f"Error creating execution DataFrame: {e}")
|
| 1090 |
-
# Return
|
| 1091 |
-
|
| 1092 |
-
"Error", "Message"
|
| 1093 |
-
]).from_records([{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1094 |
|
| 1095 |
def get_incident_dataframe(self) -> pd.DataFrame:
|
| 1096 |
"""
|
| 1097 |
-
FIXED:
|
| 1098 |
"""
|
| 1099 |
try:
|
| 1100 |
if not self.incidents:
|
|
@@ -1104,46 +1190,135 @@ class AuditTrailManager:
|
|
| 1104 |
"Confidence", "Action", "Target"
|
| 1105 |
])
|
| 1106 |
|
| 1107 |
-
# Build DataFrame from incidents
|
| 1108 |
data = []
|
| 1109 |
for i, incident in enumerate(self.incidents):
|
| 1110 |
-
|
| 1111 |
-
|
| 1112 |
-
|
| 1113 |
-
|
| 1114 |
-
|
| 1115 |
-
|
| 1116 |
-
|
| 1117 |
-
|
| 1118 |
-
|
| 1119 |
-
|
| 1120 |
-
|
| 1121 |
-
|
| 1122 |
-
|
| 1123 |
-
|
| 1124 |
-
|
| 1125 |
-
|
| 1126 |
-
|
| 1127 |
-
|
| 1128 |
-
|
| 1129 |
-
|
| 1130 |
-
|
| 1131 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1132 |
|
|
|
|
| 1133 |
df = pd.DataFrame(data)
|
| 1134 |
|
| 1135 |
-
#
|
| 1136 |
if not df.empty and "Time" in df.columns:
|
| 1137 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1138 |
|
|
|
|
| 1139 |
return df
|
| 1140 |
|
| 1141 |
except Exception as e:
|
| 1142 |
-
logger.error(f"Error creating incident DataFrame: {e}")
|
| 1143 |
-
# Return
|
| 1144 |
-
|
| 1145 |
-
"Error", "Message"
|
| 1146 |
-
]).from_records([{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1147 |
|
| 1148 |
def get_execution_table_html(self):
|
| 1149 |
"""Legacy HTML method for backward compatibility"""
|
|
|
|
| 1041 |
|
| 1042 |
def get_execution_dataframe(self) -> pd.DataFrame:
|
| 1043 |
"""
|
| 1044 |
+
FIXED: Robust pandas DataFrame creation for Gradio DataFrame component
|
| 1045 |
"""
|
| 1046 |
try:
|
| 1047 |
if not self.executions:
|
|
|
|
| 1051 |
"Start Time", "End Time", "Duration", "Boundary"
|
| 1052 |
])
|
| 1053 |
|
| 1054 |
+
# Build DataFrame from executions with safe access
|
| 1055 |
data = []
|
| 1056 |
for i, execution in enumerate(self.executions):
|
| 1057 |
+
try:
|
| 1058 |
+
# Safe access to nested dictionaries
|
| 1059 |
+
result = execution.get("result", {})
|
| 1060 |
+
|
| 1061 |
+
# Execution ID - safe extraction with fallback
|
| 1062 |
+
exec_id = result.get("execution_id", f"exec_{i:03d}")
|
| 1063 |
+
|
| 1064 |
+
# Status determination with multiple fallbacks
|
| 1065 |
+
status_text = "Unknown"
|
| 1066 |
+
if isinstance(result, dict):
|
| 1067 |
+
status_lower = str(result.get("status", "")).lower()
|
| 1068 |
+
if "success" in status_lower:
|
| 1069 |
+
status_text = "Success"
|
| 1070 |
+
elif "failed" in status_lower or "error" in status_lower:
|
| 1071 |
+
status_text = "Failed"
|
| 1072 |
+
else:
|
| 1073 |
+
# Check if there's an error key
|
| 1074 |
+
if result.get("error"):
|
| 1075 |
+
status_text = "Failed"
|
| 1076 |
+
else:
|
| 1077 |
+
status_text = "Success"
|
| 1078 |
+
|
| 1079 |
+
# Mode extraction
|
| 1080 |
+
mode = execution.get("mode", "unknown")
|
| 1081 |
+
|
| 1082 |
+
# Scenario extraction
|
| 1083 |
+
scenario = execution.get("scenario", "Unknown")
|
| 1084 |
+
|
| 1085 |
+
# Timestamp formatting with validation
|
| 1086 |
+
timestamp = execution.get("timestamp", "")
|
| 1087 |
+
start_time = ""
|
| 1088 |
+
if timestamp and len(timestamp) > 10:
|
| 1089 |
+
try:
|
| 1090 |
+
# Format: YYYY-MM-DD HH:MM:SS
|
| 1091 |
+
start_time = timestamp[:19]
|
| 1092 |
+
except Exception:
|
| 1093 |
+
start_time = timestamp # Fallback to raw string
|
| 1094 |
+
|
| 1095 |
+
# End time extraction from telemetry
|
| 1096 |
+
end_time = ""
|
| 1097 |
+
telemetry = result.get("telemetry", {})
|
| 1098 |
+
if telemetry:
|
| 1099 |
+
end_timestamp = telemetry.get("end_time", "")
|
| 1100 |
+
if end_timestamp and len(end_timestamp) > 10:
|
| 1101 |
+
try:
|
| 1102 |
+
end_time = end_timestamp[:19]
|
| 1103 |
+
except Exception:
|
| 1104 |
+
end_time = end_timestamp # Fallback
|
| 1105 |
+
|
| 1106 |
+
# Duration - mock or extract from execution
|
| 1107 |
+
duration = "12m" # Default mock duration
|
| 1108 |
+
if telemetry and "estimated_duration" in telemetry:
|
| 1109 |
+
duration = telemetry.get("estimated_duration", "12m")
|
| 1110 |
+
|
| 1111 |
+
# Boundary context
|
| 1112 |
+
boundary = execution.get("boundary_context", "Unknown")
|
| 1113 |
+
|
| 1114 |
+
data.append({
|
| 1115 |
+
"Execution ID": exec_id,
|
| 1116 |
+
"Scenario": scenario,
|
| 1117 |
+
"Status": status_text,
|
| 1118 |
+
"Mode": mode,
|
| 1119 |
+
"Start Time": start_time,
|
| 1120 |
+
"End Time": end_time,
|
| 1121 |
+
"Duration": duration,
|
| 1122 |
+
"Boundary": boundary
|
| 1123 |
+
})
|
| 1124 |
+
|
| 1125 |
+
except Exception as row_error:
|
| 1126 |
+
logger.warning(f"Error processing execution row {i}: {row_error}")
|
| 1127 |
+
# Add error row for debugging
|
| 1128 |
+
data.append({
|
| 1129 |
+
"Execution ID": f"error_{i}",
|
| 1130 |
+
"Scenario": "Error",
|
| 1131 |
+
"Status": "Failed",
|
| 1132 |
+
"Mode": "error",
|
| 1133 |
+
"Start Time": datetime.datetime.now().isoformat()[:19],
|
| 1134 |
+
"End Time": "",
|
| 1135 |
+
"Duration": "0m",
|
| 1136 |
+
"Boundary": "Error processing"
|
| 1137 |
+
})
|
| 1138 |
+
|
| 1139 |
+
if not data:
|
| 1140 |
+
logger.warning("No valid execution data found, returning empty DataFrame")
|
| 1141 |
+
return pd.DataFrame(columns=[
|
| 1142 |
+
"Execution ID", "Scenario", "Status", "Mode",
|
| 1143 |
+
"Start Time", "End Time", "Duration", "Boundary"
|
| 1144 |
+
])
|
| 1145 |
|
| 1146 |
+
# Create DataFrame
|
| 1147 |
df = pd.DataFrame(data)
|
| 1148 |
|
| 1149 |
+
# Safe sorting - only if we have valid Start Time data
|
| 1150 |
if not df.empty and "Start Time" in df.columns:
|
| 1151 |
+
# Check if Start Time column has valid data
|
| 1152 |
+
valid_times = df["Start Time"].apply(
|
| 1153 |
+
lambda x: isinstance(x, str) and len(x) > 0 and x != "None"
|
| 1154 |
+
)
|
| 1155 |
+
|
| 1156 |
+
if valid_times.any():
|
| 1157 |
+
try:
|
| 1158 |
+
# Sort by time (newest first)
|
| 1159 |
+
df = df.sort_values("Start Time", ascending=False)
|
| 1160 |
+
except Exception as sort_error:
|
| 1161 |
+
logger.warning(f"Could not sort DataFrame: {sort_error}")
|
| 1162 |
+
# Keep unsorted if sorting fails
|
| 1163 |
+
else:
|
| 1164 |
+
logger.debug("No valid timestamps for sorting")
|
| 1165 |
|
| 1166 |
+
logger.info(f"✅ Created execution DataFrame with {len(df)} rows")
|
| 1167 |
return df
|
| 1168 |
|
| 1169 |
except Exception as e:
|
| 1170 |
+
logger.error(f"❌ Error creating execution DataFrame: {e}")
|
| 1171 |
+
# Return informative error DataFrame
|
| 1172 |
+
error_df = pd.DataFrame(columns=[
|
| 1173 |
+
"Error", "Message", "Timestamp"
|
| 1174 |
+
]).from_records([{
|
| 1175 |
+
"Error": "DataFrame Creation Failed",
|
| 1176 |
+
"Message": str(e),
|
| 1177 |
+
"Timestamp": datetime.datetime.now().isoformat()[:19]
|
| 1178 |
+
}])
|
| 1179 |
+
return error_df
|
| 1180 |
|
| 1181 |
def get_incident_dataframe(self) -> pd.DataFrame:
|
| 1182 |
"""
|
| 1183 |
+
FIXED: Robust pandas DataFrame creation for Gradio DataFrame component
|
| 1184 |
"""
|
| 1185 |
try:
|
| 1186 |
if not self.incidents:
|
|
|
|
| 1190 |
"Confidence", "Action", "Target"
|
| 1191 |
])
|
| 1192 |
|
| 1193 |
+
# Build DataFrame from incidents with safe access
|
| 1194 |
data = []
|
| 1195 |
for i, incident in enumerate(self.incidents):
|
| 1196 |
+
try:
|
| 1197 |
+
# Safe extraction of basic fields
|
| 1198 |
+
scenario = incident.get("scenario", "Unknown")
|
| 1199 |
+
boundary = incident.get("boundary_context", "OSS analysis")
|
| 1200 |
+
|
| 1201 |
+
# Analysis data extraction
|
| 1202 |
+
analysis = incident.get("analysis", {})
|
| 1203 |
+
|
| 1204 |
+
# Status determination
|
| 1205 |
+
status = "Analyzed"
|
| 1206 |
+
if isinstance(analysis, dict):
|
| 1207 |
+
analysis_status = analysis.get("status", "").lower()
|
| 1208 |
+
if analysis_status:
|
| 1209 |
+
status = analysis_status.capitalize()
|
| 1210 |
+
else:
|
| 1211 |
+
# Fallback status determination
|
| 1212 |
+
if analysis.get("error"):
|
| 1213 |
+
status = "Error"
|
| 1214 |
+
elif analysis.get("analysis") or analysis.get("oss_analysis"):
|
| 1215 |
+
status = "Success"
|
| 1216 |
+
|
| 1217 |
+
# Timestamp formatting
|
| 1218 |
+
timestamp = incident.get("timestamp", "")
|
| 1219 |
+
time_display = ""
|
| 1220 |
+
if timestamp and len(timestamp) > 10:
|
| 1221 |
+
try:
|
| 1222 |
+
# Extract HH:MM:SS
|
| 1223 |
+
time_display = timestamp[11:19]
|
| 1224 |
+
except Exception:
|
| 1225 |
+
time_display = timestamp[:8] if len(timestamp) >= 8 else timestamp
|
| 1226 |
+
|
| 1227 |
+
# Extract healing intent details with multiple fallback paths
|
| 1228 |
+
confidence = 0.85 # Default confidence
|
| 1229 |
+
action = "Analysis"
|
| 1230 |
+
target = "system"
|
| 1231 |
+
|
| 1232 |
+
# Try multiple paths to find healing intent
|
| 1233 |
+
healing_intent = None
|
| 1234 |
+
|
| 1235 |
+
# Path 1: oss_analysis -> analysis -> decision
|
| 1236 |
+
oss_analysis = analysis.get("oss_analysis", {})
|
| 1237 |
+
if isinstance(oss_analysis, dict):
|
| 1238 |
+
oss_analysis_inner = oss_analysis.get("analysis", {})
|
| 1239 |
+
if isinstance(oss_analysis_inner, dict):
|
| 1240 |
+
healing_intent = oss_analysis_inner.get("decision", {})
|
| 1241 |
+
|
| 1242 |
+
# Path 2: direct analysis -> decision
|
| 1243 |
+
if not healing_intent and isinstance(analysis.get("analysis", {}), dict):
|
| 1244 |
+
healing_intent = analysis["analysis"].get("decision", {})
|
| 1245 |
+
|
| 1246 |
+
# Path 3: direct healing_intent
|
| 1247 |
+
if not healing_intent:
|
| 1248 |
+
healing_intent = analysis.get("healing_intent", {})
|
| 1249 |
+
|
| 1250 |
+
if healing_intent and isinstance(healing_intent, dict):
|
| 1251 |
+
confidence = healing_intent.get("confidence", 0.85)
|
| 1252 |
+
action = healing_intent.get("action", "Analysis")
|
| 1253 |
+
target = healing_intent.get("target", "system")
|
| 1254 |
+
|
| 1255 |
+
# Format confidence as percentage
|
| 1256 |
+
confidence_display = f"{confidence * 100:.1f}%"
|
| 1257 |
+
|
| 1258 |
+
data.append({
|
| 1259 |
+
"Scenario": scenario,
|
| 1260 |
+
"Status": status,
|
| 1261 |
+
"Boundary": boundary,
|
| 1262 |
+
"Time": time_display,
|
| 1263 |
+
"Confidence": confidence_display,
|
| 1264 |
+
"Action": action[:50], # Limit action length
|
| 1265 |
+
"Target": target[:30] # Limit target length
|
| 1266 |
+
})
|
| 1267 |
+
|
| 1268 |
+
except Exception as row_error:
|
| 1269 |
+
logger.warning(f"Error processing incident row {i}: {row_error}")
|
| 1270 |
+
# Add error row for debugging
|
| 1271 |
+
data.append({
|
| 1272 |
+
"Scenario": "Error",
|
| 1273 |
+
"Status": "Failed",
|
| 1274 |
+
"Boundary": "Error processing",
|
| 1275 |
+
"Time": datetime.datetime.now().isoformat()[11:19],
|
| 1276 |
+
"Confidence": "0.0%",
|
| 1277 |
+
"Action": "Error",
|
| 1278 |
+
"Target": "system"
|
| 1279 |
+
})
|
| 1280 |
+
|
| 1281 |
+
if not data:
|
| 1282 |
+
logger.warning("No valid incident data found, returning empty DataFrame")
|
| 1283 |
+
return pd.DataFrame(columns=[
|
| 1284 |
+
"Scenario", "Status", "Boundary", "Time",
|
| 1285 |
+
"Confidence", "Action", "Target"
|
| 1286 |
+
])
|
| 1287 |
|
| 1288 |
+
# Create DataFrame
|
| 1289 |
df = pd.DataFrame(data)
|
| 1290 |
|
| 1291 |
+
# Safe sorting - only if we have valid Time data
|
| 1292 |
if not df.empty and "Time" in df.columns:
|
| 1293 |
+
# Check if Time column has valid data
|
| 1294 |
+
valid_times = df["Time"].apply(
|
| 1295 |
+
lambda x: isinstance(x, str) and len(x) > 0 and x != "None"
|
| 1296 |
+
)
|
| 1297 |
+
|
| 1298 |
+
if valid_times.any():
|
| 1299 |
+
try:
|
| 1300 |
+
# Sort by time (newest first)
|
| 1301 |
+
df = df.sort_values("Time", ascending=False)
|
| 1302 |
+
except Exception as sort_error:
|
| 1303 |
+
logger.warning(f"Could not sort incident DataFrame: {sort_error}")
|
| 1304 |
+
# Keep unsorted if sorting fails
|
| 1305 |
+
else:
|
| 1306 |
+
logger.debug("No valid timestamps for sorting in incident DataFrame")
|
| 1307 |
|
| 1308 |
+
logger.info(f"✅ Created incident DataFrame with {len(df)} rows")
|
| 1309 |
return df
|
| 1310 |
|
| 1311 |
except Exception as e:
|
| 1312 |
+
logger.error(f"❌ Error creating incident DataFrame: {e}")
|
| 1313 |
+
# Return informative error DataFrame
|
| 1314 |
+
error_df = pd.DataFrame(columns=[
|
| 1315 |
+
"Error", "Message", "Timestamp"
|
| 1316 |
+
]).from_records([{
|
| 1317 |
+
"Error": "DataFrame Creation Failed",
|
| 1318 |
+
"Message": str(e),
|
| 1319 |
+
"Timestamp": datetime.datetime.now().isoformat()[:19]
|
| 1320 |
+
}])
|
| 1321 |
+
return error_df
|
| 1322 |
|
| 1323 |
def get_execution_table_html(self):
|
| 1324 |
"""Legacy HTML method for backward compatibility"""
|