feat(observ): Add observability UI, plugin slots, notifications settings#78
Conversation
The observability plugin gets a full native UI. A fleet overview lands under a Monitoring group in the sidebar with real time-series graph panels (CPU, memory, network) instead of flat stat lists, a recovery timeline, and a "needs attention" list. Managed deployments there deep-link straight to their metrics tab; external containers are shown but not linked, since there is nothing for FlatRun to manage. Plugins can inject sections into the app through a slot system: a deployment's detail view renders plugin-contributed tabs, and settings renders plugin-contributed configuration forms driven by each plugin's schema. Notifications are a core settings tab. Targets are configured through per-service fields (email or webhook) that build the delivery URL for the user, rather than making them hand-write shoutrrr syntax, with a test-send per target.
…ntexts Adding a database connection (and adding a notification target) failed with "crypto.randomUUID is not a function" whenever the UI was opened over plain HTTP on a LAN address, because that API only exists in a secure context. Client-side IDs now fall back to a getRandomValues-based UUID, which works everywhere.
Code Review SummaryThis PR introduces a robust observability suite and a generic plugin UI slot system. It significantly improves the monitoring capabilities of FlatRun and solves a critical bug regarding UUID generation in non-HTTPS environments. 🚀 Key Improvements
💡 Minor Suggestions
|
| bytes[6] = (bytes[6] & 0x0f) | 0x40; | ||
| bytes[8] = (bytes[8] & 0x3f) | 0x80; | ||
|
|
||
| const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")); |
There was a problem hiding this comment.
While the logic is correct, the string formatting can be simplified using Intl.NumberFormat or a cleaner slice pattern for better maintainability. Additionally, ensuring the high-order bits of the first byte of the variant (byte 8) are correct is crucial for RFC 4122 compliance, which you have handled.
| const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")); | |
| const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join(""); | |
| return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`; |
| if (form.type === "email") { | ||
| const e = form.email; | ||
| if (!e.host || !e.from || !e.to) return ""; | ||
| const cred = e.username ? `${encodeURIComponent(e.username)}:${encodeURIComponent(e.password)}@` : ""; |
There was a problem hiding this comment.
Security risk: Credentials (username/password) are being URI-encoded but still transmitted as part of a URL string. Ensure that when these are saved to the backend, the backend treats these URLs as sensitive/encrypted at rest. Also, the password should be cleared from the local reactive state after adding to the target list to minimize memory exposure.
| const cred = e.username ? `${encodeURIComponent(e.username)}:${encodeURIComponent(e.password)}@` : ""; | |
| const cred = e.username ? `${encodeURIComponent(e.username)}:${encodeURIComponent(e.password)}@` : ""; | |
| const params = new URLSearchParams({ fromAddress: e.from, toAddresses: e.to, useStartTLS: "yes" }); | |
| const url = `smtp://${cred}${e.host}:${e.port || "587"}/?${params.toString()}`; | |
| return url; |
| function render() { | ||
| if (!host.value) return; | ||
| const width = host.value.clientWidth || 320; | ||
| if (chart) { |
There was a problem hiding this comment.
Performance: chart.destroy() followed by new uPlot() on every container/data change is heavy. uPlot is designed to handle data updates via setData and series updates via addSeries/delSeries. Complete re-renders should be avoided unless the series count changes significantly.
| if (chart) { | |
| if (chart) { | |
| if (props.containers.length === chart.series.length - 1) { | |
| chart.setData(data()); | |
| return; | |
| } | |
| chart.destroy(); | |
| } |
The UI half of observability and self-recovery (#148). Pairs with flatrun/agent#165.
A fleet overview under a new Monitoring group in the sidebar, with time-series graph panels for CPU, memory, and network instead of the flat stat lists the earlier version showed, plus a recovery timeline and a "needs attention" list. Managed deployments deep-link to their metrics tab; external containers are listed but not linked, since there is nothing for FlatRun to act on.
A slot system lets any plugin inject UI: a deployment's detail view renders plugin-contributed tabs, and settings renders plugin-contributed config forms from each plugin's schema. Observability is the first consumer; the mechanism is generic.
Notifications become a core settings tab. Instead of hand-writing shoutrrr URLs, targets are configured through per-service fields (email or webhook) that build the URL, with a test-send per target.
Also fixes a client-side ID crash:
crypto.randomUUIDonly exists in a secure context, so adding a database connection or notification target failed with "not a function" over plain HTTP on a LAN address. IDs now fall back to agetRandomValuesUUID.