Line data Source code
1 : /*
2 : * Copyright (C) 2025 Red Hat, Inc.
3 : * SPDX-License-Identifier: LGPL-2.1-or-later
4 : */
5 :
6 35 : import React, { useState } from 'react';
7 : import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
8 : import { Flex, FlexItem } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js";
9 : import { Icon } from "@patternfly/react-core/dist/esm/components/Icon/index.js";
10 : import { ExclamationTriangleIcon } from "@patternfly/react-icons";
11 :
12 35 : import cockpit from "cockpit";
13 :
14 : import { useInit } from 'hooks';
15 :
16 35 : const _ = cockpit.gettext;
17 :
18 35 : export const UncleanShutdownStatus = () => {
19 35 : const [uncleanShutdownId, setUncleanShutdownId] = useState(null);
20 35 : const [uncleanShutdownStatusVisible, setUncleanShutdownStatusVisible] = useState(false);
21 :
22 35 : useInit(() => {
23 35 : cockpit.spawn(
24 35 : ["last", "--system", "--limit=2", "--time-format=iso", "shutdown", "reboot"],
25 35 : { environ: ["LC_ALL=C"], err: "message", }
26 30 : ).then((data) => {
27 30 : const previous_boot = data.split("\n")[1];
28 30 : if (previous_boot === undefined)
29 30 : return;
30 :
31 : // "crash" on wtmpdb, "still running" on util-linux
32 30 : if (!previous_boot.includes("crash") && !previous_boot.includes("still running"))
33 30 : return;
34 :
35 6 : const lines = previous_boot.split(/ +/);
36 6 : const started = new Date(lines[4]);
37 6 : if (isNaN(started)) {
38 6 : console.warn("cannot parse start date of last line", previous_boot);
39 6 : } else {
40 6 : const epoch = started.getTime().toString();
41 6 : setUncleanShutdownId(epoch);
42 6 : setUncleanShutdownStatusVisible(epoch != cockpit.sessionStorage.getItem("dismissed-unclean-shutdown-id"));
43 6 : }
44 30 : });
45 35 : });
46 :
47 6 : if (!uncleanShutdownId || !uncleanShutdownStatusVisible) {
48 35 : return null;
49 35 : }
50 :
51 0 : function hideAlert() {
52 0 : setUncleanShutdownStatusVisible(false);
53 0 : cockpit.sessionStorage.setItem('dismissed-unclean-shutdown-id', uncleanShutdownId);
54 0 : }
55 :
56 6 : return (
57 6 : <li id="unclean-shutdown-status">
58 6 : <Flex flexWrap={{ default: 'nowrap' }}>
59 6 : <FlexItem>
60 6 : <Icon status="danger">
61 6 : <ExclamationTriangleIcon />
62 6 : </Icon>
63 6 : </FlexItem>
64 6 : <div>
65 6 : <div className="pf-v6-u-text-break-word pf-v6-u-text-color-status-danger">
66 6 : {_("Unclean shutdown")}
67 6 : </div>
68 6 : <Flex>
69 6 : <Button variant="link" isInline
70 6 : className="pf-v6-u-font-size-sm"
71 0 : onClick={() => cockpit.jump("/system/logs#/?boot=-1")}>
72 6 : {_("View logs")}
73 6 : </Button>
74 6 : <Button variant="link" isInline
75 6 : className="pf-v6-u-font-size-sm"
76 0 : onClick={() => hideAlert()}
77 6 : id="unclean-shutdown-status-dismiss">
78 6 : {_("Dismiss")}
79 6 : </Button>
80 6 : </Flex>
81 6 : </div>
82 6 : </Flex>
83 6 : </li>
84 : );
85 35 : };
|