🏡 index : quine-global-labs/mirror-cockpit-project-cockpit.git

/*
 * Copyright (C) 2019 Red Hat, Inc.
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

/* NOTIFICATIONS

A page can broadcast notifications to the rest of Cockpit.  For
example, the "Software updates" page can send out a notification when
it detects that software updates are available.  The shell will then
highlight the menu entry for "Software updates" and the "System"
overview page will also mention it in its "Operating system" section.

The details are all still experimental and subject to change.

As a first step, there are only simple "page status" notifications.
When we address "event" style notifications, page status notifications
might become a special case of them.  Or not.

A page status is either null, or a JSON value with the following
fields:

 - type (string, optional)

 If specified, one of "info", "warning", "error".  The shell will put
 an appropriate icon next to the navigation entry for this page, for
 example.

 Omitting 'type' means that the page has no special status and is the
 same as using "null" as the whole status value.  This can be used to
 broadcast values in the 'details' field to other pages without
 forcing an icon into the navigation menu.

 - title (string, optional)

 A short, human readable, localized description of the status,
 suitable for a tooltip.

 - details (JSON value, optional)

 An arbitrary value.  The "System" overview page might monitor a
 couple of pages for their status and it will use 'details' to display
 a richer version of the status than possible with just type and
 title. The recognized properties are:

   * icon: custom icon name (defaults to standard icon corresponding to type)
   * pficon: PatternFly icon name; e.g. "enhancement", "bug", "security", "spinner", "check";
     see get_pficon() in pkg/systemd/page-status.jsx
   * link: custom link target (defaults to page name); if false, the
     notification will not be a link

Usage:

 import { page_status } from "notifications";

 - page_status.set_own(STATUS)

 Sets the status of the page making the call, completely overwriting
 the current status.  For example,

    page_status.set_own({
      type: "info",
      title: _("Software updates available"),
      details: {
        num_updates: 10,
        num_security_updates: 5
      }
    });

    page_status.set_own({
      type: null
      title: _("System is up to date"),
      details: {
        last_check: 81236457
      }
    });

 Calling this function with the same STATUS value multiple times is
 cheap: only the first call will actually broadcast the new status.

 - page_status.get(PAGE, [HOST])

 Retrieves the current status of page PAGE of HOST.  When HOST is
 omitted, it defaults to the default host of the calling page.

 PAGE is the same string that Cockpit uses in its URLs to identify a
 page, such as "system/terminal" or "storage".

 Until the page_status object is fully initialized (see 'valid'
 below), this function will return 'undefined'.

 - page_status.addEventListener("changed", event => { ... })

 The "changed" event is emitted whenever any page status changes.

 - page_status.valid

 The page_status objects needs to initialize itself asynchronously and
 'valid' is false until this is done.  When 'valid' changes to true, a
 "changed" event is emitted.

*/

import cockpit, { JsonValue, JsonObject } from "cockpit";
import { dequal } from 'dequal/lite';

export interface Status {
    type?: string | null;
    title?: string;
    details?: JsonObject;
}

class PageStatus extends EventTarget {
    valid: boolean = false;
    cur_own: Status | null = null;

    constructor() {
        super();
        window.addEventListener("storage", event => {
            if (event.key === "cockpit:page_status") {
                this.dispatchEvent(new CustomEvent("changed"));
            }
        });

        cockpit.transport.wait(() => {
            this.valid = true;
            this.dispatchEvent(new CustomEvent("changed"));
        });
    }

    get(page: string, host?: string): Status | null | undefined {
        let page_status;

        if (!this.valid)
            return undefined;

        if (host === undefined)
            host = cockpit.transport.host;

        try {
            page_status = JSON.parse(sessionStorage.getItem("cockpit:page_status") || "{}");
        } catch {
            return null;
        }

        if (page_status?.[host])
            return page_status[host][page] || null;
        return null;
    }

    set_own(status: Status | null) {
        if (!dequal(status, this.cur_own)) {
            this.cur_own = status;
            cockpit.transport.control("notify", { page_status: status as JsonValue });
        }
    }
}

export const page_status = new PageStatus();