Line data Source code
1 : /*
2 : * Copyright (C) 2019 Red Hat, Inc.
3 : * SPDX-License-Identifier: LGPL-2.1-or-later
4 : */
5 :
6 : /* NOTIFICATIONS
7 :
8 : A page can broadcast notifications to the rest of Cockpit. For
9 : example, the "Software updates" page can send out a notification when
10 : it detects that software updates are available. The shell will then
11 : highlight the menu entry for "Software updates" and the "System"
12 : overview page will also mention it in its "Operating system" section.
13 :
14 : The details are all still experimental and subject to change.
15 :
16 : As a first step, there are only simple "page status" notifications.
17 : When we address "event" style notifications, page status notifications
18 : might become a special case of them. Or not.
19 :
20 : A page status is either null, or a JSON value with the following
21 : fields:
22 :
23 : - type (string, optional)
24 :
25 : If specified, one of "info", "warning", "error". The shell will put
26 : an appropriate icon next to the navigation entry for this page, for
27 : example.
28 :
29 : Omitting 'type' means that the page has no special status and is the
30 : same as using "null" as the whole status value. This can be used to
31 : broadcast values in the 'details' field to other pages without
32 : forcing an icon into the navigation menu.
33 :
34 : - title (string, optional)
35 :
36 : A short, human readable, localized description of the status,
37 : suitable for a tooltip.
38 :
39 : - details (JSON value, optional)
40 :
41 : An arbitrary value. The "System" overview page might monitor a
42 : couple of pages for their status and it will use 'details' to display
43 : a richer version of the status than possible with just type and
44 : title. The recognized properties are:
45 :
46 : * icon: custom icon name (defaults to standard icon corresponding to type)
47 : * pficon: PatternFly icon name; e.g. "enhancement", "bug", "security", "spinner", "check";
48 : see get_pficon() in pkg/systemd/page-status.jsx
49 : * link: custom link target (defaults to page name); if false, the
50 : notification will not be a link
51 :
52 : Usage:
53 :
54 : import { page_status } from "notifications";
55 :
56 : - page_status.set_own(STATUS)
57 :
58 : Sets the status of the page making the call, completely overwriting
59 : the current status. For example,
60 :
61 : page_status.set_own({
62 : type: "info",
63 : title: _("Software updates available"),
64 : details: {
65 : num_updates: 10,
66 : num_security_updates: 5
67 : }
68 : });
69 :
70 : page_status.set_own({
71 : type: null
72 : title: _("System is up to date"),
73 : details: {
74 : last_check: 81236457
75 : }
76 : });
77 :
78 : Calling this function with the same STATUS value multiple times is
79 : cheap: only the first call will actually broadcast the new status.
80 :
81 : - page_status.get(PAGE, [HOST])
82 :
83 : Retrieves the current status of page PAGE of HOST. When HOST is
84 : omitted, it defaults to the default host of the calling page.
85 :
86 : PAGE is the same string that Cockpit uses in its URLs to identify a
87 : page, such as "system/terminal" or "storage".
88 :
89 : Until the page_status object is fully initialized (see 'valid'
90 : below), this function will return 'undefined'.
91 :
92 : - page_status.addEventListener("changed", event => { ... })
93 :
94 : The "changed" event is emitted whenever any page status changes.
95 :
96 : - page_status.valid
97 :
98 : The page_status objects needs to initialize itself asynchronously and
99 : 'valid' is false until this is done. When 'valid' changes to true, a
100 : "changed" event is emitted.
101 :
102 : */
103 :
104 66 : import cockpit, { JsonValue, JsonObject } from "cockpit";
105 : import { dequal } from 'dequal/lite';
106 :
107 : export interface Status {
108 : type?: string | null;
109 : title?: string;
110 : details?: JsonObject;
111 : }
112 :
113 66 : class PageStatus extends EventTarget {
114 66 : valid: boolean = false;
115 66 : cur_own: Status | null = null;
116 :
117 66 : constructor() {
118 66 : super();
119 19 : window.addEventListener("storage", event => {
120 12 : if (event.key == "cockpit:page_status") {
121 12 : this.dispatchEvent(new CustomEvent("changed"));
122 12 : }
123 19 : });
124 :
125 66 : cockpit.transport.wait(() => {
126 66 : this.valid = true;
127 66 : this.dispatchEvent(new CustomEvent("changed"));
128 66 : });
129 66 : }
130 :
131 35 : get(page: string, host?: string): Status | null | undefined {
132 35 : let page_status;
133 :
134 35 : if (!this.valid)
135 6 : return undefined;
136 :
137 35 : if (host === undefined)
138 35 : host = cockpit.transport.host;
139 :
140 35 : try {
141 34 : page_status = JSON.parse(sessionStorage.getItem("cockpit:page_status") || "{}");
142 6 : } catch {
143 6 : return null;
144 6 : }
145 :
146 35 : if (page_status?.[host])
147 7 : return page_status[host][page] || null;
148 34 : return null;
149 35 : }
150 :
151 30 : set_own(status: Status | null) {
152 11 : if (!dequal(status, this.cur_own)) {
153 11 : this.cur_own = status;
154 11 : cockpit.transport.control("notify", { page_status: status as JsonValue });
155 11 : }
156 30 : }
157 66 : }
158 :
159 66 : export const page_status = new PageStatus();
|