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