Skip to content

feat(logs): System logs tab, log rules, and server-side deletion - #101

Merged
nfebe merged 4 commits into
mainfrom
feat/system-logs-and-log-triage
Aug 10, 2026
Merged

feat(logs): System logs tab, log rules, and server-side deletion#101
nfebe merged 4 commits into
mainfrom
feat/system-logs-and-log-triage

Conversation

@nfebe

@nfebe nfebe commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

The trash button used to clear the buffer, which is what refresh already did, so it now deletes on
the server behind a confirmation naming exactly what goes.

The auth commit is a self-inflicted lockout. The agent reads a run of rejections from one address
as an attack and blocks it, and the panel fires a page of calls at once, so loading the dashboard
with a lapsed token spent a rejection on each and could block the operator. An interceptor cannot
unsend a request, so the first call of a burst now goes alone and the rest wait on what it learns.
Reading the current user built its own client and bypassed all of this, which is why it was one of
the calls in the run.

Needs flatrun/agent#206 for the endpoints it calls.

nfebe added 3 commits August 10, 2026 21:12
The logs page now has a system tab for logs the host writes rather than a deployment, starting
with the proxy's access and error logs, and the access log can be narrowed to one deployment. A
deployment's own logs can again be narrowed to a single container, which had been lost.

The trash button now empties the log on the server behind a confirmation that names exactly what
goes, rather than clearing the view, which is what refresh already did. A log entry can be handed
to the assistant on its own, so the question is about the line the reader is pointing at instead
of everything on screen.

Follow moves in with the other actions, since it acts on the stream rather than filtering it.
Alerts now covers logs as well as metrics. A rule says which lines matter, how many times within
how long, and how long to stay quiet after firing, so a repeated error raises an incident on its
own instead of waiting for someone to be watching.

Incidents read as a feed, each carrying the line that fired it, how often it recurred, and where
the assistant is configured, a short explanation and a suggested next step.
Opening the panel with a lapsed session no longer spends a rejection on every call the page
makes. The agent reads a run of rejections from one address as an attack and blocks it, so an
operator whose token expired could lock themselves out simply by loading the dashboard.

The first call of a burst now goes alone and the rest wait on what it learns, which turns a page
load into one rejection instead of a dozen. A signed-out visitor sends nothing at all. The gate
arms again once a burst has drained, so a session that lapses while the panel is open costs one
rejection rather than one per call from then on.

Reading the current user shared none of this because it built its own client; it now goes through
the same one as everything else.
@sourceant

sourceant Bot commented Aug 10, 2026

Copy link
Copy Markdown

Code Review Summary

The PR introduces significant improvements to log management, including log-based alerting (rules and incidents), system infrastructure logs, and a more resilient API client session management system. The 'Session Gate' is a standout feature addressing a critical authentication edge case.

🚀 Key Improvements

  • Implemented a SessionGate in src/services/api.ts to prevent fanned-out 401 rejections from triggering address blocks.
  • Added a system logs view for infrastructure-level observability gated by permissions.
  • Integrated AI debugging directly into the log viewer for individual log entries.
  • Replaced client-side log clearing with server-side deletion and explicit confirmation logic.

💡 Minor Suggestions

  • Normalize isPublic URL matching logic in the API client.
  • Refactor store usage in LogViewer.vue for minor performance gain.
  • De-duplicate log deletion confirmation messages between views.

@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. No specific code suggestions were generated. See the overview comment for a summary.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 10, 2026

Copy link
Copy Markdown

Deploying flatrun-ui with  Cloudflare Pages  Cloudflare Pages

Latest commit: 6711257
Status: ✅  Deploy successful!
Preview URL: https://99649fb8.flatrun-ui.pages.dev
Branch Preview URL: https://feat-system-logs-and-log-tri.flatrun-ui.pages.dev

View logs

@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.


// Debugging one entry hands over that entry alone. The toolbar's assistant reads everything on
// screen, which buries the line the reader is actually pointing at.
const debugEntry = (entry: LogEntry) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Move the store initialization outside the debugEntry function to the top-level script setup. While Pinia stores are efficient, locating the store on every single 'Debug' click is unnecessary overhead. Additionally, the regex was refined to strip '-logs' for a cleaner AI subject name.

Suggested change
const debugEntry = (entry: LogEntry) => {
const assistStore = useAssistStore();
const debugEntry = (entry: LogEntry) => {
const base = props.assistContext ?? { scope: "system" as const, subject: props.fileName.replace(/-logs\.txt$/, "") };
assistStore.open({
...base,
seedMessage: `Debug this log entry from ${base.subject}: what caused it, how serious is it, and what should I do next?`,
seedContext: `\`\`\`\n${entry.lines.join("\n")}\n\`\`\``,
});
};

Comment thread src/services/api.ts

apiClient.interceptors.request.use((config) => {
const publicPaths = ["/auth/login", "/auth/status", "/setup", "/health"];
const isPublic = (url: string) => publicPaths.some((p) => url.startsWith(p));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The isPublic check relies on startsWith. If a URL is passed without a leading slash or contains query parameters, it might behave unexpectedly. Normalizing the path or stripping queries ensures the matching is reliable.

Suggested change
const isPublic = (url: string) => publicPaths.some((p) => url.startsWith(p));
const isPublic = (url: string) => {
const path = url.split('?')[0];
const normalized = path.startsWith('/') ? path : `/${path}`;
return publicPaths.some((p) => normalized.startsWith(p));
};

@nfebe
nfebe merged commit 16e258f into main Aug 10, 2026
5 checks passed
@nfebe
nfebe deleted the feat/system-logs-and-log-triage branch August 10, 2026 20:52
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