DEV Community

Cover image for I Built a Real-Time Monitoring Dashboard for an AS/400 with n8n and React
Mychel Garzon
Mychel Garzon

Posted on

I Built a Real-Time Monitoring Dashboard for an AS/400 with n8n and React

If you work with IBM i (AS/400), you know the pain. Your system runs critical business logic, but monitoring it feels like checking a car engine by listening to the sounds it makes.

I wanted a real-time dashboard. Something visual. Something that tells me when jobs fail, when disk usage spikes, or when a scheduled task does not run on time. The problem? The AS/400 was not designed to talk to modern web apps.

So I connected the dots with n8n in the middle and React on the front end. Here is how I built it.


The Problem With AS/400 Monitoring

Most IBM i shops rely on one of two things: WRKACTJOB on a green screen, or expensive enterprise monitoring tools that cost more than your car.

Neither option works well for a small team that wants fast, visual feedback. You end up checking things manually or finding out about problems hours after they happen.

I needed something that:

  • Polls the AS/400 on a schedule
  • Collects job status, disk usage, and message queue alerts
  • Pushes that data to a frontend dashboard in near real-time
  • Sends Slack or email alerts when something breaks

The Architecture

┌─────────────┐      ODBC/SQL       ┌─────────────┐
│   IBM i      │ ◄─────────────────► │    n8n       │
│   (AS/400)   │                     │  (Workflows) │
└─────────────┘                      └──────┬──────┘
                                            │
                                     REST API / WS
                                            │
                                     ┌──────▼──────┐
                                     │    React     │
                                     │  Dashboard   │
                                     └─────────────┘
Enter fullscreen mode Exit fullscreen mode

Three layers. The AS/400 holds the data. n8n pulls it out and transforms it. React displays it.

Step 1: Querying the AS/400 From n8n

The IBM i stores system information in a set of SQL views and table functions. These are gold. You can query them with standard SQL through an ODBC connection.

In n8n, I used the MySQL node configured with an ODBC bridge to the AS/400. Some teams use a middleware layer like ODBC Gateway or a lightweight API on the IBM i side. The key point is that n8n can talk SQL to the system.

Here are the queries I used for the three main data points:

Active Jobs

SELECT JOB_NAME, JOB_USER, JOB_STATUS, CPU_TIME,
       TEMPORARY_STORAGE
FROM TABLE(QSYS2.ACTIVE_JOB_INFO(
    JOB_NAME_FILTER => '*ALL'))
WHERE JOB_STATUS = '*ACTIVE'
ORDER BY CPU_TIME DESC
FETCH FIRST 20 ROWS ONLY
Enter fullscreen mode Exit fullscreen mode

Disk Usage

SELECT ASP_NUMBER, SYSTEM_ASP_STORAGE,
       TOTAL_AUXILIARY_STORAGE,
       CURRENT_AUXILIARY_STORAGE_PERCENTAGE
FROM QSYS2.ASP_INFO
Enter fullscreen mode Exit fullscreen mode

Message Queue Alerts

SELECT MESSAGE_ID, MESSAGE_TEXT, SEVERITY,
       MESSAGE_TIMESTAMP
FROM TABLE(QSYS2.MESSAGE_QUEUE_INFO(
    MESSAGE_QUEUE_NAME => 'QSYSOPR'))
WHERE SEVERITY >= 30
ORDER BY MESSAGE_TIMESTAMP DESC
FETCH FIRST 50 ROWS ONLY
Enter fullscreen mode Exit fullscreen mode

Each of these runs on a separate n8n workflow with a Cron trigger. Jobs and messages poll every 60 seconds. Disk usage polls every 5 minutes.

Step 2: The n8n Workflow Design

Each workflow follows the same pattern:

  1. Cron Trigger - fires on schedule
  2. SQL Query Node - runs the query against the AS/400
  3. Function Node - transforms the raw data into a clean JSON shape
  4. HTTP Request Node - pushes the data to a simple Express API (or writes to a shared database)
  5. IF Node - checks for alert conditions
  6. Slack Node - sends a notification if something is wrong

Here is what the transform function looks like for active jobs:

const jobs = items.map(item => ({
  name: item.json.JOB_NAME,
  user: item.json.JOB_USER,
  status: item.json.JOB_STATUS,
  cpuTime: parseFloat(item.json.CPU_TIME),
  tempStorage: parseInt(item.json.TEMPORARY_STORAGE),
  timestamp: new Date().toISOString()
}));

return [{ json: { jobs, polledAt: new Date().toISOString() } }];
Enter fullscreen mode Exit fullscreen mode

For alerting, the IF node checks simple conditions:

  • Disk usage over 85%? Alert.
  • Severity 40+ message in QSYSOPR? Alert.
  • A critical job that should be active is missing from the active jobs list? Alert.

This is where n8n shines. You can build these rules visually and adjust thresholds without touching code.

Step 3: The Data Layer

I kept this simple. The n8n workflows push JSON to a lightweight Express server that stores the latest state in memory and also writes to a PostgreSQL table for history.

// Simplified Express endpoint
app.post('/api/metrics/:type', (req, res) => {
  const { type } = req.params;
  const data = req.body;

  // Update current state
  currentState[type] = data;

  // Write to PostgreSQL for history
  db.query(
    'INSERT INTO metrics (type, data, created_at) VALUES ($1, $2, NOW())',
    [type, JSON.stringify(data)]
  );

  // Broadcast to WebSocket clients
  wss.clients.forEach(client => {
    client.send(JSON.stringify({ type, data }));
  });

  res.json({ ok: true });
});
Enter fullscreen mode Exit fullscreen mode

The WebSocket broadcast is what makes the dashboard feel real-time. When n8n pushes new data, every open dashboard tab updates within a second.

Step 4: The React Dashboard

The frontend is a single-page React app with three panels: Active Jobs, Disk Usage, and Message Queue.

I used Recharts for the disk usage chart and a simple table component for jobs and messages.

import { useEffect, useState } from 'react';
import {
  BarChart, Bar, XAxis, YAxis, Tooltip, Cell
} from 'recharts';

function DiskUsagePanel() {
  const [data, setData] = useState([]);

  useEffect(() => {
    const ws = new WebSocket('ws://localhost:3001');
    ws.onmessage = (event) => {
      const msg = JSON.parse(event.data);
      if (msg.type === 'disk') {
        setData(msg.data);
      }
    };
    return () => ws.close();
  }, []);

  return (
    <div className="panel">
      <h2>Disk Usage (ASP)</h2>
      <BarChart width={400} height={250} data={data}>
        <XAxis dataKey="aspNumber" />
        <YAxis domain={[0, 100]} />
        <Tooltip />
        <Bar dataKey="usagePercent">
          {data.map((entry, i) => (
            <Cell
              key={i}
              fill={entry.usagePercent > 85 ? '#ef4444' : '#22c55e'}
            />
          ))}
        </Bar>
      </BarChart>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

The color logic is simple but effective. Green when everything is fine. Red when you should pay attention. No need to read numbers when the bar turns red.

For the jobs table, I highlight rows where CPU time is unusually high or where a critical job has gone missing:

function JobsPanel({ jobs, criticalJobs }) {
  const missingCritical = criticalJobs.filter(
    cj => !jobs.find(j => j.name.includes(cj))
  );

  return (
    <div className="panel">
      <h2>Active Jobs</h2>
      {missingCritical.length > 0 && (
        <div className="alert">
          Missing critical jobs: {missingCritical.join(', ')}
        </div>
      )}
      <table>
        <thead>
          <tr>
            <th>Job</th><th>User</th>
            <th>CPU Time</th><th>Temp Storage</th>
          </tr>
        </thead>
        <tbody>
          {jobs.map((job, i) => (
            <tr key={i} className={
              job.cpuTime > 10000 ? 'row-warning' : ''
            }>
              <td>{job.name}</td>
              <td>{job.user}</td>
              <td>{job.cpuTime}ms</td>
              <td>{job.tempStorage}MB</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

What I Learned

The AS/400 is more accessible than people think. The QSYS2 SQL services are powerful and well-documented. If your system is on IBM i 7.3 or later, you have access to hundreds of table functions that expose system information through standard SQL. You do not need to write CL programs or parse spool files.

n8n is perfect for this middle layer. It handles scheduling, data transformation, error handling, and alerting in one place. I could have written a Node.js cron script, but n8n gives me visibility into every execution. When a query fails at 3 AM, I can see exactly what happened in the n8n execution log the next morning.

Start with three metrics, not thirty. I started with a long list of things I wanted to monitor. That was a mistake. I scaled back to three: jobs, disk, and messages. Once those were solid, adding more was easy because the pattern was already proven.

WebSockets make a huge difference. Polling from the frontend would have worked, but the instant updates from WebSocket feel much better. When you fix something on the AS/400 and watch the dashboard turn green within a second, that feedback loop is satisfying.

Try It Yourself

If you have access to an IBM i system, you can start experimenting with the SQL queries using pub400.com, a free public AS/400 system. The QSYS2 views work there too, with some restrictions.

For the n8n side, the free self-hosted version handles everything I described here. You do not need the paid cloud version.

The React dashboard is intentionally simple. No component library needed. Recharts, a WebSocket hook, and some CSS is all it takes.


The AS/400 is not going anywhere. There are still thousands of these systems running critical workloads around the world. They deserve modern monitoring, and you do not need a six-figure IBM product to get it.

If you are working with IBM i and want to talk about this approach, drop a comment or find me on LinkedIn. I am always happy to talk about making legacy systems play nice with modern tools.


Previously: The AS/400 Can't Send a Slack Message. I Made It.

Top comments (0)