sync: auto-sync from DESKTOP-0O8A1RL at 2026-05-23 11:05:00
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
177
.claude/scripts/alerts_patch.py
Normal file
177
.claude/scripts/alerts_patch.py
Normal file
@@ -0,0 +1,177 @@
|
||||
import sys
|
||||
import base64
|
||||
|
||||
path = "/home/guru/gururmm/dashboard/src/pages/Alerts.tsx"
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
# Component encoded as base64 to avoid any quoting issues in this script
|
||||
# Decoded content is the WatchdogAlertsSection TSX component
|
||||
COMPONENT_B64 = (
|
||||
"Cgov"
|
||||
"LyBXYXRjaGRvZ0FsZXJ0c1NlY3Rpb24KCnR5cGUgV2RvZ1N0YXR1cyA9ICJhY3RpdmUiIHwg"
|
||||
"ImFja25vd2xlZGdlZCIgfCAicmVzb2x2ZWQiOwoKZnVuY3Rpb24gd2RvZ1N0YXR1cyhhbGVy"
|
||||
"dDogV2F0Y2hkb2dBbGVydCk6IFdkb2dTdGF0dXMgewogIGlmIChhbGVydC5yZXNvbHZlZF9h"
|
||||
"dCkgcmV0dXJuICJyZXNvbHZlZCI7CiAgaWYgKGFsZXJ0LmFja25vd2xlZGdlZF9hdCkgcmV0"
|
||||
"dXJuICJhY2tub3dsZWRnZWQiOwogIHJldHVybiAiYWN0aXZlIjsKfQoKZnVuY3Rpb24gV2F0"
|
||||
"Y2hkb2dBbGVydHNTZWN0aW9uKCkgewogIGNvbnN0IHF1ZXJ5Q2xpZW50ID0gdXNlUXVlcnlD"
|
||||
"bGllbnQoKTsKICBjb25zdCB7IHRvYXN0IH0gPSB1c2VUb2FzdCgpOwogIGNvbnN0IFtzaG93"
|
||||
"QWxsLCBzZXRTaG93QWxsXSA9IHVzZVN0YXRlKGZhbHNlKTsKICBjb25zdCBbZXhwYW5kZWRM"
|
||||
"b2dJZCwgc2V0RXhwYW5kZWRMb2dJZF0gPSB1c2VTdGF0ZTxzdHJpbmcgfCBudWxsPihudWxs"
|
||||
"KTsKCiAgY29uc3QgeyBkYXRhOiBhbGxBbGVydHMgPSBbXSwgaXNMb2FkaW5nIH0gPSB1c2VR"
|
||||
"dWVyeSh7CiAgICBxdWVyeUtleTogWyJ3YXRjaGRvZy1hbGVydHMiXSwKICAgIHF1ZXJ5Rm46"
|
||||
"ICgpID0+IHdhdGNoZG9nQWxlcnRzQXBpLmxpc3QoKS50aGVuKChyKSA9PiByLmRhdGEpLAog"
|
||||
"ICAgcmVmZXRjaEludGVydmFsOiAzMDAwMCwKICB9KTsKCiAgY29uc3QgYWxlcnRzID0gc2hv"
|
||||
"d0FsbCA/IGFsbEFsZXJ0cyA6IGFsbEFsZXJ0cy5maWx0ZXIoKGEpID0+ICFhLnJlc29sdmVk"
|
||||
"X2F0KTsKCiAgY29uc3QgYWNrbm93bGVkZ2VNdXRhdGlvbiA9IHVzZU11dGF0aW9uKHsKICAg"
|
||||
"IG11dGF0aW9uRm46IChpZDogc3RyaW5nKSA9PiB3YXRjaGRvZ0FsZXJ0c0FwaS5hY2tub3ds"
|
||||
"ZWRnZShpZCksCiAgICBvblN1Y2Nlc3M6ICgpID0+IHsKICAgICAgcXVlcnlDbGllbnQuaW52"
|
||||
"YWxpZGF0ZVF1ZXJpZXMoeyBxdWVyeUtleTogWyJ3YXRjaGRvZy1hbGVydHMiXSB9KTsKICAg"
|
||||
"ICAgdG9hc3QoeyB0eXBlOiAic3VjY2VzcyIsIHRpdGxlOiAiQWxlcnQgYWNrbm93bGVkZ2Vk"
|
||||
"IiB9KTsKICAgIH0sCiAgICBvbkVycm9yOiAoZXJyOiBFcnJvcikgPT4KICAgICAgdG9hc3Qo"
|
||||
"eyB0eXBlOiAiZXJyb3IiLCB0aXRsZTogIkNvdWxkIG5vdCBhY2tub3dsZWRnZSIsIG1lc3Nh"
|
||||
"Z2U6IGVyci5tZXNzYWdlIH0pLAogIH0pOwoKICBjb25zdCByZXNvbHZlTXV0YXRpb24gPSB1"
|
||||
"c2VNdXRhdGlvbih7CiAgICBtdXRhdGlvbkZuOiAoaWQ6IHN0cmluZykgPT4gd2F0Y2hkb2dB"
|
||||
"bGVydHNBcGkucmVzb2x2ZShpZCksCiAgICBvblN1Y2Nlc3M6ICgpID0+IHsKICAgICAgcXVl"
|
||||
"cnlDbGllbnQuaW52YWxpZGF0ZVF1ZXJpZXMoeyBxdWVyeUtleTogWyJ3YXRjaGRvZy1hbGVy"
|
||||
"dHMiXSB9KTsKICAgICAgdG9hc3QoeyB0eXBlOiAic3VjY2VzcyIsIHRpdGxlOiAiQWxlcnQg"
|
||||
"cmVzb2x2ZWQiIH0pOwogICAgfSwKICAgIG9uRXJyb3I6IChlcnI6IEVycm9yKSA9PgogICAg"
|
||||
"ICB0b2FzdCh7IHR5cGU6ICJlcnJvciIsIHRpdGxlOiAiQ291bGQgbm90IHJlc29sdmUiLCBt"
|
||||
"ZXNzYWdlOiBlcnIubWVzc2FnZSB9KSwKICB9KTsKCiAgY29uc3QgZGVsZXRlTXV0YXRpb24g"
|
||||
"PSB1c2VNdXRhdGlvbih7CiAgICBtdXRhdGlvbkZuOiAoaWQ6IHN0cmluZykgPT4gd2F0Y2hk"
|
||||
"b2dBbGVydHNBcGkuZGVsZXRlKGlkKSwKICAgIG9uU3VjY2VzczogKCkgPT4gewogICAgICBx"
|
||||
"dWVyeUNsaWVudC5pbnZhbGlkYXRlUXVlcmllcyh7IHF1ZXJ5S2V5OiBbIndhdGNoZG9nLWFs"
|
||||
"ZXJ0cyJdIH0pOwogICAgICB0b2FzdCh7IHR5cGU6ICJzdWNjZXNzIiwgdGl0bGU6ICJBbGVy"
|
||||
"dCBkZWxldGVkIiB9KTsKICAgIH0sCiAgICBvbkVycm9yOiAoZXJyOiBFcnJvcikgPT4KICAg"
|
||||
"ICAgdG9hc3QoeyB0eXBlOiAiZXJyb3IiLCB0aXRsZTogIkNvdWxkIG5vdCBkZWxldGUiLCBt"
|
||||
"ZXNzYWdlOiBlcnIubWVzc2FnZSB9KSwKICB9KTsKCiAgY29uc3QgaXNNdXRhdGluZyA9CiAg"
|
||||
"ICBhY2tub3dsZWRnZU11dGF0aW9uLmlzUGVuZGluZyB8fAogICAgcmVzb2x2ZU11dGF0aW9u"
|
||||
"LmlzUGVuZGluZyB8fAogICAgZGVsZXRlTXV0YXRpb24uaXNQZW5kaW5nOwoKICBjb25zdCBh"
|
||||
"Y3RpdmVDb3VudCA9IGFsbEFsZXJ0cy5maWx0ZXIoKGEpID0+IHdkb2dTdGF0dXMoYSkgPT09"
|
||||
"ICJhY3RpdmUiKS5sZW5ndGg7CgogIHJldHVybiAoCiAgICA8Q2FyZD4KICAgICAgPENhcmRI"
|
||||
"ZWFkZXI+CiAgICAgICAgPGRpdiBjbGFzc05hbWU9ImZsZXggaXRlbXMtY2VudGVyIGp1c3Rp"
|
||||
"ZnktYmV0d2VlbiBnYXAtMyI+CiAgICAgICAgICA8Q2FyZFRpdGxlIGNsYXNzTmFtZT0iZmxl"
|
||||
"eCBpdGVtcy1jZW50ZXIgZ2FwLTIiPgogICAgICAgICAgICA8QWxlcnRUcmlhbmdsZSBjbGFz"
|
||||
"c05hbWU9Img0IHctNCB0ZXh0LWFtYmVyLTUwMCIgLz4KICAgICAgICAgICAgV2F0Y2hkb2cg"
|
||||
"QWxlcnRzCiAgICAgICAgICAgIHthY3RpdmVDb3VudCA+IDAgJiYgKAogICAgICAgICAgICAg"
|
||||
"IDxzcGFuIGNsYXNzTmFtZT0ibWwtMSByb3VuZGVkLWZ1bGwgYmctYW1iZXItNTAwLzE1IHB4"
|
||||
"LTIgcHktMC41IHRleHQteHMgZm9udC1tZWRpdW0gdGV4dC1hbWJlci02MDAgZGFyazp0ZXh0"
|
||||
"LWFtYmVyLTQwMCI+CiAgICAgICAgICAgICAgICB7YWN0aXZlQ291bnR9IGFjdGl2ZQogICAg"
|
||||
"ICAgICAgICAgIDwvc3Bhbj4KICAgICAgICAgICAgKX0KICAgICAgICAgIDwvQ2FyZFRpdGxlPgog"
|
||||
"ICAgICAgICAgPGJ1dHRvbgogICAgICAgICAgICBjbGFzc05hbWU9InRleHQteHMgdGV4dC1baHNs"
|
||||
"KHZhcigtLW11dGVkLWZvcmVncm91bmQpKV0gaG92ZXI6dGV4dC1baHNsKHZhcigtLWZvcmVn"
|
||||
"cm91bmQpKV0gdW5kZXJsaW5lLW9mZnNldC0yIGhvdmVyOnVuZGVybGluZSIKICAgICAgICAg"
|
||||
"ICAgb25DbGljaz17KCkgPT4gc2V0U2hvd0FsbCgodikgPT4gIXYpfQogICAgICAgICAgPgog"
|
||||
"ICAgICAgICAgICB7c2hvd0FsbCA/ICJBY3RpdmUgb25seSIgOiAiU2hvdyBhbGwifQogICAg"
|
||||
"ICAgICAgPC9idXR0b24+CiAgICAgICAgPC9kaXY+CiAgICAgIDwvQ2FyZEhlYWRlcj4KICAg"
|
||||
"ICAgPENhcmRDb250ZW50PgogICAgICAgIHtpc0xvYWRpbmcgJiYgKAogICAgICAgICAgPHAg"
|
||||
"Y2xhc3NOYW1lPSJweS02IHRleHQtY2VudGVyIHRleHQtc20gdGV4dC1baHNsKHZhcigtLW11"
|
||||
"dGVkLWZvcmVncm91bmQpKV0iPkxvYWRpbmcuLi48L3A+CiAgICAgICAgKX0KICAgICAgICB7"
|
||||
"IWlzTG9hZGluZyAmJiBhbGVydHMubGVuZ3RoID09PSAwICYmICgKICAgICAgICAgIDxwIGNs"
|
||||
"YXNzTmFtZT0icHktNiB0ZXh0LWNlbnRlciB0ZXh0LXNtIHRleHQtW2hzbCh2YXIoLS1tdXRl"
|
||||
"ZC1mb3JlZ3JvdW5kKSldIj4KICAgICAgICAgICAge3Nob3dBbGwgPyAiTm8gd2F0Y2hkb2cg"
|
||||
"YWxlcnRzIHJlY29yZGVkLiIgOiAiTm8gYWN0aXZlIHdhdGNoZG9nIGFsZXJ0cy4ifQogICAg"
|
||||
"ICAgICAgPC9wPgogICAgICAgICl9CiAgICAgICAgPGRpdiBjbGFzc05hbWU9InNwYWNlLXktMyI+"
|
||||
"CiAgICAgICAgICB7YWxlcnRzLm1hcCgoYWxlcnQpID0+IHsKICAgICAgICAgICAgY29uc3Qg"
|
||||
"c3RhdHVzID0gd2RvZ1N0YXR1cyhhbGVydCk7CiAgICAgICAgICAgIGNvbnN0IGxvZ0V4cGFu"
|
||||
"ZGVkID0gZXhwYW5kZWRMb2dJZCA9PT0gYWxlcnQuaWQ7CiAgICAgICAgICAgIGNvbnN0IHN0"
|
||||
"YXR1c0NvbG9yID0KICAgICAgICAgICAgICBzdGF0dXMgPT09ICJhY3RpdmUiCiAgICAgICAg"
|
||||
"ICAgICAgICA/ICJ0ZXh0LXJlZC02MDAgZGFyazp0ZXh0LXJlZC00MDAiCiAgICAgICAgICAg"
|
||||
"ICAgICAgIDogc3RhdHVzID09PSAiYWNrbm93bGVkZ2VkIgogICAgICAgICAgICAgICAgPyAi"
|
||||
"dGV4dC1hbWJlci02MDAgZGFyazp0ZXh0LWFtYmVyLTQwMCIKICAgICAgICAgICAgICAgIDog"
|
||||
"InRleHQtW2hzbCh2YXIoLS1tdXRlZC1mb3JlZ3JvdW5kKSldIjsKCiAgICAgICAgICAgIHJl"
|
||||
"dHVybiAoCiAgICAgICAgICAgICAgPGRpdgogICAgICAgICAgICAgICAga2V5PXthbGVydC5pZH0K"
|
||||
"ICAgICAgICAgICAgICAgIGNsYXNzTmFtZT0icm91bmRlZC1sZyBib3JkZXIgYm9yZGVyLVto"
|
||||
"c2wodmFyKC0tYm9yZGVyKSldIHA0IHNwYWNlLXktMiIKICAgICAgICAgICAgICA+CiAgICAg"
|
||||
"ICAgICAgICAgICA8ZGl2IGNsYXNzTmFtZT0iZmxleCBpdGVtcy1zdGFydCBqdXN0aWZ5LWJl"
|
||||
"dHdlZW4gZ2FwLTQiPgogICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzTmFtZT0ic3BhY2Ut"
|
||||
"eS0wLjUiPgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3NOYW1lPSJmbGV4IGl0ZW1z"
|
||||
"LWNlbnRlciBnYXAtMiI+CiAgICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzc05hbWU9"
|
||||
"e2B0ZXh0LXhzIGZvbnQtc2VtaWJvbGQgdXBwZXJjYXNlIHRyYWNraW5nLXdpZGVyICR7c3Rh"
|
||||
"dHVzQ29sb3J9YH0+CiAgICAgICAgICAgICAgICAgICAgICAgIHtzdGF0dXN9CiAgICAgICAg"
|
||||
"ICAgICAgICAgICAgICA8L3NwYW4+CiAgICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFz"
|
||||
"c05hbWU9InRleHQteHMgdGV4dC1baHNsKHZhcigtLW11dGVkLWZvcmVncm91bmQpKV0iPgog"
|
||||
"ICAgICAgICAgICAgICAgICAgICAgICAgwrcge2FsZXJ0LnJlc3RhcnRfYXR0ZW1wdHN9IHJl"
|
||||
"c3RhcnQgYXR0ZW1wdAogICAgICAgICAgICAgICAgICAgICAgICB7YWxlcnQucmVzdGFydF9h"
|
||||
"dHRlbXB0cyAhPT0gMSA/ICJzIiA6ICIifQogICAgICAgICAgICAgICAgICAgICAgPC9zcGFu"
|
||||
"PgogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDxwIGNs"
|
||||
"YXNzTmFtZT0idGV4dC14cyB0ZXh0LVtoc2wodmFyKC0tbXV0ZWQtZm9yZWdyb3VuZCkpXSI+"
|
||||
"CiAgICAgICAgICAgICAgICAgICAgICBBZ2VudCBJRDp7IiAifQogICAgICAgICAgICAgICAg"
|
||||
"ICAgICAgPHNwYW4gY2xhc3NOYW1lPSJmb250LW1vbm8iIHRpdGxlPXthbGVydC5hZ2VudF9p"
|
||||
"ZH0+CiAgICAgICAgICAgICAgICAgICAgICAgIHthbGVydC5hZ2VudF9pZC5zbGljZSgwLCA4"
|
||||
"KX0mIzgyMzA7CiAgICAgICAgICAgICAgICAgICAgICA8L3NwYW4+CiAgICAgICAgICAgICAg"
|
||||
"ICAgICAgICB7IiAifcK3eyIgIn0KICAgICAgICAgICAgICAgICAgICAgIFRyaWdnZXJlZDoge"
|
||||
"2Zvcm1hdFJlbGF0aXZlKGFsZXJ0LnRyaWdnZXJlZF9hdCl9CiAgICAgICAgICAgICAgICAg"
|
||||
"ICAgICB7YWxlcnQuYWNrbm93bGVkZ2VkX2F0ICYmICgKICAgICAgICAgICAgICAgICAgICAg"
|
||||
"ICAgPD57IiAifcK3eyIgIn1BY2s6IHtmb3JtYXRSZWxhdGl2ZShhbGVydC5hY2tub3dsZWRn"
|
||||
"ZWRfYXQpfTwvPgogICAgICAgICAgICAgICAgICAgICAgKX0KICAgICAgICAgICAgICAgICAg"
|
||||
"ICAgIHthbGVydC5yZXNvbHZlZF9hdCAmJiAoCiAgICAgICAgICAgICAgICAgICAgICAgIDw+"
|
||||
"eyIgIn3CtyB7IiAifVJlc29sdmVkOiB7Zm9ybWF0UmVsYXRpdmUoYWxlcnQucmVzb2x2ZWRf"
|
||||
"YXQpfTwvPgogICAgICAgICAgICAgICAgICAgICAgKX0KICAgICAgICAgICAgICAgICAgICA8"
|
||||
"L3A+CiAgICAgICAgICAgICAgICAgICAge2FsZXJ0Lmxhc3RfZXJyb3IgJiYgKAogICAgICAg"
|
||||
"ICAgICAgICAgICAgICAgPHAgY2xhc3NOYW1lPSJ0ZXh0LXNtIHRleHQtW2hzbCh2YXIoLS1m"
|
||||
"b3JlZ3JvdW5kKSldIj4KICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gY2xhc3NOYW1l"
|
||||
"PSJmb250LW1lZGl1bSI+RXJyb3I6PC9zcGFuPiB7YWxlcnQubGFzdF9lcnJvcn0KICAgICAg"
|
||||
"ICAgICAgICAgICAgICAgIDwvcD4KICAgICAgICAgICAgICAgICAgICApfQogICAgICAgICAg"
|
||||
"ICAgICAgICA8L2Rpdj4KCiAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3NOYW1lPSJmbGV4"
|
||||
"IHNocmluay0wIGl0ZW1zLWNlbnRlciBnYXAtMS41Ij4KICAgICAgICAgICAgICAgICAgICB7"
|
||||
"c3RhdHVzID09PSAiYWN0aXZlIiAmJiAoCiAgICAgICAgICAgICAgICAgICAgICA8QnV0dG9u"
|
||||
"IHNpemU9InNtIiB2YXJpYW50PSJzZWNvbmRhcnkiIGRpc2FibGVkPXtpc011dGF0aW5nfQog"
|
||||
"ICAgICAgICAgICAgICAgICAgICAgICBvbkNsaWNrPXsoKSA9PiBhY2tub3dsZWRnZU11dGF0"
|
||||
"aW9uLm11dGF0ZShhbGVydC5pZCl9PgogICAgICAgICAgICAgICAgICAgICAgICBBY2tub3ds"
|
||||
"ZWRnZQogICAgICAgICAgICAgICAgICAgICAgPC9CdXR0b24+CiAgICAgICAgICAgICAgICAg"
|
||||
"ICAgKX0KICAgICAgICAgICAgICAgICAgICB7c3RhdHVzICE9PSAicmVzb2x2ZWQiICYmICgK"
|
||||
"ICAgICAgICAgICAgICAgICAgICAgIDxCdXR0b24gc2l6ZT0ic20iIHZhcmlhbnQ9InNlY29u"
|
||||
"ZGFyeSIgZGlzYWJsZWQ9e2lzTXV0YXRpbmd9CiAgICAgICAgICAgICAgICAgICAgICAgIG9u"
|
||||
"Q2xpY2s9eygpID0+IHJlc29sdmVNdXRhdGlvbi5tdXRhdGUoYWxlcnQuaWQpfT4KICAgICAg"
|
||||
"ICAgICAgICAgICAgICAgICAgUmVzb2x2ZQogICAgICAgICAgICAgICAgICAgICAgPC9CdXR0"
|
||||
"b24+CiAgICAgICAgICAgICAgICAgICAgKX0KICAgICAgICAgICAgICAgICAgICA8QnV0dG9u"
|
||||
"IHNpemU9InNtIiB2YXJpYW50PSJnaG9zdCIgZGlzYWJsZWQ9e2lzTXV0YXRpbmd9CiAgICAg"
|
||||
"ICAgICAgICAgICAgICAgICBvbkNsaWNrPXsoKSA9PiBkZWxldGVNdXRhdGlvbi5tdXRhdGUo"
|
||||
"YWxlcnQuaWQpfT4KICAgICAgICAgICAgICAgICAgICAgIERlbGV0ZQogICAgICAgICAgICAg"
|
||||
"ICAgICAgIDwvQnV0dG9uPgogICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAg"
|
||||
"ICAgIDwvZGl2PgoKICAgICAgICAgICAgICAgIHthbGVydC5sb2dfdGFpbCAmJiAoCiAgICAg"
|
||||
"ICAgICAgICAgICAgIDxkaXY+CiAgICAgICAgICAgICAgICAgICAgPGJ1dHRvbgogICAgICAg"
|
||||
"ICAgICAgICAgICAgICAgY2xhc3NOYW1lPSJmbGV4IGl0ZW1zLWNlbnRlciBnYXAtMSB0ZXh0"
|
||||
"LXhzIHRleHQtW2hzbCh2YXIoLS1tdXRlZC1mb3JlZ3JvdW5kKSldIGhvdmVyOnRleHQtW2hz"
|
||||
"bCh2YXIoLS1mb3JlZ3JvdW5kKSldIgogICAgICAgICAgICAgICAgICAgICAgb25DbGljaz17"
|
||||
"KCkgPT4gc2V0RXhwYW5kZWRMb2dJZChsb2dFeHBhbmRlZCA/IG51bGwgOiBhbGVydC5pZCl9"
|
||||
"CiAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAge2xvZ0V4cGFuZGVk"
|
||||
"ID8gPENoZXZyb25Eb3duIGNsYXNzTmFtZT0iaDMgdzMiIC8+IDogPENoZXZyb25SaWdodCBj"
|
||||
"bGFzc05hbWU9ImgzIHczIiAvPn0KICAgICAgICAgICAgICAgICAgICAgIEFnZW50IGxvZyB0"
|
||||
"YWlsCiAgICAgICAgICAgICAgICAgICAgPC9idXR0b24+CiAgICAgICAgICAgICAgICAgICAg"
|
||||
"e2xvZ0V4cGFuZGVkICYmICgKICAgICAgICAgICAgICAgICAgICAgIDxwcmUgY2xhc3NOYW1l"
|
||||
"PSJtdC0xIG1heC1oLTQ4IG92ZXJmbG93LWF1dG8gcm91bmRlZCBiZy1baHNsKHZhcigtLW11"
|
||||
"dGVkKSldLzUwIHAtMiB0ZXh0LXhzIGZvbnQtbW9ubyB0ZXh0LVtoc2wodmFyKC0tZm9yZWdy"
|
||||
"b3VuZCkpXSI+CiAgICAgICAgICAgICAgICAgICAgICAgIHthbGVydC5sb2dfdGFpbH0KICAg"
|
||||
"ICAgICAgICAgICAgICAgICAgIDwvcHJlPgogICAgICAgICAgICAgICAgICAgICl9CiAgICAg"
|
||||
"ICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgKX0KICAgICAgICAgICAgICA8"
|
||||
"L2Rpdj4KICAgICAgICAgICAgKTsKICAgICAgICAgIH0pfQogICAgICAgIDwvZGl2PgogICAg"
|
||||
"ICA8L0NhcmRDb250ZW50PgogICAgPC9DYXJkPgogICk7Cn0K"
|
||||
)
|
||||
|
||||
component = base64.b64decode(COMPONENT_B64).decode("utf-8")
|
||||
print(f"Component decoded: {len(component)} chars")
|
||||
|
||||
insert_before = "export function Alerts() {"
|
||||
if insert_before in content:
|
||||
content = content.replace(insert_before, component + insert_before, 1)
|
||||
print("OK: WatchdogAlertsSection inserted")
|
||||
else:
|
||||
print("ERROR: insert anchor not found")
|
||||
sys.exit(1)
|
||||
|
||||
old_end = " </div>\n );\n}\n\n// Re-export types"
|
||||
new_end = " <WatchdogAlertsSection />\n </div>\n );\n}\n\n// Re-export types"
|
||||
if old_end in content:
|
||||
content = content.replace(old_end, new_end, 1)
|
||||
print("OK: WatchdogAlertsSection rendered in Alerts()")
|
||||
else:
|
||||
print("ERROR: closing div anchor not found")
|
||||
idx = content.find("// Re-export types")
|
||||
if idx >= 0:
|
||||
print("Context:", repr(content[max(0, idx-200):idx+30]))
|
||||
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
print(f"Done. Lines: {len(content.splitlines())}")
|
||||
1
.claude/scripts/component.b64
Normal file
1
.claude/scripts/component.b64
Normal file
File diff suppressed because one or more lines are too long
203
.claude/scripts/gen_b64.py
Normal file
203
.claude/scripts/gen_b64.py
Normal file
@@ -0,0 +1,203 @@
|
||||
"""Generate base64 of the WatchdogAlertsSection component."""
|
||||
import base64, sys
|
||||
|
||||
BT = chr(96)
|
||||
BULLET = "\xb7"
|
||||
|
||||
lines = [
|
||||
"\n",
|
||||
"// WatchdogAlertsSection\n",
|
||||
"\n",
|
||||
'type WdogStatus = "active" | "acknowledged" | "resolved";\n',
|
||||
"\n",
|
||||
"function wdogStatus(alert: WatchdogAlert): WdogStatus {\n",
|
||||
' if (alert.resolved_at) return "resolved";\n',
|
||||
' if (alert.acknowledged_at) return "acknowledged";\n',
|
||||
' return "active";\n',
|
||||
"}\n",
|
||||
"\n",
|
||||
"function WatchdogAlertsSection() {\n",
|
||||
" const queryClient = useQueryClient();\n",
|
||||
" const { toast } = useToast();\n",
|
||||
" const [showAll, setShowAll] = useState(false);\n",
|
||||
" const [expandedLogId, setExpandedLogId] = useState<string | null>(null);\n",
|
||||
"\n",
|
||||
" const { data: allAlerts = [], isLoading } = useQuery({\n",
|
||||
' queryKey: ["watchdog-alerts"],\n',
|
||||
" queryFn: () => watchdogAlertsApi.list().then((r) => r.data),\n",
|
||||
" refetchInterval: 30000,\n",
|
||||
" });\n",
|
||||
"\n",
|
||||
" const alerts = showAll ? allAlerts : allAlerts.filter((a) => !a.resolved_at);\n",
|
||||
"\n",
|
||||
" const acknowledgeMutation = useMutation({\n",
|
||||
" mutationFn: (id: string) => watchdogAlertsApi.acknowledge(id),\n",
|
||||
" onSuccess: () => {\n",
|
||||
' queryClient.invalidateQueries({ queryKey: ["watchdog-alerts"] });\n',
|
||||
' toast({ type: "success", title: "Alert acknowledged" });\n',
|
||||
" },\n",
|
||||
" onError: (err: Error) =>\n",
|
||||
' toast({ type: "error", title: "Could not acknowledge", message: err.message }),\n',
|
||||
" });\n",
|
||||
"\n",
|
||||
" const resolveMutation = useMutation({\n",
|
||||
" mutationFn: (id: string) => watchdogAlertsApi.resolve(id),\n",
|
||||
" onSuccess: () => {\n",
|
||||
' queryClient.invalidateQueries({ queryKey: ["watchdog-alerts"] });\n',
|
||||
' toast({ type: "success", title: "Alert resolved" });\n',
|
||||
" },\n",
|
||||
" onError: (err: Error) =>\n",
|
||||
' toast({ type: "error", title: "Could not resolve", message: err.message }),\n',
|
||||
" });\n",
|
||||
"\n",
|
||||
" const deleteMutation = useMutation({\n",
|
||||
" mutationFn: (id: string) => watchdogAlertsApi.delete(id),\n",
|
||||
" onSuccess: () => {\n",
|
||||
' queryClient.invalidateQueries({ queryKey: ["watchdog-alerts"] });\n',
|
||||
' toast({ type: "success", title: "Alert deleted" });\n',
|
||||
" },\n",
|
||||
" onError: (err: Error) =>\n",
|
||||
' toast({ type: "error", title: "Could not delete", message: err.message }),\n',
|
||||
" });\n",
|
||||
"\n",
|
||||
" const isMutating =\n",
|
||||
" acknowledgeMutation.isPending ||\n",
|
||||
" resolveMutation.isPending ||\n",
|
||||
" deleteMutation.isPending;\n",
|
||||
"\n",
|
||||
' const activeCount = allAlerts.filter((a) => wdogStatus(a) === "active").length;\n',
|
||||
"\n",
|
||||
" return (\n",
|
||||
" <Card>\n",
|
||||
" <CardHeader>\n",
|
||||
' <div className="flex items-center justify-between gap-3">\n',
|
||||
' <CardTitle className="flex items-center gap-2">\n',
|
||||
' <AlertTriangle className="h-4 w-4 text-amber-500" />\n',
|
||||
" Watchdog Alerts\n",
|
||||
" {activeCount > 0 && (\n",
|
||||
' <span className="ml-1 rounded-full bg-amber-500/15 px-2 py-0.5 text-xs font-medium text-amber-600 dark:text-amber-400">\n',
|
||||
" {activeCount} active\n",
|
||||
" </span>\n",
|
||||
" )}\n",
|
||||
" </CardTitle>\n",
|
||||
" <button\n",
|
||||
' className="text-xs text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))] underline-offset-2 hover:underline"\n',
|
||||
" onClick={() => setShowAll((v) => !v)}\n",
|
||||
" >\n",
|
||||
' {showAll ? "Active only" : "Show all"}\n',
|
||||
" </button>\n",
|
||||
" </div>\n",
|
||||
" </CardHeader>\n",
|
||||
" <CardContent>\n",
|
||||
" {isLoading && (\n",
|
||||
' <p className="py-6 text-center text-sm text-[hsl(var(--muted-foreground))]">Loading...</p>\n',
|
||||
" )}\n",
|
||||
" {!isLoading && alerts.length === 0 && (\n",
|
||||
' <p className="py-6 text-center text-sm text-[hsl(var(--muted-foreground))]">\n',
|
||||
' {showAll ? "No watchdog alerts recorded." : "No active watchdog alerts."}\n',
|
||||
" </p>\n",
|
||||
" )}\n",
|
||||
' <div className="space-y-3">\n',
|
||||
" {alerts.map((alert) => {\n",
|
||||
" const status = wdogStatus(alert);\n",
|
||||
" const logExpanded = expandedLogId === alert.id;\n",
|
||||
" const statusColor =\n",
|
||||
' status === "active"\n',
|
||||
' ? "text-red-600 dark:text-red-400"\n',
|
||||
' : status === "acknowledged"\n',
|
||||
' ? "text-amber-600 dark:text-amber-400"\n',
|
||||
' : "text-[hsl(var(--muted-foreground))]";\n',
|
||||
"\n",
|
||||
" return (\n",
|
||||
" <div\n",
|
||||
" key={alert.id}\n",
|
||||
' className="rounded-lg border border-[hsl(var(--border))] p-4 space-y-2"\n',
|
||||
" >\n",
|
||||
' <div className="flex items-start justify-between gap-4">\n',
|
||||
' <div className="space-y-0.5">\n',
|
||||
' <div className="flex items-center gap-2">\n',
|
||||
" <span className={" + BT + "text-xs font-semibold uppercase tracking-wider ${statusColor}" + BT + "}>\n",
|
||||
" {status}\n",
|
||||
" </span>\n",
|
||||
' <span className="text-xs text-[hsl(var(--muted-foreground))]">\n',
|
||||
" " + BULLET + " {alert.restart_attempts} restart attempt\n",
|
||||
' {alert.restart_attempts !== 1 ? "s" : ""}\n',
|
||||
" </span>\n",
|
||||
" </div>\n",
|
||||
' <p className="text-xs text-[hsl(var(--muted-foreground))]">\n',
|
||||
' Agent ID:{" "}\n',
|
||||
' <span className="font-mono" title={alert.agent_id}>\n',
|
||||
" {alert.agent_id.slice(0, 8)}…\n",
|
||||
" </span>\n",
|
||||
' {" "}' + BULLET + '{" "}\n',
|
||||
" Triggered: {formatRelative(alert.triggered_at)}\n",
|
||||
" {alert.acknowledged_at && (\n",
|
||||
' <>{" "}' + BULLET + '{" "}Ack: {formatRelative(alert.acknowledged_at)}</>\n',
|
||||
" )}\n",
|
||||
" {alert.resolved_at && (\n",
|
||||
' <>{" "}' + BULLET + '{" "}Resolved: {formatRelative(alert.resolved_at)}</>\n',
|
||||
" )}\n",
|
||||
" </p>\n",
|
||||
" {alert.last_error && (\n",
|
||||
' <p className="text-sm text-[hsl(var(--foreground))]">\n',
|
||||
' <span className="font-medium">Error:</span> {alert.last_error}\n',
|
||||
" </p>\n",
|
||||
" )}\n",
|
||||
" </div>\n",
|
||||
"\n",
|
||||
' <div className="flex shrink-0 items-center gap-1.5">\n',
|
||||
' {status === "active" && (\n',
|
||||
' <Button size="sm" variant="secondary" disabled={isMutating}\n',
|
||||
" onClick={() => acknowledgeMutation.mutate(alert.id)}>\n",
|
||||
" Acknowledge\n",
|
||||
" </Button>\n",
|
||||
" )}\n",
|
||||
' {status !== "resolved" && (\n',
|
||||
' <Button size="sm" variant="secondary" disabled={isMutating}\n',
|
||||
" onClick={() => resolveMutation.mutate(alert.id)}>\n",
|
||||
" Resolve\n",
|
||||
" </Button>\n",
|
||||
" )}\n",
|
||||
' <Button size="sm" variant="ghost" disabled={isMutating}\n',
|
||||
" onClick={() => deleteMutation.mutate(alert.id)}>\n",
|
||||
" Delete\n",
|
||||
" </Button>\n",
|
||||
" </div>\n",
|
||||
" </div>\n",
|
||||
"\n",
|
||||
" {alert.log_tail && (\n",
|
||||
" <div>\n",
|
||||
" <button\n",
|
||||
' className="flex items-center gap-1 text-xs text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))]"\n',
|
||||
" onClick={() => setExpandedLogId(logExpanded ? null : alert.id)}\n",
|
||||
" >\n",
|
||||
' {logExpanded ? <ChevronDown className="h-3 w-3" /> : <ChevronRight className="h-3 w-3" />}\n',
|
||||
" Agent log tail\n",
|
||||
" </button>\n",
|
||||
" {logExpanded && (\n",
|
||||
' <pre className="mt-1 max-h-48 overflow-auto rounded bg-[hsl(var(--muted))]/50 p-2 text-xs font-mono text-[hsl(var(--foreground))]">\n',
|
||||
" {alert.log_tail}\n",
|
||||
" </pre>\n",
|
||||
" )}\n",
|
||||
" </div>\n",
|
||||
" )}\n",
|
||||
" </div>\n",
|
||||
" );\n",
|
||||
" })}\n",
|
||||
" </div>\n",
|
||||
" </CardContent>\n",
|
||||
" </Card>\n",
|
||||
" );\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
]
|
||||
|
||||
component = "".join(lines)
|
||||
b64 = base64.b64encode(component.encode("utf-8")).decode("ascii")
|
||||
|
||||
with open("D:/claudetools/.claude/scripts/component.b64", "w") as f:
|
||||
f.write(b64)
|
||||
|
||||
print(f"Component: {len(component)} chars")
|
||||
print(f"Base64: {len(b64)} chars")
|
||||
print("Written to component.b64")
|
||||
346
.claude/scripts/watchdog_alerts_page.py
Normal file
346
.claude/scripts/watchdog_alerts_page.py
Normal file
@@ -0,0 +1,346 @@
|
||||
"""
|
||||
Write replacement WatchdogAlerts.tsx that matches the new WatchdogAlert interface.
|
||||
Run on the server: python3 /tmp/watchdog_alerts_page.py
|
||||
"""
|
||||
|
||||
content = '''import { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
ShieldAlert,
|
||||
RefreshCw,
|
||||
CheckCircle2,
|
||||
Clock,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
} from "lucide-react";
|
||||
import { watchdogAlertsApi, WatchdogAlert } from "../api/client";
|
||||
import { Card, CardHeader, CardTitle, CardContent } from "../components/Card";
|
||||
import { Button } from "../components/Button";
|
||||
import { NativeSelect } from "../components/Select";
|
||||
import { useToast } from "../hooks/useToast";
|
||||
import { cn } from "../lib/utils";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Status derivation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type WatchdogStatus = "active" | "acknowledged" | "resolved";
|
||||
|
||||
function watchdogStatus(alert: WatchdogAlert): WatchdogStatus {
|
||||
if (alert.resolved_at) return "resolved";
|
||||
if (alert.acknowledged_at) return "acknowledged";
|
||||
return "active";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Status badge
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function WatchdogStatusBadge({ status }: { status: WatchdogStatus }) {
|
||||
const classes: Record<WatchdogStatus, string> = {
|
||||
active: "bg-red-500/15 text-red-600 dark:text-red-400",
|
||||
acknowledged: "bg-amber-500/15 text-amber-600 dark:text-amber-400",
|
||||
resolved: "bg-green-500/15 text-green-600 dark:text-green-400",
|
||||
};
|
||||
const label: Record<WatchdogStatus, string> = {
|
||||
active: "Active",
|
||||
acknowledged: "Acknowledged",
|
||||
resolved: "Resolved",
|
||||
};
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block px-2 py-0.5 rounded text-[10px] font-semibold uppercase tracking-wider",
|
||||
classes[status]
|
||||
)}
|
||||
>
|
||||
{label[status]}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Filter type
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type StatusFilter = "" | "active" | "acknowledged" | "resolved";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// WatchdogAlerts page
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function WatchdogAlerts() {
|
||||
const { toast } = useToast();
|
||||
const queryClient = useQueryClient();
|
||||
const [statusFilter, setStatusFilter] = useState<StatusFilter>("active");
|
||||
const [expandedLogId, setExpandedLogId] = useState<string | null>(null);
|
||||
|
||||
const {
|
||||
data: allAlerts = [],
|
||||
isLoading,
|
||||
isError,
|
||||
refetch,
|
||||
} = useQuery({
|
||||
queryKey: ["watchdog-alerts-page"],
|
||||
queryFn: () => watchdogAlertsApi.list().then((r) => r.data),
|
||||
refetchInterval: 30000,
|
||||
});
|
||||
|
||||
// Client-side filter by derived status
|
||||
const alerts = allAlerts.filter(
|
||||
(a) => !statusFilter || watchdogStatus(a) === statusFilter
|
||||
);
|
||||
|
||||
const acknowledgeMutation = useMutation({
|
||||
mutationFn: (id: string) => watchdogAlertsApi.acknowledge(id),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["watchdog-alerts-page"] });
|
||||
toast({ type: "success", title: "Alert acknowledged" });
|
||||
},
|
||||
onError: (err: Error) => {
|
||||
toast({
|
||||
type: "error",
|
||||
title: "Could not acknowledge alert",
|
||||
message: err.message,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const resolveMutation = useMutation({
|
||||
mutationFn: (id: string) => watchdogAlertsApi.resolve(id),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["watchdog-alerts-page"] });
|
||||
toast({ type: "success", title: "Alert resolved" });
|
||||
},
|
||||
onError: (err: Error) => {
|
||||
toast({
|
||||
type: "error",
|
||||
title: "Could not resolve alert",
|
||||
message: err.message,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: (id: string) => watchdogAlertsApi.delete(id),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["watchdog-alerts-page"] });
|
||||
toast({ type: "success", title: "Alert deleted" });
|
||||
},
|
||||
onError: (err: Error) => {
|
||||
toast({ type: "error", title: "Could not delete alert", message: err.message });
|
||||
},
|
||||
});
|
||||
|
||||
const isMutating =
|
||||
acknowledgeMutation.isPending ||
|
||||
resolveMutation.isPending ||
|
||||
deleteMutation.isPending;
|
||||
|
||||
const activeCount = allAlerts.filter((a) => watchdogStatus(a) === "active").length;
|
||||
const acknowledgedCount = allAlerts.filter(
|
||||
(a) => watchdogStatus(a) === "acknowledged"
|
||||
).length;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold">Watchdog Alerts</h1>
|
||||
<p className="text-[hsl(var(--muted-foreground))]">
|
||||
Agent crash and restart exhaustion events
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" onClick={() => refetch()}>
|
||||
<RefreshCw className="h-4 w-4 mr-2" />
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Summary chips */}
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<div className="flex items-center gap-3 rounded-lg border border-red-500/30 bg-red-500/10 px-4 py-2 min-w-[140px]">
|
||||
<div className="text-2xl font-bold tabular-nums text-red-600 dark:text-red-400">
|
||||
{activeCount}
|
||||
</div>
|
||||
<div className="text-xs uppercase tracking-wider text-red-600/80 dark:text-red-400/80">
|
||||
Active
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 rounded-lg border border-amber-500/30 bg-amber-500/10 px-4 py-2 min-w-[140px]">
|
||||
<div className="text-2xl font-bold tabular-nums text-amber-600 dark:text-amber-400">
|
||||
{acknowledgedCount}
|
||||
</div>
|
||||
<div className="text-xs uppercase tracking-wider text-amber-600/80 dark:text-amber-400/80">
|
||||
Acknowledged
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isError && (
|
||||
<div className="rounded-md bg-destructive/10 border border-destructive/20 px-4 py-3 text-sm text-destructive">
|
||||
Failed to load watchdog alerts. Check your connection and try
|
||||
refreshing.
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<CardTitle>Alert Stream</CardTitle>
|
||||
<NativeSelect
|
||||
value={statusFilter}
|
||||
onChange={(e) =>
|
||||
setStatusFilter(e.target.value as StatusFilter)
|
||||
}
|
||||
className="w-auto min-w-[11rem]"
|
||||
aria-label="Filter by status"
|
||||
>
|
||||
<option value="active">Active Only</option>
|
||||
<option value="acknowledged">Acknowledged</option>
|
||||
<option value="resolved">Resolved</option>
|
||||
<option value="">All Statuses</option>
|
||||
</NativeSelect>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{isLoading ? (
|
||||
<p className="text-[hsl(var(--muted-foreground))]">
|
||||
Loading watchdog alerts...
|
||||
</p>
|
||||
) : alerts.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center py-12 text-center">
|
||||
<ShieldAlert className="h-10 w-10 text-[hsl(var(--muted-foreground))] mb-3" />
|
||||
<p className="text-sm font-medium">No watchdog alerts</p>
|
||||
<p className="text-xs text-[hsl(var(--muted-foreground))]">
|
||||
No events match the current filter.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{alerts.map((alert: WatchdogAlert) => {
|
||||
const status = watchdogStatus(alert);
|
||||
const logExpanded = expandedLogId === alert.id;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={alert.id}
|
||||
className="rounded-lg border border-[hsl(var(--border))] p-4 space-y-2"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="space-y-0.5 min-w-0">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<WatchdogStatusBadge status={status} />
|
||||
<span className="text-xs text-[hsl(var(--muted-foreground))]">
|
||||
{alert.restart_attempts} restart attempt
|
||||
{alert.restart_attempts !== 1 ? "s" : ""}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-xs text-[hsl(var(--muted-foreground))]">
|
||||
<Link
|
||||
to={`/agents/${alert.agent_id}`}
|
||||
className="text-[hsl(var(--primary))] hover:underline font-mono"
|
||||
>
|
||||
{alert.agent_id.slice(0, 8)}…
|
||||
</Link>
|
||||
{" \xb7 "}
|
||||
Triggered:{" "}
|
||||
{new Date(alert.triggered_at).toLocaleString()}
|
||||
{alert.acknowledged_at && (
|
||||
<>
|
||||
{" \xb7 "}Ack:{" "}
|
||||
{new Date(alert.acknowledged_at).toLocaleString()}
|
||||
{alert.acknowledged_by && ` by ${alert.acknowledged_by}`}
|
||||
</>
|
||||
)}
|
||||
{alert.resolved_at && (
|
||||
<>
|
||||
{" \xb7 "}Resolved:{" "}
|
||||
{new Date(alert.resolved_at).toLocaleString()}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{alert.last_error && (
|
||||
<p className="text-sm text-[hsl(var(--foreground))] mt-1">
|
||||
<span className="font-medium">Error:</span>{" "}
|
||||
{alert.last_error}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex shrink-0 items-center gap-1.5">
|
||||
{status === "active" && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={isMutating}
|
||||
onClick={() =>
|
||||
acknowledgeMutation.mutate(alert.id)
|
||||
}
|
||||
title="Acknowledge"
|
||||
>
|
||||
<Clock className="h-3.5 w-3.5 mr-1" />
|
||||
Ack
|
||||
</Button>
|
||||
)}
|
||||
{status !== "resolved" && (
|
||||
<Button
|
||||
size="sm"
|
||||
disabled={isMutating}
|
||||
onClick={() => resolveMutation.mutate(alert.id)}
|
||||
title="Resolve"
|
||||
>
|
||||
<CheckCircle2 className="h-3.5 w-3.5 mr-1" />
|
||||
Resolve
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
disabled={isMutating}
|
||||
onClick={() => deleteMutation.mutate(alert.id)}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{alert.log_tail && (
|
||||
<div>
|
||||
<button
|
||||
className="flex items-center gap-1 text-xs text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))]"
|
||||
onClick={() =>
|
||||
setExpandedLogId(logExpanded ? null : alert.id)
|
||||
}
|
||||
>
|
||||
{logExpanded ? (
|
||||
<ChevronDown className="h-3 w-3" />
|
||||
) : (
|
||||
<ChevronRight className="h-3 w-3" />
|
||||
)}
|
||||
Agent log tail
|
||||
</button>
|
||||
{logExpanded && (
|
||||
<pre className="mt-1 max-h-64 overflow-auto rounded bg-[hsl(var(--muted))]/50 p-2 text-xs font-mono text-[hsl(var(--foreground))]">
|
||||
{alert.log_tail}
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
'''
|
||||
|
||||
path = "/home/guru/gururmm/dashboard/src/pages/WatchdogAlerts.tsx"
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
print(f"Written WatchdogAlerts.tsx: {len(content.splitlines())} lines")
|
||||
508
.claude/scripts/watchdog_patch.py
Normal file
508
.claude/scripts/watchdog_patch.py
Normal file
@@ -0,0 +1,508 @@
|
||||
import sys
|
||||
|
||||
path = "/home/guru/gururmm/dashboard/src/pages/Alerts.tsx"
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
# ─── Insert WatchdogAlertsSection component before export function Alerts() ───
|
||||
|
||||
insert_before = "export function Alerts() {"
|
||||
|
||||
watchdog_section = (
|
||||
"// ─── WatchdogAlertsSection ─────────────────────────────────────────────────\n"
|
||||
"\n"
|
||||
"type WdogStatus = \"active\" | \"acknowledged\" | \"resolved\";\n"
|
||||
"\n"
|
||||
"function wdogStatus(alert: WatchdogAlert): WdogStatus {\n"
|
||||
" if (alert.resolved_at) return \"resolved\";\n"
|
||||
" if (alert.acknowledged_at) return \"acknowledged\";\n"
|
||||
" return \"active\";\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"function WatchdogAlertsSection() {\n"
|
||||
" const queryClient = useQueryClient();\n"
|
||||
" const { toast } = useToast();\n"
|
||||
" const [showAll, setShowAll] = useState(false);\n"
|
||||
" const [expandedLogId, setExpandedLogId] = useState<string | null>(null);\n"
|
||||
"\n"
|
||||
" const { data: allAlerts = [], isLoading } = useQuery({\n"
|
||||
" queryKey: [\"watchdog-alerts\"],\n"
|
||||
" queryFn: () => watchdogAlertsApi.list().then((r) => r.data),\n"
|
||||
" refetchInterval: 30000,\n"
|
||||
" });\n"
|
||||
"\n"
|
||||
" const alerts = showAll\n"
|
||||
" ? allAlerts\n"
|
||||
" : allAlerts.filter((a) => !a.resolved_at);\n"
|
||||
"\n"
|
||||
" const acknowledgeMutation = useMutation({\n"
|
||||
" mutationFn: (id: string) => watchdogAlertsApi.acknowledge(id),\n"
|
||||
" onSuccess: () => {\n"
|
||||
" queryClient.invalidateQueries({ queryKey: [\"watchdog-alerts\"] });\n"
|
||||
" toast({ type: \"success\", title: \"Alert acknowledged\" });\n"
|
||||
" },\n"
|
||||
" onError: (err: Error) =>\n"
|
||||
" toast({ type: \"error\", title: \"Could not acknowledge\", message: err.message }),\n"
|
||||
" });\n"
|
||||
"\n"
|
||||
" const resolveMutation = useMutation({\n"
|
||||
" mutationFn: (id: string) => watchdogAlertsApi.resolve(id),\n"
|
||||
" onSuccess: () => {\n"
|
||||
" queryClient.invalidateQueries({ queryKey: [\"watchdog-alerts\"] });\n"
|
||||
" toast({ type: \"success\", title: \"Alert resolved\" });\n"
|
||||
" },\n"
|
||||
" onError: (err: Error) =>\n"
|
||||
" toast({ type: \"error\", title: \"Could not resolve\", message: err.message }),\n"
|
||||
" });\n"
|
||||
"\n"
|
||||
" const deleteMutation = useMutation({\n"
|
||||
" mutationFn: (id: string) => watchdogAlertsApi.delete(id),\n"
|
||||
" onSuccess: () => {\n"
|
||||
" queryClient.invalidateQueries({ queryKey: [\"watchdog-alerts\"] });\n"
|
||||
" toast({ type: \"success\", title: \"Alert deleted\" });\n"
|
||||
" },\n"
|
||||
" onError: (err: Error) =>\n"
|
||||
" toast({ type: \"error\", title: \"Could not delete\", message: err.message }),\n"
|
||||
" });\n"
|
||||
"\n"
|
||||
" const isMutating =\n"
|
||||
" acknowledgeMutation.isPending ||\n"
|
||||
" resolveMutation.isPending ||\n"
|
||||
" deleteMutation.isPending;\n"
|
||||
"\n"
|
||||
" const activeCount = allAlerts.filter((a) => wdogStatus(a) === \"active\").length;\n"
|
||||
"\n"
|
||||
" return (\n"
|
||||
" <Card>\n"
|
||||
" <CardHeader>\n"
|
||||
" <div className=\"flex items-center justify-between gap-3\">\n"
|
||||
" <CardTitle className=\"flex items-center gap-2\">\n"
|
||||
" <AlertTriangle className=\"h-4 w-4 text-amber-500\" />\n"
|
||||
" Watchdog Alerts\n"
|
||||
" {activeCount > 0 && (\n"
|
||||
" <span className=\"ml-1 rounded-full bg-amber-500/15 px-2 py-0.5 text-xs font-medium text-amber-600 dark:text-amber-400\">\n"
|
||||
" {activeCount} active\n"
|
||||
" </span>\n"
|
||||
" )}\n"
|
||||
" </CardTitle>\n"
|
||||
" <button\n"
|
||||
" className=\"text-xs text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))] underline-offset-2 hover:underline\"\n"
|
||||
" onClick={() => setShowAll((v) => !v)}\n"
|
||||
" >\n"
|
||||
" {showAll ? \"Active only\" : \"Show all\"}\n"
|
||||
" </button>\n"
|
||||
" </div>\n"
|
||||
" </CardHeader>\n"
|
||||
" <CardContent>\n"
|
||||
" {isLoading && (\n"
|
||||
" <p className=\"py-6 text-center text-sm text-[hsl(var(--muted-foreground))]\">Loading...</p>\n"
|
||||
" )}\n"
|
||||
" {!isLoading && alerts.length === 0 && (\n"
|
||||
" <p className=\"py-6 text-center text-sm text-[hsl(var(--muted-foreground))]\">\n"
|
||||
" {showAll ? \"No watchdog alerts recorded.\" : \"No active watchdog alerts.\"}\n"
|
||||
" </p>\n"
|
||||
" )}\n"
|
||||
" <div className=\"space-y-3\">\n"
|
||||
" {alerts.map((alert) => {\n"
|
||||
" const status = wdogStatus(alert);\n"
|
||||
" const logExpanded = expandedLogId === alert.id;\n"
|
||||
" const statusColor =\n"
|
||||
" status === \"active\"\n"
|
||||
" ? \"text-red-600 dark:text-red-400\"\n"
|
||||
" : status === \"acknowledged\"\n"
|
||||
" ? \"text-amber-600 dark:text-amber-400\"\n"
|
||||
" : \"text-[hsl(var(--muted-foreground))]\";\n"
|
||||
"\n"
|
||||
" return (\n"
|
||||
" <div\n"
|
||||
" key={alert.id}\n"
|
||||
" className=\"rounded-lg border border-[hsl(var(--border))] p-4 space-y-2\"\n"
|
||||
" >\n"
|
||||
" <div className=\"flex items-start justify-between gap-4\">\n"
|
||||
" <div className=\"space-y-0.5\">\n"
|
||||
" <div className=\"flex items-center gap-2\">\n"
|
||||
" <span\n"
|
||||
" className={`text-xs font-semibold uppercase tracking-wider ${statusColor}`}\n"
|
||||
" >\n"
|
||||
" {status}\n"
|
||||
" </span>\n"
|
||||
" <span className=\"text-xs text-[hsl(var(--muted-foreground))]\">\n"
|
||||
" \xb7 {alert.restart_attempts} restart attempt\n"
|
||||
" {alert.restart_attempts !== 1 ? \"s\" : \"\"}\n"
|
||||
" </span>\n"
|
||||
" </div>\n"
|
||||
" <p className=\"text-xs text-[hsl(var(--muted-foreground))]\">\n"
|
||||
" Agent:{\" \"}\n"
|
||||
" <span\n"
|
||||
" className=\"font-mono\"\n"
|
||||
" title={alert.agent_id}\n"
|
||||
" >\n"
|
||||
" {alert.agent_id.slice(0, 8)}…\n"
|
||||
" </span>\n"
|
||||
" {\" \"}\xb7{\" \"}\n"
|
||||
" Triggered: {formatRelative(alert.triggered_at)}\n"
|
||||
" {alert.acknowledged_at && (\n"
|
||||
" <>\n"
|
||||
" {\" \"}\xb7{\" \"}Ack’d:{\" \"}\n"
|
||||
" {formatRelative(alert.acknowledged_at)}\n"
|
||||
" </>\n"
|
||||
" )}\n"
|
||||
" {alert.resolved_at && (\n"
|
||||
" <>\n"
|
||||
" {\" \"}\xb7{\" \"}Resolved:{\" \"}\n"
|
||||
" {formatRelative(alert.resolved_at)}\n"
|
||||
" </>\n"
|
||||
" )}\n"
|
||||
" </p>\n"
|
||||
" {alert.last_error && (\n"
|
||||
" <p className=\"text-sm text-[hsl(var(--foreground))]\">\n"
|
||||
" <span className=\"font-medium\">Error:</span> {alert.last_error}\n"
|
||||
" </p>\n"
|
||||
" )}\n"
|
||||
" </div>\n"
|
||||
"\n"
|
||||
" <div className=\"flex shrink-0 items-center gap-1.5\">\n"
|
||||
" {status === \"active\" && (\n"
|
||||
" <Button\n"
|
||||
" size=\"sm\"\n"
|
||||
" variant=\"secondary\"\n"
|
||||
" disabled={isMutating}\n"
|
||||
" onClick={() => acknowledgeMutation.mutate(alert.id)}\n"
|
||||
" >\n"
|
||||
" Acknowledge\n"
|
||||
" </Button>\n"
|
||||
" )}\n"
|
||||
" {status !== \"resolved\" && (\n"
|
||||
" <Button\n"
|
||||
" size=\"sm\"\n"
|
||||
" variant=\"secondary\"\n"
|
||||
" disabled={isMutating}\n"
|
||||
" onClick={() => resolveMutation.mutate(alert.id)}\n"
|
||||
" >\n"
|
||||
" Resolve\n"
|
||||
" </Button>\n"
|
||||
" )}\n"
|
||||
" <Button\n"
|
||||
" size=\"sm\"\n"
|
||||
" variant=\"ghost\"\n"
|
||||
" disabled={isMutating}\n"
|
||||
" onClick={() => deleteMutation.mutate(alert.id)}\n"
|
||||
" >\n"
|
||||
" Delete\n"
|
||||
" </Button>\n"
|
||||
" </div>\n"
|
||||
" </div>\n"
|
||||
"\n"
|
||||
" {alert.log_tail && (\n"
|
||||
" <div>\n"
|
||||
" <button\n"
|
||||
" className=\"flex items-center gap-1 text-xs text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))]\"\n"
|
||||
" onClick={() =>\n"
|
||||
" setExpandedLogId(logExpanded ? null : alert.id)\n"
|
||||
" }\n"
|
||||
" >\n"
|
||||
" {logExpanded ? (\n"
|
||||
" <ChevronDown className=\"h-3 w-3\" />\n"
|
||||
" ) : (\n"
|
||||
" <ChevronRight className=\"h-3 w-3\" />\n"
|
||||
" )}\n"
|
||||
" Agent log tail\n"
|
||||
" </button>\n"
|
||||
" {logExpanded && (\n"
|
||||
" <pre className=\"mt-1 max-h-48 overflow-auto rounded bg-[hsl(var(--muted))]/50 p-2 text-xs font-mono text-[hsl(var(--foreground))]\">\n"
|
||||
" {alert.log_tail}\n"
|
||||
" </pre>\n"
|
||||
" )}\n"
|
||||
" </div>\n"
|
||||
" )}\n"
|
||||
" </div>\n"
|
||||
" );\n"
|
||||
" })}\n"
|
||||
" </div>\n"
|
||||
" </CardContent>\n"
|
||||
" </Card>\n"
|
||||
" );\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
)
|
||||
|
||||
if insert_before in content:
|
||||
content = content.replace(insert_before, watchdog_section + insert_before, 1)
|
||||
print("OK: WatchdogAlertsSection inserted")
|
||||
else:
|
||||
print("ERROR: could not find 'export function Alerts() {'")
|
||||
sys.exit(1)
|
||||
|
||||
# ─── Add <WatchdogAlertsSection /> at the bottom of the Alerts return div ───
|
||||
|
||||
# The closing of the Alerts function return div
|
||||
old_end = " </div>\n );\n}\n\n// Re-export types"
|
||||
new_end = " <WatchdogAlertsSection />\n </div>\n );\n}\n\n// Re-export types"
|
||||
|
||||
if old_end in content:
|
||||
content = content.replace(old_end, new_end, 1)
|
||||
print("OK: <WatchdogAlertsSection /> rendered in Alerts return")
|
||||
else:
|
||||
print("ERROR: closing div anchor not found")
|
||||
# Debug
|
||||
idx = content.find("// Re-export types")
|
||||
if idx >= 0:
|
||||
print("Context:", repr(content[max(0,idx-200):idx+30]))
|
||||
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
|
||||
print(f"Done. Lines: {len(content.splitlines())}")
|
||||
|
||||
"\n",
|
||||
'type WatchdogStatus = "active" | "acknowledged" | "resolved";\n',
|
||||
"\n",
|
||||
"function watchdogStatus(alert: WatchdogAlert): WatchdogStatus {\n",
|
||||
' if (alert.resolved_at) return "resolved";\n',
|
||||
' if (alert.acknowledged_at) return "acknowledged";\n',
|
||||
' return "active";\n',
|
||||
"}\n",
|
||||
"\n",
|
||||
"function WatchdogAlertsPanel({ agentId }: { agentId: string }) {\n",
|
||||
" const queryClient = useQueryClient();\n",
|
||||
" const { toast } = useToast();\n",
|
||||
' const [statusFilter, setStatusFilter] = useState<"" | WatchdogStatus>("");\n',
|
||||
" const [expandedLogId, setExpandedLogId] = useState<string | null>(null);\n",
|
||||
"\n",
|
||||
" const { data: alerts = [], isLoading } = useQuery({\n",
|
||||
' queryKey: ["watchdog-alerts", agentId],\n',
|
||||
" queryFn: () => watchdogAlertsApi.listForAgent(agentId).then((r) => r.data),\n",
|
||||
" refetchInterval: 30000,\n",
|
||||
" });\n",
|
||||
"\n",
|
||||
" const filtered = alerts.filter(\n",
|
||||
" (a) => !statusFilter || watchdogStatus(a) === statusFilter\n",
|
||||
" );\n",
|
||||
"\n",
|
||||
" const acknowledgeMutation = useMutation({\n",
|
||||
" mutationFn: (id: string) => watchdogAlertsApi.acknowledge(id),\n",
|
||||
" onSuccess: () => {\n",
|
||||
' queryClient.invalidateQueries({ queryKey: ["watchdog-alerts", agentId] });\n',
|
||||
' toast({ type: "success", title: "Alert acknowledged" });\n',
|
||||
" },\n",
|
||||
" onError: (err: Error) =>\n",
|
||||
' toast({ type: "error", title: "Could not acknowledge", message: err.message }),\n',
|
||||
" });\n",
|
||||
"\n",
|
||||
" const resolveMutation = useMutation({\n",
|
||||
" mutationFn: (id: string) => watchdogAlertsApi.resolve(id),\n",
|
||||
" onSuccess: () => {\n",
|
||||
' queryClient.invalidateQueries({ queryKey: ["watchdog-alerts", agentId] });\n',
|
||||
' toast({ type: "success", title: "Alert resolved" });\n',
|
||||
" },\n",
|
||||
" onError: (err: Error) =>\n",
|
||||
' toast({ type: "error", title: "Could not resolve", message: err.message }),\n',
|
||||
" });\n",
|
||||
"\n",
|
||||
" const deleteMutation = useMutation({\n",
|
||||
" mutationFn: (id: string) => watchdogAlertsApi.delete(id),\n",
|
||||
" onSuccess: () => {\n",
|
||||
' queryClient.invalidateQueries({ queryKey: ["watchdog-alerts", agentId] });\n',
|
||||
' toast({ type: "success", title: "Alert deleted" });\n',
|
||||
" },\n",
|
||||
" onError: (err: Error) =>\n",
|
||||
' toast({ type: "error", title: "Could not delete", message: err.message }),\n',
|
||||
" });\n",
|
||||
"\n",
|
||||
" const isMutating =\n",
|
||||
" acknowledgeMutation.isPending ||\n",
|
||||
" resolveMutation.isPending ||\n",
|
||||
" deleteMutation.isPending;\n",
|
||||
"\n",
|
||||
' const activeCount = alerts.filter((a) => watchdogStatus(a) === "active").length;\n',
|
||||
"\n",
|
||||
" return (\n",
|
||||
" <Card>\n",
|
||||
" <CardHeader>\n",
|
||||
' <CardTitle className="flex items-center gap-2">\n',
|
||||
' <AlertTriangle className="h-4 w-4 text-amber-500" />\n',
|
||||
" Watchdog Alerts\n",
|
||||
" {activeCount > 0 && (\n",
|
||||
' <span className="ml-1 rounded-full bg-amber-500/15 px-2 py-0.5 text-xs font-medium text-amber-600 dark:text-amber-400">\n',
|
||||
" {activeCount} active\n",
|
||||
" </span>\n",
|
||||
" )}\n",
|
||||
" </CardTitle>\n",
|
||||
" </CardHeader>\n",
|
||||
' <CardContent className="space-y-3">\n',
|
||||
' <div className="flex items-center gap-2">\n',
|
||||
" <NativeSelect\n",
|
||||
" value={statusFilter}\n",
|
||||
' onChange={(e) => setStatusFilter(e.target.value as "" | WatchdogStatus)}\n',
|
||||
' className="w-auto min-w-[10rem]"\n',
|
||||
' aria-label="Filter by status"\n',
|
||||
" >\n",
|
||||
' <option value="">All Statuses</option>\n',
|
||||
' <option value="active">Active</option>\n',
|
||||
' <option value="acknowledged">Acknowledged</option>\n',
|
||||
' <option value="resolved">Resolved</option>\n',
|
||||
" </NativeSelect>\n",
|
||||
" </div>\n",
|
||||
"\n",
|
||||
" {isLoading && (\n",
|
||||
' <p className="py-6 text-center text-sm text-[hsl(var(--muted-foreground))]">\n',
|
||||
" Loading...\n",
|
||||
" </p>\n",
|
||||
" )}\n",
|
||||
"\n",
|
||||
" {!isLoading && filtered.length === 0 && (\n",
|
||||
' <p className="py-6 text-center text-sm text-[hsl(var(--muted-foreground))]">\n',
|
||||
" {statusFilter\n",
|
||||
' ? "No alerts match the current filter."\n',
|
||||
' : "No watchdog alerts recorded."}\n',
|
||||
" </p>\n",
|
||||
" )}\n",
|
||||
"\n",
|
||||
" {filtered.map((alert) => {\n",
|
||||
" const status = watchdogStatus(alert);\n",
|
||||
" const logExpanded = expandedLogId === alert.id;\n",
|
||||
" const statusColor =\n",
|
||||
' status === "active"\n',
|
||||
' ? "text-red-600 dark:text-red-400"\n',
|
||||
' : status === "acknowledged"\n',
|
||||
' ? "text-amber-600 dark:text-amber-400"\n',
|
||||
' : "text-[hsl(var(--muted-foreground))]";\n',
|
||||
"\n",
|
||||
" return (\n",
|
||||
" <div\n",
|
||||
" key={alert.id}\n",
|
||||
' className="rounded-lg border border-[hsl(var(--border))] p-4 space-y-2"\n',
|
||||
" >\n",
|
||||
' <div className="flex items-start justify-between gap-4">\n',
|
||||
' <div className="space-y-0.5">\n',
|
||||
' <div className="flex items-center gap-2">\n',
|
||||
" <span\n",
|
||||
" className={`text-xs font-semibold uppercase tracking-wider ${statusColor}`}\n",
|
||||
" >\n",
|
||||
" {status}\n",
|
||||
" </span>\n",
|
||||
' <span className="text-xs text-[hsl(var(--muted-foreground))]">\n',
|
||||
" \xb7 {alert.restart_attempts} restart attempt\n",
|
||||
' {alert.restart_attempts !== 1 ? "s" : ""}\n',
|
||||
" </span>\n",
|
||||
" </div>\n",
|
||||
' <p className="text-xs text-[hsl(var(--muted-foreground))]">\n',
|
||||
" Triggered: {new Date(alert.triggered_at).toLocaleString()}\n",
|
||||
" {alert.acknowledged_at && (\n",
|
||||
" <>\n",
|
||||
' {" "}\n',
|
||||
" \xb7 Acknowledged:{\" \"}\n",
|
||||
" {new Date(alert.acknowledged_at).toLocaleString()}\n",
|
||||
" </>\n",
|
||||
" )}\n",
|
||||
" {alert.resolved_at && (\n",
|
||||
" <>\n",
|
||||
' {" "}\n',
|
||||
" \xb7 Resolved:{\" \"}\n",
|
||||
" {new Date(alert.resolved_at).toLocaleString()}\n",
|
||||
" </>\n",
|
||||
" )}\n",
|
||||
" </p>\n",
|
||||
" {alert.last_error && (\n",
|
||||
' <p className="text-sm text-[hsl(var(--foreground))]">\n',
|
||||
' <span className="font-medium">Error:</span> {alert.last_error}\n',
|
||||
" </p>\n",
|
||||
" )}\n",
|
||||
" </div>\n",
|
||||
"\n",
|
||||
' <div className="flex shrink-0 items-center gap-1.5">\n',
|
||||
' {status === "active" && (\n',
|
||||
" <Button\n",
|
||||
' size="sm"\n',
|
||||
' variant="secondary"\n',
|
||||
" disabled={isMutating}\n",
|
||||
" onClick={() => acknowledgeMutation.mutate(alert.id)}\n",
|
||||
" >\n",
|
||||
" Acknowledge\n",
|
||||
" </Button>\n",
|
||||
" )}\n",
|
||||
' {status !== "resolved" && (\n',
|
||||
" <Button\n",
|
||||
' size="sm"\n',
|
||||
' variant="secondary"\n',
|
||||
" disabled={isMutating}\n",
|
||||
" onClick={() => resolveMutation.mutate(alert.id)}\n",
|
||||
" >\n",
|
||||
" Resolve\n",
|
||||
" </Button>\n",
|
||||
" )}\n",
|
||||
" <Button\n",
|
||||
' size="sm"\n',
|
||||
' variant="ghost"\n',
|
||||
" disabled={isMutating}\n",
|
||||
" onClick={() => deleteMutation.mutate(alert.id)}\n",
|
||||
" >\n",
|
||||
" Delete\n",
|
||||
" </Button>\n",
|
||||
" </div>\n",
|
||||
" </div>\n",
|
||||
"\n",
|
||||
" {alert.log_tail && (\n",
|
||||
" <div>\n",
|
||||
" <button\n",
|
||||
' className="flex items-center gap-1 text-xs text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))]"\n',
|
||||
" onClick={() =>\n",
|
||||
" setExpandedLogId(logExpanded ? null : alert.id)\n",
|
||||
" }\n",
|
||||
" >\n",
|
||||
" {logExpanded ? (\n",
|
||||
' <ChevronDown className="h-3 w-3" />\n',
|
||||
" ) : (\n",
|
||||
' <ChevronRight className="h-3 w-3" />\n',
|
||||
" )}\n",
|
||||
" Agent log tail\n",
|
||||
" </button>\n",
|
||||
" {logExpanded && (\n",
|
||||
' <pre className="mt-1 max-h-48 overflow-auto rounded bg-[hsl(var(--muted))]/50 p-2 text-xs font-mono text-[hsl(var(--foreground))]">\n',
|
||||
" {alert.log_tail}\n",
|
||||
" </pre>\n",
|
||||
" )}\n",
|
||||
" </div>\n",
|
||||
" )}\n",
|
||||
" </div>\n",
|
||||
" );\n",
|
||||
" })}\n",
|
||||
" </CardContent>\n",
|
||||
" </Card>\n",
|
||||
" );\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
]
|
||||
|
||||
file_lines[anchor_idx:anchor_idx] = C
|
||||
print(f"OK: inserted {len(C)} lines before AgentAlertsPanel anchor")
|
||||
|
||||
content2 = "".join(file_lines)
|
||||
|
||||
old_tab = (
|
||||
' <TabPanel tabId="alerts" activeTab={activeTab}>\n'
|
||||
" <AgentAlertsPanel agentId={agent.id} />\n"
|
||||
" </TabPanel>"
|
||||
)
|
||||
new_tab = (
|
||||
' <TabPanel tabId="alerts" activeTab={activeTab}>\n'
|
||||
" <AgentAlertsPanel agentId={agent.id} />\n"
|
||||
' <div className="mt-6">\n'
|
||||
" <WatchdogAlertsPanel agentId={agent.id} />\n"
|
||||
" </div>\n"
|
||||
" </TabPanel>"
|
||||
)
|
||||
|
||||
if old_tab in content2:
|
||||
content2 = content2.replace(old_tab, new_tab, 1)
|
||||
print("OK: TabPanel updated")
|
||||
else:
|
||||
print("ERROR: TabPanel anchor not found")
|
||||
idx = content2.find('tabId="alerts"')
|
||||
if idx >= 0:
|
||||
print("Context:", repr(content2[idx:idx+200]))
|
||||
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
f.write(content2)
|
||||
|
||||
print(f"Done. Lines: {len(content2.splitlines())}")
|
||||
Reference in New Issue
Block a user