Verified Commit e1f4d8ba authored by Jakob Moser's avatar Jakob Moser
Browse files

Rewrite to use mithril, show loading indicator

parent 6bf00c40
Loading
Loading
Loading
Loading
+1 −26
Original line number Diff line number Diff line
@@ -66,32 +66,7 @@
            <header style="background-image:url('./lib/fscoli-next/img/header.jpg')">
                <h1>Technik-Status</h1>
            </header>
            <div class="content">
                <h2>Öffentliche Dienste</h2>
                <p>
                    Diese Dienste sollten jederzeit aus dem Internet verfübgar sein, genau dann werden sie hier grün markiert. Aktuell (Stand 2025-11-19) sind die meisten Dienste allerdings nur aus dem Uni-VPN erreichbar (was die Status-Site als „down“ interpretiert).
                </p>
                <section id="public"></section>

                <h2>Dienste hinter Basic-Auth</h2>
                <p>
                    Diese Dienste sollten zwar jederzeit aus dem Internet verfügbar sein, allerdings durch Basic-Auth geschützt. Falls sie das sind, werden sie hier grün markiert.
                </p>
                <section id="auth"></section>

                <h2>Öffentliche Dienste des Instituts</h2>
                <p>
                    Auf diese Dienste haben wir als Fachschaft keinen Einfluss, denn sie werden vom Institut für Computerlinguistik betreiben. Falls sie aber auch down sind, spricht das dafür, dass gerade ein größeres Problem besteht.
                </p>
                <section id="public@institute"></section>

                <h2>Öffentliche Dienste der Uni</h2>
                <p>
                    Auch auf diese Dienste haben wir als Fachschaft keinen Einfluss.
                </p>
                <section id="public@uni"></section>

            </div>
            <div class="content"></div>
            <footer>
            </footer>
            <script type="module" src="js/index.mjs"></script>
+3 −98
Original line number Diff line number Diff line
const apiBase = "/api/v2/services"
import "./lib/mithril/mithril.min.js"
import Status from "./pieces/Status.mjs"

/**
 * Query the api endpoint /api/v2/status, which returns a list of objects, each representing a service.
 * To see what a service object looks like, check the API documentation.
 *
 * @see https://status.fsco.li/api/v2
 * @returns A list of service objects
 */
async function getStatus() {
    /*
     * Use fetch to make an HTTPS request to the API. fetch() is an asynchronous function,
     * meaning we have to "wait" for it to return using `await` (the nice thing is that
     * the code doesn't actually have to wait when using `await`, instead it can do something
     * else and will only return here when fetch is completed).
     *
     * Because we use `await` in this function, the function `getStatus()` needs to be marked `async`,
     * and we need to use `await` whenever we want to call `getStatus()`.
     */
    const response = await fetch(apiBase)
    return response.json()
}

/**
 * @param {string} serviceHost The host name of a service, e.g. tickets.fachschaft.cl.uni-heidelberg.de
 * @returns The HTTPS URL to that service, e.g. https://tickets.fachschaft.cl.uni-heidelberg.de
 */
function getServiceUrl(serviceHost) {
    // Template strings are a lot like f-strings in Python.
    // For details, see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
    return `https://${serviceHost}`
}

/**
 * "Interpret" a given HTTP status code, that is, return a list containing a human-readable label
 * (e.g. "Up") and a CSS color class (e.g. "green").
 *
 * @param {int} statusCode An HTTP status code
 * @returns A list of the form ["Label", "color"]
 */
function getStatusInterpretation(statusCode) {
    switch (statusCode) {
        case 200:
        case 301:
        case 302:
            return ["Up", "green"]
        case 401:
            return ["Protected", "green"]
        default:
            return ["Down", "red"]
    }
}

/**
 * Create an HTML status badge for the given service object. The service object could look like this:
 *
 * {"name": "Website", "host": "fachschaft.cl.uni-heidelberg.de", "status": 200}
 *
 * The badge code would then look like this:
 *
 * <a href="https://fachschaft.cl.uni-heidelberg.de" class="badge" target="_blank"><span>Website</span><span>up</span></a>
 *
 * @param {object} service A service object
 * @returns A status badge HTML element
 */
function createStatusBadge(service) {
    // Destructuring assignment: We know the method returns a list with two elements.
    // We want to do assign the first element to a const statusName, and the second to a const cssClass.
    // This can be done in a oneliner:
    const [statusName, cssClass] = getStatusInterpretation(service.status)

    // Create an HTML hyperlink element of the form
    // <a href="https://fachschaft.cl.uni-heidelberg.de" class="badge green" target="_blank"></a>
    // The class "badge" is used by some CSS code to style the link. target="_blank" ensures
    // the link will be opened in a new tab.
    const a = document.createElement("a")
    a.href = getServiceUrl(service.host)
    a.target = "_blank"
    a.classList.add("badge")
    a.classList.add(cssClass)

    const nameEl = document.createElement("span")
    nameEl.textContent = service.name

    const statusEl = document.createElement("span")
    statusEl.textContent = statusName

    a.append(nameEl, statusEl)

    return a
}

// The lines below are run as soon as the file is loaded.
const services = await getStatus()
services.forEach(service => {
    const p = document.createElement("p")
    p.appendChild(createStatusBadge(service))
    document.getElementById(service.category).appendChild(p)
})
m.mount(document.querySelector(".content"), Status)

js/model/services.mjs

0 → 100644
+16 −0
Original line number Diff line number Diff line
const apiBase = "/api/v2"

export default {
    value: null,
    load() {
        this.value = null

        m.request(`${apiBase}/services`).then(result => {
            if(this.value === null) {
                this.value = result
            }
        })

        m.request(`${apiBase}/status`).then(result => this.value = result)
    }
}

js/pieces/Badge.mjs

0 → 100644
+64 −0
Original line number Diff line number Diff line
/**
 * "Interpret" a given HTTP status code, that is, return a list containing a human-readable label
 * (e.g. "Up") and a CSS color class (e.g. "green").
 *
 * @param {int?} statusCode An HTTP status code
 * @returns A list of the form ["Label", "color"]
 */
function getStatusInterpretation(statusCode) {
    switch (statusCode) {
        case null:
        case undefined:
            return ["Loading", "black"]
        case 200:
        case 301:
        case 302:
            return ["Up", "green"]
        case 401:
            return ["Protected", "green"]
        default:
            return ["Down", "red"]
    }
}

/**
 * @param {string} serviceHost The host name of a service, e.g. tickets.fachschaft.cl.uni-heidelberg.de
 * @returns The HTTPS URL to that service, e.g. https://tickets.fachschaft.cl.uni-heidelberg.de
 */
function getServiceUrl(serviceHost) {
    // Template strings are a lot like f-strings in Python.
    // For details, see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
    return `https://${serviceHost}`
}

/**
 * A HTML status badge for the given service object. The service object could look like this:
 *
 * {"name": "Website", "host": "fachschaft.cl.uni-heidelberg.de", "status": 200}
 *
 * The badge code would then look like this:
 *
 * <a href="https://fachschaft.cl.uni-heidelberg.de" class="badge" target="_blank"><span>Website</span><span>up</span></a>
 */
export default {
    view(vnode) {
        const {service} = vnode.attrs

        // Destructuring assignment: We know the method returns a list with two elements.
        // We want to do assign the first element to a const statusName, and the second to a const cssClass.
        // This can be done in a oneliner:
        const [statusName, cssClass] = getStatusInterpretation(service.status)

        // Create an HTML hyperlink element of the form
        // <a href="https://fachschaft.cl.uni-heidelberg.de" class="badge green" target="_blank"></a>
        // The class "badge" is used by some CSS code to style the link. target="_blank" ensures
        // the link will be opened in a new tab.
        return m(`a.badge.${cssClass}`, {
            href: getServiceUrl(service.host),
            target: "_blank",
        }, [
            m("span", service.name),
            m("span", statusName)
        ])
    }
}

js/pieces/Status.mjs

0 → 100644
+47 −0
Original line number Diff line number Diff line
import services from "../model/services.mjs"
import StatusSection from "./StatusSection.mjs"

export default {
    oninit() {
        services.load()
    },
    view() {
        return [
            m("h2", "Öffentliche Dienste"),
            m("p", 
                "Diese Dienste sollten jederzeit aus dem Internet verfübgar sein, genau dann werden sie hier grün markiert. Aktuell (Stand 2025-11-19) sind die meisten Dienste allerdings nur aus dem Uni-VPN erreichbar (was die Status-Site als „down“ interpretiert)."
            ),
            m(StatusSection, {
                category: "public",
                services: services.value
            }),
    
            m("h2", "Dienste hinter Basic-Auth"),
            m("p", 
                "Diese Dienste sollten zwar jederzeit aus dem Internet verfügbar sein, allerdings durch Basic-Auth geschützt. Falls sie das sind, werden sie hier grün markiert."
            ),
            m(StatusSection, {
                category: "auth",
                services: services.value
            }),
    
            m("h2", "Öffentliche Dienste des Instituts"),
            m("p", 
                "Auf diese Dienste haben wir als Fachschaft keinen Einfluss, denn sie werden vom Institut für Computerlinguistik betreiben. Falls sie aber auch down sind, spricht das dafür, dass gerade ein größeres Problem besteht."
            ),
            m(StatusSection, {
                category: "public@institute",
                services: services.value
            }),
    
            m("h2", "Öffentliche Dienste der Uni"),
            m("p", 
                "Auch auf diese Dienste haben wir als Fachschaft keinen Einfluss."
            ),
            m(StatusSection, {
                category: "public@uni",
                services: services.value
            }),
        ]
    }
}
Loading