diff --git a/.claude/scripts/alerts_patch.py b/.claude/scripts/alerts_patch.py new file mode 100644 index 0000000..61f89ce --- /dev/null +++ b/.claude/scripts/alerts_patch.py @@ -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 = " \n );\n}\n\n// Re-export types" +new_end = " \n \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())}") diff --git a/.claude/scripts/component.b64 b/.claude/scripts/component.b64 new file mode 100644 index 0000000..0fffb5b --- /dev/null +++ b/.claude/scripts/component.b64 @@ -0,0 +1 @@ +Ci8vIFdhdGNoZG9nQWxlcnRzU2VjdGlvbgoKdHlwZSBXZG9nU3RhdHVzID0gImFjdGl2ZSIgfCAiYWNrbm93bGVkZ2VkIiB8ICJyZXNvbHZlZCI7CgpmdW5jdGlvbiB3ZG9nU3RhdHVzKGFsZXJ0OiBXYXRjaGRvZ0FsZXJ0KTogV2RvZ1N0YXR1cyB7CiAgaWYgKGFsZXJ0LnJlc29sdmVkX2F0KSByZXR1cm4gInJlc29sdmVkIjsKICBpZiAoYWxlcnQuYWNrbm93bGVkZ2VkX2F0KSByZXR1cm4gImFja25vd2xlZGdlZCI7CiAgcmV0dXJuICJhY3RpdmUiOwp9CgpmdW5jdGlvbiBXYXRjaGRvZ0FsZXJ0c1NlY3Rpb24oKSB7CiAgY29uc3QgcXVlcnlDbGllbnQgPSB1c2VRdWVyeUNsaWVudCgpOwogIGNvbnN0IHsgdG9hc3QgfSA9IHVzZVRvYXN0KCk7CiAgY29uc3QgW3Nob3dBbGwsIHNldFNob3dBbGxdID0gdXNlU3RhdGUoZmFsc2UpOwogIGNvbnN0IFtleHBhbmRlZExvZ0lkLCBzZXRFeHBhbmRlZExvZ0lkXSA9IHVzZVN0YXRlPHN0cmluZyB8IG51bGw+KG51bGwpOwoKICBjb25zdCB7IGRhdGE6IGFsbEFsZXJ0cyA9IFtdLCBpc0xvYWRpbmcgfSA9IHVzZVF1ZXJ5KHsKICAgIHF1ZXJ5S2V5OiBbIndhdGNoZG9nLWFsZXJ0cyJdLAogICAgcXVlcnlGbjogKCkgPT4gd2F0Y2hkb2dBbGVydHNBcGkubGlzdCgpLnRoZW4oKHIpID0+IHIuZGF0YSksCiAgICByZWZldGNoSW50ZXJ2YWw6IDMwMDAwLAogIH0pOwoKICBjb25zdCBhbGVydHMgPSBzaG93QWxsID8gYWxsQWxlcnRzIDogYWxsQWxlcnRzLmZpbHRlcigoYSkgPT4gIWEucmVzb2x2ZWRfYXQpOwoKICBjb25zdCBhY2tub3dsZWRnZU11dGF0aW9uID0gdXNlTXV0YXRpb24oewogICAgbXV0YXRpb25GbjogKGlkOiBzdHJpbmcpID0+IHdhdGNoZG9nQWxlcnRzQXBpLmFja25vd2xlZGdlKGlkKSwKICAgIG9uU3VjY2VzczogKCkgPT4gewogICAgICBxdWVyeUNsaWVudC5pbnZhbGlkYXRlUXVlcmllcyh7IHF1ZXJ5S2V5OiBbIndhdGNoZG9nLWFsZXJ0cyJdIH0pOwogICAgICB0b2FzdCh7IHR5cGU6ICJzdWNjZXNzIiwgdGl0bGU6ICJBbGVydCBhY2tub3dsZWRnZWQiIH0pOwogICAgfSwKICAgIG9uRXJyb3I6IChlcnI6IEVycm9yKSA9PgogICAgICB0b2FzdCh7IHR5cGU6ICJlcnJvciIsIHRpdGxlOiAiQ291bGQgbm90IGFja25vd2xlZGdlIiwgbWVzc2FnZTogZXJyLm1lc3NhZ2UgfSksCiAgfSk7CgogIGNvbnN0IHJlc29sdmVNdXRhdGlvbiA9IHVzZU11dGF0aW9uKHsKICAgIG11dGF0aW9uRm46IChpZDogc3RyaW5nKSA9PiB3YXRjaGRvZ0FsZXJ0c0FwaS5yZXNvbHZlKGlkKSwKICAgIG9uU3VjY2VzczogKCkgPT4gewogICAgICBxdWVyeUNsaWVudC5pbnZhbGlkYXRlUXVlcmllcyh7IHF1ZXJ5S2V5OiBbIndhdGNoZG9nLWFsZXJ0cyJdIH0pOwogICAgICB0b2FzdCh7IHR5cGU6ICJzdWNjZXNzIiwgdGl0bGU6ICJBbGVydCByZXNvbHZlZCIgfSk7CiAgICB9LAogICAgb25FcnJvcjogKGVycjogRXJyb3IpID0+CiAgICAgIHRvYXN0KHsgdHlwZTogImVycm9yIiwgdGl0bGU6ICJDb3VsZCBub3QgcmVzb2x2ZSIsIG1lc3NhZ2U6IGVyci5tZXNzYWdlIH0pLAogIH0pOwoKICBjb25zdCBkZWxldGVNdXRhdGlvbiA9IHVzZU11dGF0aW9uKHsKICAgIG11dGF0aW9uRm46IChpZDogc3RyaW5nKSA9PiB3YXRjaGRvZ0FsZXJ0c0FwaS5kZWxldGUoaWQpLAogICAgb25TdWNjZXNzOiAoKSA9PiB7CiAgICAgIHF1ZXJ5Q2xpZW50LmludmFsaWRhdGVRdWVyaWVzKHsgcXVlcnlLZXk6IFsid2F0Y2hkb2ctYWxlcnRzIl0gfSk7CiAgICAgIHRvYXN0KHsgdHlwZTogInN1Y2Nlc3MiLCB0aXRsZTogIkFsZXJ0IGRlbGV0ZWQiIH0pOwogICAgfSwKICAgIG9uRXJyb3I6IChlcnI6IEVycm9yKSA9PgogICAgICB0b2FzdCh7IHR5cGU6ICJlcnJvciIsIHRpdGxlOiAiQ291bGQgbm90IGRlbGV0ZSIsIG1lc3NhZ2U6IGVyci5tZXNzYWdlIH0pLAogIH0pOwoKICBjb25zdCBpc011dGF0aW5nID0KICAgIGFja25vd2xlZGdlTXV0YXRpb24uaXNQZW5kaW5nIHx8CiAgICByZXNvbHZlTXV0YXRpb24uaXNQZW5kaW5nIHx8CiAgICBkZWxldGVNdXRhdGlvbi5pc1BlbmRpbmc7CgogIGNvbnN0IGFjdGl2ZUNvdW50ID0gYWxsQWxlcnRzLmZpbHRlcigoYSkgPT4gd2RvZ1N0YXR1cyhhKSA9PT0gImFjdGl2ZSIpLmxlbmd0aDsKCiAgcmV0dXJuICgKICAgIDxDYXJkPgogICAgICA8Q2FyZEhlYWRlcj4KICAgICAgICA8ZGl2IGNsYXNzTmFtZT0iZmxleCBpdGVtcy1jZW50ZXIganVzdGlmeS1iZXR3ZWVuIGdhcC0zIj4KICAgICAgICAgIDxDYXJkVGl0bGUgY2xhc3NOYW1lPSJmbGV4IGl0ZW1zLWNlbnRlciBnYXAtMiI+CiAgICAgICAgICAgIDxBbGVydFRyaWFuZ2xlIGNsYXNzTmFtZT0iaC00IHctNCB0ZXh0LWFtYmVyLTUwMCIgLz4KICAgICAgICAgICAgV2F0Y2hkb2cgQWxlcnRzCiAgICAgICAgICAgIHthY3RpdmVDb3VudCA+IDAgJiYgKAogICAgICAgICAgICAgIDxzcGFuIGNsYXNzTmFtZT0ibWwtMSByb3VuZGVkLWZ1bGwgYmctYW1iZXItNTAwLzE1IHB4LTIgcHktMC41IHRleHQteHMgZm9udC1tZWRpdW0gdGV4dC1hbWJlci02MDAgZGFyazp0ZXh0LWFtYmVyLTQwMCI+CiAgICAgICAgICAgICAgICB7YWN0aXZlQ291bnR9IGFjdGl2ZQogICAgICAgICAgICAgIDwvc3Bhbj4KICAgICAgICAgICAgKX0KICAgICAgICAgIDwvQ2FyZFRpdGxlPgogICAgICAgICAgPGJ1dHRvbgogICAgICAgICAgICBjbGFzc05hbWU9InRleHQteHMgdGV4dC1baHNsKHZhcigtLW11dGVkLWZvcmVncm91bmQpKV0gaG92ZXI6dGV4dC1baHNsKHZhcigtLWZvcmVncm91bmQpKV0gdW5kZXJsaW5lLW9mZnNldC0yIGhvdmVyOnVuZGVybGluZSIKICAgICAgICAgICAgb25DbGljaz17KCkgPT4gc2V0U2hvd0FsbCgodikgPT4gIXYpfQogICAgICAgICAgPgogICAgICAgICAgICB7c2hvd0FsbCA/ICJBY3RpdmUgb25seSIgOiAiU2hvdyBhbGwifQogICAgICAgICAgPC9idXR0b24+CiAgICAgICAgPC9kaXY+CiAgICAgIDwvQ2FyZEhlYWRlcj4KICAgICAgPENhcmRDb250ZW50PgogICAgICAgIHtpc0xvYWRpbmcgJiYgKAogICAgICAgICAgPHAgY2xhc3NOYW1lPSJweS02IHRleHQtY2VudGVyIHRleHQtc20gdGV4dC1baHNsKHZhcigtLW11dGVkLWZvcmVncm91bmQpKV0iPkxvYWRpbmcuLi48L3A+CiAgICAgICAgKX0KICAgICAgICB7IWlzTG9hZGluZyAmJiBhbGVydHMubGVuZ3RoID09PSAwICYmICgKICAgICAgICAgIDxwIGNsYXNzTmFtZT0icHktNiB0ZXh0LWNlbnRlciB0ZXh0LXNtIHRleHQtW2hzbCh2YXIoLS1tdXRlZC1mb3JlZ3JvdW5kKSldIj4KICAgICAgICAgICAge3Nob3dBbGwgPyAiTm8gd2F0Y2hkb2cgYWxlcnRzIHJlY29yZGVkLiIgOiAiTm8gYWN0aXZlIHdhdGNoZG9nIGFsZXJ0cy4ifQogICAgICAgICAgPC9wPgogICAgICAgICl9CiAgICAgICAgPGRpdiBjbGFzc05hbWU9InNwYWNlLXktMyI+CiAgICAgICAgICB7YWxlcnRzLm1hcCgoYWxlcnQpID0+IHsKICAgICAgICAgICAgY29uc3Qgc3RhdHVzID0gd2RvZ1N0YXR1cyhhbGVydCk7CiAgICAgICAgICAgIGNvbnN0IGxvZ0V4cGFuZGVkID0gZXhwYW5kZWRMb2dJZCA9PT0gYWxlcnQuaWQ7CiAgICAgICAgICAgIGNvbnN0IHN0YXR1c0NvbG9yID0KICAgICAgICAgICAgICBzdGF0dXMgPT09ICJhY3RpdmUiCiAgICAgICAgICAgICAgICA/ICJ0ZXh0LXJlZC02MDAgZGFyazp0ZXh0LXJlZC00MDAiCiAgICAgICAgICAgICAgICA6IHN0YXR1cyA9PT0gImFja25vd2xlZGdlZCIKICAgICAgICAgICAgICAgID8gInRleHQtYW1iZXItNjAwIGRhcms6dGV4dC1hbWJlci00MDAiCiAgICAgICAgICAgICAgICA6ICJ0ZXh0LVtoc2wodmFyKC0tbXV0ZWQtZm9yZWdyb3VuZCkpXSI7CgogICAgICAgICAgICByZXR1cm4gKAogICAgICAgICAgICAgIDxkaXYKICAgICAgICAgICAgICAgIGtleT17YWxlcnQuaWR9CiAgICAgICAgICAgICAgICBjbGFzc05hbWU9InJvdW5kZWQtbGcgYm9yZGVyIGJvcmRlci1baHNsKHZhcigtLWJvcmRlcikpXSBwLTQgc3BhY2UteS0yIgogICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3NOYW1lPSJmbGV4IGl0ZW1zLXN0YXJ0IGp1c3RpZnktYmV0d2VlbiBnYXAtNCI+CiAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3NOYW1lPSJzcGFjZS15LTAuNSI+CiAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzc05hbWU9ImZsZXggaXRlbXMtY2VudGVyIGdhcC0yIj4KICAgICAgICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzTmFtZT17YHRleHQteHMgZm9udC1zZW1pYm9sZCB1cHBlcmNhc2UgdHJhY2tpbmctd2lkZXIgJHtzdGF0dXNDb2xvcn1gfT4KICAgICAgICAgICAgICAgICAgICAgICAge3N0YXR1c30KICAgICAgICAgICAgICAgICAgICAgIDwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzTmFtZT0idGV4dC14cyB0ZXh0LVtoc2wodmFyKC0tbXV0ZWQtZm9yZWdyb3VuZCkpXSI+CiAgICAgICAgICAgICAgICAgICAgICAgIMK3IHthbGVydC5yZXN0YXJ0X2F0dGVtcHRzfSByZXN0YXJ0IGF0dGVtcHQKICAgICAgICAgICAgICAgICAgICAgICAge2FsZXJ0LnJlc3RhcnRfYXR0ZW1wdHMgIT09IDEgPyAicyIgOiAiIn0KICAgICAgICAgICAgICAgICAgICAgIDwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgICA8cCBjbGFzc05hbWU9InRleHQteHMgdGV4dC1baHNsKHZhcigtLW11dGVkLWZvcmVncm91bmQpKV0iPgogICAgICAgICAgICAgICAgICAgICAgQWdlbnQgSUQ6eyIgIn0KICAgICAgICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzTmFtZT0iZm9udC1tb25vIiB0aXRsZT17YWxlcnQuYWdlbnRfaWR9PgogICAgICAgICAgICAgICAgICAgICAgICB7YWxlcnQuYWdlbnRfaWQuc2xpY2UoMCwgOCl94oCmCiAgICAgICAgICAgICAgICAgICAgICA8L3NwYW4+CiAgICAgICAgICAgICAgICAgICAgICB7IiAifcK3eyIgIn0KICAgICAgICAgICAgICAgICAgICAgIFRyaWdnZXJlZDoge2Zvcm1hdFJlbGF0aXZlKGFsZXJ0LnRyaWdnZXJlZF9hdCl9CiAgICAgICAgICAgICAgICAgICAgICB7YWxlcnQuYWNrbm93bGVkZ2VkX2F0ICYmICgKICAgICAgICAgICAgICAgICAgICAgICAgPD57IiAifcK3eyIgIn1BY2s6IHtmb3JtYXRSZWxhdGl2ZShhbGVydC5hY2tub3dsZWRnZWRfYXQpfTwvPgogICAgICAgICAgICAgICAgICAgICAgKX0KICAgICAgICAgICAgICAgICAgICAgIHthbGVydC5yZXNvbHZlZF9hdCAmJiAoCiAgICAgICAgICAgICAgICAgICAgICAgIDw+eyIgIn3Ct3siICJ9UmVzb2x2ZWQ6IHtmb3JtYXRSZWxhdGl2ZShhbGVydC5yZXNvbHZlZF9hdCl9PC8+CiAgICAgICAgICAgICAgICAgICAgICApfQogICAgICAgICAgICAgICAgICAgIDwvcD4KICAgICAgICAgICAgICAgICAgICB7YWxlcnQubGFzdF9lcnJvciAmJiAoCiAgICAgICAgICAgICAgICAgICAgICA8cCBjbGFzc05hbWU9InRleHQtc20gdGV4dC1baHNsKHZhcigtLWZvcmVncm91bmQpKV0iPgogICAgICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzc05hbWU9ImZvbnQtbWVkaXVtIj5FcnJvcjo8L3NwYW4+IHthbGVydC5sYXN0X2Vycm9yfQogICAgICAgICAgICAgICAgICAgICAgPC9wPgogICAgICAgICAgICAgICAgICAgICl9CiAgICAgICAgICAgICAgICAgIDwvZGl2PgoKICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzc05hbWU9ImZsZXggc2hyaW5rLTAgaXRlbXMtY2VudGVyIGdhcC0xLjUiPgogICAgICAgICAgICAgICAgICAgIHtzdGF0dXMgPT09ICJhY3RpdmUiICYmICgKICAgICAgICAgICAgICAgICAgICAgIDxCdXR0b24gc2l6ZT0ic20iIHZhcmlhbnQ9InNlY29uZGFyeSIgZGlzYWJsZWQ9e2lzTXV0YXRpbmd9CiAgICAgICAgICAgICAgICAgICAgICAgIG9uQ2xpY2s9eygpID0+IGFja25vd2xlZGdlTXV0YXRpb24ubXV0YXRlKGFsZXJ0LmlkKX0+CiAgICAgICAgICAgICAgICAgICAgICAgIEFja25vd2xlZGdlCiAgICAgICAgICAgICAgICAgICAgICA8L0J1dHRvbj4KICAgICAgICAgICAgICAgICAgICApfQogICAgICAgICAgICAgICAgICAgIHtzdGF0dXMgIT09ICJyZXNvbHZlZCIgJiYgKAogICAgICAgICAgICAgICAgICAgICAgPEJ1dHRvbiBzaXplPSJzbSIgdmFyaWFudD0ic2Vjb25kYXJ5IiBkaXNhYmxlZD17aXNNdXRhdGluZ30KICAgICAgICAgICAgICAgICAgICAgICAgb25DbGljaz17KCkgPT4gcmVzb2x2ZU11dGF0aW9uLm11dGF0ZShhbGVydC5pZCl9PgogICAgICAgICAgICAgICAgICAgICAgICBSZXNvbHZlCiAgICAgICAgICAgICAgICAgICAgICA8L0J1dHRvbj4KICAgICAgICAgICAgICAgICAgICApfQogICAgICAgICAgICAgICAgICAgIDxCdXR0b24gc2l6ZT0ic20iIHZhcmlhbnQ9Imdob3N0IiBkaXNhYmxlZD17aXNNdXRhdGluZ30KICAgICAgICAgICAgICAgICAgICAgIG9uQ2xpY2s9eygpID0+IGRlbGV0ZU11dGF0aW9uLm11dGF0ZShhbGVydC5pZCl9PgogICAgICAgICAgICAgICAgICAgICAgRGVsZXRlCiAgICAgICAgICAgICAgICAgICAgPC9CdXR0b24+CiAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgPC9kaXY+CgogICAgICAgICAgICAgICAge2FsZXJ0LmxvZ190YWlsICYmICgKICAgICAgICAgICAgICAgICAgPGRpdj4KICAgICAgICAgICAgICAgICAgICA8YnV0dG9uCiAgICAgICAgICAgICAgICAgICAgICBjbGFzc05hbWU9ImZsZXggaXRlbXMtY2VudGVyIGdhcC0xIHRleHQteHMgdGV4dC1baHNsKHZhcigtLW11dGVkLWZvcmVncm91bmQpKV0gaG92ZXI6dGV4dC1baHNsKHZhcigtLWZvcmVncm91bmQpKV0iCiAgICAgICAgICAgICAgICAgICAgICBvbkNsaWNrPXsoKSA9PiBzZXRFeHBhbmRlZExvZ0lkKGxvZ0V4cGFuZGVkID8gbnVsbCA6IGFsZXJ0LmlkKX0KICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICB7bG9nRXhwYW5kZWQgPyA8Q2hldnJvbkRvd24gY2xhc3NOYW1lPSJoLTMgdy0zIiAvPiA6IDxDaGV2cm9uUmlnaHQgY2xhc3NOYW1lPSJoLTMgdy0zIiAvPn0KICAgICAgICAgICAgICAgICAgICAgIEFnZW50IGxvZyB0YWlsCiAgICAgICAgICAgICAgICAgICAgPC9idXR0b24+CiAgICAgICAgICAgICAgICAgICAge2xvZ0V4cGFuZGVkICYmICgKICAgICAgICAgICAgICAgICAgICAgIDxwcmUgY2xhc3NOYW1lPSJtdC0xIG1heC1oLTQ4IG92ZXJmbG93LWF1dG8gcm91bmRlZCBiZy1baHNsKHZhcigtLW11dGVkKSldLzUwIHAtMiB0ZXh0LXhzIGZvbnQtbW9ubyB0ZXh0LVtoc2wodmFyKC0tZm9yZWdyb3VuZCkpXSI+CiAgICAgICAgICAgICAgICAgICAgICAgIHthbGVydC5sb2dfdGFpbH0KICAgICAgICAgICAgICAgICAgICAgIDwvcHJlPgogICAgICAgICAgICAgICAgICAgICl9CiAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgKX0KICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgKTsKICAgICAgICAgIH0pfQogICAgICAgIDwvZGl2PgogICAgICA8L0NhcmRDb250ZW50PgogICAgPC9DYXJkPgogICk7Cn0KCg== \ No newline at end of file diff --git a/.claude/scripts/gen_b64.py b/.claude/scripts/gen_b64.py new file mode 100644 index 0000000..d17b7ac --- /dev/null +++ b/.claude/scripts/gen_b64.py @@ -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(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", + " \n", + " \n", + '
\n', + ' \n', + ' \n', + " Watchdog Alerts\n", + " {activeCount > 0 && (\n", + ' \n', + " {activeCount} active\n", + " \n", + " )}\n", + " \n", + " setShowAll((v) => !v)}\n", + " >\n", + ' {showAll ? "Active only" : "Show all"}\n', + " \n", + "
\n", + "
\n", + " \n", + " {isLoading && (\n", + '

Loading...

\n', + " )}\n", + " {!isLoading && alerts.length === 0 && (\n", + '

\n', + ' {showAll ? "No watchdog alerts recorded." : "No active watchdog alerts."}\n', + "

\n", + " )}\n", + '
\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", + " \n", + '
\n', + '
\n', + '
\n', + " \n", + " {status}\n", + " \n", + ' \n', + " " + BULLET + " {alert.restart_attempts} restart attempt\n", + ' {alert.restart_attempts !== 1 ? "s" : ""}\n', + " \n", + "
\n", + '

\n', + ' Agent ID:{" "}\n', + ' \n', + " {alert.agent_id.slice(0, 8)}…\n", + " \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", + "

\n", + " {alert.last_error && (\n", + '

\n', + ' Error: {alert.last_error}\n', + "

\n", + " )}\n", + "
\n", + "\n", + '
\n', + ' {status === "active" && (\n', + ' \n", + " )}\n", + ' {status !== "resolved" && (\n', + ' \n", + " )}\n", + ' \n", + "
\n", + "
\n", + "\n", + " {alert.log_tail && (\n", + "
\n", + " setExpandedLogId(logExpanded ? null : alert.id)}\n", + " >\n", + ' {logExpanded ? : }\n', + " Agent log tail\n", + " \n", + " {logExpanded && (\n", + '
\n',
+    "                        {alert.log_tail}\n",
+    "                      
\n", + " )}\n", + "
\n", + " )}\n", + "
\n", + " );\n", + " })}\n", + " \n", + "
\n", + "
\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") diff --git a/.claude/scripts/watchdog_alerts_page.py b/.claude/scripts/watchdog_alerts_page.py new file mode 100644 index 0000000..59fb78f --- /dev/null +++ b/.claude/scripts/watchdog_alerts_page.py @@ -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 = { + 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 = { + active: "Active", + acknowledged: "Acknowledged", + resolved: "Resolved", + }; + return ( + + {label[status]} + + ); +} + +// --------------------------------------------------------------------------- +// Filter type +// --------------------------------------------------------------------------- + +type StatusFilter = "" | "active" | "acknowledged" | "resolved"; + +// --------------------------------------------------------------------------- +// WatchdogAlerts page +// --------------------------------------------------------------------------- + +export function WatchdogAlerts() { + const { toast } = useToast(); + const queryClient = useQueryClient(); + const [statusFilter, setStatusFilter] = useState("active"); + const [expandedLogId, setExpandedLogId] = useState(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 ( +
+
+
+

Watchdog Alerts

+

+ Agent crash and restart exhaustion events +

+
+ +
+ + {/* Summary chips */} +
+
+
+ {activeCount} +
+
+ Active +
+
+
+
+ {acknowledgedCount} +
+
+ Acknowledged +
+
+
+ + {isError && ( +
+ Failed to load watchdog alerts. Check your connection and try + refreshing. +
+ )} + + + +
+ Alert Stream + + setStatusFilter(e.target.value as StatusFilter) + } + className="w-auto min-w-[11rem]" + aria-label="Filter by status" + > + + + + + +
+
+ + {isLoading ? ( +

+ Loading watchdog alerts... +

+ ) : alerts.length === 0 ? ( +
+ +

No watchdog alerts

+

+ No events match the current filter. +

+
+ ) : ( +
+ {alerts.map((alert: WatchdogAlert) => { + const status = watchdogStatus(alert); + const logExpanded = expandedLogId === alert.id; + + return ( +
+
+
+
+ + + {alert.restart_attempts} restart attempt + {alert.restart_attempts !== 1 ? "s" : ""} + +
+
+ + {alert.agent_id.slice(0, 8)}… + + {" \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()} + + )} +
+ {alert.last_error && ( +

+ Error:{" "} + {alert.last_error} +

+ )} +
+ +
+ {status === "active" && ( + + )} + {status !== "resolved" && ( + + )} + +
+
+ + {alert.log_tail && ( +
+ + {logExpanded && ( +
+                            {alert.log_tail}
+                          
+ )} +
+ )} +
+ ); + })} +
+ )} +
+
+
+ ); +} +''' + +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") diff --git a/.claude/scripts/watchdog_patch.py b/.claude/scripts/watchdog_patch.py new file mode 100644 index 0000000..8fd5b06 --- /dev/null +++ b/.claude/scripts/watchdog_patch.py @@ -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(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" + " \n" + " \n" + "
\n" + " \n" + " \n" + " Watchdog Alerts\n" + " {activeCount > 0 && (\n" + " \n" + " {activeCount} active\n" + " \n" + " )}\n" + " \n" + " setShowAll((v) => !v)}\n" + " >\n" + " {showAll ? \"Active only\" : \"Show all\"}\n" + " \n" + "
\n" + "
\n" + " \n" + " {isLoading && (\n" + "

Loading...

\n" + " )}\n" + " {!isLoading && alerts.length === 0 && (\n" + "

\n" + " {showAll ? \"No watchdog alerts recorded.\" : \"No active watchdog alerts.\"}\n" + "

\n" + " )}\n" + "
\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" + " \n" + "
\n" + "
\n" + "
\n" + " \n" + " {status}\n" + " \n" + " \n" + " \xb7 {alert.restart_attempts} restart attempt\n" + " {alert.restart_attempts !== 1 ? \"s\" : \"\"}\n" + " \n" + "
\n" + "

\n" + " Agent:{\" \"}\n" + " \n" + " {alert.agent_id.slice(0, 8)}…\n" + " \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" + "

\n" + " {alert.last_error && (\n" + "

\n" + " Error: {alert.last_error}\n" + "

\n" + " )}\n" + "
\n" + "\n" + "
\n" + " {status === \"active\" && (\n" + " acknowledgeMutation.mutate(alert.id)}\n" + " >\n" + " Acknowledge\n" + " \n" + " )}\n" + " {status !== \"resolved\" && (\n" + " resolveMutation.mutate(alert.id)}\n" + " >\n" + " Resolve\n" + " \n" + " )}\n" + " deleteMutation.mutate(alert.id)}\n" + " >\n" + " Delete\n" + " \n" + "
\n" + "
\n" + "\n" + " {alert.log_tail && (\n" + "
\n" + " \n" + " setExpandedLogId(logExpanded ? null : alert.id)\n" + " }\n" + " >\n" + " {logExpanded ? (\n" + " \n" + " ) : (\n" + " \n" + " )}\n" + " Agent log tail\n" + " \n" + " {logExpanded && (\n" + "
\n"
+    "                        {alert.log_tail}\n"
+    "                      
\n" + " )}\n" + "
\n" + " )}\n" + "
\n" + " );\n" + " })}\n" + " \n" + "
\n" + "
\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 at the bottom of the Alerts return div ─── + +# The closing of the Alerts function return div +old_end = " \n );\n}\n\n// Re-export types" +new_end = " \n \n );\n}\n\n// Re-export types" + +if old_end in content: + content = content.replace(old_end, new_end, 1) + print("OK: 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(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", + " \n", + " \n", + ' \n', + ' \n', + " Watchdog Alerts\n", + " {activeCount > 0 && (\n", + ' \n', + " {activeCount} active\n", + " \n", + " )}\n", + " \n", + " \n", + ' \n', + '
\n', + " setStatusFilter(e.target.value as "" | WatchdogStatus)}\n', + ' className="w-auto min-w-[10rem]"\n', + ' aria-label="Filter by status"\n', + " >\n", + ' \n', + ' \n', + ' \n', + ' \n', + " \n", + "
\n", + "\n", + " {isLoading && (\n", + '

\n', + " Loading...\n", + "

\n", + " )}\n", + "\n", + " {!isLoading && filtered.length === 0 && (\n", + '

\n', + " {statusFilter\n", + ' ? "No alerts match the current filter."\n', + ' : "No watchdog alerts recorded."}\n', + "

\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", + " \n", + '
\n', + '
\n', + '
\n', + " \n", + " {status}\n", + " \n", + ' \n', + " \xb7 {alert.restart_attempts} restart attempt\n", + ' {alert.restart_attempts !== 1 ? "s" : ""}\n', + " \n", + "
\n", + '

\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", + "

\n", + " {alert.last_error && (\n", + '

\n', + ' Error: {alert.last_error}\n', + "

\n", + " )}\n", + "
\n", + "\n", + '
\n', + ' {status === "active" && (\n', + " acknowledgeMutation.mutate(alert.id)}\n", + " >\n", + " Acknowledge\n", + " \n", + " )}\n", + ' {status !== "resolved" && (\n', + " resolveMutation.mutate(alert.id)}\n", + " >\n", + " Resolve\n", + " \n", + " )}\n", + " deleteMutation.mutate(alert.id)}\n", + " >\n", + " Delete\n", + " \n", + "
\n", + "
\n", + "\n", + " {alert.log_tail && (\n", + "
\n", + " \n", + " setExpandedLogId(logExpanded ? null : alert.id)\n", + " }\n", + " >\n", + " {logExpanded ? (\n", + ' \n', + " ) : (\n", + ' \n', + " )}\n", + " Agent log tail\n", + " \n", + " {logExpanded && (\n", + '
\n',
+    "                      {alert.log_tail}\n",
+    "                    
\n", + " )}\n", + "
\n", + " )}\n", + " \n", + " );\n", + " })}\n", + "
\n", + "
\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 = ( + ' \n' + " \n" + " " +) +new_tab = ( + ' \n' + " \n" + '
\n' + " \n" + "
\n" + "
" +) + +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())}") diff --git a/session-logs/2026-05-23-session.md b/session-logs/2026-05-23-session.md index fb4bc33..774b90f 100644 --- a/session-logs/2026-05-23-session.md +++ b/session-logs/2026-05-23-session.md @@ -182,3 +182,62 @@ Medical breakthroughs preserved as filler content: **HTML File Location:** `file:///Users/azcomputerguru/ClaudeTools/projects/radio-show/episodes/2026-05-23-show/show-prep.html` **For Howard:** Open in browser to review full show prep with color-coded sections, talking points, sources, and transitions. + +--- + +## Update: 01:20 PT — GuruRMM / Paul Key / Windows Update roadmap + +**Machine:** DESKTOP-0O8A1RL + +### Session Summary + +Completed three work items on the desktop workstation in the early hours of 2026-05-23. + +Added Paul Key as a new GuruRMM client with a "Home" site. Used the GuruRMM API directly (login → `POST /api/clients` → `POST /api/sites`) to create the client and site. Site enrollment key received and vaulted at `clients/key-paul/gururmm-site-home.sops.yaml` with SOPS age encryption. Vault committed and pushed. + +Diagnosed KEY-MEDIA (Paul Key's Windows 11 media server — i5-13420H, 15.6 GB RAM, agent 0.6.28, already enrolled and online at `10.0.0.100`). Ran three rounds of remote PowerShell diagnostics via GuruRMM command API in system context (headless machine, no user session). Found three issues: (1) recurring Kernel-Power 41 unclean shutdowns — three events over six months (11/11/2025, 01/22/2026, 05/22/2026), no BSODs or minidumps, machine was down ~4 hours on 05/22 — power loss pattern, needs UPS; (2) Ombi misconfigured with wrong Plex port — `PlexContentSync` targeting `10.0.0.100:10363` but Plex actually listens on `32400`; (3) pending reboot from six `PendingFileRenameOperations` entries. Disk health (C: 89% free, D: 4.6 TB media drive at 81% free, both Healthy SMART), memory (9 GB free), and running media stack (Plex, Sonarr, Radarr, SABnzbd, Ombi) were all clean. + +Added comprehensive Windows Update Management feature spec to `docs/FEATURE_ROADMAP.md`. Three operating modes: Monitor (passive, alerts only, user keeps WU control), Semi-Controlled (we own schedule/approval, user can still interact), Fully Managed (WU locked via registry/GP, no user access). Full stack documented: agent Windows WUA COM API with blacklist via `IUpdate.IsHidden` and real-time progress reporting; server with five new tables and approval/denial/blacklist endpoints; dashboard with per-agent WU tab, site fleet queue, policy editor, blacklist manager; approval workflow with auto-approve by severity threshold. "Patch Now" marked P1. + +Also answered a support question: Claude Code appearing to pause mid-task (timer freezes, everything catches up on Enter) is Windows Terminal selection mode — any click in the terminal buffers stdout until Enter/Escape; the process is running normally the whole time. + +### Key Decisions + +- All KEY-MEDIA diagnostics in system context — headless media server, user-session context would fail with no active session error. +- Three staged diagnostic rounds rather than one large script — easier to handle JSON escaping failures and isolate issues. +- Paul Key vault entry matches existing client GuruRMM site format (same structure as cascades-tucson, imc, kittle, stamback-septic). +- "Patch Now" marked P1 — techs need immediate install path during incidents; approval/scheduling workflow is secondary. + +### Problems Encountered + +- **JSON escaping in PowerShell-over-curl payload.** Multi-line PowerShell script in shell heredoc caused `jq` parse errors. Resolution: Python `json.dumps()` to write payload to `D:/claudetools/.claude/tmp_cmd_payload.json`, then `curl --data-binary @file`. Pattern reused for all three diagnostic rounds. +- **Session log merge conflict.** Mac session had already written `session-logs/2026-05-23-session.md` for the radio show. Desktop session created the same file. Resolved by aborting rebase, stashing staged scripts, fast-forward pulling the Mac session, then appending Desktop session as an Update section. + +### Configuration Changes + +- **CREATED** `D:/vault/clients/key-paul/gururmm-site-home.sops.yaml` — SOPS-encrypted enrollment key for Paul Key Home site. Vault commit `4df0c9c`. +- **MODIFIED** `D:/claudetools/projects/msp-tools/guru-rmm/docs/FEATURE_ROADMAP.md` — Added Windows Update Management section (~100 lines). Replaced single `[ ] Windows Update status - P2` bullet. Updated last-updated to 2026-05-23. + +### Credentials & Secrets + +**Paul Key — GuruRMM Home Site** +- Enrollment key: `grmm_EvOPzz6kCP99m5jyBuDBmGwqR4Y-I3f7` +- Vault: `clients/key-paul/gururmm-site-home.sops.yaml` +- Client ID: `9a669d23-02c8-4772-8577-fa84355361fd` +- Site ID: `a5b237db-5198-45af-8747-1fdf3aef445d` +- Site code: `IRON-WOLF-5819` +- Note: Key shown once at creation. Will not be returned by API again. + +### Pending / Incomplete Tasks + +- **KEY-MEDIA — fix Ombi port:** Change Plex URL in Ombi from `10.0.0.100:10363` to `10.0.0.100:32400`. Requires Ombi web UI access (likely `http://10.0.0.100:5000` — confirm with Paul). +- **KEY-MEDIA — reboot:** Six `PendingFileRenameOperations` pending. Schedule maintenance reboot via GuruRMM. +- **KEY-MEDIA — UPS advisory:** Three power events over six months. Recommend UPS to Paul. +- **GuruRMM Windows Update module:** Spec complete in roadmap. No implementation started. Use `/shape-spec` when prioritized. + +### Reference + +- KEY-MEDIA agent ID: `8c12d038-a017-422b-84ef-dd284188e146` +- Plex listen: `:::32400` (confirmed via `Get-NetTCPConnection`) +- Ombi path: `D:\Ombi\Ombi.exe` +- GuruRMM roadmap: `projects/msp-tools/guru-rmm/docs/FEATURE_ROADMAP.md`