How Incident Reports Are Generated and Managed in Uptime Kuma

Uptime Kuma manages incident reports through Socket.io events that validate and persist incident beans to the database, supporting create, edit, delete, resolve, and paginated history operations via the status page interface.

Incident reports in Uptime Kuma provide real-time communication about system outages and maintenance directly on public status pages. According to the louislam/uptime-kuma source code, the platform implements a complete incident lifecycle through database-backed beans, Socket.io handlers in the backend, and a Vue.js frontend interface. This article explores the technical architecture behind how incidents are created, validated, resolved, and retrieved with cursor-based pagination.

Incident Storage and Data Model

Uptime Kuma treats an incident as a database record stored in the incident table, utilizing RedBean ORM for persistence operations.

The Incident Bean Structure

The Incident class defined in server/model/incident.js extends BeanModel and provides two critical methods for managing incident state:

class Incident extends BeanModel {
    async resolve() {
        this.active = false;
        this.pin = false;
        this.last_updated_date = R.isoDateTime(dayjs.utc());
        await R.store(this);
    }

    toPublicJSON() {
        return {
            id: this.id,
            style: this.style,
            title: this.title,
            content: this.content,
            pin: !!this.pin,
            active: !!this.active,
            createdDate: this.created_date,
            lastUpdatedDate: this.last_updated_date,
            status_page_id: this.status_page_id,
        };
    }
}

The resolve() method marks incidents as inactive and unpins them from the status page, while toPublicJSON() sanitizes the bean for client consumption, exposing only necessary fields including id, title, content, style, and timestamps.

Validation Logic

Before any persistence operation, the validateIncident function in server/socket-handlers/status-page-socket-handler.js enforces strict requirements:

function validateIncident(incident) {
    if (!incident.title || incident.title.trim() === "") {
        throw new Error("Please input title");
    }
    if (!incident.content || incident.content.trim() === "") {
        throw new Error("Please input content");
    }
}

This validation ensures that both title and content fields contain non-empty strings before the server processes creation or update requests.

Server-Side Incident Lifecycle via Socket.io

All incident-related actions are performed through Socket.io events handled in server/socket-handlers/status-page-socket-handler.js, which interact with the database through RedBean ORM operations.

Creating and Editing Incidents

The postIncident event handles both creation and updates. When the frontend emits this event with an incident payload, the handler validates the data and either updates an existing row (if an id is supplied) or dispenses a new incident bean:

  • Sets pin=true and active=true for new incidents
  • Associates the incident with the target status page via status_page_id
  • Persists the bean using R.store

The editIncident event allows modification of existing records. It looks up the incident by incidentID and status_page_id, then permits updates to title, content, style, and pin fields. The system restricts style values to a specific set: info, warning, danger, primary, light, or dark.

Resolving and Deleting Incidents

Resolution occurs through the resolveIncident event, which delegates to the model's resolve() method:

// Server-side resolution logic
await incidentBean.resolve();   // marks inactive and unpins

This sets active=false, pin=false, and updates the last_updated_date field before persisting changes.

Deletion is handled by the deleteIncident event, which locates the incident by ID and status page, then permanently removes it using R.trash(bean).

Paginated History Retrieval

The getIncidentHistory event implements cursor-based pagination for archived incidents. It delegates to StatusPage.getIncidentHistory(statusPageID, cursor, isPublic) in server/model/status_page.js:

static async getIncidentHistory(statusPageId, cursor = null, isPublic = true) {
    // Query fetches incidents older than the supplied cursor
    const total = await R.count("incident", " status_page_id = ? ", [statusPageId]);
    
    const lastIncident = incidents[incidents.length - 1];
    let nextCursor = null;
    let hasMore = false;

    if (lastIncident) {
        const moreCount = await R.count(
            "incident",
            " status_page_id = ? AND created_date < ? ",
            [statusPageId, lastIncident.created_date]
        );
        hasMore = moreCount > 0;
        if (hasMore) {
            nextCursor = lastIncident.created_date;
        }
    }

    return {
        incidents: incidents.map(i => i.toPublicJSON()),
        total,
        nextCursor,
        hasMore,
    };
}

This approach uses created_date as the cursor, returning objects containing incidents, total, nextCursor, and hasMore for efficient frontend pagination.

Frontend Implementation in StatusPage.vue

The primary UI for managing incidents lives in src/pages/StatusPage.vue, which communicates with the backend through Socket.io emissions.

Creating a New Incident

When administrators submit the incident form, the component emits the postIncident event with the status page slug and incident object:

postIncident() {
    this.$root.getSocket().emit(
        "postIncident",
        this.slug,
        this.incident,
        (res) => {
            if (res.ok) {
                this.incident = {}; // reset form
                this.loadIncidents(); // refresh list
            } else {
                this.$toast.error(res.msg);
            }
        }
    );
}

Editing and Resolving Incidents

The component similarly handles modifications through editIncident emissions, and resolution through resolveIncident:

// Editing example
const edited = {
    id: incidentId,
    title: "Database outage – resolved",
    content: "Failover completed; services normal.",
    style: "info",
    pin: false,
};

this.$root.getSocket().emit("editIncident", this.slug, incidentId, edited, callback);

// Resolving example
this.$root.getSocket().emit("resolveIncident", this.slug, incidentId, callback);

Fetching Paginated History

The history tab utilizes getIncidentHistory with cursor-based navigation:

function fetchHistory(cursor = null) {
    this.$root.getSocket().emit(
        "getIncidentHistory",
        this.slug,
        cursor,
        (result) => {
            if (result.ok) {
                this.history = result.incidents;
                this.nextCursor = result.nextCursor;
                this.hasMore = result.hasMore;
            }
        }
    );
}

Summary

  • Incident reports in Uptime Kuma are stored as beans in the incident table, managed through the Incident class in server/model/incident.js.
  • Socket.io events (postIncident, editIncident, deleteIncident, resolveIncident) handle all CRUD operations in server/socket-handlers/status-page-socket-handler.js.
  • Validation requires non-empty title and content strings before database persistence.
  • Resolution logic uses the Incident.resolve() method to mark beans inactive and unpinned.
  • Pagination is cursor-based, implemented in StatusPage.getIncidentHistory() using creation dates for navigation.
  • Frontend integration occurs through Vue.js components in src/pages/StatusPage.vue that emit socket events and handle paginated responses.

Frequently Asked Questions

How are incident reports stored in Uptime Kuma?

Incident reports are stored as database records in the incident table using RedBean ORM. The Incident class in server/model/incident.js provides methods like resolve() for state management and toPublicJSON() for data sanitization, ensuring that only appropriate fields are exposed to clients.

What Socket.io events handle incident management?

The backend listens for five primary events in server/socket-handlers/status-page-socket-handler.js: postIncident (create/update), editIncident (modification), deleteIncident (removal via R.trash), resolveIncident (marking inactive), and getIncidentHistory (paginated retrieval). Each event validates input and associates operations with specific status pages through status_page_id parameters.

How does pagination work for incident history?

The system implements cursor-based pagination through the getIncidentHistory event. It uses created_date as the cursor field, querying for incidents older than the supplied cursor value. The response includes nextCursor, hasMore, and total count, allowing the frontend to efficiently navigate large incident histories without offset-based performance degradation.

What validation is required when creating an incident?

The validateIncident function requires both title and content fields to contain non-empty strings after trimming whitespace. If validation fails, the server throws an error before any database operations occur. Additionally, the style field is restricted to specific values (info, warning, danger, primary, light, dark) during edit operations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →