Notice: Due to size constraints and loading performance considerations, scripts referenced in blog posts are not attached directly. To request access, please complete the following form: Script Request Form Note: A Google account is required to access the form.
Disclaimer: I do not accept responsibility for any issues arising from scripts being run without adequate understanding. It is the user's responsibility to review and assess any code before execution. More information

Creating your own "public" status page

I wanted to create a easy to manage public status page that was driven by a file that you could update to reflect the component status of that component, it does not e-mail people about the outage but a status page does not really need to, I like one-glance status pages that tell the status with a single browse.

Components.txt File

This file will contain contain the main components and sub components, these are outlined with a "-" under the master components as you can see below, this file will need to be stored in the directory where you run your html website from:

Office 356:
-Exchange:Online
-Teams:Online
-Outlook:Online
-Mail Protection:Online
-Defender:Online
Conditional Access:Online
VPN
-Authentication:Online
-SAML:Offline

The valid status codes are as follows:

Online : Green dot and text "Online"
Degraded : Amber dot and text "Degraded"
Offline : Red dot and text "Offline"

HTML Code : statuspage.html (or indexs.html)


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>System Status</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        html, body {
            height: 100%;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            background: #f5f5f5;
        }
        
        .header {
            background: linear-gradient(45deg, #0f172a, #1e293b);
            height: 200px;
            position: relative;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .logo-container img {
            width: 300px;
            height: auto;
            max-width: 90%;
        }

        .container {
            max-width: 1200px;
            margin: 2rem auto;
            padding: 0 1rem;
        }

        .component-table {
            background: white;
            margin-bottom: 2rem;
            border-radius: 8px;
            overflow: hidden;
            width: 100%;
            transition: all 0.3s ease;
        }

        .component-table.status-Online {
            box-shadow: 0 1px 3px rgba(34, 197, 94, 0.2),
                       0 0 0 1px rgba(34, 197, 94, 0.1);
        }

        .component-table.status-Degraded {
            box-shadow: 0 1px 3px rgba(245, 158, 11, 0.3),
                       0 0 0 1px rgba(245, 158, 11, 0.2);
            background: rgba(245, 158, 11, 0.02);
        }

        .component-table.status-Offline {
            box-shadow: 0 1px 3px rgba(239, 68, 68, 0.3),
                       0 0 0 1px rgba(239, 68, 68, 0.2);
            background: rgba(239, 68, 68, 0.02);
        }

        .component-header {
            background: #1a1a1a;
            color: white;
            padding: 1rem;
            font-size: 1.2rem;
            font-weight: 500;
        }

        .status-row {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 1rem;
            border-bottom: 1px solid rgba(0, 0, 0, 0.05);
        }

        .status-row:last-child {
            border-bottom: none;
        }

        .status-dot {
            width: 8px;
            height: 8px;
            border-radius: 50%;
            margin-left: 1rem;
            transition: all 0.3s ease;
        }

        .Online { color: #22c55e; }
        .Degraded { color: #f59e0b; }
        .Offline { color: #ef4444; }

        .dot-Online { background: #22c55e; }
        .dot-Degraded { background: #f59e0b; }
        .dot-Offline { background: #ef4444; }

        @media (max-width: 640px) {
            .header {
                height: 150px;
            }
        }
    </style>
</head>
<body>
    <div class="header">
        <div class="logo-container">
            <img src="logo.png" alt="Logo">
        </div>
    </div>

    <div class="container" id="components"></div>

    <script>
        function getWorstStatus(children) {
            if (children.some(child => child.status === 'Offline')) return 'Offline';
            if (children.some(child => child.status === 'Degraded')) return 'Degraded';
            return 'Online';
        }

        function createComponentTable(name, children) {
            const worstStatus = getWorstStatus(children);
            const table = document.createElement('div');
            table.className = `component-table status-${worstStatus}`;
            
            table.innerHTML = `
                <div class="component-header">
                    ${name}
                </div>
                ${children.map(child => `
                    <div class="status-row">
                        <div>
                            <strong>${child.name}</strong> is currently <span class="${child.status}">${child.status}</span>
                        </div>
                        <span class="status-dot dot-${child.status}"></span>
                    </div>
                `).join('')}
            `;

            return table;
        }

        function updateComponents() {
            fetch('components.txt')
                .then(response => response.text())
                .then(data => {
                    const components = document.getElementById('components');
                    components.innerHTML = '';
                    
                    let currentParent = null;
                    let currentChildren = [];

                    data.split('\n').forEach(line => {
                        if (!line.trim()) return;
                        
                        const isChild = line.startsWith('-');
                        const [name, status] = (isChild ? line.substring(1) : line).split(':');

                        if (!isChild) {
                            if (currentParent && currentChildren.length) {
                                components.appendChild(createComponentTable(currentParent, currentChildren));
                            }
                            currentParent = name;
                            currentChildren = [];
                        } else {
                            currentChildren.push({name, status});
                        }
                    });

                    if (currentParent) {
                        components.appendChild(createComponentTable(currentParent, currentChildren));
                    }
                });
        }

        updateComponents();
        setInterval(updateComponents, 5000);
    </script>
</body>
</html>

This will then return a status page that looks something like this, if you choose to use a logo that will appear in the black banner at the top of the page then when you update your components.txt file a simple F5 will refresh the statuses.


Previous Post Next Post

نموذج الاتصال