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