Skip to content

feat(observ): Add observability UI, plugin slots, notifications settings#78

Merged
nfebe merged 2 commits into
mainfrom
feat/observability-plugin-platform
Jul 6, 2026
Merged

feat(observ): Add observability UI, plugin slots, notifications settings#78
nfebe merged 2 commits into
mainfrom
feat/observability-plugin-platform

Conversation

@nfebe

@nfebe nfebe commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

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.randomUUID only 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 a getRandomValues UUID.

nfebe added 2 commits July 6, 2026 11:26
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.
@sourceant

sourceant Bot commented Jul 6, 2026

Copy link
Copy Markdown

Code Review Summary

This 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

  • Introduction of PluginSlot.vue for modular UI extension.
  • Real-time monitoring charts using uplot for efficiency.
  • Standardized notification target configuration with structured fields.
  • Resilient UUID generation fallback for LAN/plain HTTP usage.

💡 Minor Suggestions

  • Optimize chart re-rendering in TimeSeriesChart.vue to use setData instead of full destruction.
  • Add a watcher for active tabs in DeploymentDetailView.vue to handle dynamic plugin state changes.

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

Comment thread src/utils/uuid.ts
bytes[6] = (bytes[6] & 0x0f) | 0x40;
bytes[8] = (bytes[8] & 0x3f) | 0x80;

const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0"));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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)}@` : "";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
if (chart) {
if (chart) {
if (props.containers.length === chart.series.length - 1) {
chart.setData(data());
return;
}
chart.destroy();
}

@nfebe nfebe merged commit c1e3697 into main Jul 6, 2026
5 checks passed
@nfebe nfebe deleted the feat/observability-plugin-platform branch July 6, 2026 16:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant