Olympus Docs
CookbookDefensive security

Saved audit views and dashboards

Common queries packaged for ops

Ops folks repeatedly run the same audit queries: "failed logins this week," "admin actions today," "MFA changes." Save them.

Storage

CREATE TABLE saved_audit_views (
  id UUID PRIMARY KEY,
  owner_id UUID,                -- whose view (NULL for global)
  name TEXT NOT NULL,
  description TEXT,
  query JSONB NOT NULL,         -- filters
  shared BOOLEAN DEFAULT false,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

Global views: shared with whole team. Personal views: only owner sees.

Sample global views

const STANDARD_VIEWS = [
  {
    name: "Failed logins (last 24h)",
    query: { event_type: "login", outcome: "failure", since: "24h" },
  },
  {
    name: "Admin actions (last 7 days)",
    query: { event_type__in: ["admin_user_locked", "admin_role_changed", "admin_session_revoked"], since: "7d" },
  },
  {
    name: "MFA changes",
    query: { event_type__in: ["mfa_enrolled", "mfa_removed"], since: "30d" },
  },
  {
    name: "Password changes",
    query: { event_type: "password_changed", since: "7d" },
  },
  {
    name: "OAuth consent revocations",
    query: { event_type: "oauth_consent_revoked", since: "30d" },
  },
];

Seeded on first deploy.

UI

Sidebar:

<aside>
  <h3>Saved views</h3>
  {standardViews.map(v => (
    <a href={`/audit?view=${v.id}`}>{v.name}</a>
  ))}
  
  <h3>My views</h3>
  {personalViews.map(v => (
    <a href={`/audit?view=${v.id}`}>{v.name}</a>
  ))}
</aside>

Click → audit page filtered.

Save current

<Button onClick={saveCurrent}>Save this view</Button>
async function saveCurrent() {
  const name = prompt("View name:");
  if (!name) return;
  await fetch("/api/audit/views", {
    method: "POST",
    body: JSON.stringify({
      name,
      query: getCurrentFilters(),
      owner_id: user.id,
    }),
  });
}

User saves current filter combo. Reuses later.

Live counters

Sidebar shows current count:

{view.name} <Badge>{liveCount}</Badge>
const liveCount = await fetch(`/api/audit/count?${querystring(view.query)}`);

User sees "Failed logins (last 24h): 234." If number spikes, attention.

Dashboards

A dashboard is multiple views laid out:

CREATE TABLE dashboards (
  id UUID PRIMARY KEY,
  name TEXT,
  layout JSONB,     -- array of {view_id, position, size}
  owner_id UUID,
  shared BOOLEAN
);
function Dashboard({ id }) {
  const { views } = useDashboard(id);
  return (
    <Grid>
      {views.map(v => (
        <Widget key={v.view_id}>
          <h4>{v.name}</h4>
          <Counter value={v.count} />
          <Sparkline data={v.history} />
        </Widget>
      ))}
    </Grid>
  );
}

Drag-and-drop arrangement.

Auto-refresh

useEffect(() => {
  const interval = setInterval(() => router.refresh(), 30000);
  return () => clearInterval(interval);
}, []);

30s refresh. Real-time-ish.

Alerts from views

CREATE TABLE view_alerts (
  view_id UUID,
  threshold INTEGER,    -- alert if count above this
  notify_channel TEXT,  -- email | slack | webhook
  created_at TIMESTAMPTZ
);

Cron checks views, alerts if threshold exceeded:

*/5 * * * * node check-view-alerts.js
for (const alert of alerts) {
  const count = await queryView(alert.view_id);
  if (count > alert.threshold) {
    await sendAlert(alert.notify_channel, `View "${alert.name}" has ${count} events (threshold: ${alert.threshold})`);
  }
}

Threshold-based monitoring.

Sharing

<Button onClick={shareView}>Share with team</Button>
async function shareView() {
  await fetch(`/api/audit/views/${id}`, {
    method: "PATCH",
    body: JSON.stringify({ shared: true }),
  });
}

Now visible to whole team.

Export

Each view supports CSV export:

<Button onClick={exportView}>Export CSV</Button>
const events = await queryView(viewId, { all: true });
const csv = toCsv(events);
download(csv, `${viewName}.csv`);

For external analysis.

Templates

For different roles:

// Support agent template
const SUPPORT_VIEWS = [
  "Recent failed logins",
  "Account lockouts (last 24h)",
  "Recovery requests (today)",
];

// Security analyst template
const SECURITY_VIEWS = [
  "Failed logins (last 7d)",
  "Brute-force IPs",
  "Sudden geo changes",
  "Admin actions on flagged accounts",
];

Seed per role.

Customizable

<form action={editView}>
  <input name="name" defaultValue={view.name} />
  <textarea name="description" defaultValue={view.description} />
  <input name="query" defaultValue={JSON.stringify(view.query)} />
  <Button>Save</Button>
</form>

Edit existing view.

Audit of audit views

Yes, log changes:

INSERT INTO security_audit (event_type, actor_id, metadata)
VALUES (
  'audit_view_created',
  $user_id,
  '{"name": "$name", "query": "$query"}'
);

Who created what.

On this page